目录
1.管道
1.1管道的简介
管道是Unix/Linux系统中最古老、最简单的进程间通信(IPC) 方式之一
管道是基于文件级别的通信方式
管道分为匿名管道(亲缘关系间** )和命名管道(任意进程间)两种**
- 管道本质上是内核维护的内存级缓冲区 ,可被视作一种内存级文件。 与普通磁盘文件最大的不同在于:管道的数据完全存储在内核内存 中,既不需要将内存数据刷新到磁盘,也不需要从磁盘加载数据到该缓冲区,从而避免了磁盘I/O的延迟和持久化开销
- 由于该内存文件中的数据只能由写端流向读端 ,所有被形象的称为管道文件
- 在实现上,管道拥有 与普通文件类似的内核数据结构(如inode、file结构体等) ,这些结构体仅存在于内存中,不关联磁盘存储。这种设计使得管道能够复用文件系统的完整接口和管理机制(文件描述符、VFS层等),同时保持内存级的高性能
- 管道的原理总结
管道是Unix/Linux系统中基于文件抽象 的进程间通信机制。其核心原理是通过内核维护的环形内存缓冲区 (类似于循环队列 )实现数据中转。当进程调用pipe()系统调用时,内核会创建独立的管道缓冲区及两个关联的文件对象(读端/写端),并以文件描述符形式返回。管道复用文件系统的接口(read()/write()),但数据完全在内核内存中交换,避免了磁盘I/O开销。匿名管道 因无文件系统路径名,只能通过fork()继承文件描述符,故仅限亲缘进程通信;命名管道 则在文件系统中创建可见节点,支持任意进程通过路径名访问。管道严格单向通信(写端→读端),通过内核的阻塞/唤醒机制实现同步,当管道为空时读进程阻塞,已满时写进程阻塞,写端关闭后读端读到EOF,读端关闭后写端会收到SIGPIPE信号。这种设计在保证进程隔离性的同时,实现了高效的内存级数据交换
1.2管道的特征
- 管道只能单向通信,数据只能在一个方向流动(写端 → 读端)(双向通信需要两个管道)

- 管道面向字节流(自定义消息边界,而不是像C语言一样规定\0为字符串结尾)
- 管道缓冲区大小有限(通常为64KB),单个进程的写入总是连续的,多个进程同时写入时:超过 PIPE_BUF(通常4KB)的写入可能被分割,≤PIPE_BUF 的写入保证原子性(不被其他写入打断)
- 管道中的数据全程在内核维护的环形缓冲区中流动,完全不经过磁盘I/O
1.3管道的4种情况
- 读写端正常,管道为空时,读进程会阻塞等待
- 读写端正常,管道达到容量上限时,写进程会阻塞等待
- 写端关闭,读端读到文件结尾EOF,返回0
- 读端关闭,写端进程被操作系统终止,触发SIGPIPE信号
2.匿名管道
2.1匿名管道的原理

