一、管道通信概述
管道是 Linux 最基础的进程间通信(IPC)方式,分为匿名管道(pipe) 和命名管道(FIFO/mkfifo) ,核心都是基于内核缓冲区实现数据传输,均为半双工(数据单向流动)。
二、匿名管道(pipe)
1. 核心特点
- 无文件系统实体,仅存在于内核中;
- 仅支持亲缘进程(父子 / 兄弟进程)间通信;
- 生命周期随进程:进程退出后管道自动销毁;
- 无需手动创建文件,直接通过系统调用分配内核缓冲区。
2. 函数原型
#include <unistd.h>
int pipe(int pipefd[2]);
3. 参数与返回值
pipefd[2]:输出型参数,保存管道的两个文件描述符:pipefd[0]:读端,仅用于读取数据;pipefd[1]:写端,仅用于写入数据;
- 返回值:成功返回 0,失败返回 -1(设置
errno)。
4. 注意事项
- 必须先创建管道,再
fork()子进程(保证子进程继承管道描述符); - 读端 / 写端需成对关闭,否则可能导致
read()阻塞; read()读取空管道时会阻塞,写端关闭后read()返回 0。
三、命名管道(FIFO/mkfifo)
1. 核心特点
- 有文件系统实体(
ls -l显示权限位开头为p); - 支持任意进程(无亲缘关系)间通信;
- 生命周期独立:进程退出后管道文件仍存在,需手动删除;
- 数据存储于内核缓冲区,文件仅作为 "访问标识"。
2. mkfifo () 函数详解
函数原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数
pathname:管道文件路径(如./myfifo);mode:文件权限,必须为八进制(如0664,最终权限 =mode & ~umask)。
返回值
| 返回值 | 场景 | errno 标识 | 处理方式 |
|---|---|---|---|
| 0 | 管道首次创建成功 | 无 | 正常使用 |
| -1 | 管道文件已存在 | EEXIST (17) | 复用管道,无需报错 |
| -1 | 真失败(权限 / 路径错误) | EACCES/ENOENT 等 | 用 perror() 打印错误并退出 |
关键错误判断代码
#include <errno.h>
int ret = mkfifo("./myfifo", 0664);
if (ret == -1) {
if (errno == EEXIST) {
printf("管道已存在,直接复用\n");
} else {
perror("mkfifo 创建失败");
return -1;
}
}
3. 常见踩坑点
- 读写端搞反 :读进程必须用
O_RDONLY,写进程必须用O_WRONLY; - read 长度错误 :❌
read(fd, buf, strlen(buf))(未初始化数组长度随机),✅read(fd, buf, sizeof(buf)-1); - open 阻塞特性 :默认
open()会阻塞,直到读写端配对,非阻塞需加O_NONBLOCK; - 管道文件残留 :通信完成后用**
unlink()**删除,避免重复创建误判。
四、unlink()函数详解
1、函数核心定位
unlink() 是 Linux 系统调用,核心作用是删除文件系统中的文件链接(目录项),对于命名管道(FIFO)而言,是清理管道文件的核心函数。
2、函数原型与头文件
#include <unistd.h>
int unlink(const char *pathname);
2.1. 参数
pathname:要删除的文件路径(如./myfifo、/tmp/fifo),支持相对 / 绝对路径。
2.2. 返回值
| 返回值 | 含义 |
|---|---|
| 0 | 成功删除文件链接 |
| -1 | 失败,设置 errno 表示原因(如文件不存在、权限不足、路径错误等) |
3、核心作用
3.1. 对普通文件:删除目录项,释放磁盘空间
- 普通文件被
unlink()后,若没有进程打开该文件,文件会立即被删除,磁盘空间释放; - 若有进程正在打开该文件,目录项先被删除(
ls看不到),但文件内容直到所有进程关闭该文件后才会释放。
3.2. 对命名管道(FIFO):删除管道文件标识
- 命名管道是 "特殊文件",
unlink()会直接删除文件系统中的管道文件(ls -l看不到); - 管道的内核缓冲区不受影响:若有进程正在通过该管道通信,
unlink()后通信仍可正常进行,直到所有进程关闭管道描述符,内核缓冲区才会释放。
4、在命名管道通信中的必用场景
4.1. 为什么必须用 unlink() 清理命名管道?
- 命名管道文件不会随进程退出自动删除,若不清理,下次运行
mkfifo()会返回-1+errno=EEXIST; - 残留的管道文件可能被其他进程误用,导致通信异常。
5、常见错误码(errno)
| errno 值 | 宏定义 | 含义 | 解决方案 |
|---|---|---|---|
| 2 | ENOENT | 文件不存在 | 检查路径是否正确,避免重复删除 |
| 13 | EACCES | 权限不足(无目录写权限) | 提升目录权限(如 chmod 775 目录) |
| 17 | EEXIST | 路径是目录(非文件) | 确认参数是文件路径,而非目录 |
| 20 | ENOTDIR | 路径中包含非目录节点 | 修正路径,确保父目录存在且是目录 |
6、注意事项
6.1. 避免重复删除
-
删除前可先检查文件是否存在(用
access()函数),避免unlink()失败:#include <unistd.h> if (access(myfifo, F_OK) == 0) { // 检查文件是否存在 unlink(myfifo); }
6.2. 命名管道通信中,unlink() 不影响正在进行的通信
- 示例:进程 A 和进程 B 已打开
./myfifo通信,此时调用unlink("./myfifo"):ls看不到myfifo文件;- 进程 A/B 仍可正常读写管道,直到双方关闭文件描述符,管道内核缓冲区才释放。
6.3. 与 rm 命令的关系
rm 文件名本质就是调用unlink()函数实现的;- 区别:
rm是用户态命令,unlink()是内核态系统调用。
7、核心总结(结合管道通信)
(1)unlink() 是清理命名管道的必备函数,解决管道文件残留问题;
(2)调用时机:通信完成、关闭文件描述符后立即调用;
(3)核心特性:删除文件目录项,不影响正在打开该文件的进程的操作;
(4)错误处理:调用后检查返回值,用 perror() 定位删除失败原因。
| 知识点 | 关联逻辑 |
|---|---|
| 命名管道创建(mkfifo) | mkfifo() 创建文件,unlink() 清理文件 |
| 管道生命周期 | 命名管道需 unlink() 手动销毁,匿名管道自动销毁 |
| 管道残留问题 | unlink() 是解决管道残留的唯一方式 |
五、匿名管道 vs 命名管道 对比表
| 特性 | 匿名管道(pipe) | 命名管道(mkfifo/FIFO) |
|---|---|---|
| 进程关系 | 仅亲缘进程(父子 / 兄弟) | 任意进程(无亲缘关系) |
| 文件系统实体 | 无(仅内核缓冲区) | 有(可见的管道文件) |
| 创建方式 | pipe() 系统调用 |
mkfifo() 系统调用 |
| 生命周期 | 进程退出后自动销毁 | 需手动 unlink()/rm 删除 |
| 阻塞特性 | read()/write() 阻塞 |
open() 阻塞(需读写端配对) |
| 核心使用场景 | 父子进程简单通信 | 无亲缘进程间通信 |
| 数据流向 | 半双工,单向流动 | 半双工,单向流动 |
六、核心总结
- 管道通信核心是内核缓冲区,匿名 / 命名仅为 "访问方式" 不同;
- 匿名管道:先
pipe()再fork(),仅亲缘进程用,自动销毁; - 命名管道:
mkfifo()创建文件,任意进程用,返回-1必看errno; - 通用规则:半双工通信,读 / 写端需配对,
read长度用sizeof(缓冲区)。