使用poll()函数时,在命名管道上使用O_RDWR。

15
我已经研究了各种不同的Linux命名管道客户端/服务器实现,但它们大多数使用默认的阻塞读/写。由于我已经使用poll()检查其他标志,所以我认为通过poll()检查传入的FIFO数据也是一个好主意...
经过所有的研究,我认为以O_RDWR模式打开管道是防止在没有写入者打开管道时出现无限数量的EOF事件的唯一方法。
这样,管道的两端都关闭了,其他客户端也可以打开可写端。要回复,我将使用单独的管道...
我的问题是,尽管我找到了一些使用O_RDWR标志的示例,但open()手册描述了当分配给FIFO时该标志未定义。(http://linux.die.net/man/3/open)
但是如果没有O_RDWR,你如何在管道上使用poll()?你认为“O_RDWR”是打开管道的合法方式吗?

你有没有读过 mkfifo(3)fifo(7) 的手册?这样你就不会收到无限数量的 EOF 事件了。你不能在一侧使用 O_RDONLY,在另一侧使用 O_WRONLY(然后适当地进行 poll)吗?而且 fifo(7) 明确指出:“以读写方式打开 FIFO 在阻塞和非阻塞模式下都将成功”。 - Basile Starynkevitch
是的,你说得对,fifo(7)在Linux下提到它是可以接受的(尽管这不是标准化的POSIX行为)。问题在于我不能只用“O_RDONLY”打开读端口,只用“O_WRONLY”打开写端口,因为默认情况下这会被阻塞,而在我的使用情况下这是不允许的。因此,虽然理论上我可以用O_RDONLY | O_NONBLOCKING打开它,但它不会被阻塞,但由于可能没有写入器EOF事件将被抛出在poll()上。所以我必须使用O_RDWR来避免open()上的阻塞行为,并能够使用poll()... - JoeFrizz
3个回答

51
首先,有一些必备条件:
使用 O_NONBLOCK 和 poll() 是常见的做法——而不是相反。要成功地运作,您需要确保正确处理所有 poll() 和 read() 的返回状态:
- read() 返回值为 0 表示EOF——另一端已关闭连接。这通常(但并非在所有操作系统上都是如此)对应于 poll() 返回一个 POLLHUP 事件。您可能想在尝试读取之前检查 POLLHUP,但这并非绝对必要,因为在写入方关闭后,read() 保证会返回 0。 - 如果在没有写入方连接的情况下调用 read(),且具有 O_RDONLY | O_NONBLOCK,则会重复获得 EOF (read() 返回 0),正如您所注意到的那样。但是,如果使用 poll() 等待 POLLIN 事件,然后再调用 read(),它将等待写入方连接,而不会产生EOF。 - read() 返回值 -1 通常表示错误。但是,如果 errno == EAGAIN,则仅表示现在没有更多可用的数据,而且您没有阻塞,因此可以回到 poll() 处理其他设备。如果 errno == EINTR,则表示 read() 在读取任何数据之前被中断,您可以回到 poll() 或立即再次调用 read()。
现在,关于 Linux:如果你使用 O_RDONLY 在读取方面打开文件,则:
  • open() 将阻塞直到有相应的写入程序打开。
  • poll() 将在数据可读或EOF发生时提供一个POLLIN事件。
  • read() 将阻塞,直到读取了所需字节数、连接关闭(返回0)、被信号中断或发生一些致命的IO错误。这种阻塞方式有点相反于使用 poll() 的目的,因此 poll() 几乎总是与 O_NONBLOCK 一起使用。您可以使用 alarm() 在超时后唤醒出 read(),但那太过复杂。
  • 如果写入程序关闭,则读取程序将接收到 poll()POLLHUP 事件,并且在此之后read()将无限期地返回0。此时,读取程序必须关闭其文件句柄并重新打开它。
如果你使用 O_RDONLY | O_NONBLOCK 在读取方面打开文件,则:
  • open() 将不会阻塞。
  • poll() 将在数据可读或EOF发生时提供一个POLLIN事件。如果没有写入程序存在,poll() 将阻塞。
  • 在读取所有当前可用的数据后,read()将返回-1并设置errno == EAGAIN,如果连接仍然打开,则这意味着是时候返回到poll()了,因为连接已打开但没有更多数据。当 errno == EINTR 时,read() 还没有读取任何字节,并且被信号中断,因此可以重新启动。
  • 如果写入程序关闭,则读取程序将接收到 poll()POLLHUP 事件,并且在此之后read()将无限期地返回0。此时,读取程序必须关闭其文件句柄并重新打开它。
(仅限Linux:)如果您使用 O_RDWR 在读取方面打开命名管道,则:
  • open() 将不会阻塞。
  • poll() 将在数据可读时提供一个POLLIN事件。但是,对于命名管道,EOF不会引起POLLINPOLLHUP事件。
  • read()将阻塞,直到读取所需的字节数、被信号中断或发生其他致命的IO错误。对于命名管道,它不会返回errno == EAGAIN,甚至在EOF上也不会返回0。它将一直等待,直到读取所请求的确切字节数,或者直到收到信号(在这种情况下,它将返回到目前为止读取的字节数,或者如果尚未读取任何字节,则返回-1并设置errno == EINTR)。
  • 如果写入程序关闭,则读取程序不会失去以后读取命名管道的能力,但读取程序也不会收到任何通知。
(仅限Linux:)如果您使用 O_RDWR | O_NONBLOCK 在读取方面打开命名管道,则:

正如您所担心的那样,在管道中使用O_RDWR并不是标准的、POSIX或其他的方法。

然而,由于这个问题似乎经常出现,在Linux上创建 "弹性命名管道" 的最佳方式是使用O_RDWR | O_NONBLOCK,它可以使管道即使在一端关闭时也保持存活,并且不会引发POLLHUP事件或返回read()0值。

我认为在Linux上处理命名管道有三种主要方法:

  1. (可移植.) 在没有poll()和单个管道的情况下:

    • open(pipe, O_RDONLY);
    • 主循环:
      • 根据需要读取数据,可能需要循环调用read().
        • 如果read() == -1errno == EINTR,则重新进行read().
        • 如果read() == 0,则连接已关闭,并且所有数据已接收。

  2. (可移植.) 使用poll(),并期望管道(包括命名管道)只被打开一次,一旦关闭,必须由读者和写者重新打开管道以设置新的管道:

    • open(pipe, O_RDONLY | O_NONBLOCK);
    • 主循环:
      • poll()等待POLLIN事件,可能同时在多个管道上等待。(注意:这可以防止在作者连接之前read()获取多个EOF。)
      • 根据需要读取数据,可能需要循环调用read().
        • 如果read() == -1errno == EAGAIN,则返回到poll()步骤。
        • 如果read() == -1errno == EINTR,则重新进行read().
        • 如果read() == 0,则连接已关闭,必须终止,或关闭并重新打开管道。

  3. (非移植,仅限于Linux.) 使用poll(),并期望命名管道永远不会终止,并且可能被连接和断开多次:

    • open(pipe, O_RDWR | O_NONBLOCK);
    • 主循环:
      • poll() 等待 POLLIN 事件,可能同时在多个管道上等待。
      • read() 获取所需的数据,可能需要多次调用 read()
        • 如果 read() == -1 并且 errno == EAGAIN,则返回到 poll() 步骤。
        • 如果 read() == -1 并且 errno == EINTR,则重新执行 read()
        • 如果 read() == 0,则表示出现问题 -- 在命名管道上不应该发生这种情况,但在无名管道或者只读管道上会发生;它表示一个已关闭的管道,必须关闭并重新打开。如果在同一 poll() 事件处理循环中混合使用命名和无名管道,则可能仍需要处理此情况。

今天真是救了我一命,谢谢! - Nekto
太棒了!这完全揭开了有关管道的所有奇怪事情的神秘面纱。 - Marco Merlini

1
根据open(2)手册,您可以传递O_RDONLY|O_NONBLOCKO_WRONLY|O_NONBLOCK以避免open系统调用被阻塞(在这种情况下,您将获得errno == ENXIO)。
正如我在评论中提到的那样,请阅读fifo(7)mkfifo(3)手册。

谢谢,但在Linux上,“O_WRONLY|O_NONBLOCK”不起作用,我猜...问题是我不能在打开时阻塞并且想要使用poll(),所以当我使用O_RDONLY|O_NONBLOCK时,它不会阻塞,但是poll()会一直触发EOF事件...因此,“O_NONBLOCK”和poll()是一个不好的组合。所以似乎我必须选择O_RDWR,尽管它不是POSIX标准。那么在没有O_RDWR的情况下如何在非阻塞管道上使用poll()呢? - JoeFrizz
我从未编写过这样的代码,但是手册上说 O_WRONLY|O_NONBLOCK 应该可以无阻塞地工作。你测试过吗?当然你不能使用 poll 来检测 open(因为 poll 要求通过 open 返回文件描述符)。 - Basile Starynkevitch
非常感谢,我一定会稍后尝试。我不会在打开时进行轮询。在成功打开管道后,我将使用POLLIN进行读取。 - JoeFrizz

0

在读取过程中,只需保持一个打开的 O_WRONLY 文件描述符与 O_RDONLY 文件描述符一起使用即可。这将实现相同的效果,确保 read() 永远不会返回文件结尾,并且 poll() 和 select() 将会阻塞。

而且它是 100% 的 POSIX 标准。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接