1. SIGCHLD产生的条件
实际上,在子进程结束的时候,会产生一个SIGCHLD信号,信号描述如下,根据man手册可以知道,子进程结束运行,其父进程会收到SIGCHLD信号,该信号的默认处理动作是忽略。
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCHLD信号产生的条件主要有以下几个:
- 子进程终止时;
- 子进程接收到SIGSTOP信号停止时;
- 子进程处在停止态,接受到SIGCONT后唤醒时;
既然子进程在退出或暂停的时候会发送SIGCHLD信号,那么我们就可以利用该信号,捕捉该信号,并在捕捉函数中完成子进程状态的回收,这样就不用使用wait函数去等待了。
2. 使用SIGCHLD信号完成子进程回收
/************************************************************ >File Name : sigchld_test.c >Author : Mindtechnist >Company : Mindtechnist >Create Time: 2022年05月23日 星期一 14时20分42秒 ************************************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> void mcatch(int signo) { printf("catch signal: %d\n", signo); pid_t pid = waitpid(-1, NULL, WNOHANG); if(pid > 0) { printf("recycle process: %d\n", pid); } } void mcatch2(int signo) { printf("catch signal: %d\n", signo); pid_t pid; while((pid = waitpid(-1, NULL, WNOHANG)) > 0) { /*如果多个子进程同时结束(主控制中没有睡眠sleep),也能保证回收*/ printf("recycle process: %d\n", pid); } } int main(void) { int i = 0; pid_t pid; /*先屏蔽SIGCHLD信号,否则的话,假如在注册信号捕捉函数之前子进程就已经结束的话, 信号捕捉函数就什么也捕捉不到了,会产生僵尸进程*/ sigset_t mset, old; sigemptyset(&mset); sigaddset(&mset, SIGCHLD); sigprocmask(SIG_BLOCK, &mset, &old); for(i = 0; i < 10; i++) { pid = fork(); if(pid == 0) { break; } } if(i == 10) /*父进程*/ { struct sigaction mact; mact.sa_flags = 0; sigemptyset(&mact.sa_mask); mact.sa_handler = mcatch; sigaction(SIGCHLD, &mact, NULL); /*恢复原来的屏蔽设置*/ sigprocmask(SIG_SETMASK, &old, NULL); while(1) { sleep(1); } } else /*子进程*/ { printf("child: %d\n", getpid()); sleep(i); /*如果没有睡眠,可能多个子进程同时结束 这样使用mcatch的时候会不稳定,可能 产生僵尸进程,使用mcatch2会更好*/ } return 0; }
根据这个例子,我们可以得到下面几点注意事项
- 子进程继承了父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending;
- 应该在fork之前,阻塞SIGCHLD信号,注册完捕捉函数后解除阻塞。这样做的目的是,假如在注册信号捕捉函数之前子进程就已经结束的话,信号捕捉函数就什么也捕捉不到了,会产生僵尸进程;
3. 中断系统调用
系统调用可分为两类:慢速系统调用和其他系统调用。
- 慢速系统调用:可能会使进程永远阻塞的一类系统调用。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期),也可以设定系统调用是否重启。比如,read、write、pause、wait等。
- 其他系统调用:getpid、getppid、fork等
通过pause系统调用,分析慢速系统调用,慢速系统调用被中断的相关行为,实际上就是pause的行为,比 如read
慢速系统调用被中断的相关行为,实际上就是pause的行为,比 如read
- 想中断pause,首先信号不能被屏蔽;
- 信号的处理方式必须是捕捉 (默认动作、忽略都不可以);
- 中断后返回-1, 设置errno为EINTR,表示被信号中断;
可以通过修改sa_flags参数来设置被信号中断后系统调用是否重启:SA_INTERRURT不重启, SA_RESTART重启。sa_flags还有很多可选参数,适用于不同情况,比如:捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号,可将sa_flags设置为SA_NODEFER,除非sa_mask中包含该信号,等等。