第 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/sysvipcipcs 列全部对象;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、ipc_perm、权限规则、client-server 协议。下三章分别讲三种机制的具体 syscall 细节。读者应重点理解「get() 返回的不一定是新建的 id」——这是 SysV IPC 与 fd 最大的语义差异。

      一、核心概念

      本章围绕 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;常在编译时 -D_XOPEN_SOURCE=600-D_GNU_SOURCE 启用;Linux 用 ipc(2) 单一 syscall 入口实现(除 IA-64/Alpha)

      get + ctl + op 形态

      每种机制都有 xxxget(key, …​) 创建/打开、xxxctl(id, cmd, …​) 控制(含 IPC_RMID 删除)、xxxop 实际操作——表 45-1 一站对照

      §45.1;id 是内核态 handle;shmat/shmdt 仅 shm 有,因为 shm 是把内存 attach 到进程

      key 与 id(关键映射)

      key 是用户态 name(int);id 是内核态 handle(int);xxxget(key, …​) 完成 key → id 映射——相同 key 永远映射到同一 id;key 通过 ftok(pathname, proj)IPC_PRIVATE(一定新建)

      §45.2;IPC_PRIVATE 总创建新对象;ftok 用 i-node+设备+proj 生成,跨进程需传相同 path+proj

      ipc_perm 子结构 + 权限

      每 SysV 对象有 struct ipc_perm:key/uid/gid/cuid/cgid/mode/seq——owner 是 uid,creator 是 cuid;权限检查按 effective UID 匹配 owner/creator 后看 mode

      §45.3;可 IPC_SET 改 uid/gid/mode;执行 get() 时还要做一次 mode 检查(要求至少当前进程的 mode 是对象 mode 的子集)

      kernel-persistent 持久性

      SysV IPC 一直存在直到显式 IPC_RMID——这与 fd 完全不同;导致「最末用户退出了,何时该删」需要额外协调

      §45.1;msg/sem 立即删除;shm 标记为「待删除」,到 nattch=0 才真删

      ipcs / ipcrm / /proc/sysvipc

      ipcs(1) 列对象;ipcrm -[qsm] {key|id} 删;/proc/sysvipc/{msg,sem,shm} 永远显示全部(无权限过滤);编程用 MSG_INFO/STAT

      §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(精简):

      操作 消息队列 信号量 共享内存

      头文件

      <sys/msg.h>

      <sys/sem.h>

      <sys/shm.h>

      数据结构

      msqid_ds

      semid_ds

      shmid_ds

      创建/打开

      msgget()

      semget()

      shmget() + shmat()

      关闭

      shmdt()

      控制

      msgctl()

      semctl()

      shmctl()

      操作

      msgsnd() / msgrcv()

      semop()

      直接访问 attach 后的内存

      关键点

      1. get 与 open 的区别get(key, IPC_CREAT) 含义 = 「key 已存在则取 id,否则新建」;加 IPC_EXCL = 「必须新建,否则失败 EEXIST」。这与 open(O_CREAT|O_EXCL) 类似但更复杂。

      2. id 是属性(不是 process attribute)——同一对象所有进程看到同一 id;进程退出不会自动删除(kernel-persistent)。

      3. 没有引用计数——内核不知道谁在用,决定「对象可删时间」是应用的事。

      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):

      1. 进程有 CAP_IPC_OWNER → 全权。

      2. 进程 effective UID 匹配 owner UID 或 creator UID → 按 mode 的 owner 位(高 3 bit)。

      3. 进程 effective GID 或 supplementary GID 匹配 owner GID 或 creator GID → 按 mode 的 group 位(中 3 bit)。

      4. 否则 → 按 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

      Whatipcs(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)
      类别 系统调用 备注

      创建/打开

      msgget(key, flags) / semget(key, nsems, flags) / shmget(key, size, flags)

      key 由 ftokIPC_PRIVATE 生成

      操作

      msgsnd/msgrcv / semop / shmat + 直接访问

      shmat 返回指针;semop 原子多操作

      控制

      msgctl(id, cmd, …​) / semctl(id, sn, cmd, …​) / shmctl(id, cmd, …​)

      cmd 包含 IPC_STAT/SET/RMID/INFO

      删除

      xxxctl(id, IPC_RMID, NULL)

      msg/sem 立即;shm 标记待删

      标识

      key_t + int id + pathname(POSIX 对照)

      SysV 用整数 key/id;POSIX 用 path

      持久性

      kernel-persistent

      显式删除前一直存在

      权限

      struct ipc_perm

      uid/gid/cuid/cgid/mode

      限制

      /proc/sys/kernel/{msg*, sem, shm*}

      调优点

      监控

      ipcs(1) / ipcrm(1) / /proc/sysvipc/* / MSG_INFO/STAT

      命令行 vs 文件 vs 编程

      SysV vs POSIX IPC
      维度 SysV IPC POSIX IPC

      标识符

      key + int id

      pathname + 类似 fd 的 handle

      持久性

      kernel-persistent

      kernel-persistent

      引用计数

      有(最末 close 删)

      协议

      get + ctl + op

      open + close + 各机制专属

      移植性

      所有 UNIX

      较新内核才全支持

      POSIX 选项

      XSI 选项

      BASE 选项

      API 风格

      偏内核、整数 ID

      偏 POSIX、pathname + fd 风格

      四、思维导图

      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

      五、重点与易错点

      1. get() 不一定是新建——默认只是「找」;加 IPC_CREAT 才在没找到时新建;加 IPC_EXCL 才「必须新建」。

      2. IPC_PRIVATE 总创建新对象——无需 IPC_CREAT/EXCL;适合 server 启动时创建,子进程通过 fork 继承 id。

      3. id 不是 fd——id 是对象属性(不是 process attribute);没有引用计数;进程退出不会自动关闭对象。

      4. kernel-persistent——一旦创建一直存在;这意味着崩溃的 server 会留下「孤儿」对象;用 ipcs 清理。

      5. ipc_perm 的 owner/creator 区分——uid 可 IPC_SET 改;cuid 是创建者 ID 不可改;权限检查按 owner/creator 匹配 effective UID。

      6. get() 内部做权限检查——即使只想「打开」对象,flags 中的 mode 也必须与对象 mode 兼容,否则 EACCES。

      7. shm 删除延迟——IPC_RMID 标记为「待删」,到所有进程 shmdt 才真正删除;其他 UNIX 多立即删(不一致)。

      8. MSG_INFO 返回 maxid,MSG_STAT 按 index 返 id——编程接口模式(§45.7)可用于写 ipcs 替代品。

      9. ftok 必须用现存文件——临时文件 / 被 unlink 过的文件会得到不同 i-node → 不同 key。

      10. ipcs(1) 按读权限过滤;/proc/sysvipc 总列全部——后者更适合脚本扫描。

      11. SysV IPC 的「no ref count」问题——决定「对象该删了吗」需要应用层协议(如 server restart 的 IPC_RMID-EXCL 循环)。

      12. Linux 上 ipc(2) 是统一入口(除 IA-64/Alpha)——应用不应直接调用 ipc(2),用 glibc 封装的 msgget/semget/shmget。

      13. SysV IPC 的 fd 化趋势——通过把对象「wrap」成 anonymous fd,POSIX 设计更胜一筹。

      14. 跨章衔接:第 44 章 pipe/FIFO;第 46 章 SysV msg 详细;第 47 章 SysV sem 详细;第 48 章 SysV shm 详细;第 49 章 mmap;第 51-54 章 POSIX IPC;第 56-61 章 socket。

      Asciidoc lint check

      asciidoctor: 无警告。