一、概述
fork之后,子进程虽然会继承父进程的“信号屏蔽和安排”,但是exec后,只有“被忽略的信号”和“设置为默认”处理行为是被保留的,被用户修改自定义函数动作的信号安排会被内核设置为系统默认动作,原因是exec后进程的上下文被替换成了被执行程序的上下文,而被替换的上下文恰恰就包括exec之前定义过的函数,此时传到信号处理函数的函数指针可能已经指向了一个无效的空间。所以在执行一个程序时,系统会把被修改为自定义函数动作的信号改为默认动作或忽略,除非这个信号在exec之前被用户设置为忽略。来看看《APUE》第10.3节是怎么说的:
当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。确切地讲,exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就不能再捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。 |
二、示例
1.自定义信号函数被内核修改至默认值的例子:
在这个例子里,父进程A在启动子进程B之前捕获了信号SIGINT并自定义了处理函数:
/*Program A*/ #include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> static void sig_usr(int signo) { if(signo == SIGINT){ printf("received SIGINT. "); }else{ printf("received signal %d. ", signo); } } int main(void){ if(signal(SIGINT, sig_usr) == SIG_ERR){ printf("cat't catch SIGINT. "); return 0; } pid_t pid = fork(); if(pid < 0){ printf("Error occurred when fork. "); } if(pid == 0){ if(execl("/lroot/c/temp/B", NULL) < 0){ printf("Error occurred when execle. "); } } int status; pid_t pid_done = wait(&status); printf("pid: %d ", pid_done); return 0; }
/*Program B*/ #include <unistd.h> int main(void){ for(;;){ pause(); } return 0; }
父进程在fork子进程后调用wait等待子进程返回,如果内核没有改写子进程的信号捕获安排,那么按照常理来说对B发出一个SIGINT信号,B会向终端打印“received SIGINT. ”。
但是现在它并没有,因为内核在exec之后把进程对SIGINT的安排设置为了系统默认处理方式,所以这里B进程终止。
2.被用户设置为忽略的信号exec后依然保留忽略的例子:
说明这种情况只需要把程序A中的捕捉函数修改为SIG_IGN就可以了。
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> int main(void){ if(signal(SIGINT, SIG_IGN) == SIG_ERR){ printf("cat't catch SIGINT. "); return 0; } pid_t pid = fork(); if(pid < 0){ printf("Error occurred when fork. "); } if(pid == 0){ if(execl("/lroot/c/temp/B", NULL) < 0){ printf("Error occurred when execle. "); } } int status; pid_t pid_done = wait(&status); printf("pid: %d ", pid_done); return 0; }
自此向进程发出SIGINT信号会被进程B忽略,不做任何处理。