工作中偶尔会碰到僵尸进程问题,有时候同事也会来问,有时候自己也会遗忘搞不清,有必要记录一下。
进程基础
- Linux进程是一棵树,除了根节点,每一个进程都由其父进程创建出来。根节点为init/systemd进程,进程号一般为1.
- 进程是一种系统资源,进程结束后,将由其父进程负责回收其剩余占用的系统资源。
孤儿进程
进程退出时,若还有运行中的子进程,这些子进程就被称为孤儿进程。孤儿进程会被init/systemd收养,也就是孤儿进程的父进程会变成init/systemd。
PS: ubuntu16.04以上,每个用户拥有一个独立的systemd进程,该用户运行的孤儿进程会被相应的systemd收养,故孤儿进程ID不一定是1.
孤儿进程本身不是一种问题。比如我们经常用脚本启动一个后台进程,脚本执行完毕退出,这个后台进程就经历了变孤儿然后被收养的流程。
孤儿进程在开发中造成困扰通常是想要结束进程时,错误的结束了它的父进程,造成了误解。一个典型的例子就是: 在supervisor中配置了shell脚本作为应用的可执行路径,shell脚本启动真正的应用程序,然后用supervisor stop 停止应用却发觉应用没有正确停止,后台残留一个ppid为1的应用程序。
孤儿进程可以正常的被kill杀死。
僵尸进程
进程退出后,在被父进程回收前的这段时间状态即为僵尸状态,每个进程都会经历这个过程。正常情况下,僵尸状态的进程会很快被父进程回收。
但是若父进程太忙,陷入阻塞无法调用wait/waitpid()来清理子进程,那么进程就变成一个可见的僵尸进程存留于系统中。
僵尸进程可以通过ps查看,STAT列值是Z/Z+/Zl/Zl+的就是僵尸进程,同时其COMMAND带有
- 通常情况下,僵尸进程无法通过kill杀死,因为kill只是通知进程退出,但是僵尸进程本身是已调用过exit()后死亡等待回收的进程。
解决的办法是杀死其父进程,让init/systemd来回收僵尸进程的资源。 - 可以被杀死的僵尸进程:
对于多线程的应用,若主线程调用pthread_exit
函数退出,其他线程将保持运行,此时进程无法被回收而显示出僵尸进程状态(Zl/Zl+)。
这时候若用ps a -eLf
查看,可以看到仅有主线程处于僵尸状态。此时可以通过kill <进程ID>
杀死整个进程,而导致僵尸进程被回收。
附: 进程状态码
1 | PROCESS STATE CODES |