Skip to content

进程间通信(IPC):六种方式与选型 ​

进程地址空间相互隔离,因此"通信"必须借道内核或共享资源。面试高频问六种 IPC 的原理、优缺点、选型,以及"为什么共享内存最快"。

一、为什么需要 IPC? ​

Q1. 为什么进程之间不能直接通信?IPC 的本质是什么? ​

答案

每个进程有独立的虚拟地址空间:A 进程的指针在 B 进程里指向完全不同的物理内存(甚至非法)。这是隔离性与安全性的代价——隔离让一个进程无法破坏另一个,但也使"直接读对方变量"不可能。

于是需要 IPC(Inter-Process Communication):由内核作为中转(管道 / 消息队列 / 信号 / socket),或由内核建立共享映射(共享内存),让进程安全地交换数据。Linux 里还有更高效的 io_uring、eventfd、binder(Android)等,但面试主答六大经典方式。

二、管道(pipe) ​

Q2. 匿名管道 pipe 的原理与阻塞行为? ​

答案

结论:管道是一个内核维护的环形缓冲区(Linux 中通常 64 KB;PIPE_BUF 为 4 KB,保证不超过该值的写是原子的),提供字节流、半双工(单向)通信。

要点:

  • pipe(int fd[2]):fd[0] 读端、fd[1] 写端。
  • 只能用于有亲缘关系的进程(父子/兄弟),因为要靠 fork 继承这两个 fd。
  • 半双工:数据单向流;要双向就开两个管道。
  • 面向字节流:没有消息边界,读多少次由实现决定(不同于消息队列)。
  • 生命周期:随进程;所有引用该管道的 fd 都关闭后,管道在内核中被销毁。

read / write 阻塞行为:

操作条件行为
read管道非空立即返回数据
read管道空且写端仍打开阻塞,直到有数据或写端关闭
read写端全部关闭返回 0(EOF)
write缓冲区未满写入并返回(不超过 PIPE_BUF 时原子)
write缓冲区满阻塞,直到有空间
write读端全部关闭触发 SIGPIPE(默认终止),write 返回 -1 / EPIPE

为什么要在 shell 里配合:ls | grep foo 里,shell 先 pipe() 建管道,再 fork 两个子进程:一个把 stdout 重定向到写端(dup2),一个把 stdin 重定向到读端,然后各自 exec。管道本质是父子间传递的 fd,天然和 fork 配合——所以命令行里的 | 就是"创建管道 + fork + dup2 重定向 + exec"。

Q3. 命名管道 FIFO 与匿名管道的区别? ​

答案

FIFO(命名管道) 是唯一能让无亲缘关系进程也用管道的形式:

  • 用 mkfifo / mkfifo() 在文件系统里创建一个特殊文件(有路径名);
  • 任意进程只要 open 这个路径即可读写;
  • 仍满足管道语义:字节流、半双工、内核缓冲、FIFO 顺序。

对比:

匿名 pipe命名管道 FIFO
标识无文件名,靠 fd 传递有文件路径
适用范围有亲缘关系的进程任意进程(知道路径即可)
生命周期随 fd 关闭而消失文件常驻(除非 unlink)
创建pipe()mkfifo

易错点:FIFO 的 open 会阻塞——只读打开会等到有写者,只写打开会等到有读者(可用 O_NONBLOCK 规避)。

三、消息队列 ​

Q4. 消息队列的原理与特点? ​

答案

结论:消息队列是内核维护的链表,每个元素是一条有格式的消息(含类型 + 数据)。

特点:

  • 有边界:以"消息"为单位收发,不像管道是字节流,读端能感知消息边界。
  • 按类型读取:System V 消息队列可指定 msgtyp 只取某类消息(选择性接收),也可按 FIFO。
  • 独立于进程:生命周期随内核(System V)或随打开状态(POSIX),可用于无关进程。
  • 容量受限:单条消息大小、队列总长度都有上限(msgmax / msgmnb)。
  • 同步:队列空时读阻塞、满时写阻塞(可设非阻塞)。

缺点:每次读写都要数据在内核与用户态间拷贝,比共享内存慢;消息长度受固定格式约束;System V 版本命名空间/生命周期管理较乱。POSIX 消息队列(mq_open / mq_send / mq_receive)接口更干净。

适用:需要消息边界、解耦、按类型分发、数据量不大的场景。

四、共享内存 ​

Q5. 共享内存为什么是最快的 IPC?有什么代价? ​

答案

结论:共享内存让多个进程把同一块物理内存映射到各自的虚拟地址空间,读写不经过内核中转,因此零次内核拷贝,是所有 IPC 里最快的。

原理(两种常见方式):

  • System V:shmget 创建 + shmat 挂接,直接拿到指针。
  • POSIX / mmap:mmap(MAP_SHARED) 把一个文件(或匿名内存)映射到地址空间,fork 后父子共享。

为什么最快:管道 / 消息队列 / socket 都要"用户态 → 内核 → 用户态"的拷贝(甚至内核内部还要再搬一次),而共享内存只读写自己地址空间内的内存,没有拷贝、没有系统调用。

代价:

  • 需要额外同步:内核不保证互斥,多进程同时读写会数据竞争,必须配信号量 / 互斥锁 / 文件锁。这就是经典组合"共享内存 + 信号量"。
  • 生命周期管理(System V 共享段需显式 shmctl 删除,否则残留)、跨进程同步原语的实现复杂度。

追问:shmget 与 mmap 的区别?前者是 System V 共享内存;后者更现代、把文件/匿名映射统一,常与 MAP_SHARED 配合做进程间共享。

