僵尸进程
什么是僵尸进程?
Liunx系统中,子进程先于父进程结束后不会消失,而是会在进程表内留下返回信息等待父进程回收,从子进程结束返回到被父进程回收的这段时间它们成为僵尸进程。
设计僵尸进程的目的是什么?
子进程返回结束以后其占用资源需要被回收,内核会将这些子进程转为僵尸进程来释放它们所占有的大部分资源,这些僵尸进程唯一保留的是内核进程表的一条记录,包括子进程ID,终止状态,资源使用数据等信息。子进程转为僵尸进程便于其父进程调用wait()来“认领”这些“僵尸”并做进一步处理。
【疑问:为什么不是内核直接把子进程的资源释放并删除子进程?
查了点stackoverflow后大概理解如下: 有些进程的操作需要通过获取子进程的退出状态来做进一步处理,而僵尸进程这一机制正为这些进程提供了支持,举个例子,你要喝水,你必须得烧水,你创建了一个烧水子进程,烧水子进程结束后系统会将结束信号返回给你让你处理,而不是让烧水壶直接消失。
如果进程不关心自己的子进程是否结束,可以将进程对SIGCHLD信号的处理设置为SIG_IGN,这样进程将忽略子进程的结束信号,子进程结束后将跳过转为僵尸进程直接被系统删除。
至于为何不让内核直接回收子进程,设想一下,一个下属自己做的事情后果最后全由领导来“收摊”的公司会怎么样?】
什么情况会导致僵尸进程的产生?
子进程结束调用exit()后并不会消失,而是会留下一个存放结束信息的数据结构,直到被父进程调用wait()回收之前,子进程都将处于“僵尸”状态留在进程表中。
一般僵尸进程都能被很快清理掉,但也会出现大量僵尸进程的情况, 如僵尸进程的父进程被某事件长期阻塞无法调用wait。
放任僵尸进程不管会有什么危害?
僵尸进程只要不被处理就会永远在内核进程表中保留一个位置,如果存在大量此类僵尸进程,它们势必会填满内核进程表,从而阻碍新进程的创建。因此僵尸进程的及时清理是有必要的,尤其对于频繁创建大量子进程的程序,如网络服务器。
如何杀死僵尸进程?
1.父进程周期性调用wait()或者waitpid()轮询来回收这些进程。
2.杀死父进程,让init进程来回收成为“孤儿”的僵尸进程
(最常见的场景就是某一软件功能导致软件卡死时在任务管理器杀死软件,或者电脑死机时重启电脑)
注:无法通过kill命令杀死僵尸进程。
扩展:
父进程是无法预知子进程何时终止的,如果父进程调用不带WNOHANG标志的wait()/waitpid()而没有捕获到已终止的子进程, 则父进程将会阻塞,效率较低;如果父进程周期性地用带WHOHANG标志的的waitpid() 进行非阻塞式轮询,则易造成CPU资源的浪费,增加应用程序设计的复杂度。
为规避上述问题,可使用SIGCHLD信号的处理程序,子进程终止时系统将会向其父进程发送SIGCHLD信号,这样就不需要父进程主动去wait()来回收进程。
【也就是把中断权从父进程交给了子进程自己来决定】
但是SIGCHLD信号是异步请求,存在若多个子进程同时退出父进程不一定都能捕获的问题,解决方案是在SIGCHLD处理程序内部进行带WNOHANG标志waitpid()的轮询直到再无终止的子进程需要处理为止。
【所以直接轮询不香吗,还是说SIGCHLD一定程度上缓解了waitpid()的轮询量?】
补充:waitpid() 相比wait() 除了要指定进程id外,还有一个标志位,可使调用者阻塞或者不阻塞:
#include <sys/wait.h>
#pid_t wait (int * statloc);
#pid_t waitpid (pid_t pid, int * statloc, int options);
WNOHANG 使用该标志,若waitpid() 指定的子进程并没有结束,则立即返回,不会阻塞,解决了wait()的阻塞问题,可用于实现对子进程的非阻塞式轮询