文章目录
- 0. 共享内存
 - 1. 共享内存示意图
 - 2. 共享内存函数
 - 2.1 shmget函数
 - 2.2 shmat函数
 - 2.3 shmdt函数
 - 2.4 shmctl函数
 - 2.5 查看共享内存指令
 - 2.6 删除共享方法
 - 2.6.1 指令删除
 - 2.6.2 代码删除
 
- 3. 实例代码
 - 3.0 log.hpp打印日志信息
 - 3.1 comm.hpp(shmServer.cc和shmClicent.cc共有文件)
 - 3.2 shmServer.cc
 - 3.2.1 创建公共的key值
 - 3.2.2 创建共享内存
 - 3.2.3 挂接
 - 3.2.4 通信
 - 3.2.5 去关联
 - 3.3.6 删除共享内存
 - 3.3.7 总代码块
 
- 3.3 shmClicent.cc
 - 3.3.1 总代码
 
- 3.4 结果展示
 
- 4. 总结
 
0. 共享内存
-  
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
共享内存=共享内存块+对应的共享内存的内核数据结构! 
1. 共享内存示意图

2. 共享内存函数
2.1 shmget函数
- 功能:用来创建共享内存
 - 原型 
  
- int shmget(key_t key, size_t size, int shmflg);
 
 - 参数 
  
- key:这个共享内存段名字
 - size:共享内存大小
 - shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。
 
 - 返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。
 
2.2 shmat函数
- 功能:将共享内存段连接到进程地址空间
 - 原型 
  
- void *shmat(int shmid, const void *shmaddr, int shmflg);
 
 - 参数 
  
- shmid: 共享内存标识
 - shmaddr:指定连接的地址
 - shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
 
 - 返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1。
 
说明:
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以	shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:	shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
 
2.3 shmdt函数
- 功能:将共享内存段与当前进程脱离
 - 原型 
  
- int shmdt(const void *shmaddr);
 
 - 参数 
  
- shmaddr: 由shmat所返回的指针
 
 - 返回值:成功返回0;失败返回-1
 - 注意:将共享内存段与当前进程脱离不等于删除共享内存段。
 
2.4 shmctl函数
- 功能:用于控制共享内存
 - 原型 
  
- int shmctl(int shmid, int cmd, struct shmid_ds *buf);
 
 - 参数 
  
- shmid:由shmget返回的共享内存标识码
 - cmd:将要采取的动作(有三个可取值)
 - buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
 
 - 返回值:成功返回0;失败返回-1。
 
| 命令 | 说明 | 
|---|---|
| IPC_STAT | 把shmid_ds结构中的数据设置为共享内存的当前关联值 | 
| IPC_SET | 在进程有足够的权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 | 
| IPC_RMID | 删除共享内存段 | 
2.5 查看共享内存指令
ipcs -m
 

- key 唯一值(共享内存名字)
 - shmid 共享内存标识
 - owner 共享内存拥有者名字
 - perms 拥有者对共享内存权限
 - bytes 共享内存空间大小
 - nattch 多少个进程关联共享内存
 - status 状态
 
2.6 删除共享方法
2.6.1 指令删除
ipcrm -m shmid
 
shmid就是上图的22
2.6.2 代码删除
int n = shmctl(shmid, IPC_RMID, nullptr);
 
3. 实例代码
3.0 log.hpp打印日志信息
#pragma once
#include <iostream>
#include <ctime>
enum e
{
    Debug,
    Notice,
    Warning,
    Error
};
const std::string msg[]{
    "Debug",
    "Notice",
    "Warning",
    "Error"};
std::ostream &log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}
 
