闲聊作为背景
EINTR
是所谓的可中断系统调用可能返回的错误。如果系统调用运行时出现信号,则不会忽略该信号。如果为其定义了信号处理程序而没有SA_RESTART
设置并且该处理程序处理该信号,则系统调用将返回EINTR
错误代码。
附带说明一下,我在 Python 中使用时经常遇到这个错误ncurses
。
问题
POSIX 标准指定的这种行为背后有什么理由吗?人们可以理解它可能无法恢复(取决于内核设计),但是,不在内核级别自动重新启动它的理由是什么?这是出于遗留原因还是技术原因?如果这是出于技术原因,那么这些原因现在仍然有效吗?如果这是出于遗留原因,那么历史是什么?
答案1
在信号处理程序中很难做一些重要的事情,因为程序的其余部分处于未知状态。大多数信号处理程序只是设置一个标志,稍后在程序的其他地方检查和处理该标志。
系统调用不自动重启的原因:
想象一个应用程序通过以下方式从套接字接收数据阻塞且不可中断 recv()
系统调用。在我们的场景中,数据传输速度非常慢,并且程序在该系统调用中驻留很长时间。该程序有一个信号处理程序,用于SIGINT
设置一个标志(在其他地方评估),并SA_RESTART
设置系统调用自动重新启动。想象一下该程序正在recv()
等待数据。但没有数据到达。系统调用阻塞。该程序现在从用户处捕获ctrl- 。c系统调用被中断,并且执行刚刚设置标志的信号处理程序。然后recv()
重启,仍然等待数据。事件循环陷入困境recv()
,没有机会评估标志并正常退出程序。
未设置时SA_RESTART
:
在上面的场景中,当SA_RESTART
未设置时,recv()
将接收EINTR
而不是重新启动。系统调用退出,因此可以继续。当然,程序应该(尽早)检查标志(由信号处理程序设置)并进行清理或执行任何操作。
答案2
理查德·加布里埃尔写了一篇论文“越坏越好”的兴起其中讨论了 Unix 中的设计选择:
两位著名人士,一位来自麻省理工学院,另一位来自伯克利大学(但从事 Unix 工作)曾经会面讨论操作系统问题。这位来自麻省理工学院的人对 ITS(麻省理工学院人工智能实验室操作系统)非常了解,并且一直在阅读 Unix 源代码。他对 Unix 如何解决 PC 失败问题很感兴趣。当用户程序调用系统例程来执行可能具有重要状态(例如 IO 缓冲区)的冗长操作时,就会出现 PC 丢失问题。如果运行过程中发生中断,则必须保存用户程序的状态。由于系统例程的调用通常是单个指令,因此用户程序的PC不能充分捕获进程的状态。系统例程必须要么退出,要么前进。正确的做法是退出并将用户程序PC恢复到调用系统例程的指令,以便在中断后恢复用户程序,例如重新进入系统例程。之所以这样称呼,是
PC loser-ing
因为 PC 被强制进入loser mode
,其中“失败者”是麻省理工学院对“用户”的亲切称呼。麻省理工学院的人没有看到任何处理此案例的代码,并询问新泽西州的人如何处理该问题。新泽西人说,Unix 人已经意识到这个问题,但解决方案是让系统例程始终完成,但有时会返回一个错误代码,表明系统例程未能完成其操作。那么,正确的用户程序必须检查错误代码以确定是否再次尝试系统例程。麻省理工学院的人不喜欢这个解决方案,因为它不是正确的事情。
新泽西人说Unix的解决方案是对的,因为Unix的设计理念是简单,而正确的事情却太复杂了。此外,程序员可以轻松插入这个额外的测试和循环。麻省理工学院的人指出,实现很简单,但功能的接口很复杂。新泽西人说,在 Unix 中选择了正确的权衡——即实现简单性比接口简单性更重要。