僵尸与孤儿进程
先来学习两种进程:
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程(僵死进程)。之前我们学习到,fork函数创建子进程的方式。但由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。那么,就有可能产生孤儿进程或僵尸进程。
对于孤儿进程,可由系统回收,暂且不做讨论。
关于僵尸进程。UNⅨ提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号,退出状态,运行时间等)。直到父进程通过wait / waitpid来取时才释放。但这样就导致了问题,如果父进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。所以僵尸进程的产生,应当避免。
SIGCHLD信号
⽤wait和waitpid函数清理僵⼫进程,⽗进程可以阻塞等待⼦进程结束,也可以⾮阻塞地查询是否有⼦进程结束等待清理(也就是轮询的⽅式)。采⽤第⼀种⽅式,⽗进程阻塞了就不能处理⾃⼰的⼯作了;采⽤第⼆种⽅式,⽗进程在处理⾃⼰的⼯作的同时还要记得时不时地轮询⼀下,程序实现复杂。
其实,⼦进程在终⽌时会给⽗进程发SIGCHLD信号。
下面编写代码验证一下⼦进程在终⽌时会给⽗进程发SIGCHLD信号的机制
来看看代码结果:
父进程等待子进程的异步代码实现
SIGCHLD信号的默认处理动作是忽略,⽗进程一般⾃定义SIGCHLD信号的处理函数,这样⽗进程只需专⼼处理⾃⼰的⼯作,不必关⼼⼦进程了,⼦进程终⽌时会通知⽗进程,⽗进程在信号处理函数调⽤wait清理⼦进程即可。
编写代码之前来看一下waitpid函数,该函数是非阻塞式调用。函数调用成功返回子进程id,失败返回-1。
- 函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
- 参数
- pid:要等待的进程的pid
- status:进程的退出信息,包括退出时是否收到信号,进程退出码等。若不关心,可设为NULL。
- options:WNOHANG表示若pid指向的子进程未结束或无子进程可回收,则waitpid返回0,不予以等待;若正常结束,则返回该子进程的id。
- pid:要等待的进程的pid
来看代码:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4
5 void handler(int sig)
6 {
7 pid_t wid;
8 while((wid = waitpid(-1, NULL, WNOHANG)) > 0);
9 {
10 printf("wait child success : %d\n", wid);
11 }
12 printf("child is quit! pid : %d\n", getpid());
13 }
14
15 int main()
16 {
17 pid_t id = fork();
18 signal(SIGCHLD, handler);
19 if(id < 0)
20 {
21 perror("fork");
22 return -1;
23 }
24 else if (id == 0)
25 {
26 printf("child pid : %d ppid : %d\n", getpid(), getppid());
27 sleep(3);
28 exit(1);
29 }
30
31 while(1)
32 {
33 sleep(1);
34 printf("father proc is doing something!\n");
35 }
36
37 return 0;
38 }
运行结果如下:
SINGCHLD置为SIG_IGN
其实⽗进程调⽤sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的⼦进程在终⽌时会⾃动清理掉,不会产⽣僵⼫进程,也不会通知⽗进程。系统默认的忽略动作和⽤户⽤sigaction函数⾃定义的忽略通常是没有区别的,但这是⼀个特例。
代码如下
此⽅法对于Linux可⽤,但不保证在其它UNIX系统上都可⽤。