尧图精选

Linux FIFO命名管道:内核原理与阻塞机制详解

🕒 发布时间:2026/9/3 22:43:45 📁 来源:尧图网络
如果你在 Linux 下做过跨进程的数据传递一定遇到过这样的困惑管道pipe用起来很简单但只能在父子进程之间通信想用文件共享数据又要处理并发锁和同步问题用 Socket 回环地址功能虽强但总觉得太重。实际上Linux 早就为你准备了一个介于两者之间的方案——FIFO命名管道。这篇文章会从内核实现的角度用图解和源码带你彻底搞懂 FIFO 的工作原理并给出可直接复制的完整示例、常见踩坑点和工程最佳实践。FIFO 最大的价值在于它把管道通信的能力从“父子进程”扩展到了“任意两个进程”。它既有管道的流式语义和内核缓冲区又以文件路径的形式暴露在文件系统中让进程可以像打开文件一样建立起通信链路。理解 FIFO不仅能解决实际开发中“需要独立进程协作但不想引入网络协议”的问题更是深入理解 Linux 进程间通信IPC机制的关键一环。读完这篇文章你将获得三项能力第一透彻理解 FIFO 在内核中的生命周期、阻塞机制和缓冲区设计第二独立写出阻塞、非阻塞、双工三种模式的完整通信代码第三在生产环境中避开 FIFO 的常见陷阱比如 SIGPIPE 信号、死锁和残留文件问题。1. 为什么要学 FIFO它解决的是哪一类通信问题在 Linux 的 IPC进程间通信工具箱里方案非常多信号、管道、消息队列、共享内存、信号量、Socket。为什么专门把 FIFO 拿出来讲因为它卡在一个非常特殊的生态位上比匿名管道通用比 Socket 轻量比共享内存安全。先看匿名管道的局限。匿名管道通过pipe()系统调用创建本质上是在内核中申请一块缓冲区返回两个文件描述符一个用于读一个用于写。问题在于这两个文件描述符只存在于创建它的进程里子进程想要访问只能通过 fork 继承。两个没有血缘关系的进程比如一个后台服务和一个运维脚本根本没有办法拿到对方的管道描述符。这时候你可能会说用文件不就行了A 进程写文件B 进程读文件。确实可以但文件方案有三个麻烦第一个麻烦是同步你怎么知道 A 写完了一整条数据用文件锁用单独的完成标记文件这些都是在重复发明轮子。第二个麻烦是“读后即焚”日志文件可以无限追加但通信数据如果不清理磁盘会被撑爆。第三个麻烦是权限控制不够灵活普通文件默认允许任意读写很难精确控制谁可以读、谁可以写。FIFO 的设计恰好解决了这些痛点它是一个在文件系统中可见的“管道文件”用mkfifo创建任何有权限的进程都可以通过路径打开它。但你打开的不是一个普通文件而是一个内核管道。读端和写端通过内核缓冲区衔接数据在内核空间流动不落盘不占磁盘空间。写入和读取天然具备流式语义写端写入多少读端就按顺序读走多少不需要自己维护游标和同步位点。用一句话概括FIFO 是“有路径的管道有管道语义的文件”。当你需要两个无亲缘关系的进程做流式数据交换且不想引入网络栈、不想处理文件锁、又不希望数据落盘时FIFO 就是最直接的选择。2. FIFO 核心概念与内核原理2.1 文件系统中的 FIFO 长什么样在终端执行下面的命令创建一个命名管道mkfifo /tmp/myfifo ls -l /tmp/myfifo输出结果中文件类型那一列是p而不是普通文件的-、目录的d、符号链接的lprw-r--r-- 1 user user 0 3月 15 10:20 /tmp/myfifo这就是 FIFO 的第一层身份它是一个文件系统条目拥有自己的路径、权限和所有者。p代表 pipe意思是“管道文件”。它的文件大小显示为 0因为数据根本不会停在这个文件上它只是通信链路的入口。2.2 内核中的 FIFO 是什么FIFO 在文件系统中只是一个“名字”真正的数据通道在内核里。当你打开一个 FIFO 并开始读写时内核会为它创建一个管道对象pipe_inode_info包含以下核心组成部分环形缓冲区ring buffer管道数据的暂存区通常由多个 pages 组成默认容量一般可以查阅/proc/sys/fs/pipe-max-size实际默认大小通常为 64KB不同内核版本略有差异。读队列readers和写队列writers等待在该 FIFO 上的读进程和写进程分别挂在等待队列上。互斥保护所有读写操作通过锁机制保障并发安全。数据流向可以这样理解写进程 ---write()--- 内核管道缓冲区 ---read()--- 读进程写入方调用write把用户空间数据拷贝到内核缓冲区读取方调用read把内核缓冲区数据拷贝到用户空间。整个过程不经过磁盘也不经过网络协议栈。2.3 为什么打开 FIFO 会阻塞这是新手最容易困惑的地方打开一个普通文件open立刻返回打开一个 FIFOopen可能卡住。原因在于 FIFO 的管道语义一个管道必须有读端和写端才能工作。如果你以只读方式打开一个 FIFO内核会检查当前是否有写者如果没有读端的open会阻塞直到某个进程以写方式打开同一个 FIFO。反过来以写方式打开时若没有读者也会阻塞。这种设计保证了管道两端就绪后才开始传输避免了“写了一堆数据却没人读”的浪费。这也是 FIFO 与普通文件最大的行为差异普通文件无所谓读端写端随时可以打开。2.4 这里需要澄清一个概念FIFO 与异步 FIFO在 FPGA 或数字电路领域经常提到“异步 FIFO”“FIFO IP 核”“FIFO 溢出”这是硬件设计中的数据缓冲队列用于跨时钟域的数据传递。它和 Linux 的 FIFO 命名管道不是一回事。Linux 的 FIFO 是操作系统提供的进程间通信机制面向软件进程硬件 FIFO 是芯片内部实现的数据缓存结构。如果之前被这两个概念绕晕现在可以彻底分清了。3. 环境准备与前置条件本文所有示例都在 Linux 环境下演示理论上适用于所有主流发行版包括 Ubuntu、CentOS、Debian 等。嵌入式 Linux 环境也可以运行但内核需要开启 FIFO 相关支持标准内核默认开启。需要准备的环境如下Linux 操作系统内核版本不限通用思路适用于 2.6 之后的所有版本gcc 编译器用于编译 C 语言示例两个终端窗口或者能来回切换的终端环境基本命令行操作能力检查编译环境gcc --version如果提示找不到 gcc先安装编译工具链。Ubuntu/Debian 使用sudo apt update sudo apt install gccCentOS/RHEL 使用sudo yum install gcc准备就绪后进入正题。本文所有示例代码都刻意保持最小化方便直接编译运行验证 FIFO 的每一个关键行为。4. 深入理解 FIFO 的 API 与阻塞机制4.1 创建 FIFOmkfifo 命令与系统调用有两种方式创建 FIFO。第一种是命令行工具mkfifo /tmp/testfifo第二种是在 C 代码中调用mkfifo()函数#include sys/types.h #include sys/stat.h int result mkfifo(/tmp/testfifo, 0644); if (result -1) { perror(mkfifo); }第二个参数是权限位和open系统调用的 mode 参数一致。0644表示所有者可读写组用户和其他用户只读。注意一个细节mkfifo创建的是文件系统条目但它不占用磁盘数据块。它只是一个“管道入口标志”。4.2 打开 FIFOopen 的四种行为模式open系统调用用于打开 FIFO行为取决于是否指定了O_NONBLOCK标志以及打开方式是读还是写。open(fifo_path, O_RDONLY)阻塞模式只读。如果没有写者调用阻塞。open(fifo_path, O_WRONLY)阻塞模式只写。如果没有读者调用阻塞。open(fifo_path, O_RDONLY | O_NONBLOCK)非阻塞只读。即使没有写者也立即返回成功。open(fifo_path, O_WRONLY | O_NONBLOCK)非阻塞只写。如果没有读者立即返回错误errno 为 ENXIO。这四种行为是 FIFO 编程的基石。实际项目中阻塞模式适合“两个进程约定好同时启动”的场景非阻塞模式适合“写进程先启动稍后读进程才就绪”的场景。4.3 读写语义read、write 与缓冲区的相互作用FIFO 的读写语义与普通文件完全不同需要特别记忆读端从 FIFO 读取数据时如果没有数据可读阻塞模式下会一直等非阻塞模式下返回 -1errno 为 EAGAIN。写端向 FIFO 写入数据时如果缓冲区剩余空间不足阻塞模式下会等读端读走数据腾出空间非阻塞模式下可能部分写入返回实际写入字节数。当所有写端都关闭后读端调用 read 会返回 0表示“读到文件尾”也就是所有写者都已离开通信即将结束。当所有读端都关闭后写端继续 write进程会收到 SIGPIPE 信号。该信号的默认动作是终止进程这是最常见的 FIFO 崩溃原因之一。4.4 生命周期从 mkfifo 到 unlinkFIFO 存在于文件系统中但它是“活的”还是“死的”取决于是否有进程打开它。没有进程打开时它只是一个路径名有进程打开后内核才真正创建管道对象。关闭所有描述符后内核回收管道对象但路径名仍然存在。要彻底删除一个 FIFO用unlink或rmrm /tmp/testfifo在代码中对应unlink(/tmp/testfifo);很多程序在启动时会先unlink再mkfifo就是为了避免上次运行残留的 FIFO 名字干扰新的通信。5. 完整示例一阻塞模式下的单写单读通信5.1 代码实现第一个示例演示最基础的模型一个写进程向 FIFO 持续写入一个读进程从 FIFO 读取数据。先创建 FIFO然后分别编译两个程序。写端代码文件路径writer.c// writer.c // FIFO 写端示例阻塞模式向命名管道持续写入 #include stdio.h #include stdlib.h #include string.h #include fcntl.h #include sys/stat.h #include sys/types.h #include unistd.h #define FIFO_PATH /tmp/myfifo int main() { int fd; const char *messages[] { Message 1: Hello FIFO, Message 2: Process communication, Message 3: Kernel buffer, Message 4: Goodbye }; int count sizeof(messages) / sizeof(messages[0]); int i; // 打开 FIFO阻塞模式。如果此时没有读进程打开 FIFO这里会阻塞。 fd open(FIFO_PATH, O_WRONLY); if (fd -1) { perror(open); exit(EXIT_FAILURE); } printf([Writer] Opened FIFO for writing.\n); for (i 0; i count; i) { // write 写入字符串注意要包含结尾的 \0方便读端按字符串处理 ssize_t len write(fd, messages[i], strlen(messages[i]) 1); if (len -1) { perror(write); break; } printf([Writer] Sent: %s\n, messages[i]); sleep(1); } close(fd); printf([Writer] Closed.\n); return 0; }读端代码文件路径reader.c// reader.c // FIFO 读端示例阻塞模式从命名管道持续读取 #include stdio.h #include stdlib.h #include string.h #include fcntl.h #include sys/stat.h #include sys/types.h #include unistd.h #define FIFO_PATH /tmp/myfifo #define BUFFER_SIZE 256 int main() { int fd; char buffer[BUFFER_SIZE]; // 打开 FIFO阻塞模式。如果此时没有写进程打开 FIFO这里会阻塞。 fd open(FIFO_PATH, O_RDONLY); if (fd -1) { perror(open); exit(EXIT_FAILURE); } printf([Reader] Opened FIFO for reading.\n); while (1) { // 清空缓冲区避免上一次残留数据 memset(buffer, 0, BUFFER_SIZE); // 阻塞读取。当写端全部关闭read 返回 0视为通信结束。 ssize_t len read(fd, buffer, BUFFER_SIZE - 1); if (len -1) { perror(read); break; } if (len 0) { printf([Reader] All writers closed. Exiting.\n); break; } printf([Reader] Received: %s\n, buffer); } close(fd); return 0; }5.2 编译与运行验证创建 FIFO然后编译两个程序mkfifo /tmp/myfifo gcc -o writer writer.c gcc -o reader reader.c先启动写端./writer现象是写端会卡在 open 调用上因为此时还没有任何进程以读方式打开 FIFO。这就是 FIFO 的“握手”机制。在另一个终端启动读端./reader启动读端的瞬间写端的 open 返回两端建立连接写进程开始逐条发送数据读进程同步接收并打印。运行预期输出如下写端终端[Writer] Opened FIFO for writing. [Writer] Sent: Message 1: Hello FIFO [Writer] Sent: Message 2: Process communication [Writer] Sent: Message 3: Kernel buffer [Writer] Sent: Message 4: Goodbye [Writer] Closed.读端终端[Reader] Opened FIFO for reading. [Reader] Received: Message 1: Hello FIFO [Reader] Received: Message 2: Process communication [Reader] Received: Message 3: Kernel buffer [Reader] Received: Message 4: Goodbye [Reader] All writers closed. Exiting.这个示例揭示了两个关键行为第一open 成功依赖另一端的出现这是 FIFO 通信的建立前提第二当所有写端关闭后读端 read 返回 0程序可以据此判断通信结束。6. 完整示例二非阻塞模式与读写竞争处理6.1 为什么需要非阻塞模式阻塞模式虽然简单但存在一个致命问题如果读进程启动很慢写进程就会一直卡在 open 上无法做任何初始化操作。更复杂的情况是一个进程需要同时监听多个 FIFO而阻塞在任何一个 open 上都会导致其他事件无法处理。非阻塞模式解决了这些问题。它让 open 立即返回由程序自行决定后续如何处理。下面用一个示例展示非阻塞读写和“轮询等待”的配合。6.2 非阻塞写端实现非阻塞写端代码文件路径writer_nonblock.c// writer_nonblock.c // FIFO 非阻塞写端示例即使读端尚未就绪也能继续做自己的事 #include stdio.h #include stdlib.h #include string.h #include fcntl.h #include sys/stat.h #include sys/types.h #include unistd.h #include errno.h #define FIFO_PATH /tmp/myfifo #define RETRY_COUNT 10 int main() { int fd; int retries 0; const char *msg Hello from non-block writer; // 以非阻塞只写方式打开。如果当前没有读端open 立即失败errno 为 ENXIO。 while (retries RETRY_COUNT) { fd open(FIFO_PATH, O_WRONLY | O_NONBLOCK); if (fd ! -1) { break; } if (errno ENXIO) { printf([WriterNonBlock] No reader yet, retry %d...\n, retries 1); retries; sleep(1); } else { perror(open); exit(EXIT_FAILURE); } } if (fd -1) { printf([WriterNonBlock] Give up: no reader after %d retries.\n, RETRY_COUNT); return 1; } printf([WriterNonBlock] Reader detected, writing...\n); ssize_t len write(fd, msg, strlen(msg) 1); if (len -1) { perror(write); close(fd); return 1; } printf([WriterNonBlock] Wrote %zd bytes.\n, len); close(fd); return 0; }6.3 非阻塞读端实现读端代码文件路径reader_nonblock.c// reader_nonblock.c // FIFO 非阻塞读端示例没有数据时立即返回 EAGAIN不阻塞进程 #include stdio.h #include stdlib.h #include string.h #include fcntl.h #include sys/stat.h #include sys/types.h #include unistd.h #include errno.h #define FIFO_PATH /tmp/myfifo #define BUFFER_SIZE 256 int main() { int fd; char buffer[BUFFER_SIZE]; // 非阻塞只读。即使没有写端open 也会立即成功。 fd open(FIFO_PATH, O_RDONLY | O_NONBLOCK); if (fd -1) { perror(open); exit(EXIT_FAILURE); } printf([ReaderNonBlock] Opened FIFO non-blocking.\n); // 主动轮询读取最多尝试 20 次 int attempts 0; while (attempts 20) { memset(buffer, 0, BUFFER_SIZE); ssize_t len read(fd, buffer, BUFFER_SIZE - 1); if (len 0) { printf([ReaderNonBlock] Received: %s\n, buffer); break; } else if (len 0) { printf([ReaderNonBlock] All writers closed.\n); break; } else { // EAGAIN 表示暂时没有数据 if (errno EAGAIN) { printf([ReaderNonBlock] No data yet (attempt %d), waiting...\n, attempts 1); attempts; sleep(1); continue; } perror(read); break; } } close(fd); return 0; }6.4 运行验证gcc -o writer_nonblock writer_nonblock.c gcc -o reader_nonblock reader_nonblock.c先启动写端观察它的重试输出./writer_nonblock输出会显示“No reader yet, retry N...”因为读端还没有启动。然后在另一个终端启动读端./reader_nonblock由于写端最终会在某次重试中成功打开 FIFO读端也会在轮询中读到数据。实际运行时可能先启动读端也可能先启动写端取决于你开终端的顺序。这种非阻塞方式的价值就在于进程可以在等待对方就绪期间继续做其他事情而不是死死卡在 open 上。7. 完整示例三双工通信与多进程协作7.1 为什么一个 FIFO 无法实现双向通信FIFO 数据流是单向的数据只能从写端流向读端。如果两个进程都要发消息使用同一个 FIFO 是不行的因为两个进程都会成为“写者”和“读者”数据会混杂在一起无法区分谁发给谁。解决方案是使用两个 FIFO每个方向一个。这类似于 TCP 连接中使用两个方向的字节流。下面演示一个简化的“客户端-服务端”模型服务端监听一个 FIFO 接收请求通过另一个 FIFO 回复响应。7.2 双 FIFO 服务端实现服务端代码文件路径server.c// server.c // 双 FIFO 服务端示例接收客户端请求并回复响应 #include stdio.h #include stdlib.h #include string.h #include fcntl.h #include sys/stat.h #include sys/types.h #include unistd.h #include errno.h #define FIFO_REQUEST /tmp/fifo_request #define FIFO_RESPONSE /tmp/fifo_response #define BUFFER_SIZE 256 int main() { int req_fd, resp_fd; char buffer[BUFFER_SIZE]; // 创建两个 FIFO一个接收请求一个发送响应 if (mkfifo(FIFO_REQUEST, 0644) -1 errno ! EEXIST) { perror(mkfifo request); exit(EXIT_FAILURE); } if (mkfifo(FIFO_RESPONSE, 0644) -1 errno ! EEXIST) { perror(mkfifo response); exit(EXIT_FAILURE); } printf([Server] Waiting for request...\n); // 阻塞等待客户端打开请求 FIFO req_fd open(FIFO_REQUEST, O_RDONLY); if (req_fd -1) { perror(open request); exit(EXIT_FAILURE); } // 打开响应 FIFO 用于写回复。如果客户端尚未打开读端这里会阻塞。 resp_fd open(FIFO_RESPONSE, O_WRONLY); if (resp_fd -1) { perror(open response); exit(EXIT_FAILURE); } printf([Server] Client connected. Waiting for data...\n); while (1) { memset(buffer, 0, BUFFER_SIZE); ssize_t len read(req_fd, buffer, BUFFER_SIZE - 1); if (len -1) { perror(read); break; } if (len 0) { printf([Server] Client closed. Exiting.\n); break; } printf([Server] Received request: %s\n, buffer); // 简单回声把请求内容转换成响应 char response[BUFFER_SIZE]; snprintf(response, sizeof(response), ACK: %s, buffer); write(resp_fd, response, strlen(response) 1); } close(req_fd); close(resp_fd); return 0; }7.3 双 FIFO 客户端实现客户端代码文件路径client.c// client.c // 双 FIFO 客户端示例发送请求并等待服务端响应 #include stdio.h #include stdlib.h #include string.h #include fcntl.h #include sys/stat.h #include sys/types.h #include unistd.h #include errno.h #define FIFO_REQUEST /tmp/fifo_request #define FIFO_RESPONSE /tmp/fifo_response #define BUFFER_SIZE 256 int main() { int req_fd, resp_fd; char buffer[BUFFER_SIZE]; printf([Client] Connecting to server...\n); // 先打开请求 FIFO 的写端服务端此时可能还在阻塞等待 req_fd open(FIFO_REQUEST, O_WRONLY); if (req_fd -1) { perror(open request); exit(EXIT_FAILURE); } printf([Client] Request FIFO connected.\n); // 打开响应 FIFO 的读端。如果服务端已经打开了写端这一步才会成功。 resp_fd open(FIFO_RESPONSE, O_RDONLY); if (resp_fd -1) { perror(open response); exit(EXIT_FAILURE); } printf([Client] Response FIFO connected. Sending request...\n); const char *request Give me current time; write(req_fd, request, strlen(request) 1); memset(buffer, 0, BUFFER_SIZE); ssize_t len read(resp_fd, buffer, BUFFER_SIZE - 1); if (len 0) { printf([Client] Got response: %s\n, buffer); } else { perror(read response); } close(req_fd); close(resp_fd); return 0; }7.4 编译与运行gcc -o server server.c gcc -o client client.c先启动服务端./server服务端会阻塞在open(FIFO_REQUEST, O_RDONLY)等待客户端连接。再启动客户端./client运行结果服务端终端[Server] Waiting for request... [Server] Client connected. Waiting for data... [Server] Received request: Give me current time [Server] Client closed. Exiting.客户端终端[Client] Connecting to server... [Client] Request FIFO connected. [Client] Response FIFO connected. Sending request... [Client] Got response: ACK: Give me current time这个示例展示了一个重要工程模式双 FIFO 可以模拟简单的客户端-服务端通信。当然它无法与 Socket 的完整能力相比不支持多客户端并发、不支持复杂协议。但在嵌入式系统、轻量级任务中比如一个监控进程收集另一个进程的状态这套方案已经完全够用而且没有网络栈开销。8. FIFO 编程的常见问题与排查方法FIFO 看似简单实际使用中却有不少隐蔽的坑。下面按问题现象整理成排查表格。问题现象可能原因排查方式解决方案open 一直阻塞程序卡住不动另一端没有进程打开同一个 FIFO检查对端进程是否启动用lsof /tmp/myfifo查看谁打开了该 FIFO确认两端启动顺序或在 open 时加 O_NONBLOCK进程写入 FIFO 后突然被终止没有输出错误读端全部关闭写端触发 SIGPIPE 信号查看 shell 输出是否显示 Broken pipe忽略 SIGPIPEsignal(SIGPIPE, SIG_IGN)并检查 write 返回值非阻塞 read 反复返回 -1暂时没有数据到达errno 为 EAGAIN打印 errno 值确认写端是否写了数据轮询或改用 select/poll 监听可读事件通信结束判断不了程序一直等写端没有关闭写描述符查看写端是否在某个分支中忘记 close(fd)确保所有写端关闭read 才会返回 0启动多个进程后数据出现交错拼接多个写者同时写入数据边界丢失检查写入长度和读取方式用固定长度消息、在每条消息前加长度头或用splice传递文件描述符FIFO 文件残留下次启动时 open 报错上次程序异常退出没有 unlink检查文件系统条目用ls -l查看类型启动时先 unlink 再 mkfifo或捕获退出信号清理mkfifo 返回 EEXIST同名 FIFO 已经存在确认是否真的是 FIFO 而非普通文件可忽略 EEXIST但必须验证文件类型常见的排错第一步是查看文件类型和占用情况ls -l /tmp/myfifo lsof /tmp/myfifolsof可以列出所有打开该 FIFO 的进程 PID 和文件描述符是定位“谁没关、谁在等”最有效的工具。9. FIFO 最佳实践与工程建议9.1 命名与权限规范FIFO 路径建议放在/tmp或专门的运行目录下比如/var/run/myapp/。不建议在项目源码目录创建 FIFO避免污染版本库。权限按最小原则设置0644通常够用如果只允许同组用户访问可以用0660。一个常见做法是在程序启动时统一创建umask(0); mkfifo(/var/run/myapp/control.fifo, 0660);9.2 清理机制捕获退出信号FIFO 是文件系统条目程序崩溃不会自动删除。为了不留下垃圾文件应该在程序退出时主动 unlink#include signal.h void cleanup(int signum) { unlink(FIFO_PATH); _exit(0); } int main() { signal(SIGINT, cleanup); signal(SIGTERM, cleanup); // ... 业务逻辑 ... }还要在启动时容忍 EEXIST 错误但不能盲目忽略要验证已存在的文件确实是 FIFOstruct stat st; if (lstat(FIFO_PATH, st) 0 !S_ISFIFO(st.st_mode)) { fprintf(stderr, %s exists and is not a FIFO!\n, FIFO_PATH); exit(EXIT_FAILURE); }9.3 处理 SIGPIPE 信号写端遇到读端关闭时默认动作是终止进程而且不打印任何错误。对长期运行的服务来说这可能是灾难一个客户端断开就导致整个服务退出。所以服务端建议忽略 SIGPIPEsignal(SIGPIPE, SIG_IGN);忽略后write会返回 -1errno 为 EPIPE程序可以根据返回值自行处理这个错误分支。这是安全退出、记录日志的正确姿势。9.4 处理多条消息的边界问题FIFO 是字节流不像 UDP 消息保留边界。写端写两次读端可能一次读走写端写一次读端也可能分多次读走。工程上通常有两种做法固定长度消息每条消息固定 N 字节读端循环读取直到凑满 N 字节。长度前缀每条消息前 4 字节存长度读端先读长度再读正文。示例长度前缀写入uint32_t len strlen(msg) 1; write(fd, len, sizeof(len)); write(fd, msg, len);读取侧uint32_t len; read(fd, len, sizeof(len)); char *buf malloc(len); read(fd, buf, len);这样就能准确切分每条消息。9.5 多读多写场景多个进程同时写同一个 FIFO写入不超过 PIPE_BUF通常为 4096 字节时write 是原子的数据不会交错。超过 PIPE_BUF 后写入可能被拆分成多个片段。因此如果需要多写者模型建议每条消息控制在 PIPE_BUF 以内。多个读进程同时读一个 FIFO内核会轮流分发数据每条数据只会被一个读者拿走。这种“竞速读取”模式可以用于简单的任务分发但要注意负载均衡并不精确不适合要求严格均分的场景。9.6 与 shell 结合FIFO 也可以在 shell 脚本中发挥巨大作用。一个经典场景是把某个后台程序的输出实时转发给另一个命令。mkfifo /tmp/pipe_out cat /tmp/pipe_out ls -l /tmp/pipe_out甚至可以用 FIFO 实现两个 shell 进程间的对话# 终端 A cat /tmp/myfifo # 终端 B echo hello /tmp/myfifo这种方式在调试和运维脚本里非常实用能快速验证通信流程再深入编写 C 代码。10. 总结与进一步学习路径FIFO 是 Linux 进程间通信体系中的一个关键成员它用文件系统的路径形式把内核管道的流式传输能力开放给了任意进程。通过本文你应该掌握了 FIFO 在内核中的生命周期逻辑为什么 open 会阻塞、read 在什么条件下返回 0、SIGPIPE 的触发条件是什么以及如何用非阻塞模式和双 FIFO 实现更灵活的双工通信。建议你把文中三个示例代码亲手编译运行一遍特别是先启动写端、观察阻塞行为再启动读端、观察握手成功的瞬间。这个“阻塞到打通”的体验比任何文字解释都更能加深对 FIFO 的理解。之后可以继续尝试用select同时监听多个 FIFO、在项目里改造一个日志收集模块、或者用 FIFO 把两个现有程序串成一个流水线。这些实践会把 FIFO 从“知道”变成“会用”再变成“用得稳”。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →