孤儿进程:父进程先于子进程结束(遇到return、exit、异常终止等情况时),则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。可以通过getppid函数来查看孤儿进程的父进程ID,即init进程的ID,init进程的ID具体是多少取决于操作系统对进程的调度,其值是不确定的。在操作系统中,init进程也不止一个,可通过ps aux详细查看。
僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
// orphan.c //shell产生子进程执行该程序
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main(void)
{ pid_t pid;pid = fork(); //创建子进程if (pid == 0) {while (1) {printf("I am child, my parent pid = %d\n", getppid());sleep(1); //子进程一直运行}} else if (pid > 0) {printf("I am parent, my pid is = %d\n", getpid());sleep(9);printf("------------parent going to die------------\n"); //父进程先于子进程结束,子进程变为孤儿进程} else {perror("fork");return 1; //等价于exit(1),都是结束进程,且进程结束状态置1表示出错}return 0;
}
[root@localhost wait]# ./orphan
I am parent, my pid is = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
I am child, my parent pid = 26900
------------parent going to die------------ //父进程正常结束
I am child, my parent pid = 1 //子进程为孤儿进程,被init进程领养,即其父进程为init进程
[root@localhost wait]# I am child, my parent pid = 1 //父进程结束,shell进程收回前台,等待命令交互
I am child, my parent pid = 1 //子进程一直执行,为孤儿进程
I am child, my parent pid = 1
I am child, my parent pid = 1
I am child, my parent pid = 1
I am child, my parent pid = 1
//zoom.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(void)
{pid_t pid;pid = fork(); if (pid == 0) {printf("---child, my parent= %d, going to sleep 10s\n", getppid());sleep(10);printf("-------------child die--------------\n"); //子进程先正常结束} else if (pid > 0) {while (1) {printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);sleep(1);} //父进程一直运行} else {perror("fork");return 1;}return 0;
}
[root@localhost wait]# ./zoom
I am parent, pid = 27152, myson = 27153
---child, my parent= 27152, going to sleep 10s
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
-------------child die-------------- //子进程死亡
I am parent, pid = 27152, myson = 27153 //父进程一直运行,一直占据前台,shell进程无法获得前台交互 且子进程结束后,父进程没有对子进程残留在内核中的PCB进行回收,从而子进程变为僵尸进程。
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
I am parent, pid = 27152, myson = 27153
[root@localhost wait] # ps aux
root 27152 0.0 0.0 4160 352 pts/1 S+ 03:11 0:00 ./zoom
root 27153 0.0 0.0 0 0 pts/1 Z+ 03:11 0:00 [zoom] <defunct>
root 27155 0.0 0.0 0 0 ? R 03:12 0:00 [kworker/3:0]
root 27163 0.0 0.0 107892 360 ? S 03:12 0:00 sleep 60
root 27164 0.0 0.0 123360 1384 pts/0 R+ 03:12 0:00 ps aux
[root@localhost wait]# kill 27152
由上可以看出,父进程27152,为S+,表示该进程在后台运行(注意,ps aux命令是在另一个终端执行的,因此相对于另一个shell终端,父进程在后台运行);子进程27153,为Z+,表示僵尸进程,说明该进程终止后,其残留在内核的PCB资源没有被父进程回收;而ps aux这个命令的进程为R+,表示在前台运行,即就在pts/0设备终端的前台运行。而前两个进程属于pts/1设备。[zoom] <defunct> defunct表示已故的,不复存在的,但其痕迹仍然残留在内核中,占用内存资源,因此需要做到及时对僵尸进程回收和清除。
总结:在每个进程退出的时候, 内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 另外,如果父进程一直不结束(不终止),在不调用wait或waitpid的情况下,其子进程结束后会变为僵尸进程,残留在内核中,此时若父进程结束了,那么这些僵尸进程因为没有了父进程,就会变为孤儿进程被init进程领养,init进程就会对这些僵尸进程进行回收,然后清除。因此,父进程结束了,其子进程会被回收。孤儿进程结束后,也会被init进程回收。如果要回收一个进程,除了通过其父进程调用wait或waitpid函数外,还可以杀死其父进程,让其变为孤儿进程,被init进程回收。
系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。可以fork两次, 父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。