共享内存是 Linux 进程间通信(IPC)中效率最高的方式,核心是让多个进程直接访问同一块物理内存区域,无需内核中转数据拷贝。本手册涵盖核心概念、关键函数、使用流程、实战示例和注意事项,适合零基础到进阶学习。
一、核心概念理解
1. 共享内存的本质
- 定义 :内核管理的一块物理内存区域,通过
mmap映射到多个进程的虚拟地址空间,进程读写该虚拟地址即操作同一块物理内存。 - 核心优势:无数据拷贝(管道 / 消息队列需 2 次拷贝,共享内存仅 0 次),是最快的 IPC 方式。
- 核心缺陷:无内置同步 / 互斥机制,多进程读写需配合信号量、互斥锁等保证数据安全。
2. 共享内存文件的特殊性
shm_open创建的共享内存是/dev/shm目录下的内存文件(tmpfs 文件系统),非磁盘文件;- 可通过
ls /dev/shm查看、cat /dev/shm/xxx读取内容,系统重启后自动消失; - 权限体系复用文件权限(如 0666),支持用户 / 组级别的访问控制。
二、核心函数详细解析
POSIX 共享内存依赖以下核心函数(需包含头文件 #include <sys/mman.h>,编译时链接 rt 库:-lrt)。
1. shm_open ():创建 / 打开共享内存对象
函数原型
int shm_open(const char *name, int oflag, mode_t mode);
参数说明
| 参数 | 作用 |
|---|---|
name |
共享内存名称,必须以 / 开头 (如 /my_shared_memory),全局唯一;仅允许一个 /,不能嵌套(如 /my/shm 非法) |
oflag |
打开标志:- O_CREAT:不存在则创建;- O_RDWR:读写权限;- O_EXCL:与 O_CREAT 配合,名称已存在则报错 |
mode |
权限位(如 0666),仅 O_CREAT 时有效;遵循 Linux 文件权限规则(r=4, w=2, x=1) |
返回值
- 成功:返回文件描述符(fd)(整型,用于后续操作);
- 失败:返回 -1,设置
errno(如EEXIST:名称已存在,EINVAL:名称格式非法)。
核心作用
创建 / 打开内核中的共享内存对象,在 /dev/shm 生成对应的内存文件(但此时大小为 0,需 ftruncate 分配物理内存)。
2. ftruncate ():设置共享内存大小
函数原型
int ftruncate(int fd, off_t length);
参数说明
| 参数 | 作用 |
|---|---|
fd |
shm_open 返回的文件描述符 |
length |
共享内存大小(字节),建议按内存页大小(4KB)对齐(如 4096、8192) |
返回值
- 成功:0;失败:-1(如
EBADF:fd 无效,EINVAL:长度非法)。
核心作用
- 内核根据
length为共享内存对象分配物理内存页; /dev/shm下的内存文件大小会更新为length,是mmap映射的前提(无此步骤映射会失败)。
3. mmap ():映射共享内存到进程虚拟地址空间
函数原型
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明
| 参数 | 作用 | |
|---|---|---|
addr |
指定映射的虚拟地址,通常传 NULL(让系统自动分配) |
|
length |
映射大小(需与 ftruncate 设置的大小一致) |
|
prot |
内存保护标志:- PROT_READ:可读;- PROT_WRITE:可写;- `PROT_READ |
PROT_WRITE`:读写 |
flags |
映射类型(核心!):- MAP_SHARED:修改同步到物理内存(所有进程可见);- MAP_PRIVATE:修改仅当前进程可见(私有拷贝,无共享意义) |
|
fd |
shm_open 返回的文件描述符 |
|
offset |
映射偏移量,通常为 0(从开头映射) |
返回值
- 成功:返回映射后的虚拟地址指针;
- 失败:返回
MAP_FAILED(需判断,而非 NULL),设置errno。
核心作用
建立进程虚拟地址 ↔ 共享物理内存的映射关系,进程可通过该虚拟地址直接读写物理内存。
4. munmap ():解除映射关系
函数原型
int munmap(void *addr, size_t length);
参数说明
| 参数 | 作用 |
|---|---|
addr |
mmap 返回的虚拟地址指针 |
length |
映射大小(需与 mmap 一致) |
返回值
- 成功:0;失败:-1(如
EINVAL:地址 / 长度非法)。
核心作用
- 断开当前进程虚拟地址与物理内存的映射,进程再读写该地址会触发段错误(SIGSEGV);
- 释放进程虚拟地址空间的页表项,但物理内存需等所有进程解除映射后才释放。
5. close ():关闭共享内存文件描述符
函数原型
int close(int fd);
参数说明
| 参数 | 作用 |
|---|---|
fd |
shm_open 返回的文件描述符 |
返回值
- 成功:0;失败:-1(如
EBADF:fd 已关闭)。
核心作用
- 释放当前进程持有的共享内存「操作句柄」,内核减少该对象的引用计数;
- 仅关闭 fd,不删除共享内存对象,也不释放物理内存。
6. shm_unlink ():删除共享内存对象
函数原型
int shm_unlink(const char *name);
参数说明
| 参数 | 作用 |
|---|---|
name |
共享内存名称(与 shm_open 一致,如 /my_shared_memory) |
返回值
- 成功:0;失败:-1(如
ENOENT:名称不存在,EACCES:权限不足)。
核心作用
- 立即删除
/dev/shm下的内存文件,后续进程无法通过shm_open打开该对象; - 标记对象为「待销毁」,当所有进程执行
munmap + close后,内核释放物理内存; - 与
rm /dev/shm/xxx效果完全等价(编程用shm_unlink,手动清理用rm)。
三、关键注意事项
1. 核心顺序(不可颠倒)
创建/打开 → 设置大小 → 映射 → 读写 → 解除映射 → 关闭fd → 删除对象
- 先
munmap再close:避免 fd 关闭后仍访问映射地址; - 先
close再shm_unlink:遵循文件操作的「先关后删」原则; - 多进程场景:子进程仅执行「解除映射 + 关闭 fd」,父进程最后执行「删除对象」。
2. 同步问题
- 共享内存无内置同步,多进程同时读写会导致「脏数据」;
- 解决方案:配合 POSIX 信号量(
sem_open/sem_wait/sem_post)或互斥锁。
3. 资源清理
- 进程异常退出时,需确保执行
shm_unlink(可通过atexit注册清理函数); - 残留的共享内存可手动删除:
rm /dev/shm/xxx。
4. 常见错误排查
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
shm_open 失败 |
名称格式非法(无 / 开头) | 名称必须以 / 开头(如 /my_shm) |
mmap 返回失败 |
未执行 ftruncate 设置大小 |
先调用 ftruncate 再映射 |
| 段错误(SIGSEGV) | 读写超出共享内存大小 / 已解除映射 | 检查大小、确保映射未解除 |
shm_unlink 失败 |
传文件描述符而非名称字符串 | 传 shm_open 用的名称字符串 |
四、总结
个人理解:
首先通过 shm_open() 函数创建/打开一个共享内存对象(该对象是真实存在于 /dev/shm 目录的内存文件);
然后通过 ftruncate() 函数设置该共享内存的大小(内核据此分配物理内存);
再通过 mmap() 将该共享内存映射为进程可操作的虚拟地址;
通过在进程里面对该虚拟地址进行读写操作,实现多进程间的数据共享;
读写操作完成后,先通过 munmap() 函数断开该虚拟地址与共享物理内存的映射关系;
然后调用 close() 函数关闭 shm_open() 返回的文件描述符;
最后使用 shm_unlink() 函数删除 /dev/shm 下的共享内存文件,当所有进程都完成映射解除和文件描述符关闭后,内核释放共享内存占用的物理内存。
核心知识点
- 共享内存是「物理内存 + 内核抽象对象 + 进程虚拟地址映射」的组合体;
shm_open创「名」,ftruncate分「实」,mmap建「映射」;munmap断「进程与内存的通路」,shm_unlink删「共享内存的名字」;- 核心优势是无数据拷贝,核心缺陷是需手动实现同步。
适用场景
- 高频、大数据量的进程间通信(如视频流、实时数据传输);
- 需低延迟的场景(如工业控制、金融交易系统)。