目录
1.System V共享内存1.介绍&原理2.共享内存函数shmget()ftok()shmat()shmdt()shmctl() 3.shmid vs key4.思考问题5.共享内存使用 2.System V信号量0.前情提要1.信号量
1.System V共享内存
1.介绍&原理
共享内存提供者 --> OS –> System V IPC****资源,生命周期随内核IPC资源必须删除,否则不会自动清除,除非重启 共享内存区是最快的IPC形式 一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据 注意:共享内存没有进行访问控制(同步与互斥)2.共享内存函数
shmget()
**功能:**用来创建共享内存原型:int shmget(key_t key, size_t size, int shmflg);参数: key**:**这个共享内存段名字 是多少不重要,只要能够在创作系统唯一即可server && client使用同一个key,只要key值相同,就是看到了同一个共享内存 size**:**共享内存大小 --> 最好是页(PAGE:4096)的整数倍shmflg**:**由九个权限标志构成,用法和创建文件时使用的mode模式标志是一样的IPC_CREAT**:**创建共享内存,如果底层已经存在,获取之,并返回,如果不存在,创建之,并返回
IPC_CREAT|IPC_EXCL**:**如果底层不存在,创建之,并返回,如果底层存在,返回出错
返回成功一定是一个全新的shmIPC_EXCL单独使用没有意义
为0时,尝试获取一个已经存在的共享内存段,如果该共享内存段不存在,则返回错误
**返回值:**共享内存的用户层标识符,类似曾经的fd 成功返回一个非负整数,即该共享内存段的标识码;失败返回-1ftok()
**功能:**用pathname和proj_id创建一个System V IPC key*原型:key_t ftok(const char pathname, int proj_id);参数: pathname**:**指向一个存在且可访问的文件(路径)proj_id**:**可随便填 **返回值:**成功返回key;失败返回-1shmat()
功能:将共享内存段连接到进程地址空间**原型:void shmat(int shmid, const void shmaddr, int shmflg);参数: shmid**:**共享内存标识shmaddr**:**指定链接的地址 shmaddr为NULL,核心自动选择一个地址shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍 公式:shmaddr - (shmaddr % SHMLBA) shmflg**:它的两个可能取值是SHM_RND和SHM_RDONLY** 填0表示使用默认的连接方式SHM_RDONLY,表示连接操作用来只读共享内存 **返回值:**成功返回一个指针,指向共享内存第一个字节;失败返回-1shmdt()
**功能:**将共享内存段与当前进程脱离*原型:int shmdt(const void shmaddr);参数: shmaddr**:**由shmat所返回的指针 返回值:成功返回0;失败返回-1**注意:**将共享内存段与当前进程脱离不等于删除共享内存段shmctl()
**功能:**用于控制共享内存*原型:int shmctl(int shmid, int cmd, struct shmid_ds buf);参数: shmid**:**由shmget返回的共享内存标识码cmd**:**cmd:将要采取的动作 IPC_STAT**:把shmid_ds结构中的数据设置为共享内存的当前关联值**IPC_SET**:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值**IPC_RMID:删除共享内存段 **返回值:**成功返回0;失败返回-13.shmid vs key
只有创建的时候用key,大部分情况用户访问共享内存,都用的是shmid4.思考问题
共享内存被删除后,则其他线程直接无法通信? 错误共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除5.共享内存使用
共享内存创建的时候,默认被清成全0将共享内存当成一个大字符串 --> char buffer[SHM_SIZE]通信双方使用shm,一方直接向共享内存中写入数据,另一方就可以立马看到对方写入的数据 所以共享内存是所有进程间通信(IPC)中,速度最快的,不需要过多的拷贝 --> 不需要将数据给操作系统 共享内存缺乏访问控制,会带来并发问题但如果想一定程度的访问控制呢?–> 通过管道控制comm.hpp#define PATH_NAME "/home/snowk"#define PROJ_ID 0x233#define SHM_SIZE 4096 // 共享内存大小,最好是页(PAGE:4096)整数倍#define FIFO_NAME "./fifo"#define READ O_RDONLY#define WRITE O_WRONLYclass Init{public: Init() { umask(0); int n = mkfifo(FIFO_NAME, 0666); assert(n == 0); (void)n; Log("create fifo success", NOTICE) << endl; } ~Init() { unlink(FIFO_NAME); Log("remove fifo success", NOTICE) << endl; }};int OpenFifo(string pathname, int flags){ int fd = open(pathname.c_str(), flags); assert(fd >= 0); return fd;}void CloseFifo(int fd){ close(fd);}void Wait(int fd){ Log("等待中...", NOTICE) << endl; uint32_t tmp = 0; ssize_t s = read(fd, &tmp, sizeof(uint32_t)); assert(s == sizeof(uint32_t)); (void)s;}void Signal(int fd){ uint32_t tmp = 1; ssize_t s = write(fd, &tmp, sizeof(uint32_t)); // 写什么不重要,主要是通过管道来进行访问控制 assert(s == sizeof(uint32_t)); (void)s; Log("唤醒中", NOTICE) << endl;}
Server // 程序加载的时候,自动构建全局变量,调用该类的构造函数 --> 创建管道文件// 程序退出的时候,全局变量析构,自动删除管道文件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); // 2.创建共享内存 -- 建议创建一个全新的共享内存 -- 通信的发起者 int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); // 3.将指定的共享内存,挂接到自己的地址空间 char *shmaddr = (char*)shmat(shmid, nullptr, 0); // 这里就是通信逻辑,将共享内存看作一个大数组 -> char buffer[SHM_SIZE] // 这里添加一定的访问控制(依赖管道) int fd = OpenFifo(FIFO_NAME, READ); while(true) { Wait(fd); // 写慢,读快,管道没有数据的时候,读必须等待 cout << shmaddr << endl; if(strcmp(shmaddr, "quit") == 0) { break; } } // 4.将指定的共享内存,从自己的地址空间去关联 int n = shmdt(shmaddr); // 5.删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存 n = shmctl(shmid, IPC_RMID, nullptr); CloseFifo(fd); return 0;}
Client int main(){ // 1.创建公共的key值 key_t k = ftok(PATH_NAME, PROJ_ID); // 2.获取共享内存 int shmid = shmget(k, SHM_SIZE, 0); // 3.将指定的共享内存,挂接到自己的地址空间 char *shmaddr = (char *)shmat(shmid, nullptr, 0); // 通信逻辑 int fd = OpenFifo(FIFO_NAME, WRITE); while(true) { ssize_t s = read(0, shmaddr, SHM_SIZE - 1); // 从键盘读数据,存入shm if (s > 0) { shmaddr[s - 1] = 0; // 最后会键入\n,把\n替换成\0 Signal(fd); // 已经获取到数据,唤醒服务端 if(strcmp(shmaddr, "quit") == 0) { break; } } } CloseFifo(fd); // 4.将指定的共享内存,从自己的地址空间中去关联 int n = shmdt(shmaddr); // client不需要shmctl删除 return 0;}