这个博客主要介绍Linux下的共享内存(Shared Memory)机制,包括其概念、使用方法以及注意事项。共享内存是一种高效的进程间通信(IPC)方式,允许多个进程直接访问同一块内存区域,从而实现数据的快速交换。
共享内存概述
POSIX 共享内存的完整生命周期分为四个核心阶段:打开/创建 -> 设定容量 -> 内存映射 -> 释放销毁。
每个阶段的 API 都有严格的参数和标记(Flag)要求,任何一处不匹配都会导致常见的诸如段错误(Segmentation fault)或总线错误(Bus error)的崩溃。
第一阶段:创建或打开 (shm_open)
shm_open 负责在 Linux 的虚拟文件系统(/dev/shm)中拿到一个操作句柄(文件描述符 fd)。
int fd = shm_open(const char *name, int oflag, mode_t mode);-
name(共享内存名称) 必须以/开头,且中间不能有/。例如"/my_app_shm"。 -
oflag(行为标志位) 这是控制角色(创建者还是使用者)的核心参数。支持按位或(|)组合: -
O_RDWR:以可读可写方式打开。几乎所有场景都必须带上它,因为共享内存的目的就是读写。 -
O_RDONLY:只读模式打开。如果读进程绝对不会去修改数据,可以使用此标志。 -
O_CREAT:(创建者专属) 如果指定的名称不存在,则创建一个新的共享内存;如果已存在,则直接打开现有的。 -
O_EXCL:(配合 O_CREAT 使用) 确保“我是唯一的创建者”。如果加了这个标志且共享内存已存在,shm_open会直接报错返回-1。这在工程上非常有用:可以防止两个进程同时启动时,互相覆盖对方正在初始化的数据。 -
mode(权限掩码) 仅在带有O_CREAT标志时生效。通常填0666(即rw-rw-rw-),表示所有用户都可以读写。注意:实际权限还会受系统umask限制。如果不带O_CREAT(即单纯打开),填0即可。
第二阶段:分配物理内存 (ftruncate)
新创建的 POSIX 共享内存大小是 0 字节。如果不主动撑大它,后续任何写入操作都会直接导致 Bus error (SIGBUS) 崩溃。
int ret = ftruncate(int fd, off_t length);fd:第一阶段返回的文件描述符。length:你期望的共享内存大小(字节数)。通常是你要共享的 C++ 结构体的大小,例如sizeof(SharedData)。
注意: 只有带有
O_RDWR权限打开的 fd 才能调用ftruncate。只读端进程不需要(也不能)调用ftruncate,它只需获取数据即可。
第三阶段:映射到进程空间 (mmap)
这是最重要的一步,将 /dev/shm 中的虚拟文件映射到当前进程的虚拟地址空间,将 fd 转换成 C++ 可以直接操作的指针(void*)。
void* ptr = mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);-
addr(期望的映射地址) 通常传NULL(或nullptr)。表示让 Linux 内核自动为你寻找一块空闲的虚拟内存地址来映射。强行指定地址极易冲突导致失败。 -
length(映射长度) 必须与ftruncate设置的大小一致(或小于它)。 -
prot(内存保护标志) 决定了你对这块映射内存有什么操作权限。必须与shm_open的oflag匹配! -
PROT_READ | PROT_WRITE:可读可写。前提是前面shm_open用了O_RDWR。 -
PROT_READ:只读。如果违反限制尝试写入,会触发 Segmentation fault (SIGSEGV)。 -
flags(映射类型) **必须使用MAP_SHARED**。 -
MAP_SHARED:将对内存的修改同步给所有映射了该对象的进程。这是实现 IPC(进程间通信)的灵魂。 -
MAP_PRIVATE:写时复制(Copy-On-Write)。如果误用了这个,当前进程的写入只会修改自己私有的副本,其他进程根本看不到,共享内存彻底失效。 -
**
fd和offset**传入前面的fd。offset填0,表示从共享内存的开头进行映射。(如果 offset 不为 0,必须是操作系统页大小,通常为 4KB 的整数倍)。
第四阶段:释放与销毁
当业务逻辑结束,必须严谨地归还资源,否则会导致句柄和物理内存的双重泄漏。
munmap(void *addr, size_t length):解除内存映射 将mmap返回的指针和长度传给它。调用后,该进程不能再访问这个指针。close(int fd):关闭文件描述符 释放进程的文件描述符配额(Linux 下单进程默认通常是 1024 个)。mmap成功后,内核内部会维护引用计数,所以你可以(且应该)尽早调用close(fd),这不会影响已经建立的mmap指针的读写。shm_unlink(const char *name):销毁系统中的共享内存 删除/dev/shm目录下的名字,断开物理内存与名字的关联。通常只有在确认所有进程都不再使用这块内存时,由主干进程调用一次即可。
脚本维护
有,而且比 System V 的 ipcrm 简单直观得多。因为 Linux 将 POSIX 共享内存(shm_open)完美融合到了虚拟文件系统中,它们全部作为普通文件挂载在 /dev/shm/ 目录下。
因此,你不需要学习任何专属的新命令,所有标准的 Linux 文件管理命令都可以直接用来维护和调试 POSIX 共享内存。
以下是 POSIX 共享内存与常用 bash 命令的对应清单:
1. 批量清理所有共享内存 (等价于 ipcrm -a shm)
在 bash 脚本中,直接使用 rm 命令清空目录即可。
# 清理所有 POSIX 共享内存(请谨慎,会影响其他正常运行的程序)rm -rf /dev/shm/*
# 更安全的做法:只清理你自己程序专属前缀的共享内存rm -f /dev/shm/my_cpp11_shm_*验证方式: 运行 ls -a /dev/shm,确认目标文件已不存在。
2. 查看当前有哪些共享内存 (等价于 ipcs -m)
使用 ls 可以直接看到共享内存的名称、权限、拥有者和当前大小。
ls -lh /dev/shm输出示例: -rw-r--r-- 1 user user 260 Nov 2 13:40 my_cpp11_shm
3. 查看共享内存的总额度与占用率
/dev/shm 实际上是一个 tmpfs 内存文件系统,默认大小通常是物理内存的一半。可以使用 df 命令查看是否爆满。
df -h /dev/shm4. 直接查看共享内存里的数据 (调试利器)
这是 POSIX 比 System V 强悍的地方。由于它被视作文件,你可以直接用终端工具“偷窥”里面的二进制数据,这对于排查 C++ 结构体对齐问题或脏数据非常有用。
# 以十六进制和 ASCII 码双列对照查看数据hexdump -C /dev/shm/my_cpp11_shm终极清理脚本:揪出占用者并强制回收
如果你发现用 rm 删除了 /dev/shm/ 下的文件,但 df -h /dev/shm 空间依然没有恢复,说明有“幽灵进程”正在占用。你可以用以下脚本找出并清理它们:
#!/bin/bash# 找出所有已被删除但仍被进程占用的 /dev/shm 内存区块lsof | grep '/dev/shm' | grep '(deleted)'
# 终端会输出类似:# my_server 12345 user 3u REG 0,20 260 98765 /dev/shm/my_shm (deleted)# 这里的 12345 就是死锁该内存的进程 PID。直接 kill 掉它即可彻底释放物理内存:# kill -9 12345