linux僵尸进程和孤儿进程一点记录


工作中偶尔会碰到僵尸进程问题,有时候同事也会来问,有时候自己也会遗忘搞不清,有必要记录一下。

进程基础

  1. Linux进程是一棵树,除了根节点,每一个进程都由其父进程创建出来。根节点为init/systemd进程,进程号一般为1.
  2. 进程是一种系统资源,进程结束后,将由其父进程负责回收其剩余占用的系统资源。

孤儿进程

进程退出时,若还有运行中的子进程,这些子进程就被称为孤儿进程。孤儿进程会被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带有标志。

  1. 通常情况下,僵尸进程无法通过kill杀死,因为kill只是通知进程退出,但是僵尸进程本身是已调用过exit()后死亡等待回收的进程。
    解决的办法是杀死其父进程,让init/systemd来回收僵尸进程的资源。
  2. 可以被杀死的僵尸进程
    对于多线程的应用,若主线程调用pthread_exit函数退出,其他线程将保持运行,此时进程无法被回收而显示出僵尸进程状态(Zl/Zl+)。
    这时候若用ps a -eLf查看,可以看到仅有主线程处于僵尸状态。此时可以通过kill <进程ID>杀死整个进程,而导致僵尸进程被回收。

附: 进程状态码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
PROCESS STATE CODES
Here are the different values that the s, stat and state output
specifiers (header "STAT" or "S") will display to describe the state of
a process:

D uninterruptible sleep (usually IO)
I Idle kernel thread
R running or runnable (on run queue)
S interruptible sleep (waiting for an event to complete)
T stopped by job control signal
t stopped by debugger during the tracing
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z defunct ("zombie") process, terminated but not reaped by
its parent

For BSD formats and when the stat keyword is used, additional
characters may be displayed:

< high-priority (not nice to other users)
N low-priority (nice to other users)
L has pages locked into memory (for real-time and custom IO)
s is a session leader
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads
do)
+ is in the foreground process group

参考文档