非阻塞 – EINTR和非阻塞呼叫

众所周知,像read和write这样的阻塞调用将返回-1并将errno设置为EINTR,我们需要处理这个.

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

由于一些文章和资料来源我已经看到,非阻塞电话不需要麻烦,但我没有发现没有权威的参考.如果是,应用跨不同的实现?

最佳答案
我不能给你一个明确的答案这个问题,答案可能会有所不同,从系统到系统,但我希望一个非阻塞套接字永远不会失败与EINTR.如果您查看各种系统的手册页,可以使用以下套接字函数bind(),connect(),send()和receive(),或者查看POSIX标准中的一些,您会注意到有趣的事情:除了一个以外的所有这些功能可以返回-1并将errno设置为EINTR.没有记录的一个功能是永远不会与EINTR失败的是bind().而bind()也是该列表的唯一功能,默认情况下它永远不会被阻止.所以似乎只有阻塞函数可能会因为EINTR而丢失,包括read()和write(),但是如果这些函数永远不会阻塞,它们也永远不会与EINTR失败,如果使用O_NONBLOCK,这些函数将永远不会被阻塞.

从逻辑角度来看也是没有意义的.例如.考虑到您正在使用阻塞I / O,并且调用read(),并且该调用必须被阻塞,但是当它被阻塞时,信号被发送到你的进程,因此读请求被解除.系统如何处理这种情况?声称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()的函数将当前线程置于休眠状态.该功能是documented here(正如我所说,OS X在其内核中使用大量的FreeBSD代码).此功能使当前线程休眠,直到它被明确唤醒(如果数据准备好读取的话)或超时已经被击中(例如,您可以在套接字上设置接收超时).然而线程也被唤醒,如果一个信号被传送,在这种情况下,msleep()本身返回EINTR,而下一个较高的层只是传递这个错误.所以是产生EINTR错误的msleep(),但如果设置了O_NONBLOCK标志,则首先不会调用msleep(),因此无法返回此错误.

当然这是MacOS / FreeBSD,其他系统可能会有所不同,但是由于大多数系统尝试在这些API中保持至少一定程度的一致性,所以如果系统中断这个假设,那么非阻塞I / O调用永远不会失败因为EINTR,这可能不是意图,甚至可以修复如果你的报告.

转载注明原文:非阻塞 – EINTR和非阻塞呼叫 - 代码日志