System V 共享内存:原理剖析、代码架构分析与双端通信实战
system V一、基本定义System V是早期商用 UNIX 操作系统的一个分支由贝尔实验室推出是经典的 UNIX 版本系列。我们现在 Linux 里说的System V IPC就是这套系统遗留下来的进程间通信标准包含三类共享内存、消息队列、信号量。本质就是遵循老式 UNIX System V 规范实现的一套进程间通信机制。二、核心原理System V 共享内存 内核开辟一块物理内存 → 让多个进程同时映射到自己的虚拟地址空间 → 大家直接读写同一块物理内存。理解1. 映射到虚拟地址空间的共享区2. 共享内存原理是一个简化版本的动态库映射3. 共享内存管理结构体 共享内存本身 共享内存4. 共享内存使用步骤1. 创建 2. 关联挂接 3. 使用 4. 去关联 5. 释放共享内存三、相关接口1.shmgetint shmget(key_t key, size_t size, int shmflg);作用向内核申请一块共享内存或获取已存在的共享内存。key共享内存的编号size共享内存大小必须 0内核自动按页对齐shmflgIPC_CREAT不存在则创建已存在就用之——获取IPC_CREAT | IPC_EXCL不存在创建已存在则报错——创建0664权限和文件权限一样返回成功返回 shmid共享内存 ID失败 -1解析key键值需要共享内存时如何知道这个内存在不在呢那么在内核中会对shm进行标识实现共享内存的唯一性。但是我们发现这个跟内核打交道的标识竟然要用户自己填为什么不能像文件描符fd那样让内核自己处理好之后将上层提供给进程然后把这玩意儿交给进程之间不就好了吗但是如果是这样那进程之间也没有通信方式来看到这个上层的东西啊所以不能让内核自己弄所以需要进程之间约定好这个key键值所以这个key值理论上来说就可以自己随便给给1、2、3都行但是一般不这么干。一般会通过一个函数生成一个key值。ftokkey钥匙生成器。key_t ftok(const char *pathname, int proj_id);作用把一个文件路径 一个数字转换成一个唯一的 key 值给shmget/msgget/semget使用。参数pathname一个真实存在的文件路径目录 / 普通文件都行只要存在就行内容不重要proj_id一个1 字节的数字0~255随便给同一个文件不同 proj_id → 生成不同 key返回值成功返回key_t 类型的 key失败返回-1// 1. 用当前目录 . 生成 key key_t key ftok(., 66); // . 一定存在66 是自定义数字 // 2. 用这个 key 创建共享内存 int shmid shmget(key, 4096, IPC_CREAT|0666);此时相当于server创建好了一个共享内存那么client端就需要获取这个共享内存。所以Shm类内还需要一个获取共享内存的接口这个接口大致跟创建接口差不多只是在shmget()时传的参数不一样需要只用IPC_CRETA参数获取。此时运行之后再看看结果那么这样就形成了进程间的联系。解析shmget返回值shmid我们有了key键值那函数shmget返回值shmid是什么呢我们可以通过代码查看一下解析共享内存的生命周期那如果我再运行一下这个./server会发生报错显示File exists此时我们还可以通过一个命令查看当前系统中的IPCipcs问题为啥进程结束了这个共享内存还能查到呢**结果**共享内存包括system V IPC标准下其他的两类通信生命周期随内核即用户如果不主动删除IPC资源IPC资源会和操作系统一样一直存在除非重启系统。手动命令删除共享内存ipcrm -m [shmid]不用key键值删除共享内存删除之后再次运行./servershmid就变成了1再次重复创建就意料之内会失败但是我们得思考是因为什么冲突了导致重复创建会失败显而易见是key键值冲突。对比key与shmid**1. key只在内核中标识共享内存的唯一性用户不使用key**所以命令ipcrm -m要用shmid2. shmid 只在用户中使用在代码中使用shmid来访问共享内存2.shmctlcontrolint shmctl(int shmid, int cmd, struct shm_ds *buf);作用获取信息、设置权限、删除共享内存。因为删除就是控制的一种常用cmdIPC_RMID删除共享内存最常用IPC_STAT获取状态信息IPC_SET设置权限第三个参数传*buf作用内核把共享内存的信息大小、权限、创建时间、挂载进程数写到buf里void Delete() { int n shmctl(_shmid, IPC_RMID, nullptr); if (n 0) { std::cerr shmctl 删除失败: strerror(errno) std::endl; } }3.shmatattachvoid *shmat(int shmid, const void *shmaddr, int shmflg);作用把共享内存挂接到当前进程的虚拟地址空间。参数2shmaddr想让共享内存映射到哪个虚拟地址→直接填 NULL让内核自动选99% 场景参数3shmflg0 → 可读可写一般直接填0SHM_RDONLY→ 只读返回值重要成功返回一个void*类型的指针指向映射后的共享内存起始地址。跟malloc差不多失败返回(void *) -1所以不能用判断 NULL 的方式检查 shmat 是否失败if (p NULL) { ... } // 错错错正确写法if (p (void*)-1) { ... } // 对对对int main() { Shm sharedmem; sharedmem.Create(); sleep(5); sharedmem.Attach(); sharedmem.Delete(); return 0; }void Attach() { _start_addr shmat(_shmid, nullptr, 0); if (_start_addr (void*)-1) { std::cerr shmat挂接失败: strerror(errno) std::endl; exit(3); } }nattach表示此共享内存的挂接数。perms表示此共享内存的权限。// 2. 创建共享内存 _shmid shmget(k, _size, IPC_CREAT | IPC_EXCL | 0666); if (_shmid 0) { std::cerr shmget创建共享内存失败: strerror(errno) std::endl; exit(2); }bytes属性在shmget传参时传入的size就是设置的共享内存大小必须是4096的整数倍4.shmdtdetach解绑定当进程不适用此共享内存之后最好的办法不是直接删除这个共享内存而是将自己与这个共享内存解除绑定需要用到接口shmdt。int shmdt(const void *shmaddr);作用解除当前进程与共享内存的挂接关系。参数shmaddr填shmat返回的那个地址指针返回值成功 → 返回 0失败 → 返回 -1。void Detach() { int n shmdt(_start_addr); if (n -1) { std::cerr shmdt解除挂接失败: strerror(errno) std::endl; exit(4); } std::cout 解除挂接成功 std::endl; }// ./server #include shm.hpp #include unistd.h int main() { Shm sharedmem; sharedmem.Create(); sharedmem.Attach(); sleep(5); sharedmem.Detach(); sleep(5); sharedmem.Delete(); return 0; }四、两端演示// ./client #include shm.hpp int main() { // 不需要创建内核级共享内存当然也不需要删除 Shm sharedmem; sharedmem.Get(); sleep(5); sharedmem.Attach(); sleep(5); sharedmem.Detach(); return 0; }// ./server #include shm.hpp int main() { // 谁创建谁删除 Shm sharedmem; sharedmem.Create(); sharedmem.Attach(); sleep(5); sharedmem.Detach(); sleep(5); sharedmem.Delete(); return 0; }挂载数nattch0-1-2-1-0-删除。// ./server #include shm.hpp int main() { // 谁创建谁删除 Shm sharedmem; sharedmem.Create(); sharedmem.Attach(); char* shm_start (char*)sharedmem.Addr(); int size sharedmem.Size(); while (true) { for (int i 0; i size; i) { std::cout shm_start[i] ; } std::cout std:: endl; sleep(3); } sharedmem.Detach(); sharedmem.Delete(); return 0; }// ./client #include shm.hpp int main() { // 不需要创建内核级共享内存当然也不需要删除 Shm sharedmem; sharedmem.Get(); sharedmem.Attach(); char* shm_start (char*)sharedmem.Addr(); int size sharedmem.Size(); int index 0; while (true) { std::cout 请输入 ; char ch; std::cin ch; shm_start[index] ch; index % size; } sharedmem.Detach(); return 0; }五、共享内存特点访问共享内存不需要系统调用写端写入其他端立即能看到速度快拷贝少缺点没有资源保护机制没有同步或者互斥没有阻塞六、完整代码// ./shm.hpp #pragma once #include sys/ipc.h #include sys/shm.h #include unistd.h #include iostream #include cstring #include cstdio // 用户指明 #define PATHNAME . #define PROJ_ID 0x66 const int gsize 4096; class Shm { public: Shm() : _shmid(-1) , _size(gsize) , _start_addr(nullptr) {} ~Shm() {} void Delete() { int n shmctl(_shmid, IPC_RMID, nullptr); if (n 0) { std::cerr shmctl 删除失败: strerror(errno) std::endl; exit(5); } std::cout 删除成功 std::endl; } void Attach() { _start_addr shmat(_shmid, nullptr, 0); if (_start_addr (void*)-1) { std::cerr shmat挂接失败: strerror(errno) std::endl; exit(3); } } void Detach() { int n shmdt(_start_addr); if (n -1) { std::cerr shmdt解除挂接失败: strerror(errno) std::endl; exit(4); } std::cout 解除挂接成功 std::endl; } void Get() { GetHelper(IPC_CREAT); } void Create() { GetHelper(IPC_CREAT | IPC_EXCL | 0666); } void* Addr() { return _start_addr; } int Size() { return _size; } private: key_t Getkey() { return ftok(PATHNAME, PROJ_ID); } void GetHelper(int shmflg) { // 1. 构建键值 key_t k Getkey(); if (k 0) { std::cerr 获取key键值失败: strerror(errno) std::endl; exit(1); } // 2. 创建共享内存 _shmid shmget(k, _size, shmflg); if (_shmid 0) { std::cerr shmget创建共享内存失败: strerror(errno) std::endl; exit(2); } printf(16进制key:0x%x; _shmid:%d\n, k, _shmid); } private: int _shmid; int _size; void* _start_addr; };// ./server #include shm.hpp int main() { // 谁创建谁删除 Shm sharedmem; sharedmem.Create(); sharedmem.Attach(); char* shm_start (char*)sharedmem.Addr(); int size sharedmem.Size(); while (true) { for (int i 0; i size; i) { std::cout shm_start[i] ; } std::cout std:: endl; sleep(3); } sharedmem.Detach(); sharedmem.Delete(); return 0; }// ./client #include shm.hpp int main() { // 不需要创建内核级共享内存当然也不需要删除 Shm sharedmem; sharedmem.Get(); sharedmem.Attach(); char* shm_start (char*)sharedmem.Addr(); int size sharedmem.Size(); int index 0; while (true) { std::cout 请输入 ; char ch; std::cin ch; shm_start[index] ch; index % size; } sharedmem.Detach(); return 0; }