Linux 进程通信:System V 共享内存原理与 C++ 封装实战
System V 共享内存是 Linux 下速度最快的进程间通信方式,数据直接驻留物理内存无需拷贝。介绍其核心概念、映射原理及 ftok/shmget/shmat/shmdt/shmctl 五大接口。通过 C++ 类封装实现 Server-Client 通信,并结合 FIFO 解决同步问题。同时分析了权限拒绝、大小对齐、sleep 时序及删除后访问等常见 Bug 的解决方案。

System V 共享内存是 Linux 下速度最快的进程间通信方式,数据直接驻留物理内存无需拷贝。介绍其核心概念、映射原理及 ftok/shmget/shmat/shmdt/shmctl 五大接口。通过 C++ 类封装实现 Server-Client 通信,并结合 FIFO 解决同步问题。同时分析了权限拒绝、大小对齐、sleep 时序及删除后访问等常见 Bug 的解决方案。

System V 是 Linux 内核支持的一套 IPC(进程间通信)标准,其中共享内存是速度最快的 IPC 方式——因为数据直接存在'共享物理内存'中,进程读写无需拷贝(其他 IPC 如 FIFO 需要内核中转)。
每个进程都有独立的虚拟地址空间,分为内核区、栈、堆、共享库区等,默认情况下进程间的内存互不干扰。

OS 通过 3 步让进程共享内存:
OS 用一个'内核结构体'(struct shmid_ds)描述共享内存的信息(如大小、权限、引用计数),再用链表/哈希表组织所有共享内存,方便管理。
引用计数:记录有多少进程正在使用这块共享内存。只有引用计数为 0 时,OS 才会真正释放物理内存(避免进程还在使用时内存被删)。

操作共享内存需 4 个核心接口:ftok(生成唯一 key)、shmget(创建/获取共享内存)、shmat(将共享内存映射到进程虚拟地址)、shmdt(解除映射)、shmctl(管理共享内存,如删除)。
key_t 类型值(key),用于标识共享内存(确保不同进程能找到同一块共享内存)。
pathname:必须是已存在的文件路径(比如 "./")。proj_id:1~255 的整数(自定义,只要进程间一致即可)。示例:两个进程都用 ftok("./", 100),会生成相同的 key,从而找到同一块共享内存。


接口参数与返回值:

key:由 ftok 生成的唯一标识。size:共享内存的大小(必须是 4KB 的整数倍,OS 按页分配内存,不足 4KB 会自动补齐)。shmflg:标志位,常用组合:
IPC_CREAT:如果共享内存不存在,则创建;如果已存在,则获取。IPC_CREAT | IPC_EXCL | 0664:如果共享内存已存在,则报错(确保创建的是全新的共享内存);0664 是共享内存的权限(和文件权限一致)。返回值说明:

接口参数与返回值:

shmid:shmget 返回的共享内存 ID。shmaddr:指定映射到进程虚拟地址的哪个位置(一般设为 NULL,让 OS 自动分配,避免冲突)。shmflg:映射标志(一般设为 0,默认权限)。void* 类型),失败返回 (void*)-1。示例:


接口参数与返回值:

shmaddr:shmat 返回的虚拟地址(必须和映射时的地址一致)。shmctl 删除)。接口参数与返回值:

shmid:shmget 返回的共享内存 ID。cmd:命令,常用 IPC_RMID(删除共享内存)。buf:指向 struct shmid_ds 的指针(用于传递/接收共享内存的信息,删除时可设为 NULL)。shmctl(shmid, IPC_RMID, NULL) 后,共享内存会被'标记为删除',但不会立刻消失——直到所有进程都 shmdt 解除映射(引用计数为 0),OS 才会真正释放物理内存。删除共享内存示例:

接口参数与返回值:

shmid:共享内存 ID(删除时用)。key:ftok 生成的标识。owner:创建共享内存的用户。segsz:共享内存大小(4KB 的整数倍)。nattch:引用计数(当前有多少进程映射了这块内存)。示例:

shmctl(shmid, IPC_RMID, NULL))。
ipcrm 删除,或代码中 shmctl 删除),避免内存泄漏。示例:

为简化代码,用 C++ 类封装共享内存的操作(创建/获取、映射、读写、删除)。
删除共享内存:提供 Destroy(调用 shmctl)成员函数,在析构函数中调用 Detach。

映射与解除映射:提供 Attach(调用 shmat)和 Detach(调用 shmdt)成员函数。

若 shmget 失败(比如共享内存已存在),报错信息如下:

创建/获取共享内存:在构造函数中调用 ftok 和 shmget。

核心逻辑:创建共享内存 → 映射到虚拟地址 → 循环读数据 → 解除映射 → 删除共享内存。
代码示例:


核心逻辑:获取已存在的共享内存 → 映射到虚拟地址 → 写数据 → 解除映射(无需删除共享内存)。
代码示例:

客户端写入数据后,服务器能立刻读到共享内存中的内容(无需内核中转,速度极快)。

共享内存本身没有同步机制——如果服务器还没读,客户端就写了新数据,会覆盖旧数据;如果两个进程同时写,会导致数据混乱。
解决方法:结合 FIFO 实现同步(用 FIFO 作为'信号通道',控制读写顺序)。
核心思路:

服务器先从 FIFO 读'信号'(阻塞等待客户端写完),再读共享内存。


客户端先写共享内存,再向 FIFO 写'信号'(唤醒服务器读)。


调用 shmat 时若报错'权限拒绝',是因为共享内存的权限不足(创建时 shmflg 没加权限)。
Bug 效果:

解决方法:创建共享内存时,在 shmflg 中加上权限(如 0664)。

修改后效果(映射成功):

OS 按'页'(4KB)分配共享内存,若 size 不是 4KB 的整数倍,OS 会自动向上补齐(比如 size=4097,实际分配 8192 字节)。
但 ipcs -m 查看时,segsz 会显示用户设置的 size(而非补齐后的大小),容易误导。

建议:创建共享内存时,手动将 size 设为 4KB 的整数倍(比如 size=4096、size=8192),避免浪费。
若用 sleep 控制读写顺序(比如客户端 sleep(1) 后写,服务器 sleep(2) 后读),可能因时间差导致同步失败(比如 sleep 时间不够,服务器还没准备好,客户端就写了)。
Bug 效果:

解决方法:不用 sleep,改用 FIFO 等同步机制(如前所述),确保'写后再读'。

调用 shmctl(shmid, IPC_RMID, NULL) 后,共享内存被'标记为删除',但只要有进程还在映射(引用计数>0),进程仍能访问它;只有所有进程都 shmdt 后,共享内存才会真正消失。
示例:服务器删除共享内存后,客户端仍能读数据(直到客户端 shmdt)。



OS 用 struct shmid_ds 描述共享内存的详细信息,这个结构体包含以下核心字段:

shm_perm:权限相关信息(如所有者、组、权限)。shm_segsz:共享内存大小(用户设置的 size)。shm_nattch:引用计数(当前映射的进程数)。shm_atime:最后一次 shmat 的时间。shm_dtime:最后一次 shmdt 的时间。shm_ctime:最后一次修改(如权限、大小)的时间。当调用 shmget 创建共享内存时,OS 会初始化这个结构体,并将 key 存入 shm_perm.__key 字段,用于标识共享内存。



微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online