第 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 模型 |
|
§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 引用计数删除 |
|
§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 |
二、详细笔记
54.1 shm_open / shm_unlink 模型
What:shm_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 引用计数删除语义
What:shm_unlink 立即删名字;引用(mmap 计数)归零才真销毁。
Why:POSIX IPC 三件套统一语义——避免 SysV 「最后用户忘记删」的问题。
How:
| 操作 | 文件名 | 对象 |
|---|---|---|
|
存在 |
refcount = N+1(mmap 计数) |
|
仍存在 |
refcount = N-1 |
|
立即删除 |
对象保留到所有 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.c 用 shmp→cnt + shmp→buf 简单结构,不嵌套指针——是「反例」(也是好习惯)。
查看 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
五、重点与易错点
-
shm_open 返回 fd 但不是普通文件——必须在 tmpfs;不能用普通文件 I/O(read/write/lseek);必须 mmap 后用指针访问。
-
必须 ftruncate 设大小——shm_open 创建的对象初始 0;不 ftruncate 直接 mmap 看不到任何字节。
-
扩展后需 munmap + mmap——ftruncate 扩大的部分不自动让现有映射看到;必须重新 mmap 或用 mremap。
-
mmap 后 close(fd) 安全——fd 仅用于 mmap;mmap 已建立映射;fd 关了不影响映射。
-
shm_unlink 不影响现有映射——unlink 后已 mmap 的进程仍可用;refcount=0 才真销毁。
-
close-on-exec 默认开启——shm_open 返回的 fd 默认 FD_CLOEXEC;exec 自动关闭。
-
Linux shm 在 /dev/shm——tmpfs 文件系统;sticky 位——非特权只能 unlink 自己的对象。
-
tmpfs 默认 256 MB——大量 shm 需调整;
mount -o remount,size=N。 -
POSIX shm vs SysV shm 选择——新项目 Linux 选 POSIX(API 一致 + 可调大小);多 UNIX 选 SysV。
-
POSIX shm vs shared file mapping——不需持久化用 POSIX shm(无磁盘开销);需跨重启持久化用 file mapping。
-
共享内存必须同步——shm 不自动同步;用 POSIX sem(第 53 章)或 pthread mutex 协调。
-
shm 内指针用 offset——绝对指针在跨进程会崩溃;链表/树用 offset 串起来。
-
shm_open 不支持 O_WRONLY——只支持 O_RDONLY 与 O_RDWR;这与 open 不同。
-
POSIX shm 不被 unlink 前一直存在——内核持久;不像 mmap anon 是「fork 后父子共享」;shm 跨 fork/exec 持久。
-
跨章衔接:第 48 章 SysV shm(key+id);第 49 章 mmap(基础);第 53 章 POSIX sem(同步 shm 的工具);第 47 章 SysV sem(与 SysV shm 配对);第 51 章 POSIX IPC 概述(API 模型)。