1834 字
9 分钟
共享内存

这个博客主要介绍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 的整数倍)。

第四阶段:释放与销毁#

当业务逻辑结束,必须严谨地归还资源,否则会导致句柄和物理内存的双重泄漏。

  1. munmap(void *addr, size_t length):解除内存映射 将 mmap 返回的指针和长度传给它。调用后,该进程不能再访问这个指针。
  2. close(int fd):关闭文件描述符 释放进程的文件描述符配额(Linux 下单进程默认通常是 1024 个)。mmap 成功后,内核内部会维护引用计数,所以你可以(且应该)尽早调用 close(fd),这不会影响已经建立的 mmap 指针的读写。
  3. 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 命令清空目录即可。

Terminal window
# 清理所有 POSIX 共享内存(请谨慎,会影响其他正常运行的程序)
rm -rf /dev/shm/*
# 更安全的做法:只清理你自己程序专属前缀的共享内存
rm -f /dev/shm/my_cpp11_shm_*

验证方式: 运行 ls -a /dev/shm,确认目标文件已不存在。

2. 查看当前有哪些共享内存 (等价于 ipcs -m)#

使用 ls 可以直接看到共享内存的名称、权限、拥有者和当前大小。

Terminal window
ls -lh /dev/shm

输出示例: -rw-r--r-- 1 user user 260 Nov 2 13:40 my_cpp11_shm

3. 查看共享内存的总额度与占用率#

/dev/shm 实际上是一个 tmpfs 内存文件系统,默认大小通常是物理内存的一半。可以使用 df 命令查看是否爆满。

Terminal window
df -h /dev/shm

4. 直接查看共享内存里的数据 (调试利器)#

这是 POSIX 比 System V 强悍的地方。由于它被视作文件,你可以直接用终端工具“偷窥”里面的二进制数据,这对于排查 C++ 结构体对齐问题或脏数据非常有用。

Terminal window
# 以十六进制和 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
共享内存
https://blog.xiaobaizhang.top/posts/shared_memory/
作者
张小白
发布于
2026-11-02
许可协议
CC BY-NC-SA 4.0