第 45 章 System V IPC 概述 (Introduction to System V IPC)
核心结论
-
SysV IPC 三件套:message queues (消息队列)、semaphores (信号量)、shared memory (共享内存)——三种机制功能差异大,但 API 相似:都通过「key 找到 id」再操作。
-
核心 API 形态:每种机制都有
xxxget(key, …)创建/打开、xxxctl(id, cmd, …)控制、xxxop/ 直接访问。详见表 45-1:msgget/msgctl/msgsnd/msgrcv、semget/semctl/semop、shmget/shmctl/shmat/shmdt。 -
SysV key 与 id:key 是用户态的「名字」;id 是内核态的「handle」;多个进程用同一 key → 同一 id;key 通过
ftok(pathname, proj)生成(基于 i-node + 设备号 + proj)或用IPC_PRIVATE(一定新建)。 -
关联数据结构 ipc_perm:每个 SysV 对象都有
ipc_perm子结构记录 owner/creator uid/gid/permissions/mode;权限检查按 effective UID/组匹配 owner 或 creator。 -
持久性:SysV IPC 都是 kernel-persistent——创建后一直存在直到显式
IPC_RMID或系统重启;不像 fd 关闭就丢。 -
ipcs / ipcrm / /proc/sysvipc:
ipcs列全部对象;ipcrm -[qsm] key|id删;/proc/sysvipc/{msg,sem,shm}提供无权限过滤的列表;MSG_INFO/SEM_INFO/SHM_INFO+MSG_STAT/SEM_STAT/SHM_STAT是编程接口。
|
本章主旨
System V IPC 三件套是「老牌但有点怪」的 IPC 机制——70 年代末出现,83 年随 SysV 进入主流,标准化为 XSI IPC。三者 API 形态高度相似(get/ctl/op),方便学习但设计有缺陷:没有 fd 引用计数(决定何时删除对象难)、接口与 UNIX I/O 不一致。本章先把 45-48 三章的共性梳理清楚——key/id、 |
一、核心概念
本章围绕 6 个核心概念展开:从 SysV IPC 三件套、API 表、key 与 id、关联数据结构、权限规则、ipcs/ipcrm/limits。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
SysV IPC 三件套 |
消息队列(msgget/msgsnd/msgrcv)、信号量(semget/semop/semctl)、共享内存(shmget/shmat/shmdt/shmctl)——1970s 末出,SysV 推广,XSI 标准化 |
§45.0;常在编译时 |
get + ctl + op 形态 |
每种机制都有 |
§45.1;id 是内核态 handle; |
key 与 id(关键映射) |
key 是用户态 name(int);id 是内核态 handle(int); |
§45.2; |
ipc_perm 子结构 + 权限 |
每 SysV 对象有 |
§45.3;可 IPC_SET 改 uid/gid/mode;执行 get() 时还要做一次 mode 检查(要求至少当前进程的 mode 是对象 mode 的子集) |
kernel-persistent 持久性 |
SysV IPC 一直存在直到显式 |
§45.1;msg/sem 立即删除;shm 标记为「待删除」,到 nattch=0 才真删 |
ipcs / ipcrm / /proc/sysvipc |
|
§45.6-7;MSG_INFO 查 maxid + msginfo 结构,MSG_STAT 按 index 查 id |
二、详细笔记
45.1 API 概览
What:三机制 + 各自 API 形态(get/ctl/op);表 45-1 一目了然。
Why:让读者快速建立 API 心智模型——以后查具体 syscall 不必重新学。
How——表 45-1(精简):
| 操作 | 消息队列 | 信号量 | 共享内存 |
|---|---|---|---|
头文件 |
|
|
|
数据结构 |
|
|
|
创建/打开 |
|
|
|
关闭 |
— |
— |
|
控制 |
|
|
|
操作 |
|
|
直接访问 attach 后的内存 |
关键点:
-
get 与 open 的区别:
get(key, IPC_CREAT)含义 = 「key 已存在则取 id,否则新建」;加IPC_EXCL= 「必须新建,否则失败 EEXIST」。这与open(O_CREAT|O_EXCL)类似但更复杂。 -
id 是属性(不是 process attribute)——同一对象所有进程看到同一 id;进程退出不会自动删除(kernel-persistent)。
-
没有引用计数——内核不知道谁在用,决定「对象可删时间」是应用的事。
When:写需要 kernel-persistent 共享状态的程序时用 SysV IPC。
45.2 IPC Keys
What:key 是 SysV 对象的「用户态名字」;xxxget(key, …) 用 key 找/创建对象。
Why:跨进程定位同一对象——通过 pathname + proj 派生。
How——三方法生成 key:
// 摘自《The Linux Programming Interface》 第 45 章
#include <sys/ipc.h>
key_t ftok(char *pathname, int proj);
/* 1. 随机整数(不推荐——可能撞车) */
id = msgget(0x1234, IPC_CREAT | 0660);
/* 2. IPC_PRIVATE(一定新建) */
id = msgget(IPC_PRIVATE, 0660); /* 注意:无需 IPC_CREAT */
/* 3. ftok 派生(推荐) */
key = ftok("/some/file", 'x'); /* pathname 必须存在;proj 只用低 8 bit */
id = msgget(key, IPC_CREAT | 0660);
ftok 算法(Linux):proj(8bit) | dev(8bit) | ino(16bit)。同 path + proj → 同 key;不同 path + proj → 极小概率撞 key。
注意:
. ftok 依赖 i-node——不要先 unlink 再创建(i-node 会变,key 就变了)。
. proj=0 在 SUSv3 行为未定义;AIX 5.1 返回 -1;建议 1..255。
. IPC_PRIVATE 用法特殊:get 时不需要 IPC_CREAT/EXCL;总创建新对象——但子进程 fork 继承 id(§45.2 例)。
When:跨进程协作时用 ftok;server 启动时用 IPC_PRIVATE 创建 → 把 id 写到文件或环境变量让 client 读。
45.3 ipc_perm + 权限
What:每 SysV 对象有 struct ipc_perm 子结构;权限按 owner/creator 比对。
Why:决定哪些进程能操作对象。
How——权限检查顺序(来自 §45.3):
-
进程有
CAP_IPC_OWNER→ 全权。 -
进程 effective UID 匹配 owner UID 或 creator UID → 按 mode 的 owner 位(高 3 bit)。
-
进程 effective GID 或 supplementary GID 匹配 owner GID 或 creator GID → 按 mode 的 group 位(中 3 bit)。
-
否则 → 按 mode 的 other 位(低 3 bit)。
ipc_perm 结构:
// 摘自《The Linux Programming Interface》 第 45 章
struct ipc_perm {
key_t __key; /* get() 时传进来的 key */
uid_t uid, gid; /* owner — 可 IPC_SET 改 */
uid_t cuid, cgid; /* creator — 不可改 */
mode_t mode; /* 9-bit permissions */
unsigned long __seq; /* 序列号(id 生成用) */
};
mode 是文件 mode:owner rwx(高 3) + group rwx(中 3) + other rwx(低 3);r=读/写/操作,w=改/写/操作;x 位常被忽略。
修改 owner:
struct shmid_ds ds;
shmctl(id, IPC_STAT, &ds); /* 取内核拷贝 */
ds.shm_perm.uid = newuid;
shmctl(id, IPC_SET, &ds); /* 写回 */
删除:xxxctl(id, IPC_RMID, NULL)——msg/sem 立即删,shm 标记待删(待 nattch=0)。
When:写 SysV server 必读——默认创建对象是 0600(仅自己)。
45.4 client-server 协议
What:server 用 IPC_CREAT 创建对象;client 不带 IPC_CREAT 打开;如果 client 用了 IPC_CREAT|IPC_EXCL 而对象存在则失败 EEXIST——可用于「old server restart 检测」。
Why:处理 server 崩溃重启后「对象是 old server 留的」的情况。
How——server 启动模式(来自 §45.4 Listing 45-1):
// 摘自《The Linux Programming Interface》 第 45 章
while ((msqid = msgget(key, IPC_CREAT | IPC_EXCL | MQ_PERMS)) == -1) {
if (errno == EEXIST) { /* 已有——可能是 old server 的 */
msqid = msgget(key, 0); /* 取旧 id */
msgctl(msqid, IPC_RMID, NULL); /* 删掉 */
printf("Removed old message queue (id=%d)\n", msqid);
/* 下次循环重建 */
} else errExit("msgget");
}
/* 现在 msqid 是新建的 */
Why this works:id 生成用 seq 计数器,id=index + seq*SEQ_MULTIPLIER;删旧建新 → seq 增 → 新 id 不同——持有旧 id 的 client 后续操作会失败 EIDRM(或 EINVAL)——能感知 server 重启。
When:写 SysV server 的「健壮启动」必用。
45.5 SysV IPC id 生成算法
What:内核维护每类机制的 ipc_ids 结构:entries[] 数组(每个 in-use slot 指向 xxxid_ds)、max_id/in_use/size/seq 计数。
Why:id 几乎不重用(即使 key 相同),让 client 进程能通过 id 失效检测 server 重启。
How——id 计算(来自 §45.5):
identifier = index + xxx_perm.__seq * SEQ_MULTIPLIER;
/* SEQ_MULTIPLIER = 32768 (IPCMNI) */
index = identifier % SEQ_MULTIPLIER; /* 反查 index */
两个错误: . index 槽位空 → EINVAL。 . index 槽位有但 seq 不匹配 → EIDRM(对象已被删并重用——理论上几乎不会发生,除非 65535 次创建)。
When:调试「对象失效」时理解这两个错误的含义。
45.6 ipcs 与 ipcrm
What:ipcs(1) 列当前系统上的所有 SysV 对象(按读权限过滤);ipcrm(1) 删。
Why:命令行排查。
How:
$ ipcs # 全列
$ ipcs -l # 列限制
$ ipcs -q # 仅消息队列
$ ipcrm -Q key # 按 key 删消息队列
$ ipcrm -q id # 按 id 删
$ ipcrm -S key -s id # 信号量同理
$ ipcrm -M key -m id # 共享内存同理
When:清理服务器崩溃留下的 SysV 对象。
45.7 /proc/sysvipc 与编程 API
What:/proc/sysvipc/{msg,sem,shm} 总列出全部对象(无读权限过滤);MSG_INFO/STAT 是 Linux 扩展编程接口。
Why:脚本扫描 + 不依赖 ipcs。
How——MSG_INFO + MSG_STAT 模式(来自 §45.6 Listing 46-6 风格):
// 摘自《The Linux Programming Interface》 第 45 章
#define _GNU_SOURCE
struct msginfo info;
int maxind = msgctl(0, MSG_INFO, (struct msqid_ds *)&info); /* 拿 maxind */
for (int i = 0; i <= maxind; i++) {
struct msqid_ds ds;
int msqid = msgctl(i, MSG_STAT, &ds);
if (msqid == -1) {
if (errno != EINVAL && errno != EACCES) errMsg("...");
continue; /* 跳过空 slot / 无权限 slot */
}
/* 打印 msqid, key, qnum, ... */
}
When:写 SysV 监控工具(ipcs 类似);脚本扫描。
45.8 IPC Limits
What:kernel 通过 /proc/sys/kernel/{msgmni, msgmax, msgmnb, shmmni, shmmax, shmall, sem} 限制 SysV IPC;ipcs -l 查看。
Why:防止资源耗尽;安装大系统时可能要改。
How:
$ ipcs -l # 查看
$ cat /proc/sys/kernel/msgmni
748
$ echo 4096 | sudo tee /proc/sys/kernel/msgmni # 改(运行时)
$ cat /proc/sys/kernel/sem
250 32000 32 128 # SEMMSL SEMMNS SEMOPM SEMMNI
When:装分布式系统、消息队列、共享内存池时调优。
三、关键图表
|
非可视化条目(SysV IPC 关键 syscall)
|
|
SysV vs POSIX IPC
|
四、思维导图
mindmap
root((第 45 章 SysV IPC 总论))
三件套
消息队列
信号量
共享内存
get ctl op 形态
msgget semget shmget
msgctl semctl shmctl
msgsnd msgrcv semop shmat
key id 映射
key 用户态名
id 内核态 handle
ftok 派生
IPC_PRIVATE 总新建
ipc_perm 权限
owner creator 区分
CAP_IPC_OWNER
按 mode 查
kernel-persistent
显式 IPC_RMID 才删
shm 待 nattch 0 删
client-server 协议
server IPC_CREAT
client 不带 CREAT
IPC_EXCL 检测重启
id 生成
id = index + seq * 32768
seq 几乎不重用
监控
ipcs ipcrm
proc sysvipc
MSG INFO STAT
五、重点与易错点
-
get() 不一定是新建——默认只是「找」;加 IPC_CREAT 才在没找到时新建;加 IPC_EXCL 才「必须新建」。
-
IPC_PRIVATE 总创建新对象——无需 IPC_CREAT/EXCL;适合 server 启动时创建,子进程通过 fork 继承 id。
-
id 不是 fd——id 是对象属性(不是 process attribute);没有引用计数;进程退出不会自动关闭对象。
-
kernel-persistent——一旦创建一直存在;这意味着崩溃的 server 会留下「孤儿」对象;用
ipcs清理。 -
ipc_perm 的 owner/creator 区分——uid 可 IPC_SET 改;cuid 是创建者 ID 不可改;权限检查按 owner/creator 匹配 effective UID。
-
get() 内部做权限检查——即使只想「打开」对象,flags 中的 mode 也必须与对象 mode 兼容,否则 EACCES。
-
shm 删除延迟——IPC_RMID 标记为「待删」,到所有进程 shmdt 才真正删除;其他 UNIX 多立即删(不一致)。
-
MSG_INFO 返回 maxid,MSG_STAT 按 index 返 id——编程接口模式(§45.7)可用于写 ipcs 替代品。
-
ftok 必须用现存文件——临时文件 / 被 unlink 过的文件会得到不同 i-node → 不同 key。
-
ipcs(1) 按读权限过滤;/proc/sysvipc 总列全部——后者更适合脚本扫描。
-
SysV IPC 的「no ref count」问题——决定「对象该删了吗」需要应用层协议(如 server restart 的 IPC_RMID-EXCL 循环)。
-
Linux 上 ipc(2) 是统一入口(除 IA-64/Alpha)——应用不应直接调用 ipc(2),用 glibc 封装的 msgget/semget/shmget。
-
SysV IPC 的 fd 化趋势——通过把对象「wrap」成 anonymous fd,POSIX 设计更胜一筹。
-
跨章衔接:第 44 章 pipe/FIFO;第 46 章 SysV msg 详细;第 47 章 SysV sem 详细;第 48 章 SysV shm 详细;第 49 章 mmap;第 51-54 章 POSIX IPC;第 56-61 章 socket。