一、什么是进程间通信

在 Linux 系统中,每个进程都拥有独立的虚拟地址空间,相互隔离、互不可见。这种隔离虽然保证了安全和稳定,但也带来了一个根本问题:不同进程之间如何交换数据、协调状态?

这正是 进程间通信(Inter-Process Communication,IPC)要解决的问题。Linux 内核提供了多种 IPC 机制,每种各有侧重——有的擅长字节流、有的擅长结构化消息、有的擅长高速大数据、有的擅长事件通知与同步控制。掌握它们的差异,是设计高性能系统服务和并发程序的基础。

Linux 中五种最经典的 IPC 方式

管道
Pipe

单向字节流

消息队列
Message Queue

结构化消息

信号
Signal

异步事件通知

信号量
Semaphore

同步与互斥

共享内存
Shared Memory

零拷贝高速通信

二、内核态 vs 用户态:通信路径对照

不同 IPC 机制走的路径不同:管道、消息队列、信号量等都要经过内核数据结构中转,而共享内存则直接映射到用户空间,跳过内核拷贝。

路径 A:经内核中转

📤 进程 A 调用系统调用 write()
⬇️ 数据拷贝到内核缓冲区
🗄️ 内核维护 IPC 对象(pipe/msgq/sem)
⬇️ 进程 B 调用 read() 触发拷贝
📥 进程 B 用户空间收到数据
⚠️ 两次拷贝 + 上下文切换开销
性能演进
零拷贝

路径 B:共享内存直连

📤 进程 A 调用 shmat() 映射段
🧠 同一物理页同时映射到 A、B 的虚拟空间
⚡ A 写入即 B 可见(无拷贝)
🔒 需配合信号量做同步
📥 进程 B 直接访问内存
✅ 速度最快,适合大数据交换

三、机制详解 · 五大 IPC 拆解

① 管道(Pipe)

核心定义:单向字节流,把一个进程的标准输出连接到另一个进程的标准输入。是 Unix 哲学"组合小工具"的基石。

分两类

  • 匿名管道(Anonymous Pipe):仅用于具有亲缘关系的进程(父子、兄弟),通过 fork 继承文件描述符共享。
  • 命名管道(Named Pipe / FIFO):通过文件系统中的特殊文件存在,无血缘的进程也可通过 open(path) 通信。
# Shell 中最常见的管道用法
$ ps aux | grep nginx | wc -l

# C 代码:创建匿名管道
int fd[2];
pipe(fd);             // fd[0] 读端, fd[1] 写端
if (fork() == 0) {
    close(fd[0]);
    write(fd[1], "hello", 5);
} else {
    close(fd[1]);
    char buf[16];
    read(fd[0], buf, 5);
}

特点:实现简单、半双工、容量有限(一般 64KB),适合简单父子进程数据流。

② 消息队列(Message Queue)

核心定义:由内核维护的链表结构,多个进程可向其写入"消息",一个或多个进程从中读取。每条消息携带类型标签(mtype),消费者可按类型过滤接收。

两种实现System V 消息队列(msgget/msgsnd/msgrcv) 与 POSIX 消息队列(mq_open/mq_send/mq_receive,更现代、支持 select/poll)。

// POSIX 消息队列示例
struct mq_attr attr = { .mq_maxmsg = 10, .mq_msgsize = 256 };
mqd_t mq = mq_open("/order_queue", O_CREAT|O_RDWR, 0644, &attr);

// 发送
mq_send(mq, "order#1001", 10, 5);  // 优先级 5

// 接收(带优先级)
char buf[256];
unsigned prio;
mq_receive(mq, buf, 256, &prio);

特点:支持结构化消息、按类型/优先级筛选、可异步通信,无需双方同时在线,适合任务分发场景。

③ 信号(Signal)

核心定义:Unix 系统最古老的 IPC 方式之一,是一种异步事件通知机制。信号可由键盘中断(如 Ctrl+C)、硬件异常(如非法内存访问)、内核行为(子进程退出)或其他进程主动 kill 发送。

常见信号

SIGINT (2)
键盘 Ctrl+C 中断
SIGTERM (15)
优雅终止请求
SIGKILL (9)
强制杀死,不可捕获
SIGSEGV (11)
段错误(非法访存)
SIGCHLD (17)
子进程状态变化
SIGHUP (1)
挂断 / 重新加载配置
SIGUSR1/2
用户自定义
SIGPIPE (13)
写入已关闭的管道
// 注册信号处理器,实现优雅退出
void handler(int sig) {
    printf("接收到信号 %d,准备清理资源...\n", sig);
    cleanup();
    exit(0);
}
signal(SIGTERM, handler);
signal(SIGINT,  handler);

特点:仅传递事件,不传递数据载体(实时信号 SIGRTMIN+ 可带 sigval);属于"通知型 IPC"。

④ 信号量(Semaphore)

核心定义:内核中的一个计数器变量,多个进程可以对其执行原子的"测试-设置"操作。当资源不可用时,请求进程会被挂起(sleep),直到其他进程释放资源后被唤醒。

本质:信号量并不传输数据,而是用于同步、互斥与资源计数,是共享内存等无锁机制的"配套伴侣"。

