SIGCHLD信号

1SIGCHLD信号产生的条件

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信号。注册完捕捉函数后解除阻塞。

  • 9
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
SIGCHLD信号是在一个子进程结束运行后,向其父进程发送的一个信号。这个信号可以让父进程知道子进程已经结束,从而可以回收子进程的资源。 当一个子进程结束时,操作系统会向其父进程发送SIGCHLD信号。父进程可以通过调用wait()或waitpid()函数来回收子进程的资源。在父进程中,可以通过设置一个信号处理函数来处理SIGCHLD信号,以便在子进程结束时自动回收资源。 下面是一个使用waitpid()函数来回收子进程资源的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t pid = fork(); if (pid == 0) { // 子进程 printf("child process\n"); exit(0); } else if (pid > 0) { // 父进程 printf("parent process\n"); int status; waitpid(pid, &status, 0); // 回收子进程资源 if (WIFEXITED(status)) { printf("child process exited with code %d\n", WEXITSTATUS(status)); } } else { // fork失败 perror("fork"); exit(1); } return 0; } ``` 在上面的代码中,父进程调用waitpid()函数来等待子进程结束,并回收子进程的资源。如果子进程正常结束,父进程就可以通过WIFEXITED(status)函数判断子进程是否正常结束,并通过WEXITSTATUS(status)函数获取子进程的退出码。 需要注意的是,如果父进程没有调用wait()或waitpid()函数来回收子进程的资源,那么子进程就会成为一个僵尸进程,占用系统资源。因此,在父进程中一定要及时回收子进程的资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值