一、什么是共享内存
共享内存是最快的 IPC 形式。一旦共享内存映射到进程的地址空间,数据传递不再涉及内核,进程无需通过系
统调用即可直接读写内存。
特点:
- 最快:无内核介入,直接访问
- 需要同步:必须配合信号量使用,否则会有数据竞争
- 内核持久:共享内存不会随进程退出而自动销毁,必须手动删除。需要显式删除,命令行使用 ipcrm,代码中使用 shmctl IPC_RMID。
注:System V 接口较复杂,新项目推荐使用 POSIX 共享内存。
二、共享内存示意图
管道、消息队列,数据都需要经过内核拷贝。共享内存,是最快的 IPC 方式,因为它不经过内核。
进程 A 有一个虚拟地址(比如 0x7f…),通过它的页表,指向了物理内存中的某个位置。进程 B 也有一个虚拟地址,通过它的页表,指向了同一块物理内存。
进程 A 和进程 B 读写的是同一块物理内存,整个过程没有经过内核拷贝。A 写完后,B 直接就能看到,不需要调用 read 或 write 系统调用。
三、共享内存数据结构
structshmid_ds{structipc_permshm_perm;intshm_segsz;__kernel_time_t shm_atime;__kernel_time_t shm_dtime;__kernel_time_t shm_ctime;__kernel_ipc_pid_t shm_cpid;__kernel_ipc_pid_t shm_lpid;unsignedshortshm_nattch;unsignedshortshm_unused;void*shm_unused2;void*shm_unused3;};
核心要点:
- 权限由 shm_perm 控制
- 大小 shm_segsz 创建后固定
- shm_nattch 为0时才能安全删除共享内存
- 时间戳和PID字段记录操作历史
四、共享内存函数
- shmget
#include<sys/ipc.h>#include<sys/shm.h>intshmget(key_tkey,size_tsize,intshmflg);- 参数:
- key :共享内存键值( ftok() 生成 或 IPC_PRIVATE )
- size :共享内存大小(字节)
- shmflg : IPC_CREAT (创建)、 IPC_EXCL (确保新建)、权限位(如 0644 )
- 返回值:成功返回 shmid ,失败返回 -1
- 示例:
intshmid=shmget(IPC_PRIVATE,4096,IPC_CREAT|0644);- shmat
#include<sys/types.h>#include<sys/shm.h>void*shmat(intshmid,constvoid*shmaddr,intshmflg);- 参数:
- shmid : shmget 返回的标识符
- shmaddr : NULL (系统自动选地址) 或 指定地址
- shmflg : SHM_RDONLY (只读)、 SHM_RND (地址对齐)
- 返回值:成功返回共享内存首地址指针,失败返回 (void *)-1
- 连接地址规则
1.shmaddr 为 NULL:系统自动选择连接地址
2.shmaddr 非 NULL 且无 SHM_RND:使用指定地址
3.shmaddr 非 NULL 且有 SHM_RND:地址自动向下对齐到 SHMLBA 的整数倍 - 示例:
char*addr=(char*)shmat(shmid,NULL,0);- shmdt
#include<sys/types.h>#include<sys/shm.h>intshmdt(constvoid*shmaddr);- 参数: shmat 返回的地址指针
- 返回值:成功返回 0 ,失败返回 -1
- 注意:分离≠删除,进程终止会自动分离
- shmctl
#include<sys/ipc.h>#include<sys/shm.h>intshmctl(intshmid,intcmd,structshmid_ds*buf);参数:
shmid :共享内存标识符
cmd : IPC_STAT (获取状态)、 IPC_SET (设置属性)、 IPC_RMID (标记删除)
buf : shmid_ds 结构体指针
返回值:成功返回 0 ,失败返回 -1
示例:
shmctl(shmid,IPC_RMID,NULL);// 删除共享内存五、共享内存与信号量配合使用
- 为什么需要信号量
共享内存本身不提供任何同步机制,如果多个进程同时读写同一块共享内存,会导致数据竞争。例如,进程 A 正在写入共享内存但只写了一半,进程 B 同时读取,就会读到不完整的数据。解决方案是使用信号量实现互斥访问,保证同一时刻只有一个进程读写共享内存。 - 信号量类型选择
与 System V 共享内存配合使用时,推荐使用 POSIX 命名信号量。
命名信号量通过 sem_open 创建和打开,使用 sem_close 关闭,sem_unlink 删除,适用于无亲缘关系的进程间同步。
无名信号量则通过 sem_init 初始化sem_destroy 销毁,主要用于线程间或有亲缘
关系的进程。 - 核心操作
- 创建或打开信号量使用 sem_open;
- 加锁使用 sem_wait(P 操作);
- 解锁使用 sem_post(V 操作);
- 关闭信号量使用 sem_close;
最后用 sem_unlink 删除信号量。
使用流程
注意事项
- 信号量初始值必须设为 1 才能实现互斥锁的效果。
- P 操作必须在访问共享内存之前调用,V 操作必须在访问共享内存之后调用,并且要确保解锁操作会被执行以防止死锁。
- 最后一个使用共享内存和信号量的进程负责删除它们。
- 编译时需要链接 -lrt 和 -lpthread 库。注意以下常见错误:
- 忘记加锁会导致数据竞争,读到不完整的数据;
- 忘记解锁会导致死锁,其他进程永远等待;
- 信号量未初始化会导致未定义行为;
- 未处理异常退出会导致信号量未被释放,造成死锁。