信号对系统调用的作用---自动重启动或被中断而停止执行
早期的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对信号的的处理更加灵活。