如果面试官让你说一种最简单、也最能看出“内核功底”的进程间通信方式,FIFO 基本是必选项。它不需要共享内存那样手动管理锁,不需要 socket 那样先建立连接,两个没有父子关系的进程,只要约定一个文件路径,就能把数据流对接起来。
这次我们从使用方式、阻塞规则、C 代码实操再到内核源码路径,把 FIFO(命名管道)进程间通信完整过一遍。整体按“能跑通 + 能排查 + 能说清原理”的顺序展开,最后给出一份可以直接套用的排错清单。适合正在学 Linux 系统编程的开发者、准备面试的人,以及在嵌入式或服务端项目里需要轻量级本地 IPC 的工程师。
先说一个直接结论:FIFO 适合在一台机器上,让两个没有血缘关系的进程传递字节流。它比匿名管道灵活,比 socket 轻量,比共享内存安全边界更清晰。下面从核心能力开始。
1. FIFO 进程间通信核心能力速览
1.1 FIFO 是什么
FIFO 全称 First In First Out,也叫命名管道(Named Pipe)。在 Linux 文件系统里,它是一个类型为p的特殊文件节点。进程对这个节点做open、read、write、close,数据就会在内核管道缓冲区里流动。
FIFO 和匿名管道pipe()最大的区别是:匿名管道只能在有亲缘关系的进程之间传递,比如父子进程;FIFO 有路径名,任何进程只要知道路径并且有权限,就能打开它参与通信。
1.2 核心能力表
| 能力项 | 说明 |
|---|---|
| 类型 | 命名管道,内核缓冲区实现的进程间通信 |
| 使用接口 | mkfifo / open / read / write / close / unlink |
| 是否要求父子进程关系 | 不要求,任意进程通过路径名访问 |
| 数据流向 | 单向流,字节流式读取,无消息边界 |
| 阻塞行为 | 默认阻塞,可切换 O_NONBLOCK |
| 原子性 | 单次写入不超过 PIPE_BUF 时原子 |
| 内核依赖 | inode + pipe_inode_info + pipe_buffer 数组 |
| 持久化 | FIFO 节点存在于文件系统,数据只在内核内存中流动 |
| 依赖外部库 | 无,Linux 内核自带支持 |
| 适合场景 | 同机进程任务分发、日志采集、模块间解耦 |
| 平台 | 支持情况 |
|---|---|
| Linux | 原生支持 |
| Windows | 可通过 WSL、虚拟机或 MSYS 环境验证,原理一致 |
| macOS / BSD | 支持 POSIX FIFO,行为基本相同 |
这里要强调:FIFO 节点虽然显示在文件系统里,但读写的数据不会写到磁盘,而是存放在内核页缓存构成的管道缓冲区中。所以它适合做高频短消息流转,不适合当持久化存储使用。
2. FIFO 的基本模型:从管道到命名管道
2.1 匿名管道和命名管道的区别
匿名管道由pipe()系统调用创建,内核返回两个文件描述符,一个读端一个写端。调用fork()之后,父子进程各自继承两个描述符,再各自关闭不需要的一端,形成单向数据流。
#include <unistd.h> int pipefd[2]; pipe(pipefd); // pipefd[0] 读端,pipefd[1] 写端这个过程的问题在于:pipe()返回的描述符没有路径,其他进程无法通过文件系统找到它。两个没有共同祖先的进程,拿不到这对描述符,也就无法通信。
FIFO 解决的核心问题就是“路径可见性”。它先通过mkfifo在文件系统里创建一个节点,之后任意进程都能用open()打开。这相当于把原来只有父子进程能用的一对隐藏描述符,变成了一扇有门牌的公共大门。
2.2 FIFO 数据流模型
用一张图理解:
+----------------+ write() +-----------------------+ | 进程 A | -------------------> | | | (生产者) | | FIFO 文件节点 | +----------------+ | /tmp/my_fifo | | 关联内核管道缓冲区 | +----------------+ | | | 进程 B | <------------------- | | | (消费者) | read() +-----------------------+ +----------------+进程 A 打开 FIFO 的写端,进程 B 打开 FIFO 的读端。A 写入的数据先进入内核管道缓冲区,B 通过read()读走。数据在内核空间走一圈,不经过磁盘,也不经过网络协议栈。
注意一个关键字:单向。FIFO 本身是单向数据流。如果两个进程需要互发数据,通常要建两个 FIFO,一个方向一个。
3. 环境准备与前置条件
FIFO 是 Linux 内核自带机制,不需要安装额外动态库。只要是一个正常工作的 Linux 发行版,都能直接用。
操作系统层面,建议准备:
- Linux 发行版,例如 Ubuntu、Debian、CentOS、openEuler 等。
- Windows 环境可以用 WSL 或者虚拟机跑,命令和编译过程一致。
- 内核版本没有硬性要求,常见发行版默认内核均可。
编译工具链方面,需要有gcc和标准 C 库头文件。先检查一下:
gcc --version uname -a ls -l /usr/include/fcntl.h /usr/include/sys/stat.h如果gcc不存在,安装即可:
# Debian / Ubuntu sudo apt update sudo apt install -y build-essential # RHEL / CentOS sudo yum install -y gcc glibc-devel准备一个干净的工作目录:
mkdir -p ~/fifo-lab cd ~/fifo-lab后续的源码文件和 FIFO 节点都放在这个目录里。注意:不要直接在产品环境的全局临时目录里反复创建和删除 FIFO,容易造成路径冲突。
4. 创建 FIFO:命令与系统调用
4.1 使用 mkfifo 命令
Linux 自带mkfifo命令,一行命令创建 FIFO 节点:
mkfifo /tmp/my_fifo ls -l /tmp/my_fifo输出类似:
prw-r--r-- 1 user user 0 Jun 10 10:00 /tmp/my_fifo看第一个字符,p表示这是一个管道(pipe)文件。普通文件的第一个字符是-,目录是d。文件大小显示为 0,因为数据不在文件节点里存放。
如果要指定权限,可以在后面加 mode:
mkfifo -m 0644 /tmp/my_fifo这个 mode 会受进程umask影响。如果 umask 是 0022,那么0644创建出来的实际权限是0644 & ~0022,也就是0644。可以用umask命令查看当前值:
umask4.2 使用 mkfifo() 系统调用
在 C 代码里创建 FIFO,用mkfifo()函数:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_PATH "/tmp/my_fifo" int main(void) { if (mkfifo(FIFO_PATH, 0644) < 0) { perror("mkfifo"); return 1; } printf("create fifo: %s\n", FIFO_PATH); return 0; }编译运行:
gcc -Wall -o create_fifo create_fifo.c ./create_fifo ls -l /tmp/my_fifo注意:第二次运行时,因为
/tmp/my_fifo已经存在,mkfifo()会返回 -1,errno设置为EEXIST。实际工程里,要么先unlink,要么处理EEXIST情况。
mkfifo底层最终会走到内核的mknod相关路径,现代内核对应的是mknodat系统调用。glibc 的mkfifo()封装了这层逻辑,把S_IFIFO和传入的权限位拼到一起,交给内核创建节点。
4.3 如何确认创建成功
可以通过stat命令确认文件类型:
stat /tmp/my_fifo输出里有File: /tmp/my_fifo,类型字段会显示fifo。
也可以通过 C 代码里的stat()判断:
struct stat st; stat(FIFO_PATH, &st); if (S_ISFIFO(st.st_mode)) { printf("it is a fifo\n"); }5. FIFO 读写规则与阻塞行为
这是 FIFO 最容易出错的部分。很多进程卡住,问题都出在打开和读写的阻塞规则上。下面分阶段拆开讲。
5.1 打开阶段的阻塞
FIFO 的open()行为,取决于打开方向、是否设置O_NONBLOCK、以及对端是否存在。
| 打开方式 | 没有对端进程时的行为 |
|---|---|
open(path, O_RDONLY) | 默认阻塞,直到有写者打开该 FIFO |
open(path, O_WRONLY) | 默认阻塞,直到有读者打开该 FIFO |
open(path, O_RDONLY | O_NONBLOCK) | 不阻塞,立即成功返回 |
open(path, O_WRONLY | O_NONBLOCK) | 如果没有读者,open 失败,errno 为 ENXIO |
这个规则非常关键。比如你只启动写进程:
./writer如果没有任何读者打开 FIFO,writer 会一直卡在open()里。这不是程序死锁,而是 FIFO 设计上要求“读端和写端都就位,才开始通信”。
所以调试的时候,先开一个终端启动 reader,再开另一个终端启动 writer,这是最稳的顺序。
5.2 读写阶段的阻塞
打开成功之后,读写阶段的阻塞规则:
read():管道为空且仍有写端打开时,阻塞等待数据。read():所有写端关闭后,返回 0,表示 EOF。write():管道缓冲区满时,阻塞等待读者消费。write():没有任何读者打开时,进程收到SIGPIPE信号,默认终止进程;如果用signal(SIGPIPE, SIG_IGN)忽略信号,write()返回 -1,errno为EPIPE。
管道的缓冲区大小通常有限。很多发行版上默认是 64KB,具体以当前内核配置为准:
cat /proc/sys/fs/pipe-max-size5.3 原子性与 PIPE_BUF
FIFO 不是消息队列,它是字节流。两个写者同时向同一个 FIFO 写数据,数据可能交错。
但 POSIX 保证:只要单次写入长度不超过PIPE_BUF,这次写入就是原子的。在 Linux 上,PIPE_BUF通常为 4096 字节。可以查看系统定义:
getconf PIPE_BUF /tmp/my_fifo| 写入方式 | 原子性 |
|---|---|
| 单次写入 <= PIPE_BUF | 原子,不会与其它写者交错 |
| 单次写入 > PIPE_BUF | 可能与其他写者写入的数据交错 |
这意味着:如果多个进程并发往同一个 FIFO 写消息,每条消息必须小于等于 4096 字节,才能保证消息不会互相穿插。更长的数据,建议加锁,或者使用单独的 FIFO。
5.4 非阻塞模式
非阻塞模式的用途是:进程不想干等对端出现,或者需要把 FIFO 接进poll/epoll事件循环。
int fd; int flags; fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); if (fd < 0) { perror("open nonblock"); return -1; } // 也可以打开后再设置非阻塞 flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);非阻塞模式下:
- 读端没有数据时,
read()返回 -1,errno为EAGAIN。 - 写端缓冲区满时,
write()返回 -1,errno为EAGAIN。
注意这里要区分ENXIO和EAGAIN。ENXIO出现在打开阶段,表示“对方不存在”;EAGAIN出现在读写阶段,表示“资源暂时不可用,再试一次”。
6. C 语言源码实操
下面用完整的可编译代码,演示单向通信、双向通信和非阻塞读取。所有源码都放在~/fifo-lab里。
6.1 单向通信:写端与读端
写端程序writer.c:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_PATH "/tmp/my_fifo" int main(void) { int fd; fd = open(FIFO_PATH, O_WRONLY); if (fd < 0) { perror("open"); exit(EXIT_FAILURE); } const char *msg = "hello fifo"; if (write(fd, msg, strlen(msg)) < 0) { perror("write"); close(fd); exit(EXIT_FAILURE); } printf("writer send: %s\n", msg); close(fd); return 0; }读端程序reader.c:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_PATH "/tmp/my_fifo" int main(void) { int fd; char buf[1024]; ssize_t n; fd = open(FIFO_PATH, O_RDONLY); if (fd < 0) { perror("open"); exit(EXIT_FAILURE); } n = read(fd, buf, sizeof(buf) - 1); if (n < 0) { perror("read"); close(fd); exit(EXIT_FAILURE); } buf[n] = '\0'; printf("reader recv: %s\n", buf); close(fd); return 0; }6.2 编译与运行
gcc -Wall -o writer writer.c gcc -Wall -o reader reader.c先创建 FIFO:
mkfifo /tmp/my_fifo接着打开终端一,启动读端:
cd ~/fifo-lab ./reader此时 reader 会卡在open(),因为还没有写者。这时不要急着关,打开终端二,启动写端:
cd ~/fifo-lab ./writer回车之后,writer 输出:
writer send: hello fiforeader 输出:
reader recv: hello fifo然后两个进程都退出。这就是 FIFO 最基础的“先读后写”流程。
如果反过来,先启动 writer,writer 会一直卡在open(FIFO_PATH, O_WRONLY)。这是预期行为,不是 bug。
6.3 双向通信:双 FIFO 方案
FIFO 是单向的。实现两个进程互发数据,最干净的做法是建两个 FIFO,一个方向一个。
#define FIFO_A2B "/tmp/fifo_a2b" // 进程 A 向进程 B 发数据 #define FIFO_B2A "/tmp/fifo_b2a" // 进程 B 向进程 A 发数据进程 A:
int fd_write = open(FIFO_A2B, O_WRONLY); int fd_read = open(FIFO_B2A, O_RDONLY);进程 B:
int fd_read = open(FIFO_A2B, O_RDONLY); int fd_write = open(FIFO_B2A, O_WRONLY);这里最容易踩的坑是打开顺序。两个进程如果都先等对方出现,可能互相卡死。比如进程 A 先执行open(FIFO_A2B, O_WRONLY),会等待 B 打开FIFO_A2B的读端;如果 B 还没执行到这一步,A 就卡住,后面的open(FIFO_B2A, O_RDONLY)永远不会执行。
实际操作里,建议统一约定:先打开读端,再打开写端。或者用非阻塞方式打开写端,失败就重试。还有一种取巧方式是open(path, O_RDWR),但这种方式对 FIFO 的阻塞语义在不同平台上有差异,测试环境能用,生产环境不建议依赖它。
6.4 非阻塞读取与 poll 超时
服务端程序经常需要做超时控制,不能无限阻塞。一个常见模式是 O_NONBLOCK + poll。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <poll.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #define FIFO_PATH "/tmp/my_fifo" int main(void) { int fd; struct pollfd pfd; char buf[1024]; ssize_t n; fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); if (fd < 0) { perror("open"); exit(EXIT_FAILURE); } pfd.fd = fd; pfd.events = POLLIN; int ret = poll(&pfd, 1, 5000); // 5 秒超时 if (ret < 0) { perror("poll"); close(fd); exit(EXIT_FAILURE); } if (ret == 0) { printf("timeout, no data\n"); close(fd); return 0; } if (pfd.revents & POLLIN) { n = read(fd, buf, sizeof(buf) - 1); if (n >= 0) { buf[n] = '\0'; printf("recv: %s\n", buf); } } close(fd); return 0; }当写端全部关闭后,poll()会返回POLLHUP事件,此时read()会返回 0。做长驻服务时,要区分“暂时没数据”和“对方已经退出”两种情况。
7. 内核源码视角:FIFO 在内核中怎么工作
标题里带了“源码”,所以这里必须把内核路径讲清楚。不需要背完整函数,但关键文件、关键结构体和调用流程值得了解。
7.1 文件系统层面的创建路径
用户层调用mkfifo()后,glibc 封装最终走到内核的mknodat系统调用。内核创建 inode 时,会根据文件类型做特殊初始化。对于 FIFO,VFS 会调用init_special_inode(),把 inode 的文件操作表设置为fifo_fops。
这一步非常关键。它决定了之后所有open、read、write操作走进哪套逻辑。普通文件的f_op指向 ext4、tmpfs 等文件系统的实现;FIFO 的f_op直接指向管道专用实现。
对应源码位置:
fs/fifo.c:FIFO 的打开逻辑。fs/pipe.c:管道读写逻辑。include/linux/pipe_fs_i.h:关键结构体定义。
7.2 fifo_open 与管道对象
当进程open()一个 FIFO 时,内核会进入fifo_open()。核心流程大概如下:
- 如果这个 inode 上还没有管道对象,调用
alloc_pipe_info()分配一个struct pipe_inode_info,挂到 inode 的i_pipe字段。 - 根据本次打开方向,更新管道对象里的
readers、writers计数。 - 如果打开读端但当前没有写者,默认阻塞模式下进程会进入等待队列,直到一个写者出现。
- 如果打开写端但当前没有读者,默认阻塞模式下同样进入等待队列;非阻塞写打开则返回
ENXIO。
数据结构关系可以这样看:
进程 A open() 后 进程 B open() 后 +-------------+ +-------------+ | struct file | | struct file | +-------------+ +-------------+ | f_op = fifo_fops | f_op = fifo_fops +----------------+-----------------+ v +----------------------+ | inode (FIFO 节点) | | i_pipe ------------+----> struct pipe_inode_info +----------------------+ | v +---------------------+ | pipe_buffer 数组 | | head / tail | | readers / writers | | 等待队列 | +---------------------+struct pipe_inode_info里的几个核心字段:
bufs:指向struct pipe_buffer数组,每个 buffer 对应一个页缓存页。head、tail:读写位置的环形索引。tail表示读位置,head表示写位置。readers、writers:当前读端、写端的打开数量。wr_wait、rd_wait:写者等待队列、读者等待队列。mutex:管道对象的内核互斥锁,保护并发读写。
7.3 读写数据的内核路径
写入数据时,内核走pipe_write():
- 检查管道缓冲区是否有可用空间。如果没有,写进程进入
wr_wait等待队列。 - 有空间时,从页缓存分配一个 page,或者复用已有的 pipe_buffer。
- 将用户态数据通过
copy_from_user()拷贝到页缓存。 - 更新
head索引,唤醒等待在rd_wait上的读者。
读取数据时,内核走pipe_read():
- 检查
tail和head是否重合。重合表示缓冲区为空,读者进入rd_wait等待队列。 - 有数据时,将页缓存数据通过
copy_to_user()拷贝到用户态缓冲区。 - 更新
tail,唤醒等待在wr_wait上的写者。
所以 FIFO 的数据流本质上是一份内核页缓存拷贝。写入不直接落盘,读取也不经过磁盘 IO。这也是为什么它比“写文件 + 另一个进程读文件”的方实要高很多。
读端全部关闭后,写端下一次写入时,内核逻辑会判定“没有读者”,触发SIGPIPE信号,同时write()返回EPIPE。这个行为也是pipe_write()里处理pipe->readers == 0分支产生的。
8. 性能观察与资源占用
8.1 数据流向与磁盘 IO
FIFO 读写只以内核内存为媒介,不触发块设备 IO。测试时可以用iostat观察磁盘利用率,正常情况下读写 FIFO 不会增加磁盘繁忙度。
iostat -d 1如果你看到 FIFO 测试过程中磁盘 IO 没有明显变化,说明数据确实走的是内核缓冲区,这符合预期。
8.2 管道缓冲区与调整
管道缓冲区默认大小通常由内核参数控制。查看方法:
cat /proc/sys/fs/pipe-max-size ulimit -a | grep pipeLinux 还支持通过fcntl()调整单个管道的大小:
#include <fcntl.h> #include <unistd.h> int fd; int new_size = 65536; fd = open("/tmp/my_fifo", O_RDONLY | O_NONBLOCK); fcntl(fd, F_SETPIPE_SZ, new_size);注意:F_SETPIPE_SZ设置的值不能超过/proc/sys/fs/pipe-max-size,否则内核会按上限限制。实际可用值以内核返回为准。
8.3 用系统调用观察行为
使用strace可以直接看到进程在系统调用层面是如何阻塞和唤醒的:
strace -e trace=open,read,write,close ./reader strace -e trace=open,read,write,close ./writer读端启动后,strace会停在open("/tmp/my_fifo", O_RDONLY )这一步。写端启动后,读端的open返回,随后阻塞在read()。这些输出对排查“为什么卡住”非常有帮助。
测量吞吐量可以用dd加time:
time dd if=/dev/zero of=/tmp/my_fifo bs=4096 count=100000 & time dd if=/tmp/my_fifo of=/dev/null bs=4096 count=100000第一个命令在后台往 FIFO 写,第二个命令前台读。最终time输出的耗时是本机实际测量结果,不同机器差异会很大,因此这里不给固定预期值,以你本机跑出来的结果为准。
8.4 与其他进程间通信方式对比
| 方式 | 通信范围 | 数据边界 | 复杂度 | 典型场景 |
|---|---|---|---|---|
| FIFO | 同机 | 无,字节流 | 低 | 日志采集、任务分发 |
| 匿名管道 | 同机,需父子关系 | 无 | 最低 | 命令管道 |
| System V 消息队列 | 同机 | 有消息边界 | 中 | 小消息按类型分发 |
| 共享内存 | 同机 | 无 | 高 | 大数据量高性能共享 |
| Unix domain socket | 同机 | 有,支持 SOCK_STREAM / SOCK_DGRAM | 中 | 本地 RPC、服务通信 |
| TCP socket | 跨机器 | 无 | 高 | 网络通信 |
FIFO 的优势在于简单和路径可见。它不适合跨机器通信,也不适合传递大对象,因为每次数据都要经过内核缓冲区拷贝。需要超大吞吐时,共享内存更合适;需要消息边界时,Unix domain socket 通常更方便。
9. 常见问题与排查方法
下面是一份可以直接对照的排错清单。
| 问题现象 | 可能原因 | 排查方式 | 解决方法 |
|---|---|---|---|
open(O_RDONLY)一直不返回 | 没有写进程打开 FIFO | 用 ps 查看对端进程;用 strace 跟踪 open 调用 | 先启动写端进程;或改用 O_NONBLOCK |
open(O_WRONLY | O_NONBLOCK)报错 | 当前没有读端打开 | 检查 errno 是否为 ENXIO | 先启动读端;代码里对 ENXIO 做重试 |
read()返回 0 | 所有写端已经关闭 | 检查写进程是否退出 | 按 EOF 处理;业务需要时重启写端 |
write()进程被信号杀死 | 读端全部关闭,触发 SIGPIPE | 日志或终端显示 Broken pipe | 忽略 SIGPIPE,并检查 write 返回 EPIPE |
| 多个写者数据互相穿插 | 单次写入超过 PIPE_BUF,或并发写同一 FIFO | 统计每次写入长度 | 控制单次写入 <= 4096 字节;或加锁 |
| 再次运行报 File exists | FIFO 节点没有被清理 | ls -l /tmp/my_fifo确认 | 启动前unlink(),或处理 EEXIST |
| 写端写不进去,进程挂住 | 缓冲区满,读端消费太慢 | 观察读进程是否在运行;查看阻塞位置 | 加速读端;调整管道大小;改用非阻塞 |
| 双 FIFO 程序互相等待 | open 顺序错误导致互相等对端 | strace 打开顺序 | 统一先读后写;或非阻塞打开加重试 |
| 权限不足无法打开 | FIFO 权限位限制了其他用户 | ls -l查看属主和权限 | 调整 mkfifo mode,或使用相同用户运行 |
还有一个高频神坑:程序正常收发一次后,第二次运行 reader 时发现read()立即返回 0。原因往往是上一步的close(fd)关掉了读端,且此时没有新的写者。这不代表 FIFO 坏了,而是“所有写端关闭后读端会收到 EOF”这个语义在起作用。
10. 最佳实践与使用建议
10.1 进程启动顺序
先启动服务端,服务端 open 读端(可以阻塞等待),再启动客户端写端。这样最不容易出现启动顺序导致的“假死”。如果启动顺序无法保证,服务端读端可以用O_RDONLY | O_NONBLOCK,避免被 open 卡住。
10.2 信号与异常处理
写端需要处理SIGPIPE。建议在主函数里忽略该信号,然后通过write()的返回值和errno判断对端是否关闭:
#include <signal.h> signal(SIGPIPE, SIG_IGN);这样写端不会因为对端退出而被默认信号动作杀死,而是得到一次处理异常的机会。
10.3 工程目录与清理
FIFO 节点建议放在固定目录,例如/var/run/myapp/或/tmp/myapp/。进程启动时先unlink再mkfifo,避免上次残留节点造成EEXIST。进程退出时可以再次unlink。如果服务崩溃,残留的 FIFO 节点会在下次启动时被覆盖,这比依赖手动清理更强。
10.4 生产使用边界
多写者场景,所有消息必须控制在PIPE_BUF以内,否则要加锁。多读者场景,每条数据只会被其中一个读者读走,数据不是广播。需要广播时,用多个 FIFO 或者换用消息队列。
写端和读端速率不匹配时,建议设置超时,避免长时间占住进程。接入事件循环时,用O_NONBLOCK+poll/epoll,不要用阻塞模式硬等。
权限方面,注意 FIFO 节点对文件系统内所有有权限的用户可见。传输敏感数据时,要确认 FIFO 的 mode 和运行用户,避免其他用户能读到本应受限的数据