五、信号量(Semaphore) ​

Q6. 信号量如何用于 IPC 同步? ​

答案

结论:信号量是内核维护的计数器,P(申请,sem_wait,减一)与 V(释放,sem_post,加一)是原子操作,用于跨进程的同步与互斥。

  • System V 信号量:semget / semop / semctl,一次可操作一组信号量。
  • POSIX 信号量:sem_init(放在共享内存中)配 sem_wait / sem_post;或命名信号量 sem_open(有名字,可用于无关进程)。接口更简单。

用途:

  • 互斥:初值 1 的二值信号量,保护共享内存临界区;
  • 同步:初值 0 的信号量,实现"生产者先写、消费者后读"的顺序;
  • 计数:初值 N 表示 N 个可用资源(如空槽数)。

关键:信号量本身不传数据,只做同步/互斥;传数据靠共享内存或其他通道。它之所以被列为一种 IPC,是因为提供了进程间同步通信能力。

c
sem_t *s = sem_open("/mysem", O_CREAT, 0644, 1); // 初值 1:互斥
sem_wait(s);   // P
/* 临界区:操作共享内存 */
sem_post(s);   // V
sem_close(s);

六、信号(Signal) ​

Q7. 信号的工作原理?哪些信号不能被捕获? ​

答案

结论:信号是内核发给进程的异步通知,是一种"软中断",用于事件通知(而非批量数据)。

常见信号:

信号含义默认动作
SIGINTCtrl+C 中断终止
SIGTERM请求终止(可捕获做优雅退出)终止
SIGKILL强制杀死终止,不可捕获 / 阻塞 / 忽略
SIGSTOP强制暂停暂停,不可捕获
SIGSEGV段错误(非法内存访问)终止 + core
SIGCHLD子进程状态变化忽略
SIGPIPE写已关闭的管道终止
SIGHUP挂起(终端断开)终止

发送方式:kill(pid, sig)、raise、alarm、终端按键、内核事件。处理方式:默认动作 / 忽略 / 自定义处理函数(signal / sigaction)。

为什么 SIGKILL / SIGSTOP 不可捕获:内核需要保证"总能杀死 / 暂停一个失控进程",否则进程可以忽略一切信号而无法被管理。

信号处理与可重入:处理函数可能在任意时刻打断主流程(异步),因此函数内只能调用 async-signal-safe 的函数(如 write、_exit),不能调用非可重入的 printf / malloc,否则会与主流程正在使用的内部状态冲突。

易错点:信号"不排队"(同类信号可能合并丢失);处理期间同类信号默认被阻塞;常用 volatile sig_atomic_t 作为处理函数与主流程共享的标志位。

七、socket ​

Q8. socket 也能本机通信?AF_UNIX 与 AF_INET 的区别? ​

答案

结论:socket 既支持跨主机通信,也支持本机进程间通信,是最通用的一类 IPC。

  • AF_UNIX(AF_LOCAL):本机通信,用文件路径寻址。数据不经过网络协议栈(不封包、不算校验和),因此比 TCP 快得多;可靠、有序,支持字节流(SOCK_STREAM)与数据报(SOCK_DGRAM),还能传递文件描述符(SCM_RIGHTS,这是管道 / 共享内存做不到的)。
  • AF_INET / AF_INET6:跨主机通信,走 TCP / UDP。
    • TCP:面向连接、可靠、有序、字节流。
    • UDP:无连接、不可靠、有消息边界、开销小。

怎么选:只在本机且追求性能 → AF_UNIX;需要跨主机 → AF_INET。许多本地服务(MySQL、Docker、systemd)都提供 UNIX socket 作为高效本地通道。

易错点:socket 是全双工的(对比管道的半双工),且天然支持多对多连接模型。

八、六种方式对比与选型 ​

Q9. 六种 IPC 方式对比?如何选型? ​

答案
方式方向适用范围数据边界速度需要额外同步典型场景
匿名管道半双工仅亲缘进程字节流中否(自带阻塞)shell 管道、父传子
命名管道 FIFO半双工任意本机进程字节流中否与本机无关进程简单通信
消息队列双向任意本机进程有中否解耦、按类型分发
共享内存双向任意本机进程无(自定)最快需要(信号量)大数据、高性能本机共享
信号量同步用任意本机进程———与共享内存配套做互斥/同步
信号单向通知任意进程无(信息量小)快—事件通知、优雅退出
socket全双工本机 + 跨主机流/报文视协议否网络通信、通用 IPC

选型口诀:

  • 求速度 → 共享内存(+ 信号量同步);
  • 求简单、亲缘进程 → 管道;
  • 要求消息边界 / 解耦 → 消息队列;
  • 只需事件通知 → 信号;
  • 跨主机 / 通用 → socket(本机优先 AF_UNIX)。

Q10. 为什么"共享内存 + 信号量"组合最常见?与管道对比? ​

答案

结论:共享内存负责高效传数据(零拷贝),信号量负责协调读写次序与互斥(内核提供原子同步)。二者职责分离、各司其职,是高性能本机 IPC 的标准组合。

与管道对比:

管道共享内存 + 信号量
数据拷贝用户→内核→用户(2 次)0 次
同步管道自带阻塞语义需自己配信号量
数据模型字节流、先进先出任意结构、需自管边界
复杂度低高(要处理同步/一致性)

追问:共享内存有什么风险?多进程并发写会数据竞争;进程崩溃可能留下不一致数据;System V 段若忘记删除会残留(ipcs 可查、ipcrm 可删)。

相关 ​

糌粑的面经笔记 · VitePress 版