当父进程调用pipe()系统调用时,内核会创建一个独立的内核级缓冲区(管道缓冲区),并同时创建两个相关联的文件对象,分别代表管道的读端和写端。这两个文件对象会被放入父进程的文件描述符表中。随后,当父进程通过fork()创建子进程时,子进程会继承父进程的文件描述符表,这意味着子进程获得了指向相同文件对象的文件描述符。由于这些文件对象都指向同一个内核管道缓冲区,因此父子进程最终都能通过各自的文件描述符访问到同一份共享资源,从而实现进程间通信
为了建立清晰、可靠的管道通信,每个进程必须明确自己的角色(要么只读,要么只写),并通过关闭不需要的文件描述符来确保通信方向是单向的
上述管道没有在文件系统中创建持久化的路径名,只能通过继承文件描述符的方式被后代进程访问,所以叫做匿名管道由此得出结论:匿名管道只能用于具有亲缘关系的进程间通信
匿名管道 = 无持久化文件系统路径名 + 依赖描述符继承 + 仅限亲缘进程通信 + 内核的共享环形缓冲区
- 无持久化文件系统路径名
- 不创建 /tmp/xxx 这样的文件路径
- 不在磁盘上分配 inode 或目录项
- 仅存在于内核内存中
-
依赖描述符继承
// 唯一的传递方式
int pipefd[2];
pipe(pipefd); // 父进程创建
fork(); // 子进程继承pipefd[0]和pipefd[1]
- 无法通过 open("/some/path") 访问
- 无法被无关进程发现或打开
- 仅限亲缘进程通信
- 适用:父子进程、兄弟进程
- 不适用:任意两个独立进程
2.2匿名管道的创建之pipe
|----------|------------------------------------------------------------------------------|
| 函数原型 | int pipe(int pipefd[2]); |
| 头文件 | <unistd.h> |
| 功能 | 创建匿名管道,用于进程间通信 |
| 参数 | pipefd[2]:包含两个文件描述符的数组 pipefd[0]:管道的读端(嘴读) pipefd[1]:管道的写端(笔写) |
| 返回值 | 成功:返回 0 失败:返回 -1,并设置 errno |
| 通信方向 | 单向通信 :数据只能从写端(pipefd[1])流向读端(pipefd[0]) |
| 缓冲区 | 内核维护的环形内存缓冲区,默认大小通常为 64KB |
| 适用关系 | 仅限有亲缘关系的进程(如父子进程、兄弟进程) |
| 生命周期 | 随创建进程的结束而自动销毁 |
| 阻塞行为 | 默认阻塞模式(读写端均正常): - 管道为空时,read() 阻塞 - 管道已满时,write() 阻塞 |
| 关闭行为 | - 所有写端关闭:read() 返回 0(EOF) - 所有读端关闭:write() 触发 SIGPIPE 信号 |
| 原子性 | 写入数据 ≤ PIPE_BUF(通常 4KB)时保证原子性 |
| 典型应用 | Shell 管道符 |、父子进程通信 |
管道返回两个文件描述符的根本原因在于:读写操作需要独立的位置指针和状态管理,单一描述符会导致读写位置冲突,增加同步复杂度;分离为读端和写端两个描述符,让各自拥有独立的文件位置和状态,既简化了实现又提高了效率
2.3匿名管道的应用场景
Shell命令
具有血缘关系的进程间通信


父子进程间通信(父读子写)
进程池
- 先描述管道,然后利用vector管理这些管道
- 父进程一次性创建多个子进程(注意关闭子进程继承的写端以确保管道的单写单读)
- 父进程通过选择任务、选择进程、调用系统调用write实现对特定管道文件的写,对应子进程接收任务码,执行对应任务后阻塞等待下一次任务码读取
- 最后关闭写端,子进程读到文件结尾由父进程等待回收后程序结束
3.命名管道
3.1命名管道的原理

当进程调用mkfifo()系统调用时,内核会在文件系统中创建一个特殊类型文件(FIFO文件)。不同进程通过相同的路径名 打开这个FIFO文件时,内核会执行关键的打开同步:以只读方式打开的进程会阻塞,直到有进程以只写方式打开同一FIFO(反之亦然)。一旦读写配对成功,内核即为这些进程分配共享的管道缓冲区,所有进程的文件描述符都指向这同一个内核缓冲区,因此不同进程都能通过各自的文件描述符访问到同一份共享资源,从而实现进程间通信
为了建立清晰、可靠的管道通信,每个进程必须明确自己的角色(要么只读,要么只写),并通过关闭不需要的文件描述符来确保通信方向是单向的
上述管道在文件系统中创建持久化的路径名,所以叫做命名管道所有文件的路径是唯一的,由此得出结论:命名管道能用于任意进程间通信
命名管道在文件系统中的类型标识(ls -l显示为p)
命名管道 = 持久化的文件系统路径名 + 不依赖描述符继承 + 任意进程间通信 + 内核的共享环形缓冲区
- 持久化的文件系统路径名
- 文件系统中的实体

