早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno设置为EINTR。这样处理是因为一个信号发生了,进程捕捉到它,这意味着已经发生了某种事情,所以是个好机会应当唤醒阻塞的系统调用。

在这里,我们必须区分系统调用和函数。当捕捉到某个信号时,被中断的是内核中执行的系统调用。

为了支持这种特性,将系统调用分成两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:

  • 如果某些类型文件(如读管道、终端设备和网络设备)的数据不存在,则读操作可能会使调用者永远阻塞;
  • 如果这些数据不能被相同的类型文件立即接受,则写操作可能会使调用者永远阻塞;
  • 在某种条件发生之前打开某些类型文件,可能会发生阻塞(例如要打开一个终端设备,需要先等待与之连接的调制解调器应答);
  • pause函数(按照定义,它使调用进程休眠直至捕捉到一个信号)和wait函数;
  • 某些ioctl操作;
  • 某些进程间通信函数(见第15章)。

在这些低速系统调用中,一个值得注意的例外是与磁盘I/O有关的系统调用。虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间),但是除非发生硬件错误,I/O操作总会很快返回,并使调用者不再处于阻塞状态。

可以用中断系统调用这种方法来处理的一个例子是:一个进程启动了读终端操作,而使用该终端设备的用户却离开该终端很长时间。在这种情况下,进程可能处于阻塞状态几个小时甚至数天,除非系统停机,否则一直如此。

对于中断的read、write系统调用,POSIX.1的语义在该标准的2001版有所改变。对于如何处理已 read、write 部分数据量的相应系统调用,早期版本允许实现自行选择。如若 read系统调用已接收并传送数据至应用程序缓冲区,但尚未接收到应用程序请求的全部数据,此时被中断,操作系统可以认为该系统调用失败,并将 errno 设置为 EINTR;另一种处理方式是允许该系统调用成功返回,返回值是已接收到的数据量。与此类似,如若write巳传输了应用程序缓冲区中的部分数据,然后被中断,操作系统可以认为该系统调用失败,并将errno设置为EINTR;另一种处理方式是允许该系统调用成功返回,返回值是已写部分的数据量。历史上,从System V派生的实现将这种系统调用视为失败,而 BSD 派生的实现则处理为部分成功返回。2001 版POSIX.1标准采用BSD风格的语义。

与被中断的系统调用相关的问题是必须显式地处理出错返回。典型的代码序列(假定进行一个读操作,它被中断,我们希望重新启动它)如下:

again :
    if((n = read(fd, buf, BUFFSIZE)) < 0){
        if(errno == EINTR){
            goto again; // just an interrupted system call
            // handle other errors
        }
    }

为了帮助应用程序使其不必处理被中断的系统调用,4.2BSD引进了某些被中断系统调用的自动重启动。自动重启动的系统调用包括:ioctl、read、readv、write、writev、wait 和wait-pid。如前所述,其中前5个函数只有对低速设备进行操作时才会被信号中断。而wait和waitpid 在捕捉到信号时总是被中断。因为这种自动重启动的处理方式也会带来问题,某些应用程序并不希望这些函数被中断后重启动。为此4.3BSD允许进程基于每个信号禁用此功能。

POSIX.1 要求只有中断信号的SA_RESTART标志有效时,实现才重启动系统调用。在10.14节将看到,sigaction函数使用这个标志允许应用程序请求重启动被中断的系统调用。

历史上,使用signal函数建立信号处理程序时,对于如何处理被中断的系统调用,各种实现的做法各不相同。System V的默认工作方式是从不重启动系统调用。而BSD则重启动被信号中断的系统调用。FreeBSD 8.0、Linux 3.2.0和Mac OS X 10.6.8中,当信号处理程序是用signal函数时,被中断的系统调用会重启动。但 Solaris 10 的默认方式是出错返回,将 errno 设置为EINTR。使用用户自己实现的signal函数(见图10-18)可以避免必须处理这些差异的麻烦。

4.2BSD引入自动重启动功能的一个理由是:有时用户并不知道所使用的输入、输出设备是否是低速设备。如果我们编写的程序可以用交互方式运行,则它可能读、写终端低速设备。如果在程序中捕捉信号,而且系统并不提供重启动功能,则对每次读、写系统调用就要进行是否出错返回的测试,如果是被中断的,则再调用读、写系统调用。

图10-3列出了几种实现所提供的与信号有关的函数及它们的语义。

图10-3 几种信号实现所提供的功能

应当了解,其他厂商提供的UNIX系统可能不同于图10-3中所示的情况。例如,SunOS 4.1.2中的sigaction默认方式是重启动被中断的系统调用,这与列在图10-3中的各平台不同。

在图10-18中,提供了我们自己的signal函数版本,它自动地尝试重启动被中断的系统调用(除 SIGALRM信号外)。在图10-19中则提供了另一个函数signal_intr,它不进行重启动。

在14.4节说明select和poll函数时,还将更多涉及被中断的系统调用。

results matching ""

    No results matching ""