EINTR和非阻塞呼叫

hao*_*lee 15 nonblocking eintr

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

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

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

Mec*_*cki 24

我无法给出这个问题的明确答案,答案可能因系统而异,但我希望非阻塞套接字永远不会失败EINTR.如果你看看各个系统的手册页下面的socket函数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,这可能不是故意的,如果你的报告,甚至可能会得到修复.