3.2 中断的系统调用
(1)进程调用“慢”系统调用时,如果发生了信号,内核会重启系统调用(即“慢”系统调用被信号中断后,当信号处理完毕后,该系统调用会被重新从头开始执行,而不是从中断的地方继续执行!注意,这是重启的方式。除此之处,还有设置SA_RESTART和忽略信号等方式)
(2)慢系统调用
①可能会永远阻塞的系统调用
②从终端设备、管道或网络设备上的文件读取。
③向上述文件写入
④某些设备上的文件打开
⑤pause和wait系统调用
⑥一些设备的ioctl操作
⑦一些进程间的通信函数
(3)如何处理被中断的系统调用
①重启被中断的系统调用:如accept、read、write、select、wait、waitpid和open等。(是重启系统调用还是让系统调用失败?在早期的Unix是让系统调用失败,并返回-1,同时设置errno,如pause被中断唤醒而不是重启)
②安装信号时设置SA_RESTART属性(使用sigaction安装信号,该方法对有的系统调用无效)
③忽略信号,让系统不产生信号中断。
【编程实验】中断系统调用
//signal_syscall.c
#include <unistd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> void sig_handler(int signo) { //ctrl-z信号 if(signo == SIGTSTP){ printf("SIGTSTP occured "); } } int main(void) { char buffer[512]; ssize_t size; //注册信号处理函数 if(signal(SIGTSTP, sig_handler) == SIG_ERR){ perror("signal sigtstp error"); } printf("begin running and waiting for signal "); //由于read会阻塞等待输入,如果此时先输入一些内容,然后按ctrl-z //这里由于read被信号中断,会重启read函数,表现出来的是重新生成 //提示符等待用户输入,之前的输入全部清空。 size = read(STDIN_FILENO, buffer, sizeof(buffer)); //慢系统调用 if(size < 0){ perror("read error"); } printf("reading finished "); if(write(STDOUT_FILENO, buffer, size) != size){ //慢系统调用 perror("write error"); } printf("end running "); return 0; } /*输出结果: [root@localhost]# bin/signal_syscall begin running and waiting for signal abcd //输完内容后,按回车正常结束一行的输入 reading finished abcd end running [root@localhost]# bin/signal_syscall begin running and waiting for signal abcaksdfj ^ZSIGTSTP occured //输入一些内容后,按ctrl-z产生信号,中断read mnk //这里会重启read,原内容被清空,重新生成提示符 reading finished mnk end running */
【编程实验】中断用户自定义的函数
//signal_usercall.c
#include <signal.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> void sig_handler(int signo) { if(signo == SIGTSTP){ printf("SIGTSTP occured "); } } //当用户函数被信号中断后,信号处理完之后,会从中断的地方 //继续执行,而不会重启整个函数。这是与慢系统调用不同的。 void call_fun(void) { printf("begin running call_fun "); sleep(5); //演示在这期间产生信号中断现象 printf("end running call_fun "); } int main(void) { if(signal(SIGTSTP, sig_handler) == SIG_ERR){ perror("signal sigtstp error"); } printf("begin running main "); call_fun(); printf("end running main "); return 0; } /* [root@localhost]# bin/signal_usercall begin running main //正常流程 begin running call_fun end running call_fun end running main [root@localhost]# bin/signal_usercall begin running main //在call_fun执行过程中按ctrl-z中断 begin running call_fun ^ZSIGTSTP occured //发生信号,当信号处理完毕后,从中 end running call_fun //断的地方开始继续执行,而不是重启整个函数 end running main */
3.3 函数的可重入性
(1)在调用某个函数过程中出现信号,且该信号处理函数中再次调用该函数,就可能产生可重放入性问题。
(2)访问全局或静态变量的函数是不可重入函数
(3)程序片断
//可重入函数 int double(int a){ return a * 2; } //不可重入函数 void foo(){ static int array[28]={0}; static int index = 0; if(index > 19) return; array[index] = 9; index++; }
【编程实验】函数的可重入性
//signal_reentry.c
#include <unistd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <malloc.h> int g_v[10]; //全局数组 int* h_v; //堆数组 void set(int val) { int a_v[10]; //局部数组 int i = 0; for(; i<10; i++){ a_v[i] = val; g_v[i] = val; h_v[i] = val; sleep(1); } printf("g_v:"); for(i=0; i<10; i++){ if(i != 0) printf(",%d", g_v[i]); else printf("%d", g_v[i]); } printf(" "); printf("h_v:"); for(i=0; i<10; i++){ if(i != 0) printf(",%d", h_v[i]); else printf("%d", h_v[i]); } printf(" "); printf("a_v:"); for(i=0; i<10; i++){ if(i != 0) printf(",%d", a_v[i]); else printf("%d", a_v[i]); } printf(" "); } //信号处理函数 void sig_handler(int signo) { if(signo == SIGTSTP){ printf("SIGTSTP occured "); set(20); //信号处理函数内部再次调用set,以验证函数的 //可重入性。注意,传入20 printf("end SIGTSTP "); } } int main(void) { if(signal(SIGTSTP, sig_handler) == SIG_ERR){ perror("signal sigtstp error"); } h_v = (int*)calloc(10, sizeof(int)); printf("begin running main "); set(10); //传入10 printf("end running main "); free(h_v); } /* [root@localhost]# bin/signal_reentry begin running main //正常执行,不会触发信号中断 g_v:10,10,10,10,10,10,10,10,10,10 h_v:10,10,10,10,10,10,10,10,10,10 a_v:10,10,10,10,10,10,10,10,10,10 end running main [root@localhost]# bin/signal_reentry begin running main //运行2-3秒后,按ctrl-z触发信号中断 ^ZSIGTSTP occured //在信号处理函数中调用set赋值 g_v:20,20,20,20,20,20,20,20,20,20 h_v:20,20,20,20,20,20,20,20,20,20 a_v:20,20,20,20,20,20,20,20,20,20 end SIGTSTP //信号处理完,由原中断处继续执行,这里从第4个元素继续赋值(10) g_v:20,20,20,10,10,10,10,10,10,10 h_v:20,20,20,10,10,10,10,10,10,10 a_v:10,10,10,10,10,10,10,10,10,10 end running main */
3.4 信号的特点
(1)信号的发生是随机的,但信号在何种条件下发生是可预测的(如按ctrl-z将发送SIGTSTP信号)。
(2)进程刚开始启动时所有信号的处理方式要么默认,要么忽略,忽略的是SIGUSR1和SIGUSR2两个信号,其它都采取默认的方式(大多数是终止进程)
(3)进程在调用exec函数后,原有信号的捕捉函数失效。
(4)子进程的诞生总是继承父进程的信号处理方式。
(5)在系统层面上,信号的发生是可靠的,在linux中的可靠性只保证一次,进程在处理信号期间若发生同类型的信号不会丢失(内核会保留),但会被延迟处理,但同类型信号的多次发生只会保留一次(即被处理一次)。若不同类型的信号发生也会被内核保留直接被处理,处理完后再处理原有信号。
(6)用户层面可靠性,依赖于信号而执行的用户代码放置在信号处理程序内部执行,否则不一定可靠。
(7)在信号发生时,慢系统调用可以被中断并在信号处理后系统调用会被重启。
(8)在信号发生时,用户函数可以被中断但不能被重启,沿着中断点继续执行(在用户函数中要保证数据一致性,即可重入性不要去访问全局变量和静态变量,堆中的变量若在用户函数内部分配没有关系,否则会出现不可重入性)