进程间通信(IPC,Inter-Process Communication)是操作系统提供的机制,让不同进程能够交换数据、协调工作。由于每个进程拥有独立的地址空间,无法直接访问对方的内存,因此必须通过内核管理的通道来传递信息。Linux 提供了多种 IPC 方式,各有适用场景,选择得当能大幅提升程序性能和可维护性。
1. 管道(Pipe)
管道是最古老、最常用的 IPC 形式,它将前一个进程的标准输出直接连接到后一个进程的标准输入。Shell 中的竖线 | 就是匿名管道的直接体现:
ps aux | grep nginx
ps 的输出被送入 grep,两个进程通过内核缓冲区单向传递数据。匿名管道仅适用于有亲缘关系(父子进程)的进程间通信,且是单向的。
若需无关进程之间的双向通信,可使用命名管道(FIFO)。它是一个存在于文件系统中的特殊文件(类型 p),任何进程只要知道路径就可以打开并读写:
mkfifo /tmp/my_pipe
# 终端1:向管道写入
echo "hello" > /tmp/my_pipe
# 终端2:从管道读取
cat /tmp/my_pipe
管道传输的是无格式字节流,因此发送方和接收方需要约定好消息边界(比如每行一条消息),否则会出现半条或黏连的消息。管道实现简单,开销小,适合串联命令行工具或单机上的简单数据流处理。
2. 消息队列(Message Queue)
消息队列允许进程以“消息”为单位交换数据,每个消息带有类型字段,接收方可以按类型选择性读取(而不是先进先出)。Linux 提供了两种消息队列接口:
- System V 消息队列:老式 API,使用
msgget、msgsnd、msgrcv函数,通过整数键标识队列。 - POSIX 消息队列:较新的标准,使用
mq_open、mq_send、mq_receive,以名字(如/myqueue)标识队列,更符合文件系统风格。
消息队列的好处是消息有边界,发送 10 次,接收方可以准确收到 10 次,无需自己处理粘包。此外,可以选择紧急消息的优先级,接收时按类型取出。消息队列存在于内核中,即使发送进程退出,消息仍可以被保留直到被取走,可用于简单的异步任务分发。不过它比管道复杂,且消息大小和队列总长度有上限(通常可调),适用于需要结构化消息传递且不要求极低延迟的场景。
3. 共享内存(Shared Memory)
共享内存是最快的一种 IPC 方式,因为它从根本上消除了复制数据。两个或多个进程将同一块物理内存区域映射到各自的虚拟地址空间,此后对这块内存的读写就如同操作自己的变量一样,无需任何系统调用(读写操作在内核外完成)。
典型用法:
- System V 共享内存:
shmget创建/获取段,shmat映射到进程地址空间。 - POSIX 共享内存:
shm_open创建文件形式的内存对象,用mmap映射。 - 内存映射文件:普通文件(或匿名映射)通过
mmap也可以达到多进程共享的效果。
共享内存本身不提供同步机制,因此多进程同时读写时必须搭配信号量或互斥锁来防止竞争条件。它非常适合需要高频交换大量数据的场景,如图像处理流水线、数据库缓存、实时数据处理等。缺点是需要谨慎管理同步,代码复杂度较高。
4. 信号量(Semaphore)
信号量并非用于传输数据,而是一个同步原语,主要用来控制多个进程对共享资源的访问(如共享内存),或用于进程间执行顺序的协调。
Linux 提供两种主要信号量:
- System V 信号量:可操作一组信号量,支持原子地增加或减少多个信号量的值,适合复杂同步。
- POSIX 信号量:分为命名信号量(
sem_open,无关进程共享)和无名信号量(sem_init,线程或亲缘进程共享),接口更清晰。
信号量的典型使用模型是互斥和计数。例如,将信号量初始化为 1,访问共享内存前执行 P 操作(减 1,即加锁),访问后执行 V 操作(加 1,即解锁),就能保证同一时刻只有一个进程进入临界区。如果初值设为 0,可以让一个进程等待另一个进程完成某项工作后 V 操作,实现通知。
信号量的操作是原子的,内核负责维护,能安全地协调并发。在实际应用中常与共享内存配合出现。
5. Socket(套接字)
Socket 原本设计用于网络通信,但同样可以用于单机进程间通信,并且功能强大、跨平台。它支持双向、面向连接(TCP 类型)或无连接(UDP 类型)的数据流。
对于本机通信,最常用的是 Unix 域套接字(Unix Domain Socket)。它不使用网络协议栈,数据在内核中直接传递,效率远高于 TCP 环回,且支持传递文件描述符等辅助数据。创建时指定一个文件路径(如 /var/run/app.sock),客户端和服务端用同样的地址族 AF_UNIX 进行通信。
实用示例:Docker 守护进程与本机 docker CLI 之间,就是通过 /var/run/docker.sock 这个 Unix 域套接字通信的;MySQL、PostgreSQL 等数据库也支持 Unix 域套接字连接,速度比 TCP 更快,且可以结合文件权限实现本地访问控制。
Socket 的优势在于接口统一,开发者只需掌握一套编程模型(socket、bind、listen、accept、connect、send、recv),就可以同时用于本机和网络通信,便于构建分布式系统。相对管道和消息队列,Socket 更重量级,但提供了流控制和可靠传输。
选择建议
- 简单的文本流串接:管道。
- 有结构消息,带类型和优先级:消息队列。
- 高频大数据量交换,允许额外开发同步代码:共享内存 + 信号量。
- 仅需进程同步或资源计数:信号量(单独或配合共享内存)。
- 需要双向通信,或可能扩展到跨主机:Socket(优先考虑 Unix 域套接字为本机使用)。
理解每种 IPC 机制的特性和成本,才能在设计和优化系统时做出最合适的选择。