【问题标题】:EINTR and non-blocking callsEINTR 和非阻塞调用
【发布时间】:2021-07-04 03:14:26
【问题描述】:

众所周知,像readwrite 这样的一些阻塞调用会返回-1 并将errno 设置为EINTR,我们需要处理这个问题。

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

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

【问题讨论】:

    标签: nonblocking eintr


    【解决方案1】:

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

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

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

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

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

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

    【讨论】:

    • 感谢您的详细解答。
    • 很好的解释。谢谢。
    【解决方案2】:

    @Mecki 很好的解释。为了增加已接受的答案,“Unix 网络编程 - 第 1 卷,第三版”(Stevens)一书在第 5.9 章/第 5.9 节 - “处理中断的系统调用”中区分了慢速系统调用和其他系统调用。我引用了书中的内容-

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

    在同一节的下一段 -

    这里适用的基本规则是,当一个进程被阻塞时 一个缓慢的系统调用和进程捕获一个信号和信号 handler返回,系统调用可以返回EINTR的错误。

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

    【讨论】:

      【解决方案3】:

      只是为了给@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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-03-23
        • 2016-02-24
        • 2016-07-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多