【Linux】Linux 管道面试必看:特性、通信情景、SIGPIPE 处理
前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》
文章目录
- Linux管道:核心特性与通信情景
- 一、管道的5个核心特性
- 1. 自带同步机制
- 2. 面向字节流
- 3. 单向通信
- 4. 生命周期随进程
- 基于血缘关系的通信
- 二、管道通信的4种实际情景
- 1. 情景1:写慢,读快
- 2. 情景2:写快,读慢
- 3. 情景3:写端关闭,继续读
- 4. 情景4:读端关闭,写继续
Linux管道:核心特性与通信情景
管道是Linux进程间通信的“轻量工具”,就像进程间传数据的“临时水管”。下面从它的核心特性和实际通信情景入手,结合图片和代码逻辑,用大白话讲透管道的关键知识点。
一、管道的5个核心特性
1. 自带同步机制
管道不用手动控制读写顺序,自带“等待功能”,就像传送带会等货物准备好再动:
- 没数据时,读进程“阻塞”(暂停),直到写进程写入数据才继续读;
- 有数据时,写进程写完前,读进程会等数据写完整再读。
简单说就是“不写读不到,有数据才能读”,完全不用我们额外加等待逻辑。
以下是提取的代码:
```c++
void ChildWrite(int wfd)
{
char buffer[1024];
int cnt = 0;
while (true)
{
snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);
write(wfd, buffer, strlen(buffer));
sleep(1);
}
}
void FatherRead(int rfd)
{
char buffer[1024];
while (true)
{
buffer[0] = 0;
ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::cout << "child say: " << buffer << std::endl;
}
}
}
2. 面向字节流
管道里的数据是连续的“字节流”,就像水流一样,读和写的次数没有必然关系:
- 先看读写速度对比:不管是
read比write慢,还是write比read慢,数据都会按顺序传递,只是节奏不同,下图能直观看到两种速度下的运行状态。

- 注意“写快读慢”的问题:管道容量是固定的(比如Linux默认64KB),如果写得太快,管道会被写满。此时写进程会立刻“阻塞”,必须等读进程读走一部分数据、腾出空间,才能继续写。

- 关键规则:单次读写的数据量,由管道当前的剩余容量(写的时候)或剩余数据(读的时候)决定;比如写进程分3次写100字节,读进程可以一次把300字节全读走,不用跟写的次数对应。
3. 单向通信
管道是“单行道”,设计之初就只支持单向数据传递,属于“半双工”通信:
- 半双工:同一时间,只能一个进程发数据、另一个进程收数据(像对讲机,你说的时候我只能听,我说完你才能说);
- 全双工(对比):同一时间能双向传数据(像打电话,你说的同时我也能说),但管道做不到。
如果需要双向通信,得建两个管道(一个A→B,一个B→A)。
4. 生命周期随进程
管道是“临时水管”,不会像普通文件那样长期存在:
- 只要有一个使用管道的进程(比如创建管道的父进程、它的子进程)还在运行,管道就存在;
- 所有相关进程都退出后,管道会被系统自动清理,里面的数据也会消失,不会占用磁盘空间。
基于血缘关系的通信
二、管道通信的4种实际情景
实际用管道时,读写进程的速度、是否提前退出,会出现4种不同结果,也是面试高频考点,结合图片和代码逻辑能更清楚理解。
void ChildWrite(int wfd)
{
char buffer[1024];
int cnt = 0;
while (true)
{
snprintf(buffer, sizeof(buffer), "I am child, pid: %d, cnt: %d", getpid(), cnt++);
write(wfd, buffer, strlen(buffer));
printf("child: %d
", cnt);
// sleep(3);
}
}
void FatherRead(int rfd)
{
char buffer[1024];
while (true)
{
sleep(5);
buffer[0] = 0;
ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::cout << "child say: " << buffer << std::endl;
// sleep(2);
}
}
}
1. 情景1:写慢,读快
- 现象:读进程速度比写进程快(比如读每秒1000字节,写每秒500字节);
- 结果:读进程读完管道里的少量数据后,发现没新数据了,会立刻“阻塞”,直到写进程再写入数据,才恢复运行。
- 举例:用
cat largefile.txt | grep "test"时,如果cat读大文件慢,grep就会等cat传数据。
2. 情景2:写快,读慢
- 现象:写进程速度远超读进程(比如写每秒1000字节,读每秒200字节);
- 结果:管道会快速被写满,此时写进程会“阻塞”,直到读进程读走一部分数据、腾出空间,写进程才继续写,下图能看到写满后阻塞的状态。
3. 情景3:写端关闭,继续读
-
现象:写进程写完数据后,主动关闭了“写接口”(代码里关闭
fd[1],fd[1]是管道的写文件描述符),但读进程还在继续读; -
结果:读进程会先读完管道里的剩余数据,之后再调用
read时,read会返回0(和读普通文件到结尾的返回值一样),读进程看到返回0就知道“没数据了,写端关了”,可以退出了。 -
第一步:写端修改(关闭写接口
fd[1]),代码里会加close(fd[1]),对应下图的写端状态变化。

- 第二步:读端处理(判断
read返回值),读端会用循环判断read的返回值,返回0就退出,对应下图的读端逻辑。

- 整体结构:下图能看到写端关闭后,读端读空数据、返回0退出的完整流程。

4. 情景4:读端关闭,写继续
-
现象:读进程提前退出(或主动关闭“读接口”
fd[0]),但写进程还在往管道里写数据; -
本质:数据没人读,写进去也没用,操作系统不会做“无意义的操作”,所以会直接杀死写进程,还会抛出
13号信号(SIGPIPE,管道断裂信号)。 -
原理:下图能看到读端关闭后,写端继续写的无效状态,OS会触发
SIGPIPE信号。

- 代码验证:我们可以写一段代码测试——父进程创建管道后,让子进程当“写端”持续写数据,父进程提前关闭“读端”
fd[0],就能看到子进程被杀死的现象,对应下图的代码逻辑。

- 子进程退出信息:用
waitpid获取子进程退出状态,会看到退出码是141(128+13,13是SIGPIPE的编号),说明子进程是被SIGPIPE杀死的,对应下图的输出。

- 进程监控:用
ps命令监控进程,能看到子进程(写端)一开始存在,之后很快被杀死,对应下图的监控结果。

- 整体流程:下图是读端关闭、写端被杀死的结构示意图,能清晰看到信号传递和进程退出的过程。

- 最终输出:下图是实际运行代码后的输出结果,能看到写端只写了几次就被杀死,和我们的分析一致。


要不要我帮你整理一份管道操作的核心代码片段?包含管道创建、读写逻辑和异常处理(比如捕获SIGPIPE信号),直接编译就能验证文中的情景。











