进程间通信(IPC)详解:管道、消息队列、共享内存、Socket 与 Binder
每个进程拥有独立的虚拟地址空间,进程之间要交换数据或协调动作,必须借助内核提供的 IPC 机制。本文对比管道、FIFO、消息队列、共享内存、信号量、信号和 Socket 七种方式的差异,并解释共享内存与信号量这对经典组合的分工。
为什么进程间通信必须借助内核
虚拟内存让每个进程活在自己的地址空间里:两个进程中相同的虚拟地址,映射的是不同的物理页。A 进程写的变量,B 进程根本看不见。隔离保证了安全与稳定,但也意味着进程要协作时只能靠内核出面:要么内核代收代发数据(管道、消息队列、Socket),要么内核建立一份双方都能访问的共享对象(共享内存)。
七种 IPC 方式逐个看
管道与命名管道
管道是内核里的一段缓冲区,表现为单向字节流。pipe() 返回读、写两个文件描述符,父进程 fork() 后子进程继承描述符,因此匿名管道天然适合父子等有亲缘关系的进程,Shell 里的 ps aux | grep java 走的就是它。命名管道(FIFO)用 mkfifo 在文件系统里建一个特殊文件,没有亲缘关系的进程按路径打开即可通信——路径只负责命名,数据仍在内存缓冲区,并不落盘。两者都不保留消息边界,写三次不等于读三次。
消息队列
消息队列把数据封装成一条条消息存放在内核队列中,发送方 msgsnd(),接收方 msgrcv(),System V 版本还支持按消息类型选择性接收。相比管道,它保留了消息边界,收发可以异步解耦;代价是每条消息要在用户态和内核态之间拷贝两次,且受内核参数限制,只适合控制命令、状态事件这类小消息,不要把它当成 Kafka 那种消息中间件。
共享内存
共享内存让多个进程把同一段物理内存映射进各自的地址空间。映射完成后,读写就是普通的内存访问,不必每次陷入内核、也不经过中间拷贝,是所有 IPC 中数据通路最短的一种,适合本机大块数据交换(视频帧、日志批、数据库缓存)。但它只解决”看得见同一份数据”,不解决”谁先写、谁后读”。
信号量与信号
信号量(Semaphore)是一个计数器:sem_wait() 减一、减不动就阻塞,sem_post() 加一并唤醒等待者,用来控制进入临界区的数量或做事件计数,它本身不传业务数据。信号(Signal)是异步事件通知:SIGTERM 请求退出、SIGCHLD 报告子进程状态变化。信号能携带的信息极少,处理函数还受 async-signal-safe 限制,只适合”出事了”级别的通知,不适合传数据。
Socket
Socket 是最通用的一种:同一台机器用 Unix Domain Socket(地址族 AF_UNIX),支持双向字节流和数据报,还能借助 SCM_RIGHTS 把文件描述符传给对端;跨机器就换成 TCP/UDP 网络 Socket。它接口统一、天然支持请求-响应模型,但字节流语义意味着分包、粘包要应用自己处理。
七种方式横向对比
| 方式 | 速度 | 容量 | 同步语义 | 跨机器 |
|---|---|---|---|---|
| 匿名管道 | 中,两次拷贝 | 内核缓冲区,默认 64KB | 读空阻塞、写满阻塞 | 否 |
| 命名管道 | 中,两次拷贝 | 内核缓冲区 | 同管道,靠路径寻址 | 否 |
| 消息队列 | 中,两次拷贝 | 受队列与单条消息上限约束 | 保留消息边界,可异步 | 否 |
| 共享内存 | 最快,数据零额外拷贝 | 创建时决定,可很大 | 无内置同步 | 否 |
| 信号量 | 极快,计数操作 | 不传数据 | 阻塞式计数同步 | 否 |
| 信号 | 极快 | 仅事件编号 | 异步打断,标准信号不排队 | 否 |
| Socket | 较慢,协议栈开销 | 流式不限,受缓冲区控制 | 连接管理,字节流/数据报 | 是 |
表中”速度”是定性对比,真实性能还受上下文切换、消息大小与锁竞争影响。
共享内存 + 信号量:为什么经典
这对组合恰好互补。共享内存的短板是没有同步语义:生产者还没把一条记录写完,消费者就可能读到半成品。信号量的短板是不能传数据,只有计数。两者拼起来——共享内存负责数据通路,信号量负责访问时序(一个信号量记”已写条目数”,一个记”剩余空位”,必要时再加互斥量保护读写指针)——就得到一个完整的高吞吐生产者-消费者通道。数据库共享缓冲区、音视频处理流水线里大量使用这个模式。
实践要点
- 选型先看语义再看速度:要消息边界选消息队列,要双向与请求-响应选 Unix Socket,跨机器只有 Socket。
- 共享内存区域里不要放进程内指针——不同进程映射的基址不同,应存偏移量或数组下标。
- 谈性能不要只数拷贝次数,同步成本(锁、信号量、唤醒)常常才是瓶颈。
- 信号处理器里只做置标志位这类最小操作,复杂逻辑交给主循环统一处理。
小结
- 进程地址空间彼此隔离,IPC 要么让内核中转数据,要么让内核建立共享映射。
- 管道/FIFO 是单向字节流;消息队列保留边界但适合小消息;Socket 通用且唯一能跨机器。
- 共享内存最快但无同步,信号量管同步不传数据,两者组合是经典的高吞吐方案。
- 选型按数据量、消息边界、通信范围与同步需求来定,而不是按名词熟悉度。

