
🎉博主首页: 有趣的中国人
🎉专栏首页: 操作系统原理
文章目录
- 【操作系统原理】进程间通信:管道(pipe/FIFO)
-
- [1. 为什么需要进程间通信(IPC)](#1. 为什么需要进程间通信(IPC))
- [2. 进程为什么不能直接"互相读写内存"](#2. 进程为什么不能直接“互相读写内存”)
- [3. IPC 方式总览](#3. IPC 方式总览)
-
- [3.1 本地通信(同一台机器)](#3.1 本地通信(同一台机器))
- [3.2 网络通信(跨机器)](#3.2 网络通信(跨机器))
- [4. 匿名管道 pipe:是什么](#4. 匿名管道 pipe:是什么)
-
- [4.1 pipe 的核心模型](#4.1 pipe 的核心模型)
- [4.2 重要限制](#4.2 重要限制)
- [5. 管道的 4 种关键情形(面试高频)](#5. 管道的 4 种关键情形(面试高频))
-
- [5.1 管道为空 + 写端存在](#5.1 管道为空 + 写端存在)
- [5.2 管道写满 + 读端存在](#5.2 管道写满 + 读端存在)
- [5.3 写端全部关闭(read 读到 EOF)](#5.3 写端全部关闭(read 读到 EOF))
- [5.4 读端全部关闭(Broken Pipe / SIGPIPE)](#5.4 读端全部关闭(Broken Pipe / SIGPIPE))
- [6. 管道的 5 个重要特征(理解到内核视角)](#6. 管道的 5 个重要特征(理解到内核视角))
-
- [6.1 只能在亲缘进程间使用(匿名 pipe)](#6.1 只能在亲缘进程间使用(匿名 pipe))
- [6.2 内部自带同步机制(顺序性)](#6.2 内部自带同步机制(顺序性))
- [6.3 生命周期随进程(匿名 pipe 的"匿名")](#6.3 生命周期随进程(匿名 pipe 的“匿名”))
- [6.4 面向字节流:消息边界会丢失](#6.4 面向字节流:消息边界会丢失)
- [6.5 半双工模型(特殊)](#6.5 半双工模型(特殊))
- [7. 命名管道 FIFO:解决"无亲缘也要通信"](#7. 命名管道 FIFO:解决“无亲缘也要通信”)
- [8. 管道在工程里的经典用法:进程池(Master-Worker)](#8. 管道在工程里的经典用法:进程池(Master-Worker))
-
- [8.1 进程池做什么](#8.1 进程池做什么)
- [8.2 常见结构](#8.2 常见结构)
- [8.3 最容易踩的坑](#8.3 最容易踩的坑)
- [9. pipe / FIFO / socket 怎么选(快速决策)](#9. pipe / FIFO / socket 怎么选(快速决策))
- [10. 小结](#10. 小结)
【操作系统原理】进程间通信:管道(pipe/FIFO)
1. 为什么需要进程间通信(IPC)
进程是"资源分配的基本单位",各自有独立的虚拟地址空间,默认互不干扰。
但真实系统里经常需要:
- 多进程协作完成同一件事(流水线、进程池、服务拆分)
- 数据共享与传递(任务、日志、控制指令、结果)
- 提高吞吐与响应(并行处理)
结论:进程需要协同 → 必须通信。
2. 进程为什么不能直接"互相读写内存"
因为这会破坏隔离性与安全性,成本也非常高。
系统的折中方案是:
让不同进程去访问同一份 OS 管理的"共享资源"
例如:内核缓冲区、共享内存段、消息队列对象、文件对象......
也就是:IPC 的核心不是"直接互访内存",而是"借助 OS 提供的通信媒介"。
3. IPC 方式总览
从常见使用场景看,大致分为两类:
3.1 本地通信(同一台机器)
- 匿名管道(pipe)
- 命名管道(FIFO)
- System V IPC:消息队列 / 共享内存 / 信号量
- Unix Domain Socket(本地 socket)
3.2 网络通信(跨机器)
- TCP/UDP socket(走协议栈、可跨主机)
4. 匿名管道 pipe:是什么
匿名管道是最经典的本地 IPC。
4.1 pipe 的核心模型
- pipe 由内核维护一段缓冲区(常见实现为环形队列)
- 通过两个文件描述符访问:
- 读端 fd:从缓冲区取数据
- 写端 fd:向缓冲区写数据
4.2 重要限制
- 只能用于"有血缘关系"的进程(通常是 fork 出来的父子进程)
- 典型场景:父进程写、子进程读(或反过来)
- 管道天然偏向"单向通信"(要双向需要两根管道)
5. 管道的 4 种关键情形(面试高频)
下面所有行为都可以用一句话记住:
空则读等,满则写等;写端全关读到 0;读端全关写会炸。
5.1 管道为空 + 写端存在
- 读进程 read:阻塞等待
- 这就是典型的"生产者-消费者"
5.2 管道写满 + 读端存在
- 写进程 write:阻塞等待
- 直到读端读走部分数据腾出空间
5.3 写端全部关闭(read 读到 EOF)
- 当所有写端 fd 都关闭后:
- 读端 read 会返回 0(EOF)
- 这也是为什么:必须 close 不用的写端
否则读端永远不会得到"结束"信号,可能一直阻塞。
5.4 读端全部关闭(Broken Pipe / SIGPIPE)
- 当所有读端 fd 都关闭后:
- 写端 write 会触发 Broken Pipe
- 默认会收到 SIGPIPE(很多程序因此直接被终止)
- 工程上通常会:
- 忽略/捕捉 SIGPIPE
- 或者对 write 的返回值进行错误处理(EPIPE)
6. 管道的 5 个重要特征(理解到内核视角)
6.1 只能在亲缘进程间使用(匿名 pipe)
因为 pipe 的 fd 是通过 fork 继承过去的。
6.2 内部自带同步机制(顺序性)
- 同一根 pipe 上的数据是"字节流"顺序进入、顺序读出
- 多写者场景下,内核会保证一定的原子性(与 PIPE_BUF 有关)
6.3 生命周期随进程(匿名 pipe 的"匿名")
- pipe 本身没有文件名
- 当进程退出、fd 全部关闭后,内核对象就会被回收
6.4 面向字节流:消息边界会丢失
- 写入两次 ≠ 读两次(读到的数据可能粘在一起,也可能被拆开)
- 所以工程上必须做"应用层协议":
- 定长包
- 分隔符
- length + payload(最常见)
6.5 半双工模型(特殊)
一根 pipe 更天然的使用方式是"单向",像对讲机:
同一时刻更适合一边说一边听的单向流动。
7. 命名管道 FIFO:解决"无亲缘也要通信"
匿名 pipe 只能亲缘进程用,FIFO 用"文件名"把管道暴露出来:
- FIFO 在文件系统里有一个名字(路径)
- 无亲缘关系的进程也能通过这个路径打开同一条管道进行通信
- 依然是:内核缓冲区 + 读写 fd(本质不变)
适用:同机上的两个独立程序(例如:日志采集器 ↔ 业务进程)。
8. 管道在工程里的经典用法:进程池(Master-Worker)
8.1 进程池做什么
- Master:负责接收任务、分发任务、负载均衡
- Worker:负责执行任务、返回结果(可选)
8.2 常见结构
- Master 与每个 Worker 建立一条(或两条)管道
- Master 端只保留写端,Worker 端只保留读端(其余全 close)
- 分发策略:
- 轮询(round-robin)
- 最短队列(按繁忙度)
- 哈希分桶(同类任务固定 worker)
8.3 最容易踩的坑
- 忘记关闭无用端 → read 永远不返回 0 / write 触发 SIGPIPE 行为不清晰
- 不做消息边界协议 → "粘包/拆包"导致任务解析错乱
- 不处理 worker 退出 → master 写入触发 broken pipe
9. pipe / FIFO / socket 怎么选(快速决策)
- 只在本机、亲缘进程:pipe(简单直接)
- 本机、无亲缘:FIFO 或 Unix Domain Socket
- 跨机器:TCP/UDP socket
- 大块数据共享、追求极致性能:共享内存 + 信号量(或 futex 等)
10. 小结
- IPC 的本质:借助 OS 提供的共享媒介
- pipe/FIFO:内核缓冲区 + fd 读写 + 阻塞语义
- 面试 4 情形必须背到"条件反射"
- 工程落地一定要做:关闭无用端 + 应用层协议 + 异常处理