第 54 章 POSIX 共享内存 (POSIX Shared Memory)

      +

      核心结论

      • POSIX shm 定位:避免 SysV shm 的「key+id」非文件模型;避免共享 file mapping 必须创建磁盘文件的开销——纯内存、无文件 I/O 开销。

      • 两步使用法:shm_open()(返回 fd,类似 open)+ mmap(MAP_SHARED)(映射到地址空间)——所有 POSIX shm 操作基于 fd。

      • 核心 API:shm_open / shm_unlink / ftruncate / mmap / munmap / close / fstat / fchmod / fchown。

      • 创建后必须 ftruncate:shm_open 返回的对象初始长度 0——必须 ftruncate(fd, size) 设大小后才能 mmap;扩展后新字节自动初始化 0。

      • close-on-exec:shm_open 返回的 fd 默认 FD_CLOEXEC——exec 自动关闭;mmap 后可 close(fd)(fd 仅用于 mmap)。

      • Linux 实现:kernel 2.4+ 支持;用 tmpfs(/dev/shm);总大小受 tmpfs 大小限制(默认 256 MB);可用 mount -o remount,size=N 调整。

      • vs SysV shm:POSIX 优点——文件模型一致(用 fstat/ftruncate)、可 ftruncate 调整大小;POSIX 缺点——可移植性略差(Linux 2.4+)。

      • 共享内存三件套:SysV shm(key+id)、shared file mapping(有 disk file)、POSIX shm(无 disk file)——共同点:fast IPC、需 sem 同步、用 offset 不用绝对指针。

      本章主旨

      POSIX 共享内存是第 48 章 SysV 共享内存的现代替代——核心改进是「文件模型」(shm_open 返回 fd)+「可调整大小」(ftruncate)。读者需要建立三组对比:(1) POSIX shm vs SysV shm——POSIX 用 fd 一致性更好、SysV 用 key+id 较老;(2) POSIX shm vs shared file mapping——POSIX 无磁盘开销、file mapping 持久化;(3) mmap(MAP_SHARED) 通用——POSIX shm + shared file mapping 都用 mmap 映射。共享内存必须配合 sem(第 53 章)做同步。

      一、核心概念

      本章围绕 6 个核心概念展开:shm_open 模型、ftruncate 设大小、mmap 映射、引用计数删除、三种 shm 对比、offset 存储。

      概念 定义 + 重要性 实现提示

      shm_open 模型

      shm_open(name, oflag, mode) 返回 fd;类似 open 但不创建磁盘文件;oflag 含 O_CREAT/O_EXCL/O_RDONLY/O_RDWR/O_TRUNC;mode 在 O_CREAT 时设置

      §54.2;表 54-1;close-on-exec 默认开启;返回的 fd 可用 fstat/ftruncate/fchmod

      ftruncate 设大小

      新创建的对象初始长度 0——必须 ftruncate(fd, size) 设大小后才能 mmap;扩展后新字节自动 0;可后续再次 ftruncate 调整

      §54.2;ftruncate + mmap 是 POSIX shm 的标准两步;扩展后再 mmap 需 munmap + mmap 或 mremap

      mmap 映射

      `mmap(NULL, size, PROT_READ

      PROT_WRITE, MAP_SHARED, fd, 0)` 把 shm 对象映射到进程地址空间;返回指针

      §54.3;mmap 后可 close(fd)(fd 仅需用于 mmap);mmap 与 shm_open 解耦

      shm_unlink 引用计数删除

      shm_unlink(name) 立即删名字;引用归零(所有进程 munmap)才真销毁;不影响已建立的映射

      §54.4;与 SysV IPC_RMID 显式删除不同;unlink 后再 shm_open 创建新对象

      vs SysV shm vs shared file mapping

      POSIX 优点:文件模型、可调大小、fstat;缺点:可移植性差;shared file mapping 优点:可持久化跨重启

      §54.5;新应用选 POSIX shm 或 shared file mapping;老应用维持 SysV

      offset 而非绝对指针

      shm 在不同进程映射到不同地址——结构内的指针用 offset(相对 baseaddr);dereference 时 baseaddr + offset

      二、详细笔记

      Whatshm_open 返回 fd(类似 open);shm_unlink 删除(引用归零销毁)。

      Why:与文件模型一致——共享内存对象可用 fstat/ftruncate/fchmod 等通用 fd 操作。

      How

      // 摘自《The Linux Programming Interface》 第 54 章
      #include <fcntl.h>
      #include <sys/stat.h>
      #include <sys/mman.h>
      
      int shm_open(const char *name, int oflag, mode_t mode);
      int shm_unlink(const char *name);
      
      /* 创建 */
      int fd = shm_open("/demo", O_CREAT|O_RDWR, 0660);
      if (fd == -1) errExit("shm_open");
      ftruncate(fd, 10000);
      
      /* 打开已存在 */
      int fd = shm_open("/demo", O_RDWR, 0);   /* mode 忽略但需传 */
      
      /* 删除 */
      shm_unlink("/demo");

      oflag(表 54-1):O_CREAT、O_EXCL、O_RDONLY、O_RDWR、O_TRUNC。

      When:(1) 无关进程间共享内存——首选 POSIX shm;(2) 需调整大小——ftruncate 多次;(3) 守护进程创建后立即 unlink(避免名字残留)。

      Example:第 54 章 pshm_create -c /demo_shm 10000 创建 10 KB 共享内存;ls -l /dev/shm 看到 demo_shm

      54.2 ftruncate 与 mmap 两步法

      What:POSIX shm 必须先 ftruncate 设大小,再 mmap 映射。

      Why:shm_open 创建的对象初始长度 0——不 ftruncate 直接 mmap 看不到任何字节。

      How

      // 摘自《The Linux Programming Interface》 第 54 章
      /* 完整两步法 */
      int fd = shm_open("/demo", O_CREAT|O_RDWR, 0660);
      if (fd == -1) errExit("shm_open");
      
      if (ftruncate(fd, 10000) == -1)         /* 必须设大小 */
          errExit("ftruncate");
      
      char *addr = mmap(NULL, 10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
      if (addr == MAP_FAILED) errExit("mmap");
      
      /* mmap 后 fd 可关——mmap 已建立映射 */
      close(fd);
      
      /* 写数据 */
      strcpy(addr, "hello, shm");

      When:(1) 创建新对象——shm_open + ftruncate + mmap;(2) 调整大小——ftruncate 后需 munmap + mmap 重新映射;(3) mmap 后 close(fd) 安全——fd 仅用于 mmap。

      Example:第 54 章 pshm_write.c open 现有对象 → ftruncate(fd, strlen(argv[2])) → mmap → memcpy(addr, argv[2], len)

      54.3 引用计数删除语义

      Whatshm_unlink 立即删名字;引用(mmap 计数)归零才真销毁。

      Why:POSIX IPC 三件套统一语义——避免 SysV 「最后用户忘记删」的问题。

      How

      操作 文件名 对象

      shm_open

      存在

      refcount = N+1(mmap 计数)

      munmap

      仍存在

      refcount = N-1

      shm_unlink

      立即删除

      对象保留到所有 munmap

      refcount = 0

      已删除

      对象销毁(ftruncate 之前的 0)

      When:(1) 进程退出——自动 munmap;(2) unlink 不影响已 mmap 的进程;(3) 守护进程创建后立即 unlink——避免名字残留。

      Example:服务器 shm_open("/x")shm_unlink("/x") → mmap → 服务;客户端 shm_open("/x")(重建空对象,但 unlink 时 refcount=0)→ 失败。所以通常服务器 unlink 必须在所有客户端都 mmap 之后,或根本不 unlink。

      54.4 与 SysV shm、shared file mapping 对比

      What:三种进程间共享内存技术——SysV shm、shared file mapping、POSIX shm。

      Why:理解三者取舍——决定项目用哪个。

      How

      维度 SysV shm shared file mapping POSIX shm

      标识

      key + id(shmget)

      文件名 + fd(open)

      名字 + fd(shm_open)

      持久化

      内核持久

      文件持久(跨重启)

      内核持久(tmpfs)

      大小调整

      固定(shmget 时)

      ftruncate 灵活

      ftruncate 灵活

      实现

      tmpfs(内核)

      真实磁盘文件

      tmpfs(无磁盘文件)

      可移植性

      SUSv3 强制

      SUSv3 + POSIX

      SUSv3 可选

      操作 API

      shmctl(专用)

      fstat/ftruncate/fchmod(通用)

      fstat/ftruncate/fchmod(通用)

      文件系统位置

      (无)

      用户指定路径

      /dev/shm/

      Linux 支持

      早期

      早期

      kernel 2.4+

      When:(1) 新项目 Linux 为主 + 不需持久化——POSIX shm;(2) 需跨重启持久化——shared file mapping;(3) 多 UNIX 平台——SysV shm(最可移植);(4) POSIX shm + sem 是现代 Linux 首选组合。

      Example:第 48 章 svshm_xfr 用 SysV shm;第 49 章 t_mmap 用 shared file mapping;本章 pshm_* 用 POSIX shm——同一应用三种实现。

      54.5 offset 而非绝对指针

      What:shm 在不同进程可能映射到不同虚拟地址——共享内存数据结构用 offset 而非绝对指针。

      Why:避免「A 进程的指针在 B 进程无效」导致崩溃。

      How

      // 摘自第 48 章 §48.6 模式(POSIX shm 同理)
      struct shm_data {
          int cnt;
          off_t next_off;            /* 链表下一节点偏移(不是指针) */
          char buf[BUF_SIZE];
      };
      
      /* 写入:相对 baseaddr 的偏移 */
      struct shm_data *base = (struct shm_data *)shm_addr;
      base->next_off = (char *)target - (char *)base;
      
      /* 读取:baseaddr + offset dereference */
      struct shm_data *next = (struct shm_data *)((char *)base + base->next_off);

      When:(1) shm 内的链表/树——用 offset 串起来;(2) shm 内的指针字段——绝对指针 = 数据损坏;(3) shm 内字符串指针——也用 offset。

      Example:第 48 章 svshm_xfr_writer.cshmp→cnt + shmp→buf 简单结构,不嵌套指针——是「反例」(也是好习惯)。

      54.6 tmpfs 与 /dev/shm

      What:Linux 把 POSIX shm 对象放在 tmpfs 虚拟文件系统,挂在 /dev/shm

      Why:tmpfs 是 RAM-backed 文件系统——shm 操作无磁盘 I/O 开销。

      How

      查看 POSIX shm

      ls -l /dev/shm/ # -rw------- 1 user users 10000 Jun 20 11:31 demo_shm

      查看 tmpfs 容量

      df -h /dev/shm

      调整 tmpfs 大小(需要 root)

      mount -o remount,size=1G /dev/shm

      挂载点有 sticky 位——非特权只能删自己的

      ls -ld /dev/shm # drwxrwxrwt …​ /dev/shm

      *When*:(1) 调试——`ls /dev/shm/` 看 shm 残留;(2) tmpfs 满——`df` 看是否超限;(3) 大量 shm——调整 tmpfs 大小(默认 256 MB)。
      
      *Example*:第 54 章示例——`./pshm_create -c /demo_shm 10000; ls -l /dev/shm` 看到 `demo_shm` 10000 字节。
      
      == 三、关键图表
      
      [NOTE]
      .非可视化条目(API / 对比)
      ====
      [cols="1,3", options="header"]
      |===
      | 类别 | 内容
      
      | 核心 API
      | `shm_open(name, oflag, mode)` → fd;`shm_unlink(name)`;`ftruncate(fd, size)`;`mmap(..., MAP_SHARED, fd, 0)`;`munmap`;`close(fd)`
      
      | shm_open oflag
      | O_CREAT、O_EXCL、O_RDONLY、O_RDWR、O_TRUNC
      
      | 默认 fd 标志
      | close-on-exec(FD_CLOEXEC);exec 自动关闭
      
      | 初始大小
      | 0;必须 ftruncate 设大小后才能 mmap
      
      | 扩展行为
      | 扩展后新字节自动 0;ftruncate 后需 munmap + mmap
      
      | 文件系统
      | Linux tmpfs `/dev/shm`;sticky 位;默认 256 MB
      
      | 调整大小
      | `mount -o remount,size=N /dev/shm`(root)
      
      | 删除
      | `shm_unlink` 立即删名字;引用归零销毁;不影响现有映射
      
      | 删除命令行
      | `rm /dev/shm/<name>`(非特权只能删自己的)
      
      | 通用 fd 操作
      | fstat、ftruncate、fchmod、fchown 都可用
      
      | Linux 支持
      | kernel 2.4+
      
      | vs SysV shm
      | POSIX 用 fd 一致性好;可调大小;可移植性差
      
      | vs shared file mapping
      | POSIX 无磁盘文件;file mapping 跨重启持久化
      
      | offset 存储
      | 共享内存内指针用 offset(相对 baseaddr);绝对指针 = 崩溃
      |===
      ====
      
      == 四、思维导图
      
      [source,mermaid]

      mindmap root第 54 章 POSIX 共享内存 shm_open 模型 返回 fd 类似 open close on exec ftruncate 设大小 mmap 映射 tmpfs dev shm 虚拟文件系统 sticky 位 默认 256 MB mount remount 调整 ftruncate mmap 初始大小 0 必须 ftruncate mmap 后 close fd 扩展新字节 0 shm_unlink 立即删名字 引用归零销毁 不影响现有映射 vs SysV IPC_RMID 三种共享内存 SysV shm key id shared file mapping POSIX shm fd POSIX 现代首选 SysV 最可移植 offset 存储 不同进程不同地址 指针用 offset baseaddr 相对 链表树用 offset

      五、重点与易错点

      1. shm_open 返回 fd 但不是普通文件——必须在 tmpfs;不能用普通文件 I/O(read/write/lseek);必须 mmap 后用指针访问。

      2. 必须 ftruncate 设大小——shm_open 创建的对象初始 0;不 ftruncate 直接 mmap 看不到任何字节。

      3. 扩展后需 munmap + mmap——ftruncate 扩大的部分不自动让现有映射看到;必须重新 mmap 或用 mremap。

      4. mmap 后 close(fd) 安全——fd 仅用于 mmap;mmap 已建立映射;fd 关了不影响映射。

      5. shm_unlink 不影响现有映射——unlink 后已 mmap 的进程仍可用;refcount=0 才真销毁。

      6. close-on-exec 默认开启——shm_open 返回的 fd 默认 FD_CLOEXEC;exec 自动关闭。

      7. Linux shm 在 /dev/shm——tmpfs 文件系统;sticky 位——非特权只能 unlink 自己的对象。

      8. tmpfs 默认 256 MB——大量 shm 需调整;mount -o remount,size=N

      9. POSIX shm vs SysV shm 选择——新项目 Linux 选 POSIX(API 一致 + 可调大小);多 UNIX 选 SysV。

      10. POSIX shm vs shared file mapping——不需持久化用 POSIX shm(无磁盘开销);需跨重启持久化用 file mapping。

      11. 共享内存必须同步——shm 不自动同步;用 POSIX sem(第 53 章)或 pthread mutex 协调。

      12. shm 内指针用 offset——绝对指针在跨进程会崩溃;链表/树用 offset 串起来。

      13. shm_open 不支持 O_WRONLY——只支持 O_RDONLY 与 O_RDWR;这与 open 不同。

      14. POSIX shm 不被 unlink 前一直存在——内核持久;不像 mmap anon 是「fork 后父子共享」;shm 跨 fork/exec 持久。

      15. 跨章衔接:第 48 章 SysV shm(key+id);第 49 章 mmap(基础);第 53 章 POSIX sem(同步 shm 的工具);第 47 章 SysV sem(与 SysV shm 配对);第 51 章 POSIX IPC 概述(API 模型)。