被中断的系统调用
早期UNIX系统的一个特性是如果进程被一个“慢”系统调用阻塞的同时捕获了一个信号(这个系统调用已经被中断)。系统调用返回一个错误并且设置errno的值为EINTR。假定这发生在信号发生并且进程捕获到它时。这有可能会唤醒被阻塞的系统调用。(这里必须区分系统调用和函数的不同,系统调用是在内核中,当信号被捕捉时才会被中断。)
为了支持这一功能,系统调用分为两类:“慢”系统调用和其它。慢系统调用是能被永远阻塞的。包含以下几种:
- 如果数据没有提供某种文件类型(pipes,终端设备和网络设备),读的时候就能被永久阻塞。
- 如果数据不能被同一文件类型立即接收,写的时候就能被永久阻塞。
- 当打开文件时,阻塞会持续到需要一些条件发生在某些文件类型上(像是打开一个终端设备,而该终端设备要等待绑定的调制解调器的拨号应答)。
- pause函数(用于休眠一个进程,直到信号被捕获)和wait函数。
- 某种ioctl操作
- 一些进程间通讯函数
“慢”系统调用中重要的异常都是和磁盘I/O有关的。虽然磁盘文件的读或写能临时阻塞调用者(当磁盘驱动程序对请求进行排队之后响应这些请求),除非发生硬件错误,否则I/O操作一直快速地返回并解锁调用者。
有一种情况可以用被中断的系统调用处理。例如:当一个进程创建了从终端设备上读的操作,并且用户离开了终端很长的时间。在这种情况下,进程可能被阻塞几个小时或几天并且保留进程,除非系统关闭。
被中断的系统调用有一个问题,那就是我们现在必须显示的处理返回的错误。典型的代码序列可能是(假设一个读操作并且假设我们想重新开始读,即使它已经被中断):
again:
if ((n = read(fd, buf, BUFFSIZE)) < 0) {
if (errno == EINTR)
goto again; /* just an interrupted system call */
/* handle other errors */
}
|
为了防止应用程序必须处理被中断的系统调用,BSD4.2引入了自动重启某些被中断的系统调用。能被自动重启的系统调用有:ioctl, read, readv, write, writev, wait 和waitpid。如同我们提到过的前五个函数如果操作一个慢设备,那么会被一个信号中断。wait和waitpid当信号被捕获时总是被中断。因为这会引起一些问题,像一些应用不希望操作被中断后重新启动,BSD4.3允许进程在每个信号上屏蔽这个功能。
BSD4.2引入自动重启系统调用这个功能的原因之一是:有时我们并不知道输入或输出设备是一个慢速设备。如果我们写了一个用于交互的程序,只要终端进入了这个分类,那么它可能读或写一个慢速设备。如果在这个程序运行期间我们捕获了一个信号,并且当前系统不提供自动重启这个功能,那么我们必须为返回的中断错误测试每一个读写,并重新读写(reissue the read or write)。
Figure 10.3汇总了信号函数和各个版本的用法(their semantics provided by the various implementations)
|
不同发行商的实现可能上表中的值也有所不同。例如:sigaction函数,在SunOS 4.1.2重启被中断的系统调用是默认,Figure10.3列出了不同平台的区别。
在Figure 10.18我们提供了我们自己的signal函数,它尝试自动重启被中断的系统调用(不同于SIGALRM信号)。在Figure 10.19我们提供了另一个函数,signal_intr,它尝试从不重启。