首页 > 技术文章 > 信号对系统调用的作用---自动重启动或被中断而停止执行

black-mamba 2017-05-17 23:26 原文

  早期的UNIX系统的一个特性:如果进程在执行一个低速系统调用而阻塞期间,捕捉到一个信号,则该系统调用就被中断而不再继续执行。该系统调用返回出错,其error被设置为EINTR。即信号中断系统调用的执行
这样处理的理由是:因为一个信号发生了,进程捕捉到了它,那么意味着已经发生了某种事情,所以是个唤醒 被阻塞的系统调用 的机会。
为了支持系统调用被信号打断而不再执行的特性,将系统调用分为两类:低速系统调用和其他系统调用。
低速系统调用是可能会使进程永远阻塞的一类系统调用,他们包括:
1. read()、readv(),在读某些类型的文件(管道、终端设备以及网络设备)时,如果数据并不存在则可能会使调用者永远阻塞;
2. write()、writev(),在写这些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远阻塞;
3. 打开某些类型的文件,在某种条件发生之前也可能会使调用者阻塞(例如,打开终端设备,他要等待直到所连接的调制解调器应答了电话)--->这种类型还没有实际例子测试
4. pause()和wait();
5. 某些ioctl;
6. 某些进程间通信函数;

 

什么是系统调用的自动重启动?
  当系统调用被信号中断时,并不返回,而是继续执行。如果read()阻塞等待,当进程接受到信号时,并不将read返回,而是继续阻塞等待。
为什么要引入自动重启动的?
  有时用户并不知道所使用的输入、输出设备是否是低速设备。如果编写的程序可以用交互方式运行,则他可能读、写低速终端设备。
  如果在程序中捕捉到信号,而系统调用并不提供重启动功能,则对每次读、写系统调用都要进行是否出错返回的测试,如果是被信号中断,则再调用读、写系统调用。
什么时候引入系统调用的自动重启动?
  4.2BSD支持某些被中断系统调用的自动重启动。
  4.3BSD允许进程基于每个信号禁用自动重启动功能(因为也存在某些应用程序并不希望系统调用被中断后自动重启)

默认自动重启动的系统调用包括:ioctl(),read(),readv(),write(),writev(),wait(),waitpid();其中前5个函数只有在对低速设备进行操作时才会被信号中断。而wait和waitpid在捕捉到信号时总是被中断。

 

我的测试环境是4.3BSD,下面以read()为例来说明自动重启动和被中断。

 

自动重启动:

restart.c

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <string.h>
 5 #include <signal.h>
 6 #include <errno.h>
 7 
 8 #define BUFSIZE (1024)
 9 
10 static void sig_alrm(int signo)
11 {
12     printf("caught alarm...\n");
13 }
14 
15 void sig_usr(int signo)
16 {
17     if (signo == SIGUSR1)
18         printf("received SIGUSR1\n");
19     else if (signo == SIGUSR2)
20         printf("received SIGUSR2\n");
21     else
22         printf("received signal %d\n", signo);
23 }
24 
25 int main(int argc, char** argv)
26 {
27     int nSize = 0;
28     char acBuf[BUFSIZE] = {0};
29 
30     signal(SIGUSR1, sig_usr);
31     signal(SIGALRM, sig_alrm);
32 
33     alarm(5);
34 
35 //    while(1)
36     {
37         
38         memset(acBuf, '\0', BUFSIZE);
39         nSize = read(STDIN_FILENO, acBuf, BUFSIZE); 
40         if (errno == EINTR)
41             printf("interrupt, size=%d\n", nSize);
42 
43         if (1 == nSize && acBuf[0] == 10)
44             ;
45         else if (nSize != -1)
46         {
47             printf("nSize=%d, acBuf=%s", nSize, acBuf);        
48         }
49     }
50 
51     return 0;
52 }

运行结果是:

定时器计数到5秒后,会发送alarm信号,从打印可以看出确实接收到了alarm信号,且read()并没有返回而是继续阻塞接受标准输入;然后在手动发送一个SIGUSR1信号,同样,read()并没有被中断,当输入jfdk后,read能正常读出。说明signal()默认是将信号设置为自动重启动

 

被信号中断停止执行:

no_restart.c

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <string.h>
 5 #include <signal.h>
 6 #include <errno.h>
 7 
 8 #define BUFSIZE (1024)
 9 
10 static void sig_alrm(int signo)
11 {
12     printf("caught alarm...\n");
13 }
14 
15 void sig_usr(int signo)
16 {
17     if (signo == SIGUSR1)
18         printf("received SIGUSR1\n");
19     else if (signo == SIGUSR2)
20         printf("received SIGUSR2\n");
21     else
22         printf("received signal %d\n", signo);
23 }
24 
25 int main(int argc, char** argv)
26 {
27     int nSize = 0;
28     char acBuf[BUFSIZE] = {0};
29     struct sigaction act, oact;
30 
31     act.sa_handler = sig_usr;
32     sigemptyset(&act.sa_mask);
33     act.sa_flags = 0|SA_INTERRUPT;
34 
35     sigaction(SIGUSR1, &act, &oact);
36     signal(SIGALRM, sig_alrm);
37 
38     alarm(5);
39 
40     //while(1)
41     {
42         
43         memset(acBuf, '\0', BUFSIZE);
44         nSize = read(STDIN_FILENO, acBuf, BUFSIZE); 
45         if (errno == EINTR)
46             printf("interrupt, size=%d\n", nSize);
47 
48         if (1 == nSize && acBuf[0] == 10)
49             ;
50         else if (nSize != -1)
51         {
52             printf("nSize=%d, acBuf=%s", nSize, acBuf);
53         }
54     }
55 
56     return 0;
57 }

运行结果为:

此例子中,alarm信号还是自动重启动,手动发送一个SIGUSR1信号后,SIGUSR1信号处理程序执行完后,read()立马就返回了。

从上面两个例子中还可以看出signal()和sigaction()两个函数的差异,显然sigaction对信号的的处理更加灵活。

推荐阅读