子进程是父进程调用fork生成的,如果父进程在子进程之前结束,那么对于对应的父进程都已经结束的所有子进程,它们的父进程都会变成init进程,也就是这些子进程被init进程领养;在一个进程结束时,内核会逐个检查所有的活动进程,来判断它是否是正要结束的子进程,如果是,就会将该进程的父进程ID改为1,也就是init这个进程,如此就保证了每个进程都有一个父进程
如果子进程在父进程之前结束,子进程会将其结束状态返回给父进程;内核为每个结束的子进程保存了一定的信息,所以当子进程的父进程在调用wait或者waitpid的时候,是可以得到这些状态信息的,比如进程ID,结束状态等
进程终止后,内核会释放该进程所使用的所有存储区,关闭所有打开的文件,而一个已经终止但是其父进程尚未对其进行善后处理,也就是没有获取终止子进程的有关信息,释放它仍占用的资源,这样的一个已经终止的进程就是僵尸进程
前面的例子,父进程打印1次,而子进程打印3次,只需要将他们打印执行的次数交换一下,即父进程打印3次,子进程打印1次,并且不进行任何wait,在执行这个程序过程中,不停通过ps来查看进程,就会看到僵尸进程
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int main(){ pid_t pid; char *message; int n; if ((pid = fork()) < 0) { printf("fork failed...\n"); exit(1); } else if (pid == 0){ message = "child now..."; n = 1; } else { message = "parent now..."; n = 3; } for(; n > 0; n--){ puts(message); sleep(1); } return 0; }
编译运行,与此同时执行ps命令:
$ ./a.exe parent now... child now... $ ps aux PID PPID PGID WINPID TTY UID STIME COMMAND 8244 3964 8244 4704 pty1 1000 14:09:50 /usr/bin/ps 3964 4604 3964 5092 pty1 1000 12:03:03 /usr/bin/bash 5048 1 5048 5048 ? 1000 11:42:52 /usr/bin/mintty 6960 2216 2216 6960 pty0 1000 14:09:49 /home/lihui/a <defunct> 2216 4372 2216 6432 pty0 1000 14:09:49 /home/lihui/a 4372 5048 4372 4228 pty0 1000 11:42:52 /usr/bin/bash 4604 1 4604 4604 ? 1000 12:03:03 /usr/bin/mintty
由于父进程并没有wait等函数,因此子进程结束之后变成了僵尸进程,在父进程还没有完全打印完的时候,可以看到一个defunct的进程,父进程此刻没有为其善后,可是过了一会儿,再用ps命令查看,僵尸进程没了,想要看更直观更明显效果的话,可以直接父进程里面来一个sleep N秒,这样僵尸进程就会一直存在,直到父进程苏醒
$ ./a.exe parent now... child now... parent now... parent now... $ ps aux PID PPID PGID WINPID TTY UID STIME COMMAND 3964 4604 3964 5092 pty1 1000 12:03:03 /usr/bin/bash 5048 1 5048 5048 ? 1000 11:42:52 /usr/bin/mintty I 4372 5048 4372 4228 pty0 1000 11:42:52 /usr/bin/bash 7000 3964 7000 4772 pty1 1000 14:37:54 /usr/bin/ps 4604 1 4604 4604 ? 1000 12:03:03 /usr/bin/mintty
但是由于父进程并没有添加wait和waitpid,因此父进程不会主动去回收子进程资源善后,我觉得是父进程结束之后,子进程被init收养,最终结束的~!