18

众所周知,一些阻塞调用像readandwrite会返回 -1 并设置errnoEINTR,我们需要处理这个问题。

我的问题是:这是否适用于非阻塞调用,例如,将套接字设置为O_NONBLOCK

由于我读过的一些文章和资料说非阻塞调用不需要为此烦恼,但我没有找到关于它的权威参考。如果是这样,它是否适用于不同的实现?

4

3 回答 3

30

对于这个问题,我无法给你一个明确的答案,而且答案可能会因系统而异,但我希望非阻塞套接字永远不会因EINTR. 如果您查看各种系统的以下套接字函数bind()connect()send()和的手册页receive(),或者查看 POSIX 标准中的那些,您会发现一些有趣的东西:除了一个之外,所有这些函数都可能返回-1并设置errnoEINTR。没有记录到失败的一个功能EINTRbind(). 并且bind()也是该列表中唯一不会默认阻止的功能。所以似乎只有阻塞功能可能会因为EINTR, 包括read()write(), 但是如果这些函数从不阻塞,它们也永远不会失败,EINTR如果你使用O_NONBLOCK,这些函数永远不会阻塞。

从逻辑的角度来看,这也是没有意义的。例如,考虑您正在使用阻塞 I/O 并且您调用read()并且此调用必须阻塞,但是当它阻塞时,会向您的进程发送一个信号,因此读取请求被解除阻塞。系统应该如何处理这种情况?声称read()确实成功了?那将是一个谎言,它没有成功,因为没有读取任何数据。声称它确实成功了,但是读取了零字节数据?这也不正确,因为“零读取结果”用于指示流结束(或文件结束),因此您的进程会假设没有读取任何数据,因为文件已到达(或套接字/管道已在另一端关闭),但事实并非如此。如果您调用,则尚未到达文件结尾(或流结尾)read()再次,它将能够返回更多数据。所以这也是一个谎言。您的期望是此读取调用要么成功并读取数据,要么失败并出现错误。因此,在这种情况下,读取调用必须失败并返回-1,但errno系统应该设置什么值?所有其他错误值都表明文件描述符存在严重错误,但没有严重错误,表明此类错误也是谎言。这就是为什么errno设置为EINTR,这意味着:“流没有任何问题。你的读取调用刚刚失败,因为它被信号中断了。如果它没有被中断,它可能仍然成功,所以如果你仍然关心对于数据,请重试。”

如果现在切换到非阻塞 I/O,就不会出现上述情况。读取调用永远不会阻塞,如果它不能立即读取数据,它将失败并出现错误EAGAIN(POSIX)或EWOULDBLOCK(非官方,在 Linux 上两者都是相同的错误,只是它的替代名称),这意味着:“没有数据现在可用,因此您的读取调用必须阻塞并等待数据到达,但不允许阻塞,因此它失败了。” 因此,对于可能出现的每种情况都有错误。

当然,即使使用非阻塞 I/O,读取调用也可能暂时被信号中断,但为什么系统必须指示呢?每个函数调用,无论是系统函数还是用户编写的函数,都可能会被信号暂时中断,真的是每一个,也不例外。如果系统必须在发生这种情况时通知用户,则所有系统功能都可能因EINTR. 然而,即使有信号中断,这些函数通常也会一直执行它们的任务,这就是为什么这个中断是无关紧要的。错误EINTR用于告诉调用者他请求的动作由于信号中断而没有执行,但在非阻塞 I/O 的情况下,函数没有理由不执行读取或写入请求,除非它现在无法执行,但可以通过适当的错误指示。

为了证实我的理论,我查看了 MacOS(10.8)的内核,它仍然主要基于 FreeBSD 内核,似乎证实了我的怀疑。如果当前无法进行读取调用,因为没有可用的数据,内核会检查O_NONBLOCK文件描述符标志中的标志。如果设置了这个标志,它会立即失败并显示EAGAIN. 如果未设置,则通过调用名为 的函数使当前线程进入睡眠状态msleep()。该功能记录在这里(正如我所说,OS X 在其内核中使用了大量的 FreeBSD 代码)。该函数使当前线程休眠,直到它被显式唤醒(如果数据准备好读取)或超时(例如,您可以在套接字上设置接收超时)。然而,如果传递了一个信号,线程也会被唤醒,在这种情况下,它msleep()本身会返回EINTR,并且下一个更高层只是传递这个错误。所以它会msleep()产生EINTR错误,但如果O_NONBLOCK设置了标志,msleep()则永远不会首先调用,因此无法返回此错误。

当然那是 MacOS/FreeBSD,其他系统可能会有所不同,但是由于大多数系统都试图在这些 API 之间保持至少一定程度的一致性,如果系统打破假设,非阻塞 I/O 调用永远不会失败因为EINTR,这可能不是故意的,如果您报告它,甚至可能会得到解决。

于 2013-01-23T17:06:04.430 回答
2

@Mecki 很好的解释。为了增加接受的答案,“Unix Network Programming - Volume 1, Third Edition”(Stevens)一书在第 5.9 章/第 5.9 节 - “处理中断的系统调用”中区分了慢速系统调用和其他系统调用。我引用这本书-

我们使用术语“慢速系统调用”来描述accept,并且我们将这个术语用于任何可以永久阻塞的系统调用。也就是说,系统调用永远不需要返回。

在同一节的下一段 -

这里适用的基本规则是,当进程在慢速系统调用中被阻塞并且进程捕获信号并且信号处理程序返回时,系统调用可以返回错误EINTR

按照这种解释,非阻塞套接字上的read/不是慢速系统调用,因此不应返回.writeEINTR

于 2019-10-29T08:29:22.697 回答
0

只是为了给@Mecki 的答案添加一些证据,我发现这个讨论是关于修复 Linux 中的一个错误,其中一个补丁导致非阻塞 recvmsg 返回 EINTR。有人说:

EINTR 总是意味着你请求了一个阻塞操作,同时有一个信号到达。

一旦你反转了那组条件的“阻塞”部分,EINTR 就变成了一个不可能的事件。

还:

看看我们为 AF_INET 做了什么。我们以正确的方式处理这个问题。

如果我们在 lock_sock(), recvmsg() 在非阻塞套接字上休眠时被信号“中断”,我们会正确返回 -EAGAIN,而不是 -EINTR。

我们可能会休眠以获取套接字锁定的事实对于用户是隐藏的,这是内核的实现细节。

我们从不返回 -EINTR,如非阻塞套接字手册页中所述。

来源:https ://patchwork.ozlabs.org/project/netdev/patch/1395798147.12610.196.camel@edumazet-glaptop2.roam.corp.google.com/#741015

于 2021-07-03T19:14:26.380 回答