【问题标题】:Linux named fifo non-blocking read select returns bogus read_fdsLinux 命名 fifo 非阻塞读取选择返回伪造的 read_fds
【发布时间】:2018-08-25 09:03:29
【问题描述】:

类似于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>

现在fdselect() 返回之后出现在read_fds 中。 fd 上的 read() 将返回一个字节的数据。到现在为止还挺好。

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

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

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

【问题讨论】:

标签: c linux select named-pipes nonblocking


【解决方案1】:

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

如果您查看man 2 select,它清楚地表明,如果readfds 中的描述符不会阻塞(在select() 调用时),则该描述符上的read() 已设置。

如果您使用poll(),它也会立即返回POLLHUP in revents


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

因为 Linux 内核只维护一个内部管道对象来表示每个打开的 FIFO(请参阅 man 7 fifoman 7 pipe),Linux 中稳健的方法是在遇到输入结束时打开另一个描述符到 FIFO( read() 返回 0),然后关闭原件。在 两个 描述符打开期间,它们引用同一个内核管道对象,因此不存在竞争窗口或数据丢失风险。

在伪 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 将是编号最小的空闲描述符。

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

【讨论】:

  • 您好,如果我再次打开fifo的写端,读端会恢复正常。这也是指定的行为吗?我使用了sleep 5 >> FIFONAME,看到读取端在 5 秒内没有从 read() 获得 readfds 和零。
  • @minghua:是的,这也符合预期。当另一个(或同一个)写入器打开 fifo 再次写入时,它会更改读取结束状态(从流结束),因为现在有可能获得新数据。在(最后一个)写入器关闭 fifo 之后,并且在(第一个)新写入器打开它之前,读取端处于输入结束状态。读取端可以“重置”该状态的唯一方法是重新打开 fifo(这在计算上根本不昂贵)。另一个写入器打开 fifo 进行写入也会重置该状态。这样就清楚了吗?
猜你喜欢
  • 1970-01-01
  • 2015-05-05
  • 2017-04-26
  • 2014-11-17
  • 1970-01-01
  • 2012-07-17
  • 2011-11-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多