写入未读取的FIFO:字节去向与内核读写管理机制问询
FIFO管道的内核读写机制疑问与实验验证
初始实验与核心疑问
初始代码
Reader程序:
// reader #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main() { int fd = open("fifo.txt", O_RDONLY); sleep(15); close(fd); return 0; }
Writer程序:
// writer #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main() { int fd = open("fifo.txt", O_WRONLY); write(fd, "hi", 3); close(fd); return 0; }
实验操作与现象
- 先通过
mkfifo fifo.txt创建命名管道 - 执行
./reader & ./writer &并行运行两个程序 - 待写入者结束、读者仍处于休眠状态时,用
cat fifo.txt尝试读取管道,无数据返回;读者结束后再次尝试,依然无法读取写入的3字节 - 只有在读者关闭管道前执行读操作,才能获取到这3字节
核心疑问
- 若无人读取但管道文件仍存在,写入的3字节数据去向何方?
- 内核如何管理FIFO的读写权限与数据流转(比如多进程非阻塞打开、读写后退出的场景)?
补充实验与现象分析
修改后的实验代码
Reader程序:
// reader #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main() { char buf[3] = {'\0'}; int fd = open("fifo.txt", O_RDONLY); sleep(15); ssize_t bytes = read(fd, buf, sizeof(buf)); printf("[Reader] Read %s (%d chars)\n", buf, (int)bytes); fflush(stdout); close(fd); return 0; }
Writer程序:
// writer #include <fcntl.h> #include <unistd.h> int main() { int fd = open("fifo.txt", O_WRONLY); sleep(2); write(fd, "hi", 3); write(fd, "ha", 3); close(fd); return 0; }
实验现象
- 单个写入者+单个读者:读者最多读取3字节(受缓冲区大小限制)
- 单个写入者+两个读者:一个读者读取到"hi",另一个读取到"ha"
- 若将读者缓冲区增大至10字节:一个读者读取到
hi\0ha\0,另一个读取0字节
基于现象猜测:内核可能维护写入发生时已打开FIFO的进程队列,仅这些进程可读取数据,但需要明确内核管理的具体细节。
内核对FIFO的管理细节
1. 未被读取的数据去向
FIFO是内核维护的临时字节流缓冲区,不具备持久化存储能力:
- 当写端写入数据时,只有存在已打开的读端进程,数据才会被存入内核缓冲区;
- 若所有读端进程关闭,即使写端曾写入数据,内核会直接丢弃缓冲区中未被读取的数据——因为FIFO的设计逻辑是“为正在通信的读写进程提供中转”,没有读端存在时,数据没有接收方,自然不会保留;
- 实验中,写入者结束后读者仍休眠,此时唯一的读端还打开,但
cat fifo.txt是新打开的读端,此时缓冲区的数据仅对原读端(休眠的reader进程)可见;当原读端未读取就关闭时,数据会被内核直接丢弃,后续新打开的读端只能读到EOF。
2. FIFO的读写权限与进程管理逻辑
内核通过读写端引用计数和环形缓冲区管理FIFO:
- 打开规则:
- 阻塞模式(默认):
O_RDONLY打开会阻塞到至少一个写端打开;O_WRONLY打开会阻塞到至少一个读端打开;O_RDWR模式直接打开,不会阻塞(因为自身同时具备读写端)。 - 非阻塞模式(
O_NONBLOCK):O_RDONLY直接打开(即使无写端);O_WRONLY如果当前无读端,会直接返回ENXIO错误。
- 阻塞模式(默认):
- 数据流转规则:
- FIFO是字节流模式,所有打开的读端共享同一个内核缓冲区,数据被任意一个读端读取后,就会从缓冲区中移除,不会被其他读端重复读取;
- 当有多个读端时,数据会按“先到先得”的流式方式分配,比如实验中两个小缓冲区读端会各分到一段数据,而大缓冲区读端会一次性读走所有数据,导致另一个读端无数据可读;
- 不存在“写入时已打开的进程队列”,只要读端处于打开状态,就可以读取缓冲区中的数据,直到数据被读完或写端关闭触发EOF。
- 资源回收:
- 当所有读端关闭,写端的后续写操作会触发
SIGPIPE信号(默认导致进程退出),同时返回EPIPE错误; - 当所有写端关闭,读端的后续读操作会返回0(表示EOF);
- 当所有读写端都关闭,内核会清理FIFO的缓冲区资源,但FIFO的文件节点(在文件系统中)会保留,直到被
unlink删除。
- 当所有读端关闭,写端的后续写操作会触发
内容的提问来源于stack exchange,提问作者ABu
相关产品推荐
相关产品推荐

