Android随笔-pipe是什么?

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() 时缓冲区为空 阻塞,直到有数据 返回 -1errno = EAGAIN
write() 时缓冲区满 阻塞,直到有空间 返回 -1errno = 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。

相关推荐
帅次1 小时前
Android 高级工程师面试:Flow 与状态流 近1年高频追问 22 题
android·面试·职场和发展
小趴菜え1 小时前
升级Ubuntu20.04版本至Ubuntu22.04
linux·运维
雨季余静1 小时前
RustDesk Android APK 从零编译全步骤
android
辰三1 小时前
统信 UOS + GBase 8s 国产化环境部署实战:从虚拟机到数据库连接完整指南
linux·c#
辰合软件1 小时前
通达OA目录结构与核心文件解析
android·数据库·oracle
通玄1 小时前
Jetpack Compose 入门系列(八):Compose 中的副作用处理
android
Darkwanderor2 小时前
Linux进程优先级操作
linux·运维·c语言·c++
流氓也是种气质 _Cookie2 小时前
如何创建MCP
android·adb
AOwhisky2 小时前
Linux(CentOS)系统管理入门笔记(第一期)——从 Multics 到主流发行版
linux·运维·笔记·centos·云计算