pipe 是 Linux 内核提供的进程间通信(IPC)机制 ,本质上是一个内核管理的环形缓冲区(ring buffer) ,让两个进程可以通过文件描述符(FD)单向传输字节流。

一、pipe 是什么?
| 特性 | 说明 |
|---|---|
| 本质 | 内核中的环形缓冲区,存在于 RAM 中 |
| 方向 | 单向(半双工):数据只能从一个方向流动 |
| 顺序 | FIFO(先进先出) |
| 生命周期 | 随进程结束而销毁,不持久化到磁盘 |
| 创建 | pipe() 系统调用创建 2 个 FD |
二、核心 API
c
#include <unistd.h>
int pipe(int pipefd[2]);
// pipefd[0] = 读端(read end)
// pipefd[1] = 写端(write end)
// 现代版本:
int pipe2(int pipefd[2], int flags);
// flags: O_NONBLOCK(非阻塞), O_CLOEXEC(执行时关闭)
// 示例:
int fd[2];
pipe(fd); // 或 pipe2(fd, O_NONBLOCK | O_CLOEXEC)
write(fd[1], "Hello", 5); // 向写端写入
read(fd[0], buf, 100); // 从读端读取
三、架构图

四、内核实现:环形缓冲区
c
// Linux Kernel: fs/pipe.c
struct pipe_inode_info {
struct mutex mutex;
struct pipe_buffer *bufs; // 环形缓冲区数组
unsigned int head; // 写指针
unsigned int tail; // 读指针
unsigned int ring_size; // 缓冲区大小(默认 16 页 = 64KB)
struct page *tmp_page;
struct fasync_struct *fasync_readers;
struct fasync_struct *fasync_writers;
struct inode *inode;
struct user_struct *user;
};
关键点:
- 内核分配一个页(page)数组作为缓冲区
- 默认大小:16 页 = 64KB(PIPE_BUF)
- 写指针和读指针维护 FIFO 顺序
- 缓冲区满时,write() 阻塞(默认)或返回 EAGAIN(非阻塞)
五、阻塞 vs 非阻塞行为
| 场景 | 阻塞模式(默认) | 非阻塞模式(O_NONBLOCK) |
|---|---|---|
read() 时缓冲区为空 |
阻塞,直到有数据 | 返回 -1,errno = EAGAIN |
write() 时缓冲区满 |
阻塞,直到有空间 | 返回 -1,errno = EAGAIN |
write() < PIPE_BUF |
原子写入(全有或全无) | 原子写入 |
write() > PIPE_BUF |
可能拆分写入 | 可能拆分写入 |
六、pipe 在 Android Looper 中的应用(Legacy)
旧版 Android(Android 5.0 之前)用 pipe 实现 Looper 的线程唤醒:
cpp
// system/core/libutils/Looper.cpp (Legacy)
Looper::Looper(bool allowNonCallbacks) {
// 创建 pipe
int wakeFds[2];
pipe(wakeFds); // or pipe2(wakeFds, O_CLOEXEC)
mWakeReadPipeFd = wakeFds[0]; // 读端
mWakeWritePipeFd = wakeFds[1]; // 写端
// 将读端注册到 epoll
struct epoll_event eventItem;
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
void Looper::wake() {
// 向写端写入 1 字节,触发 epoll_wait 返回
write(mWakeWritePipeFd, "W", 1);
}
void Looper::awoken() {
// 从读端读取,清空 pipe
char buffer[16];
read(mWakeReadPipeFd, buffer, sizeof(buffer));
}
七、为什么 Android 从 pipe 切换到 eventfd?
| 对比项 | pipe | eventfd |
|---|---|---|
| FD 数量 | 2(读+写) | 1 |
| 内核缓冲区 | 2 个 ring buffer | 1 个 64 位计数器 |
| 原子性 | 不保证 | 保证 64 位原子操作 |
| 系统调用开销 | pipe() + 2 次 fcntl() |
eventfd() 一次 |
| 适用场景 | 数据传输 | 简单通知 |
| Android 使用 | Legacy(pre-5.0) | Modern(5.0+) |
eventfd 更适合"通知"场景,因为它就是设计来做轻量级事件通知的。
八、常见使用场景
| 场景 | 说明 | |
|---|---|---|
| Shell 管道 | `ls | grep txt---ls的 stdout 通过 pipe 连接到grep` 的 stdin |
| 父子进程通信 | fork() 后,pipe() 让父子进程交换数据 |
|
| Android Looper | Legacy 唤醒机制(已被 eventfd 替代) | |
| 自管道技巧 | 信号处理中,信号处理函数向 pipe 写,主循环从 pipe 读,避免信号处理中的异步问题 |
九、总结
pipe 是 Linux 内核提供的单向字节流 IPC 机制,通过 pipe() 创建两个文件描述符(fd0 读端、fd1 写端),数据在内核环形缓冲区中按 FIFO 顺序流动。旧版 Android Looper 用 pipe 实现线程唤醒(写端触发 epoll_wait 返回),现代 Android 用更高效的 eventfd 替代了 pipe。