// POSIX 信号量:保护共享资源
#include <semaphore.h>

sem_t sem;
sem_init(&sem, 1, 1);   // 进程间共享,初值 1(互斥锁)

sem_wait(&sem);            // P 操作:值-1,若 <0 则阻塞
// ---- 临界区:访问共享资源 ----
sem_post(&sem);            // V 操作:值+1,唤醒等待者

典型用途:与共享内存配合,避免竞态;控制并发线程数(如连接池限流);实现生产者-消费者模型。

⑤ 共享内存(Shared Memory)

核心定义:让一段物理内存同时映射到多个进程的虚拟地址空间。一个进程对该内存的修改,其它进程可立即看到。当进程不再需要共享时,通过 detach 解除映射。

为什么最快:避免了内核态-用户态的数据拷贝(普通 IPC 至少两次拷贝),是 Linux IPC 中性能最高的方式。

// POSIX 共享内存示例
int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0644);
ftruncate(fd, 4096);

void *ptr = mmap(NULL, 4096,
                 PROT_READ|PROT_WRITE,
                 MAP_SHARED, fd, 0);

strcpy((char*)ptr, "hello from process A");

// 另一进程 mmap 同一对象,即可直接读到该字符串
munmap(ptr, 4096);
shm_unlink("/my_shm");

注意:必须搭配信号量或互斥锁使用,否则极易出现数据竞争。是数据库(如 PostgreSQL)、视频流处理、AI 框架(如 PyTorch 多进程)的常用底层机制。

四、五大 IPC 综合特性对比

机制 传输方向 数据类型 是否亲缘限制 性能 是否需同步 典型应用 推荐度
管道 单向 字节流 匿名需亲缘
FIFO 无限制
中等 内核已保证 Shell 流水线、父子进程 ★★★★
消息队列 多对多 结构化消息 中等 内核已保证 任务分发、日志收集 ★★★★
信号 异步通知 无载荷 极快 不适用 进程控制、异常处理 ★★★★★
信号量 不传输数据 计数值 自身即同步原语 资源互斥、并发控制 ★★★★★
共享内存 多对多 任意二进制 最快 必须额外同步 数据库、AI 训练、音视频流 ★★★★★

五、选型指南:什么场景该用哪种

选管道(Pipe / FIFO)

  • 父子进程间简单字节流传递
  • Shell 工具链组合(cmd1 | cmd2)
  • 实现命令行风格的"过滤器"
  • 数据量小、单向、无需结构

选消息队列(Message Queue)

  • 多进程任务分发、生产/消费模型
  • 需要按类型/优先级过滤消息
  • 双方不需同时在线的解耦通信
  • 小型嵌入式系统的事件总线

选信号(Signal)

  • 需要异步通知特定事件(如配置 reload)
  • 实现优雅退出、清理资源
  • 父进程监听子进程退出(SIGCHLD)
  • 异常/错误处理(SIGSEGV/SIGPIPE)

选信号量(Semaphore)

  • 多进程访问共享资源的互斥
  • 限制同时运行的工作线程数
  • 实现生产者-消费者缓冲
  • 与共享内存搭配做同步

选共享内存(Shared Memory)

  • 大数据量(MB ~ GB)跨进程传递,如视频帧、模型张量
  • 对延迟极敏感的高频通信场景,如高频交易系统
  • 数据库共享缓冲池(PostgreSQL shared buffers / MySQL InnoDB buffer pool)
  • AI/ML 多进程数据加载(PyTorch DataLoader workers)
  • 必须配合信号量或原子操作做并发同步

六、Linux IPC 的演进路径

1970sUnix 信号 + 管道

早期 Unix 系统提供最基础的 IPC:进程通过信号传递事件,通过匿名管道串联工具链。

1980sSystem V IPC 三件套

AT&T Unix 引入 System V IPC,提供消息队列、信号量、共享内存三大经典机制,奠定 IPC 范式。

1990sPOSIX IPC 标准化

IEEE POSIX 标准化 IPC 接口:mq_*、sem_*、shm_*。语义更现代,支持 fd 多路复用。

2000sUnix Domain Socket 兴起

更通用的本地 socket(AF_UNIX)成为主流,兼具消息边界与流式传输,被 Docker、systemd 大量采用。

2010seventfd / signalfd / memfd

新一代 fd 化 IPC 原语出现,可统一被 epoll 监听,简化异步事件循环编程。

2020sio_uring 与零拷贝

io_uring + memfd_create 进一步压榨性能,配合 shared memory ringbuffer 成为高性能服务的新基础设施。

七、关键要点回顾

📌 核心问题:进程地址空间相互隔离,必须通过内核提供的 IPC 机制交换数据或事件。

📌 五大经典机制:管道、消息队列、信号、信号量、共享内存,各司其职。

📌 性能金字塔:共享内存(最快)> 信号 / 信号量 > 管道 ≈ 消息队列。

📌 同步与通信分离:共享内存负责"通信",信号量负责"同步",两者搭配是性能最优解。

📌 选型口诀:小数据流走管道,结构化消息走队列,事件通知用信号,并发互斥用信号量,大块数据用共享内存。