Linux 命名为 fifo 非阻塞读取 select returns 伪造 read_fds

Linux named fifo non-blocking read select returns bogus read_fds

类似于 problem asked a while ago on kernel 3.x,但我是在 4.9.37 上看到的。 命名的 fifo 是用 mkfifo -m 0666 创建的。在阅读方面,它以

打开
int fd = open(FIFO_NAME, O_RDONLY | O_NONBLOCK);

结果 fd 被传递到对 select() 的调用中。一切正常,直到我 运行 echo >> <fifo-name>

现在 fd 出现在 select() return 之后的 read_fds 中。 fd 上的 read() 将 return 一个字节的数据。到目前为止,一切都很好。

下次调用 select() 时 returns,fd 仍会出现在 read_fds 中,但 read() 将始终 return 零表示没有数据。实际上,读取端会消耗 100% 的处理器容量。这与所引用问题所观察到的问题完全相同。

有人遇到过同样的问题吗?以及如何正确解决或解决它?

我想通了,如果关闭fifo的读端,再重新打开,就可以正常工作了。这可能没问题,因为我们没有发送大量数据。虽然这不是一个好的或一般的解决方法。

这是预期的行为,因为输入结束的情况导致 read() 不阻塞;它 return 立即变为 0。

如果您查看 man 2 select,它清楚地表明如果 readfds 中的描述符不会阻塞(在 select()呼叫)。

如果您使用 poll(),它也会立即 return 和 revents 中的 POLLHUP


如 OP 所述,正确的解决方法是重新打开 FIFO。

因为Linux内核只维护一个内部管道对象来表示每个打开的FIFO(参见man 7 fifo and man 7 pipe),Linux中的稳健方法是打开另一个描述符到FIFO每当遇到输入结束时 (read() returning 0),并关闭原始输入。在两个描述符都打开的时候,它们指的是同一个内核管道对象,所以不存在竞争window或数据丢失的风险。

在伪 C 中:

fifoflags = O_RDONLY | O_NONBLOCK;
fifofd = open(fifoname, fifoflags);
if (fifofd == -1) {
    /* Error checking */
}

/* ... */

/* select() readfds contains fifofd, or
   poll() returns POLLIN for fifofd: */

    n = read(fifofd, buffer, sizeof buffer)
    if (!n) {
        int tempfd;

        tempfd = open(fifopath, fifoflags);
        if (tempfd == -1) {
            const int cause = errno;
            close(fifofd);

            /* Error handling */

        }
        close(fifofd);
        fifofd = tempfd;

        /* A writer has closed the FIFO. */

    } else
        /* Handling for the other read() result cases */

Linux 中的文件描述符分配策略是 tempfd 将成为编号最小的空闲描述符。

在我的系统(酷睿 i5-7200U 笔记本电脑)上,以这种方式重新打开 FIFO 只需不到 1.5 微秒。也就是说,它每秒可以进行大约 680,000 次。我不认为这种重新开放是任何合理场景的瓶颈,即使是在低功率嵌入式 Linux 机器上也是如此。