read() 不会阻塞在没有 O_NONBLOCK 标志的情况下打开的空 FIFO

Posted

技术标签:

【中文标题】read() 不会阻塞在没有 O_NONBLOCK 标志的情况下打开的空 FIFO【英文标题】:read() doesn't block on empty FIFOs opened without O_NONBLOCK flag 【发布时间】:2020-11-26 15:06:32 【问题描述】:

pipe(7) 说:

如果一个进程试图从一个空管道中读取,那么 read(2) 将阻塞直到数据可用。如果一个进程试图写入一个完整的管道(见下文),那么 write(2) 会阻塞,直到从管道中读取了足够的数据以允许写入完成。通过使用 fcntl(2) F_SETFL 操作启用 O_NONBLOCK 打开文件状态标志,可以实现非阻塞 I/O。

下面我有两个用gcc在linux上编译的简单C程序:

reader.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define STACKBUF_SIZE 128
#define FIFO_PATH "/home/bogdan/.imagedata"

signed int main(int argc, char **argv) 
    int fifo_fd = open(FIFO_PATH, O_RDONLY); // blocking... - notice no O_NONBLOCK flag
    if (fifo_fd != -1) 
        fprintf(stdout, "open() call succeeded\n");
    

    while (1) 
        char buf[STACKBUF_SIZE] = 0;
        ssize_t bread = read(fifo_fd, buf, STACKBUF_SIZE);
        fprintf(stdout, "%d - %s\n", bread, buf);
        sleep(1);
    

    close(fifo_fd);

    return EXIT_SUCCESS;

writer.c

#define STACKBUF_SIZE 128
#define FIFO_PATH "/home/bogdan/.imagedata"
#define DATA "data"

int main(void) 
    int fifo_fd = open(FIFO_PATH, O_WRONLY); // blocks until reader opens on the reader end, however we always first open the reader so...
    if(fifo_fd != -1) 
        ssize_t bwritten = write(fifo_fd, DATA, 5);
        fprintf(stdout, "writer wrote %ld bytes\n", bwritten);
    
    
    close(fifo_fd);
    return EXIT_SUCCESS;


这些文件被编译成两个独立的二进制文件,gcc writer.c -Og -g -o ./writer,对读者来说也是如此。

我首先从 shell 执行 reader 二进制文件,并且正如预期的那样,最初的 open() 调用阻塞,直到我也执行 writer。然后我执行编写器,其open() 调用立即成功,并将 5 个字节写入 FIFO(读取器正确显示),然后关闭 fd,让 FIFO 为空( ?)。

但是,阅读器的 while 循环中的以下 read() 调用根本不会阻塞,而只是返回 0

除非我遗漏了某些东西(我可能是遗漏了),否则这与 pipe(7) 手册页概述的语义相冲突,因为 FIFO fd 在读取器和写入器中都没有O_NONBLOCK 标志是打开的。

【问题讨论】:

Joseph 指出这不是文档所说的,而且它没有意义:阻塞的目的是等待某些事情发生(这里,数据可用)。当管道的另一端关闭时,数据将永远无法使用,因此您的语义将使程序永远卡住,这不可能是处理这种情况的有用方法。 实际语义还可以方便地模仿从常规文件中读取时发生的情况,这有助于使管道透明并确保大多数程序可以自动处理管道输入。 【参考方案1】:

您引用的手册部分仅适用于具有开放编写器的管道。两段下来,它是这样说的:

如果引用管道写入端的所有文件描述符都已关闭,则尝试从管道读取 (2) 将看到文件结尾(读取 (2) 将返回 0)。

【讨论】:

以上是关于read() 不会阻塞在没有 O_NONBLOCK 标志的情况下打开的空 FIFO的主要内容,如果未能解决你的问题,请参考以下文章

EPOLLIN EPOLLOUT EAGAIN

linux socket读数据错误解释

fcntl函数

检查使用O_NONBLOCK打开的文件描述符是否准备就绪

FIFO调用open时阻塞

阻塞非阻塞