什么是僵尸进程?
简单的说,僵尸进程就是子进程先于父进程退出,并且父进程并没有调用wait
系统调用,即使其进程映像中占用的系统资源(比如内存,页表等)都会被释放回收,但这时子进程的进程描述符(PCB
)结构仍然存在于系统中得不到释放,就称这样的进程为僵尸进程(Zombie
)。
为什么会产生僵尸进程?
1 | void exit(int status); |
首先,如果一个进程不成为僵尸进程的话,就要请求内核来回收它最后遗留的PCB
。而内核给出了exit
和wait
系统调用来提供回收进程PCB
的途径。进程到最后或者是主动调用exit
结束运行,或者是通过return
语句使得编译器安插exit
调用代码,目的都是向父进程传递一个“退出状态值”(这个状态值就是exit
函数的那个参数,或者是return
语句后面那个整数),我们就叫它临终遗言吧。这个遗言就保存在进程遗留的PCB
中,等待着父进程调用wait
系统调用去取它的遗言,也就是内核会把这个遗言放在wait
函数的参数所指向的内存,取完之后,内核就会回收这块PCB
资源。这些都是在执行系统调用期间发生的,也就是说调用了这两个系统调用,给了内核机会来回收遗留的PCB
。
如果父进程在派生出子进程后并没有调用wait
等待接收子进程的返回值,这时某个子进程调用exit
退出了,自然没人来接收返回值了。因此其PCB
所占的空间不能释放,没人为其“收尸”,自然就成了“僵 尸”。
僵尸进程有什么危害以及如何处理?
虽然进程的退出状态未被父进程取出前,除了PCB
以外,其他所有资源都可以释放。但由于PCB
不释放,它原本的pid
也会继续被占用,当僵尸进程数量很大时,系统将无可用pid
分配给新进程,从而加载进程失败。
既然产生了僵尸进程,就说明父进程违背了和内核的约定,不想回收子进程。那么,内核也提供了毫不客气的办法来处理,那就是直接kill
掉父进程,在Linux
中可以利用ps –ef
查看所有任务的pid
和ppid
,找到状态为Z
的进程,查看其ppid
,跟着向pid
为ppid
的进程发送kill -9
。
什么是孤儿进程?
在子进程提交给父进程返回值的时候,有这样一种情况,当父进程提前退出时,它所有的子进程还在运行,没有一个执行了exit
,因为它们的生命周期尚未结束,还在运行中,个个都拥有“全尸”(完整的进程映像), 这些进程就称为孤儿进程。这时候所有的子进程会被pid
为1
的init
进程收养,init
进程会成为这些子进程的新父亲,当子进程退出时会由init
负责为其“收尸”。init
进程是所有进程的祖先进程,所有进程最初都是通过它派生出来的。
对系统而言,有了init
进程的“收养“,孤儿进程并没有什么危害,init
会很好地为其善后,因此并不会额外占用资源,它和普通的进程一样,原理上对系统不会产生不良影响。
总结
exit
是由子进程调用的,表面上功能是使子进程结束运行并传递返回值给内核,本质上是内核在幕后会将进程除PCB
以外的所有资源都回收。wait
是父进程调用的,表面上功能是使父进程阻塞自己,直到子进程调用exit
结束运行,然后获得子进程的返回值,本质上是内核在幕后将子进程的返回值传递给父进程并会唤醒父进程,然后将子进程的PCB
回收。