Linux下的SIGCHLD信号
- 2020 年 3 月 9 日
- 筆記
对于父子进程来说,父进程可以通过wait或这waitpid函数来释放子进程的资源,也可以通过waitpid获得子进程的退出状态。那么这个实现过程是通过阻塞等待,或者不断的轮询。那么这两个操作对于父进程来说都不是理想的,因此可以通过SIGCHLD信号来实现异步的操作。也就是当子进程结束的时候通过SIGCHLD信号告诉父进程,然后父进程再去释放其资源,如果没有收到该信号也不影响父进程的运行。
那么对于SIGCHLD信号来说,只有在以下三个条件中才会向父进程发送SIGCHLD信号:
1. 子进程终止时 2. 子进程接收到SIGSTOP信号停止时 3. 子进程处在停止态,接受到SIGCONT后唤醒时
下面我们通过示例来进一步详细说明,我们实现一个父进程来创建10个子进程,然后通过捕捉信号来实现上述所说的功能。首先我们需要考虑,当我们创建子进程的时候,如果父进程还没有定义捕捉函数子进程就结束了,那么这个子进程就变为了僵尸进程,所以在定义捕捉函数之前需要先将SIGCHLD信号进行阻塞,在定义捕捉函数后再去UNBLOCK,就可以捕捉到子进程的信号了。还有需要注意的是因为SIGCHLD不能叠加,所以如果多个子进程结束了也只有一个SIGCHLD,那么对于这种情况我们在捕捉函数中使用循环来处理多个子进程结束的情况,下面就直接看代码吧:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <signal.h> #include <unistd.h> void sys_err(char *str){ perror(str); exit(1); } void sig_handler(int num){ int status; pid_t pid; /* 由于该信号不能叠加,所以可能同时有多个子进程已经结束 所以循环wait */ while((pid = waitpid(0, &status, WNOHANG)) > 0){ if (WIFEXITED(status)) // 判断子进程的退出状态 是否是正常退出 printf("-----child %d exit with %dn", pid, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) // 判断子进程是否是 通过信号退出 printf("child %d killed by the %dth signaln", pid, WTERMSIG(status)); } } int main(void) { pid_t pid; int i; /* 首先我们先将SIGCHLD信号阻塞 保证在子进程结束前设置父进程的捕捉函数 */ sigset_t nmask, omask; sigemptyset(&nmask); sigaddset(&nmask, SIGCHLD); sigprocmask(SIG_BLOCK, &nmask, &omask); for(i=0;i<10;i++){ pid = fork(); if(pid == 0) break; if(pid == -1) sys_err("fork"); } /* 子进程中循环10s */ if(pid == 0){ for(int i=0;i<10;i++){ printf("This is son pid %dn", getpid()); sleep(1); } return i + 1; } else if(pid > 0){ /* 父进程首先要设置好捕捉函数 */ struct sigaction sig; sig.sa_handler = sig_handler; sigemptyset(&sig.sa_mask); sig.sa_flags = 0; sigaction(SIGCHLD, &sig, NULL); /* 然后再unblock */ sigdelset(&omask, SIGCHLD); sigprocmask(SIG_SETMASK, &omask, NULL); while(1){ printf("Parent pid %dn", getpid()); sleep(1); } } return 0; }
运行结果如下:
