`EINTR`:它背后有什么理由吗?

Hib*_*u57 10 process signals system-calls posix

作为背景的闲聊

EINTR是所谓的可中断系统调用可能返回的错误。如果在系统调用运行时发生信号,则不会忽略该信号,并且为它定义了SA_RESTART未设置的信号处理程序并且该处理程序处理该信号,则系统调用将返回EINTR错误代码。

作为旁注,我经常ncurses在 Python 中使用这个错误。

问题

POSIX 标准指定的这种行为背后是否有基本原理?可以理解可能无法恢复(取决于内核设计),但是,不在内核级别自动重新启动它的理由是什么?这是出于传统原因还是技术原因?如果这是出于技术原因,这些原因现在仍然有效吗?如果这是出于遗留原因,那么历史是什么?

cha*_*aos 16

在信号处理程序中很难做一些重要的事情,因为程序的其余部分处于未知状态。大多数信号处理程序只是设置一个标志,稍后在程序的其他地方检查和处理。

没有自动重启系统调用的原因:

想象一个应用程序通过阻塞和 不可中断的recv()系统调用从套接字接收数据。在我们的场景中,数据传输速度非常慢,并且程序在该系统调用中驻留很长时间。该程序有一个信号处理程序,用于SIGINT设置一个标志(在别处评估),并SA_RESTART设置为系统调用自动重新启动。想象一下,程序在recv()其中等待数据。但是没有数据到达。系统调用阻塞。该程序现在捕获ctrl-c从用户。系统调用被中断,信号处理程序,它只是设置了标志被执行。然后recv()就是重启,还在等待数据。事件循环卡在其中recv(),没有机会评估标志并优雅地退出程序。

随着SA_RESTART未设置:

在上述场景中,whenSA_RESTART未设置,recv()将接收EINTR而不是重新启动。系统调用退出,因此可以继续。当然,程序应该(尽可能早地)检查标志(由信号处理程序设置)并进行清理或执行任何操作。

  • 即使设置了“SA_RESTART”,也并非所有系统调用都会自动重新启动。对于 [example](http://man7.org/linux/man-pages/man2/msgop.2.html),Linux 不会重启 `msgsnd()` 或 `msgrcv()`。 (2认同)

小智 6

Richard Gabriel 写了一篇论文The Rise of 'Worse is Better'讨论了 Unix 中的设计选择:

两位名人,一位来自麻省理工学院,另一位来自伯克利分校(但在 Unix 上工作)曾经见面讨论操作系统问题。麻省理工学院的人对 ITS(麻省理工学院人工智能实验室操作系统)非常了解,并且一直在阅读 Unix 资源。他对 Unix 如何解决 PC 失败问题很感兴趣。当用户程序调用系统例程来执行可能具有重要状态的冗长操作(例如 IO 缓冲区)时,就会出现 PC 丢失问题。如果在操作过程中发生中断,则必须保存用户程序的状态。因为系统例程的调用通常是单条指令,所以用户程序的PC 不能充分捕捉进程的状态。系统例程必须退出或向前推进。正确的做法是将用户程序PC回退并恢复到调用系统例程的指令,以便中断后恢复用户程序,例如重新进入系统例程。它被称为PC loser-ing因为 PC 被强制使用loser mode,其中“失败者”是麻省理工学院“用户”的亲切名称。

麻省理工学院的人没有看到任何处理此案的代码,并询问新泽西州的人是如何处理问题的。新泽西人说 Unix 的人都知道这个问题,但解决方案是让系统例程始终完成,但有时会返回一个错误代码,表示系统例程未能完成其操作。然后,正确的用户程序必须检查错误代码以确定是否再次尝试系统例程。麻省理工学院的人不喜欢这个解决方案,因为这不是正确的事情。

新泽西人说 Unix 解决方案是正确的,因为 Unix 的设计理念是简单,而正确的东西太复杂了。此外,程序员可以轻松插入这个额外的测试和循环。麻省理工学院的人指出,实现很简单,但功能的接口很复杂。新泽西州的家伙说在 Unix 中已经选择了正确的权衡——即实现简单性比接口简单性更重要。