土法炼钢兴趣小组的算法知识备份

【操作系统百科】消息队列:SysV、POSIX mq、kdbus 夭折

文章导航

分类入口
os
标签入口
#message-queue#ipc#mqueue#posix-mq#sysv#kdbus#dbus

目录

管道是字节流,socket 是连接或数据报,共享内存是无结构字节区——都不自带”消息”语义。Linux 上提供”带边界、可优先级排序”的消息队列其实有两代 API(SysV msg、POSIX mq),以及一个曾经想成为第三代但夭折的 kdbus。本文讲清楚它们的差别以及为什么新代码一般不选它们。

一、先看图

flowchart LR
    subgraph SysV[SysV msg · 内核队列]
      S1[msgget key]-->S2[msgid]
      S2-->S3[msgsnd type=N]
      S2-->S4[msgrcv type=0/+N/-N]
    end
    subgraph POSIX[POSIX mq · /dev/mqueue]
      P1[mq_open /name]-->P2[fd-like mqd]
      P2-->P3[mq_send prio]
      P2-->P4[mq_receive 最高prio]
      P2-->P5[mq_notify signal/fd]
    end
    subgraph Alt[现代替代]
      A1[UNIX socket<br/>SOCK_SEQPACKET]
      A2[broker: NATS/Redis/Kafka]
      A3[dbus-broker]
    end
    classDef old fill:#63636e22,stroke:#636e7b,color:#adbac7;
    classDef posix fill:#388bfd22,stroke:#388bfd,color:#adbac7;
    classDef alt fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    class S1,S2,S3,S4 old
    class P1,P2,P3,P4,P5 posix
    class A1,A2,A3 alt

二、SysV msg

int id = msgget(IPC_PRIVATE, 0600 | IPC_CREAT);
struct { long mtype; char data[64]; } buf = { 1, "hi" };
msgsnd(id, &buf, sizeof(buf.data), 0);
msgrcv(id, &buf, sizeof(buf.data), 1, 0);

特性:

现代使用:几乎无。嵌入式或旧 Unix 代码还留。

三、POSIX mq_* 家族

struct mq_attr attr = { .mq_maxmsg = 10, .mq_msgsize = 1024 };
mqd_t mq = mq_open("/foo", O_CREAT | O_RDWR, 0600, &attr);
mq_send(mq, buf, len, 5 /* priority */);
mq_receive(mq, buf, sizeof(buf), &prio);
mq_close(mq);
mq_unlink("/foo");

特性:

限制:

比 SysV 干净,但仍很少见。大部分项目觉得不如 Unix socket + 小序列化来得灵活。

四、kdbus 的夭折

2013-2015 年 systemd 团队力推 kdbus——把 D-Bus 的消息路由搬进内核,解决 userspace dbus-daemon 成为瓶颈的问题。提 mainline 被拒(设计复杂、安全面宽、被认为”应该在 userspace 做更快”)。

取而代之的是 dbus-broker(userspace),用 epoll + SCM_RIGHTS + 精心优化把传统 dbus-daemon 的性能提高几倍。systemd 现在默认用 dbus-broker。

教训:内核 API 一旦加了就永远得维护,内核社区对新 IPC 子系统极其保守。

五、为什么大家都转向 socket + 库

IPC 选型经验:

场景 推荐
进程间小消息、低延迟 AF_UNIX SOCK_SEQPACKET
父子共享、有序字节流 pipe
跨机、持久化、多订阅 broker(NATS/Kafka)
进程总线、系统服务 D-Bus(via dbus-broker)
超高性能、大批量 shared memory ring + futex

只有当”多进程、需要优先级、不想自己写 broker”时才可能用 POSIX mq。

六、mq_notify 的三种变体

  1. SignalSIGEV_SIGNAL):空转非空送 signal。信号的老毛病(B-13)
  2. ThreadSIGEV_THREAD):内部起线程调回调函数。glibc 每次 notify 都起一个新线程,非常重
  3. fdSIGEV_THREAD_ID + event fd 魔法,或直接 poll/epoll mqd):现代姿势

只用 poll/epoll 的话连 notify 都用不上——和普通 fd 一样。

七、D-Bus 的地位

Linux 桌面、systemd、NetworkManager、BlueZ、PulseAudio 的系统总线。提供:

实现:userspace dbus-broker(Red Hat 开发,性能接近当年 kdbus 目标)。内核仅提供 Unix socket。

在系统编程里:你要和 systemd / NetworkManager / Upower 打交道就用 D-Bus;纯服务间 RPC 用 gRPC / Thrift / Cap’n Proto。

八、消息队列常见 bug

bug A:生产速度 > 消费,mq 满 原因:默认 10 条 解决:调大 maxmsg、设 nonblocking、反压到生产端

bug B:kdbus/dbus 性能不够 解决:换 dbus-broker;或评估是否真需要总线

bug C:重启后 POSIX mq 还在 原因/dev/mqueue 持久;进程退出不自动 unlink 解决:启动时 mq_unlink 清理

bug D:cgroup 限制内存,mq 段被计入 原因:POSIX mq 内存走 kernel.msg_max 但计入进程内存;某些老内核有争议 诊断/proc/<pid>/status、cgroup memory stats

九、把消息队列当 FIFO 跨进程缓冲?

一个反模式:把 POSIX mq 当”持久 FIFO”。它不是:

真要持久性:用 Redis Streams、NATS JetStream、Kafka。

十、小结

下一篇 B-17 讲 namespace——容器的内核根基,Linux 最重要的进程属性扩展。


参考文献

工具


上一篇共享内存:SysV vs POSIX vs memfd 下一篇namespace:容器的内核根基

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-18 · os

【操作系统百科】共享内存:SysV vs POSIX vs memfd

共享内存是最快的 IPC,但 API 分裂成三代:SysV shmget/shmat、POSIX shm_open、现代 memfd_create。本文讲三者的内核差别、hugetlbfs 的大页共享、memfd sealing 在 GPU/Wayland 里的安全作用、以及 /dev/shm 的 tmpfs 真相。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。


By .