- 与匿名管道的对比
匿名管道:内存对象,procfs(进程文件系统)可见 (/proc/pid/fd/)
命名管道:文件系统实体,目录可见
- 不依赖描述符继承
-
通过路径名直接访问
// 进程A(任意进程)
int fd = open("/tmp/shared_fifo", O_WRONLY);// 进程B(无亲缘关系)
int fd = open("/tmp/shared_fifo", O_RDONLY); -
核心访问方式
匿名管道:只能通过继承的文件描述符
命名管道:通过已知路径名open()打开
- 任意进程间通信
- 支持所有进程关系
适用:父子进程、兄弟进程、同用户独立进程、不同用户进程、跨会话进程
不适用:无(只要知道路径且有权限)
3.2命名管道的创建之mkfifo
- shell指令
| 格式 | 说明 | 示例 |
|---|---|---|
| 基础语法 | mkfifo [选项]... 文件名... |
mkfifo pipe1 |
| 创建多个 | mkfifo 文件1 文件2 ... |
mkfifo pipe1 pipe2 pipe3 |
| 设置权限 | mkfifo -m 权限模式 文件名 |
mkfifo -m 644 mypipe |
| 完整格式 | mkfifo [选项] 文件名1 [文件名2 ...] |
mkfifo -m 600 /tmp/pipe1 /tmp/pipe2 |
mkfifo命令是用于在文件系统中创建命名管道(FIFO)的专用工具,为无亲缘关系的进程提供基于文件路径的通信通道。
- 系统调用
| 项目 | 说明 |
|---|---|
| 函数原型 | int mkfifo(const char *pathname, mode_t mode); |
| 头文件 | <sys/types.h> <sys/stat.h> |
| 功能 | 创建命名管道(FIFO特殊文件),用于任意进程间通信 |
| 参数 | pathname:管道在文件系统中的路径名 mode:管道文件的访问权限模式(八进制) |
| 返回值 | 成功 :返回 0 失败 :返回 -1,并设置 errno |
| 通信方向 | 单向通信:需要明确读写角色,通常一个进程读、另一个进程写 |
| 文件系统 | 在文件系统中创建持久化的特殊文件(类型为p) |
| 适用关系 | 任意进程间通信(无需亲缘关系) |
| 生命周期 | 持久存在,需显式调用 unlink() 删除 |
| 阻塞行为 | 打开时阻塞 : - 只读打开(O_RDONLY):阻塞直到有写进程打开 - 只写打开(O_WRONLY):阻塞直到有读进程打开 |
| 缓冲区 | 内核维护的环形内存缓冲区,默认大小与pipe相同 |
| 关闭行为 | - 所有写端关闭:read() 返回 0(EOF) - 所有读端关闭:write() 触发 SIGPIPE 信号或返回EPIPE错误 |
| 原子性 | 写入数据 ≤ PIPE_BUF (通常 4KB)时保证原子性 |
| 权限控制 | 受umask影响,最终权限 = mode & ~umask |
| 典型应用 | 独立进程间的持久化通信、客户端-服务器模型 |
3.3命名管道的删除之unlink
| 方法 | 命令示例 | 说明 |
|---|---|---|
| 1. unlink 命令 | unlink /tmp/myfifo |
专门删除文件的系统工具 |
| 2. rm 命令 | rm /tmp/myfifo |
通用删除命令,更常用 |
| 3. C语言unlink() | unlink("/tmp/myfifo") |
系统调用,编程中使用 |
| 项目 | 说明 |
|---|---|
| 函数原型 | intunlink(const char *pathname); |
| 头文件 | <unistd.h> |
| 功能 | 从文件系统中删除一个目录项(删除文件或命名管道) |
| 参数 | pathname:要删除的文件或管道的路径名 |
| 返回值 | 成功 :返回 0 失败 :返回 -1,并设置 errno |
| 删除机制 | 递减文件的链接计数,当链接数为0且无进程打开该文件时,真正释放文件资源 |
| 对普通文件 | 删除目录项,文件数据可能继续存在直到所有打开的文件描述符关闭 |
| 对命名管道 | 删除FIFO文件在文件系统中的目录项,已打开的管道可继续使用 |
| 打开文件处理 | 已打开的文件可继续正常读写,删除操作不影响已打开的文件描述符 |
| 错误条件 | 常见errno: - ENOENT:文件不存在 - EACCES:无目录写权限 - EPERM:目标为目录且无权限 - EBUSY:文件正在被使用(某些系统) |
| 原子性 | 删除操作是原子的,在多进程环境中是安全的 |
| 权限要求 | 需要对包含该文件的目录具有写和执行权限 |
与remove()关系 |
remove() 是C标准库函数,对文件调用 unlink(),对目录调用 rmdir() |
| 与命名管道 | 删除命名管道后,已建立的通信可继续,但新进程无法再通过路径打开 |
| 典型应用 | 临时文件清理、命名管道使用后的资源释放、原子文件替换操作 |
3.4命名管道的应用场景
- 用命名管道实现 server&client 通信
- 使用一个类管理命名管道,类似于智能指针
- server端创建并打开命名管道后用来读,client端打开命名管道后用来写
- 进程退出,命名管道类对象自动析构,从而自动删除命名管道