pipe 是 Linux 内核提供的进程间通信(IPC)机制,本质上是一个内核管理的环形缓冲区(ring buffer),让两个进程可以通过文件描述符(FD)单向传输字节流。
一、pipe 是什么?
| 特性 | 说明 |
|---|---|
| 本质 | 内核中的环形缓冲区,存在于 RAM 中 |
| 方向 | 单向(半双工):数据只能从一个方向流动 |
| 顺序 | FIFO(先进先出) |
| 生命周期 | 随进程结束而销毁,不持久化到磁盘 |
| 创建 | pipe()系统调用创建 2 个 FD |
二、核心 API
#include<unistd.h>intpipe(intpipefd[2]);// pipefd[0] = 读端(read end)// pipefd[1] = 写端(write end)// 现代版本:intpipe2(intpipefd[2],intflags);// flags: O_NONBLOCK(非阻塞), O_CLOEXEC(执行时关闭)// 示例:intfd[2];pipe(fd);// 或 pipe2(fd, O_NONBLOCK | O_CLOEXEC)write(fd[1],"Hello",5);// 向写端写入read(fd[0],buf,100);// 从读端读取三、架构图
四、内核实现:环形缓冲区
// Linux Kernel: fs/pipe.cstructpipe_inode_info{structmutexmutex;structpipe_buffer*bufs;// 环形缓冲区数组unsignedinthead;// 写指针unsignedinttail;// 读指针unsignedintring_size;// 缓冲区大小(默认 16 页 = 64KB)structpage*tmp_page;structfasync_struct*fasync_readers;structfasync_struct*fasync_writers;structinode*inode;structuser_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 的线程唤醒:
// system/core/libutils/Looper.cpp (Legacy)Looper::Looper(boolallowNonCallbacks){// 创建 pipeintwakeFds[2];pipe(wakeFds);// or pipe2(wakeFds, O_CLOEXEC)mWakeReadPipeFd=wakeFds[0];// 读端mWakeWritePipeFd=wakeFds[1];// 写端// 将读端注册到 epollstructepoll_eventeventItem;eventItem.events=EPOLLIN;eventItem.data.fd=mWakeReadPipeFd;epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeReadPipeFd,&eventItem);}voidLooper::wake(){// 向写端写入 1 字节,触发 epoll_wait 返回write(mWakeWritePipeFd,"W",1);}voidLooper::awoken(){// 从读端读取,清空 pipecharbuffer[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() 创建两个文件描述符(fd[0] 读端、fd[1] 写端),数据在内核环形缓冲区中按 FIFO 顺序流动。旧版 Android Looper 用 pipe 实现线程唤醒(写端触发 epoll_wait 返回),现代 Android 用更高效的 eventfd 替代了 pipe。