3.1 comm.hpp(shmServer.cc和shmClicent.cc共有文件)
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cassert>
#include "log.hpp"
using namespace std;
#define PATH_NAME "/home/Ding"
#define PROJ_ID 0X66
#define SHM_SIZE 4096 //共享内存的大小(512字节的整数倍)
#define FIFO_NAME "./fifo"
//命名管道---控制不让通信的时候它疯狂的读取数据;解决时序性问题
class Init
{
public:
    Init()
    {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        log("创建管道文件成功", Notice) << "\n";
    }
    ~Init()
    {
        unlink(FIFO_NAME);
        log("删除管道文件成功", Notice) << "\n";
    }
};
#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenFIFO(string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    (void)fd;
}
void Wait(int fd)
{
    log("等待中...", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}
void Signl(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    log("唤醒中...", Notice) << "\n";
}
void CloseFIfo(int fd)
{
    close(fd);
}
 
PATH_NAME 路径只有你有权限进入的目录都可以
 
3.2 shmServer.cc
3.2.1 创建公共的key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    log("创建公共key值", Debug) << "server key: " << k << endl;
 
- ftok 作用是生成一个唯一值(相同的算法)
 - 这样两个进程就能找到同一块共享内存;就像试锁开门一样。
 
3.2.2 创建共享内存
// 2. 创建一个全新的共享内存---通信的发起者
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    log("创建共享内存", Debug) << "shmid: " << shmid << endl;
 
- PATH_NAME 单独使用的时候无论有没有共享内存都创建
 - PROJ_ID 单独使用无异于
 - 上面两个同时使用时,没有共享内存创建;有就报错
 
3.2.3 挂接
// 3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    log("挂接共享内存成功", Debug) << endl;
    sleep(10);
 
3.2.4 通信
// 这里就是通信的逻辑了
    int fd = OpenFIFO(FIFO_NAME, READ);
    for (;;)
    {
        Wait(fd);
        printf("%s\n", shmaddr);
        if (strcmp(shmaddr, "quit") == 0)
            break;
        // sleep(1);
    }
 
3.2.5 去关联
// 4. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    log("去挂接共享内存成功", Debug) << "shmdt: " << n << endl;
 
3.3.6 删除共享内存
// 5. 删除共享内存, IPC_RMID即便是当前进程与shm挂接,仍删除。
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    log("删除共享内存成功", Debug) << "shmctl: " << n << endl;
 
3.3.7 总代码块
#include "comm.hpp"
//在加载的时候,会自动构建全局变量,就要调用该类的构造函数 -- 创建管道文件
// 程序退出的时候,全局变量会被析构,自动调用析构函数,会自动删除管道文件
Init init;
string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}
int main()
{
    // 1.创建公共的key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    log("创建公共key值", Debug) << "server key: " << TransToHex(k) << endl;
    // 2. 创建一个全新的共享内存---通信的发起者
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    log("创建共享内存", Debug) << "shmid: " << shmid << endl;
    // sleep(10);
    //  3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    log("挂接共享内存成功", Debug) << endl;
    // sleep(10);
    // 这里就是通信的逻辑了
    int fd = OpenFIFO(FIFO_NAME, READ);
    for (;;)
    {
        Wait(fd);
        printf("%s\n", shmaddr);
        if (strcmp(shmaddr, "quit") == 0)
            break;
        // sleep(1);
    }
    // 4. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    log("去挂接共享内存成功", Debug) << "shmdt: " << n << endl;
    // sleep(10);
    // 5. 删除共享内存, IPC_RMID即便是当前进程与shm挂接,仍删除。
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    log("删除共享内存成功", Debug) << "shmctl: " << n << endl;
    CloseFIfo(fd);
    return 0;
}
 
3.3 shmClicent.cc
3.3.1 总代码
#include "comm.hpp"
int main()
{
    // 1.创建公共的key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0)
    {
        log("创建公共key值失败", Debug) << "server key: " << k << endl;
        exit(1);
    }
    log("创建公共key值", Debug) << "clicent key" << k << endl;
    // 2. 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if (shmid < 0)
    {
        log("获取共享内存失败", Debug) << "shmid: " << shmid << endl;
        exit(2);
    }
    log("获取共享内存", Debug) << "shmid: " << shmid << endl;
    // sleep(10);
    // 3. 挂接共享内存
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr)
    {
        log("挂接共享内存失败", Debug) << endl;
        exit(3);
    }
    log("挂接共享内存成功", Debug) << endl;
    // sleep(10);
    //使用
    int fd = OpenFIFO(FIFO_NAME, WRITE);
    while (true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if (s > 0)
        {
            shmaddr[s - 1] = 0;
            Signl(fd);
            if (strcmp(shmaddr, "quit") == 0)
                break;
        }
    }
    // 4. 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    log("去挂接共享内存成功", Debug) << "shmdt: " << n << endl;
    //不需要chmctl删除
    return 0;
}
 
3.4 结果展示

4. 总结
将共享内存当成一个大字符串,通信的时候可以像malloc一样使用 char buffer[SHM_SIZE];
- 结论1: 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到对方写入的数据。共享内存是所有进程间通信(IPC),速度最快的!不需要过多的拷贝!!(不需要将数据给操作系统)
 - 结论2: 共享内存缺乏访问控制!会带来并发问题 【如果我想一定程度的访问控制呢? 能】利用命名管道加以控制。
 



















