SIGCHLD信号
(1)SIGCHLD信号产生的条件1.子进程终止时会向父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,因此父进程仍然不会去回收子进程,需要捕捉处理实现子进程的回收;2.子进程接收到SIGSTOP(19)信号停止时;3.子进程处在停止态,接受到SIGCONT后唤醒时。综上:子进程结束、接收到SIGSTOP停止(挂起)和接收到SIGCONT唤醒时都会向父...
(1)SIGCHLD信号产生的条件
1.子进程终止时会向父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,因此父进程仍然不会去回收子进程,需要捕捉处理实现子进程的回收;
2.子进程接收到SIGSTOP(19)信号停止时;
3.子进程处在停止态,接受到SIGCONT后唤醒时。
综上:子进程结束、接收到SIGSTOP停止(挂起)和接收到SIGCONT唤醒时都会向父进程发送SIGCHLD信号。父进程可以捕捉该信号,来实现对子进程的回收,或者了解子进程所处的状态。
(2)借助SIGCHLD信号回收子进程
子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
//分析例子:结合 17)SIGCHLD 信号默认动作,掌握父使用捕捉函数回收子进程的方法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void sys_err(char *str)
{
perror(str);
exit(1);
}
void do_sig_child(int signo)
{
int status;
pid_t pid;
// if ((pid = waitpid(0, &status, WNOHANG)) > 0) {
while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
if (WIFEXITED(status))
printf("------------child %d exit with %d\n", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("child %d killed by the %dth signal\n", pid, WTERMSIG(status));
}
}
int main(void)
{
pid_t pid;
int i;
//阻塞SIGCHLD
for (i = 0; i < 10; i++) {
if ((pid = fork()) == 0)
break;
else if (pid < 0)
sys_err("fork");
}
if (pid == 0) { //10个子进程
int n = 1;
while (n--) {
printf("child ID %d\n", getpid());
sleep(1);
}
return i+1; //子进程结束状态依次为1、2、••••••、10
} else if (pid > 0) {
struct sigaction act;
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
//解除对SIGCHLD的阻塞
while (1) {
printf("Parent ID %d\n", getpid());
sleep(1);
}
}
return 0;
}
[root@localhost 01_signal_test]# ./sigchild
child ID 11129
child ID 11130
child ID 11131
child ID 11132
child ID 11133
child ID 11134
child ID 11135
child ID 11136
child ID 11137
Parent ID 11128 //表明在此时父进程肯定完成了对信号的注册工作
child ID 11138
------------child 11129 exit with 1
------------child 11130 exit with 2
------------child 11132 exit with 4
------------child 11133 exit with 5
------------child 11131 exit with 3
Parent ID 11128
------------child 11134 exit with 6
------------child 11137 exit with 9
Parent ID 11128
------------child 11136 exit with 8
Parent ID 11128
------------child 11135 exit with 7
Parent ID 11128
------------child 11138 exit with 10 //可见父进程的确完成了对全部子进程的回收
Parent ID 11128
Parent ID 11128
分析如下:
1.在上述程序中,每个子进程在结束之前都会睡眠1s,这是为了留出足够的时间保证在所有子进程结束之前,父进程能够完成对SIGCHLD信号的注册。否则所有子进程都结束了,父进程还没有完成注册,则此时信号都被忽略,从而子进程不能被父进程回收。但是,只要有一个子进程在信号注册后结束,所有子进程都可以被回收,因为使用了while循环回收子进程。除了使用sleep函数来实现这一点外,其实更加高效而又精确的办法(其实在负载较大的 情况下,sleep函数也不能确保能够做到)就是在父进程注册函数之前就将SIGCHLD信号设置为阻塞(通过信号集操作函数加入到屏蔽字中),在注册完成时立即解除对该信号的阻塞即可。
2.不可以将捕捉函数内部的while替换为if。因为在执行捕捉函数期间,发送了多次SIGCHLD信号,未决信号集只是记录了一次,因此下一次再调用捕捉函数时,if只能完成对一个子进程的回收(即使有多个子进程都发了信号,但是只是调用一次捕捉函数)。而while循环则可以对所有结束了的子进程都完成回收。因此对于多个子进程的回收,最好采用循环的方式,不采用if。
3.之前在管道进程间通信,父子进程实现ls | wc -l时(父进程exec函数族执行ls,子进程exec执行wc -l),父进程无法完成对子进程的回收,因为父进程执行exec函数族就离开了,不能回来。但是现在可以利用信号捕捉实现,在父进程调用exec函数族之前就注册SIGCHLD的捕捉函数,但是需要注意以下几点:1.仍然要考虑对SIGCHLD信号的屏蔽和解除屏蔽操作;2.要考虑子进程向父进程发送信号时,父进程已经结束了,此时父进程仍然无法对子进程回收,可以让父进程睡眠等待,让其后结束(但这在实际中是没有意义的,父进程结束了,子进程变为孤儿进程,会被init进程回收,因此实际编程不需要考虑这一点);3.在ls | wc -l这个例子中,由于使用了标准输出的重定向(dup2),因此用户处理函数输出的回收状态信息不能显示在屏幕上,因此在用户处理函数中还需要再次进行重定向,恢复输出重定向为屏幕。
总结:
1.子进程继承了父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending。
2.注意注册信号捕捉函数的位置。
3.应该在fork之前,阻塞SIGCHLD信号。注册完捕捉函数后解除阻塞。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)