子进程对父进程信号的继承情况
父进程fork出子进程时,子进程会继承父进程很多的属性,其中就包括信号。
fork创建子进程,但是没有exec加载新程序时
在fork子进程之前,如果父进程调用signal设置了某个信号的处理方式的话,那么fork出的子进程会继承父进程对该信号设置的处理方式,比如fork前,父进程将SIGINT设置为了捕获或者忽略,子进程将继承设置的这个处理方式。
父进程将信号的处理方式设置为捕获时,捕获函数对子进程也是有效的。
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
int main()
{
pid_t ret = 0;
ret = fork();
//signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
if(ret>0)
{
}
else if(ret == 0)
{
}
while(1);
return 0;
}
我们可以看到现在父子进程都运行起来了:
这个时候两个进程都已经把SIGINT信号修改为SIG_IGN信号
我们可以看到这个时候是没有办法使用ctrl+c进行进程终止操作的。
由于父进程在fork之前进行了signal函数设置,所以子进程的SIGINT信号也是忽略,所以两个进程都忽略了,两个进程仍然处于运行状态。
接下来我们进行修改,把信号修改为捕获:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signal)
{
printf("PID:%d,signal = %d\n",getpid(),signal);
}
int main()
{
pid_t ret = 0;
ret = fork();
//signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
if(ret>0)
{
}
else if(ret == 0)
{
}
while(1);
return 0;
}
运行结果为:
我们可以看到两个进程都在运行。
这和时候我们通过ctrl+c发送信号,结果如下:
父进程和子进程的处理函数都去调用signal_fun函数,子进程会继承处理方式。并且信号出来函数对于子进程也是有效的。
接下来我们还原为默认处理方式:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signal)
{
printf("PID:%d,signal = %d\n",getpid(),signal);
}
int main()
{
pid_t ret = 0;
ret = fork();
//signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
//signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
signal(SIGINT,SIG_DFL); //把终止进程(ctrl+c)设置为默认处理方式
if(ret>0)
{
}
else if(ret == 0)
{
}
while(1);
return 0;
}
运行结果为:
我们可以看到退出之后父进程和子进程都退出了:
SIGINT信号会发送给父子进程。
再次强调,只有在fork之前,父进程所设置的信号处理方式,才会被子进程继承。
为什么捕获函数在子进程里面依然有效?
因为子进程复制了父进程的代码和数据,子进程自然也会包含信号处理函数的代码,所在子进程中依然有效。
子进程当然可以自己调用signal函数,修改掉所继承的处理方式。
代码演示:我们在父进程设置为默认,子进程设置为捕获:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signal)
{
printf("PID:%d,signal = %d\n",getpid(),signal);
}
int main()
{
pid_t ret = 0;
ret = fork();
//signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
//signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
//signal(SIGINT,SIG_DFL); //把终止进程(ctrl+c)设置为默认处理方式
if(ret>0)
{
signal(SIGINT,SIG_DFL); //把终止进程(ctrl+c)设置为默认处理方式
}
else if(ret == 0)
{
signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
}
while(1);
return 0;
}
运行结果为;
我们可以看到父子进程现在都在运行:
我们现在按下ctrl+c:
运行结果为:
这个时候我们进行进程查看的时候:
父进程已经终止,但是子进程只是捕获信号进行打印,并没有退出,所以父子进程的处理方式不同。
那如果父进程是在if(ret > 0){}里面设置得呢?
这就是父进程自己的设置,跟子进程没有关系。
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signo)
{
printf("PID:%d, signo=%d\n", getpid(), signo);
}
int main(int argc, char **argv, char **environ)
{
pid_t ret = 0;
//signal(SIGINT, SIG_IGN);
//signal(SIGINT, signal_fun);
//signal(SIGINT, SIG_DFL);
ret = fork();
if(ret > 0)
{
signal(SIGINT, signal_fun);
}
else if(ret == 0)
{
}
while(1);
return 0;
}
运行结果为:
这个时候我们可以看到:
父进程调用捕获函数,子进程默认退出就变成了僵尸进程。
当有调用exec加载新程序时
情况一
fork之前,父进程设置的处理方式是忽略 或 默认时exec加载新程序后,忽略和默认设置依然有效。
我们在父进程fork之前把SIGINT设置为忽略。
代码演示:
signal_exec.c 文件代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signal)
{
printf("PID:%d,signal = %d\n",getpid(),signal);
}
int main(int argc,char **argv,char ** environ)
{
pid_t ret = 0;
ret = fork();
signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
//signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
//signal(SIGINT,SIG_DFL); //把终止进程(ctrl+c)设置为默认处理方式
if(ret>0)
{
}
else if(ret == 0)
{
execve("./new_signal",argv,environ);
}
while(1);
return 0;
}
new_signal.c文件内容为:
#include <stdio.h>
#include <stdlib.h>
int main()
{
while(1);
return 0;
}
运行结果为:
我们可以看到自己进程还是在运行,都没有结束。
我们再还原为默认方式:
signal_exec.c 文件代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signal)
{
printf("PID:%d,signal = %d\n",getpid(),signal);
}
int main(int argc,char **argv,char ** environ)
{
pid_t ret = 0;
ret = fork();
//signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
//signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
//signal(SIGINT,SIG_DFL); //把终止进程(ctrl+c)设置为默认处理方式
if(ret>0)
{
}
else if(ret == 0)
{
execve("./new_signal",argv,environ);
}
while(1);
return 0;
}
new_signal.c文件内容为:
#include <stdio.h>
#include <stdlib.h>
int main()
{
while(1);
return 0;
}
运行结果为:
父子进程都不存在了。
情况二
fork之前,父进程设置处理方式是捕获时
新程序的代码会覆盖子进程中原有的父进程的代码,信号捕获函数的代码也会被覆盖,既然捕获函数已经不存在了,捕获处理方式自然也就没有意义了,所以信号的处理方式会被还原为默认处理方式。
终之,如果子进程所继承的信号处理方式是捕获的话,exec加载新程序后,捕获处理方式会被还原为默认处理方式。
代码演示:
我们在父进程fork之前把信号设置为捕获函数:
signal_exec.c 文件代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signal)
{
printf("PID:%d,signal = %d\n",getpid(),signal);
}
int main(int argc,char **argv,char ** environ)
{
pid_t ret = 0;
ret = fork();
//signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
//signal(SIGINT,SIG_DFL); //把终止进程(ctrl+c)设置为默认处理方式
if(ret>0)
{
}
else if(ret == 0)
{
execve("./new_signal",argv,environ);
}
while(1);
return 0;
}
new_signal.c文件内容为:
#include <stdio.h>
#include <stdlib.h>
int main()
{
while(1);
return 0;
}
运行结果为:
当我们按下ctrl+c之后结果为:
我们可以看到父进程调用信号捕获函数,打印出来PID和信号值,子进程已经终止,所以子进程就变成了僵尸进程。
如果想让新程序去捕获某个信号只能在新程序里面独立的设置。
代码演示:
signal_exec.c文件内容为:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void signal_fun(int signal)
{
printf("PID:%d,signal = %d\n",getpid(),signal);
}
int main(int argc,char **argv,char ** environ)
{
pid_t ret = 0;
ret = fork();
//signal(SIGINT,SIG_IGN); //把终止进程(ctrl+c)信号转换为忽略信号
signal(SIGINT,signal_fun); //把终止进程(ctrl+c)信号转换为捕获信号
//signal(SIGINT,SIG_DFL); //把终止进程(ctrl+c)设置为默认处理方式
if(ret>0)
{
}
else if(ret == 0)
{
execve("./new_signal",argv,environ);
}
while(1);
return 0;
}
new_signal.c 文件内容为:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void signal_fun(int signo)
{
printf("new_pro pid:%d, signo=%d\n", getpid(), signo);
}
int main(void)
{
signal(SIGINT, signal_fun);
while(1);
return 0;
}
运行结果为:
父子进程都设置为捕获。
总结
仅fork时子进程会继承父进程fork之前所设置的信号处理方式。
当有exec加载新程序时
(1)子进程继承的处理方式是忽略 或 默认处理方式时,exec新程序后设置依然有效。
(2)如果子进程继承是捕获处理方式时,exec新程序后将被还原为默认处理方式。