08-文件描述符何时就绪
63.2.3 文件描述符何时就绪
正确使用select()和poll()需要理解在什么情况下文件描述符会表示为就绪态。SUSv3中说:如果对I/O函数的调用不会阻塞,而不论该函数是否能够实际传输数据,此时文件描述符(未指定O_NONBLOCK标志)被认为是就绪的。select()和poll()只会告诉我们I/O操作是否会阻塞,而不是告诉我们到底能否成功传输数据。按照这个思路,让我们考虑一下这些系统调用在不同类型的文件描述符上所做的操作。我们将这些信息在表格中以两列来显示。
- select()这一列表示文件描述符是否被标记为可读(r),可写(w)还是有异常情况(x)。
- poll()这一列表示在revents字段中返回的位掩码。在这些表格中,我们忽略POLLRDNORM、POLLWRNORM、POLLRDBAND 以及 POLLWRBAND。尽管在很多情况下这些标志会在revents中返回(如果在events字段中指定过这些标志),但它们相对于 POLLIN、POLLOUT、POLLHUP以及 POLLERR 来说,并没有提供更多有用的信息。
普通文件
代表普通文件的文件描述符总是被 select()标记为可读和可写。对于 poll()来说,则会在revents字段中返回POLLIN和POLLOUT标志。原因如下。
- read()总是会立刻返回数据、文件结尾符或者错误(例如,文件并没有因为读操作而打开)。
- write()总是会立刻传输数据或者因出现某些错误而失败。
SUSv3中说select()应该也为代表普通文件的文件描述符标记异常情况(尽管这么做对普通文件来说没有明显的意义)。只有一些UNIX实现才会这么做,而Linux是其中一种不会这样处理的实现之一。
终端和伪终端
表63-3总结了在终端和伪终端上(见第64章)select()和poll()的行为表现。
当伪终端对的其中一端处于关闭状态时,另一端由 poll()返回的 revents 将取决于具体实现。在 Linux 上至少会设置 POLLHUP 标志。但是,在其他实现上将返回各种不同的标志来表示这个事件——比如,POLLHUP、POLLERR或者 POLLIN。此外,在一些实现中,设定什么样的标志取决于被检查的是伪终端主设备还是从设备。
| 条件或事件 | select() | poll() | | :----- | :----- | :----- | :----- | :----- | | 有输入 | 可输出 | 伪终端对端调用close()后 | 处于信包模式下的伪终端主设备检测到从设备端状态改变 | r | w | rw | x | POLLIN | POLLOUT | 见上文 | POLLPRI |
管道和FIFO
表63-4中总结了管道或FIFO的读端细节。“管道中有数据?”这一列表示管道中是否至少有1字节数据可读。在这个表格中,我们假设已经在events字段中指定了POLLIN标志。
在其他一些 UNIX 实现中,如果管道的写端是关闭状态,那么 poll()不会返回POLLHUP,而会返回POLLIN标志(因为read()遇到文件结尾符会立刻返回)。可移植性高的程序应该检查这两个标志从而得知read()是否会阻塞。
表63-5 总结了管道写端的细节。在这个表格中,我们假设已经在 events 字段中指定了POLLOUT标志。“有PIPE_BUF个字节的空间吗?”这一列表示管道中是否有足够剩余空间能够以原子方式写入 PIPE_BUF 个字节而不会阻塞。这是 Linux 判定管道是否写就绪的标准方法。其他一些UNIX实现也采用相同的标准;还有一些实现中认为只要可以写入1个字节,那么管道就是写就绪的。(在Linux 2.6.10版之前,管道的负载能力就是PIPE_BUF个字节。这表示如果管道只包含1字节数据,那么就认为它是不可写的。)
在其他一些UNIX实现中,如果管道的读端关闭,那么poll()并不会返回POLLERR标志,相反,要么会返回POLLOUT,要么返回POLLHUP。可移植的程序需要检查这些标志,以此来判断write()是否会阻塞。
| 条件或事件 | select() | poll() | | :----- | :----- | :----- | :----- | :----- | | 有PIPE_BUF 个字节的空间吗? | 读端打开了吗? | | 否 | 否 | w | POLLERR | | 是 | 是 | w | POLLOUT | | 是 | 否 | w | POLLOUT | POLLERR |
套接字
表63-6总结了 select()和 poll()在套接字上的行为表现。对于 poll()这一列,我们假设events字段已经指定了(POLLIN | POLLOUT | POLLPRI)标志位。对于select()这一列,我们假设需要检查文件描述符的输入、输出以及异常情况是否发生。(即,文件描述符在所有传递给select()的 3 个集合中都有指定)。该表只涵盖了常见的情况,并不包含所有可能出现的情况。
| 条件或事件 | select() | poll() | | :----- | :----- | :----- | :----- | :----- | | 有输入 | r | POLLIN | | 可输出 | w | POLLOUT | | 在监听套接字上建立连接 | r | POLLIN | | 接收到带外数据(只限TCP) | x | POLLPRI | | 流套接字的对端关闭连接或 | rw | POLLIN│POLLOUT│ | | 执行了shutdown(SHUT_WR) | | POLLRDHUP |
Linux 下,UNIX域套接字对端调用close()后,poll()表现的行为同表63-6中所展示的不一样。除了其他标志外,poll()还会在revents中额外返回POLLHUP。
Linux专有的POLLRDHUP标志(从Linux 2.6.17以来就一直存在)需要做进一步的解释。其实,这个标志的实际形式是EPOLLRDHUP——主要被设计用于epoll API的边缘触发模式下(见63.4节)。当流式套接字连接的远端关闭了写连接时会返回该标志。使用这个标志能让采用了 epoll 边缘触发模式的应用程序使用更简洁的代码来判断远端是否已经关闭。(另一种可选的方法是,在应用程序中设定POLLIN标志,然后执行一次read(),如果返回0则表示远端已经关闭了。)