Linux

Linux僵尸进程小结

一僵尸进程的现象

通过top命令输出结果中的第2行最后1个字段:zombie显示的相关进程信息,前面的数字表示系统当前有多少个僵尸进程。以及输出进程信息里看到进程状态S列为Z的进程,也表示僵尸进程信息。如下:

[root@node-1 ~]# top
top - 15:06:43 up 31 days,  2:29,  2 users,  load average: 0.82, 0.33, 0.20
Tasks: 177 total,   4 running, 167 sleeping,   0 stopped,   6 zombie
%Cpu(s):  2.7 us, 51.7 sy,  0.0 ni, 42.0 id,  3.5 wa,  0.0 hi,  0.2 si,  0.0 st
KiB Mem :  3881352 total,   492036 free,  1436204 used,  1953112 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  1750232 avail Mem 
​
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                  
20083 root      20   0       0      0      0 Z  28.8  0.0   0:01.68 app                                                                                                                                                      
20082 root      20   0       0      0      0 Z  27.8  0.0   0:01.67 app                                                                                                                                                      
20065 root      20   0       0      0      0 Z  14.9  0.0   0:01.79 app                                                                                                                                                      
20064 root      20   0       0      0      0 Z  14.2  0.0   0:01.77 app                                                                                                                                                      
20098 root      20   0   70040  65536     44 R   6.3  1.7   0:00.19 app                                                                                                                                                      
20099 root      20   0   70040  65536     44 R   6.3  1.7   0:00.19 app                                                                                                                                                      
 1039 root      20   0 1748860  70876  16388 S   5.0  1.8   2091:04 kubelet                                                                                                                                                  
 1032 root      20   0 1164324  57500  10684 S   1.0  1.5 768:29.58 dockerd-current                                                                                                                                          
 9917 root      20   0  885160  72892   9664 S   1.0  1.9 246:18.85 agent                                                                                                                                                    
    9 root      20   0       0      0      0 R   0.3  0.0  54:57.40 rcu_sched                                                                                                                                                
   30 root      20   0       0      0      0 S   0.3  0.0   1:26.06 kswapd0                                                                                                                                                  
  706 root      20   0  477428   5036   2376 S   0.3  0.1  58:27.33 NetworkManager                                                                                                                                           
 1345 root      20   0  838576   8624   1908 S   0.3  0.2  68:30.79 docker-containe                                                                                                                                          
 2540 root      20   0 1343456  15040   3860 S   0.3  0.4  61:38.02 flanneld                                                                                                                                                 
 5555 root      20   0 1932124 197328   3716 S   0.3  5.1  28:23.63 java                 

系统当前有6个僵尸进程。从进程状态列S看到有4个状态为Z的进程,它们也是僵尸进程。

二僵尸进程的成因

在Linux系统里,当一个进程创建了子进程之后:

  • 它需要通过系统调用wait()或者waitpid()来等待子进程的结束,然后回收子进程的资源,比如文件描述符file descriptor以及pid信息。
  • 或者,父进程应当注册SIGCHLD信号的处理函数来回收它的子进程的资源。因为,当子进程执行结束之后,它会向父进程发送SIGCHLD信号。

如果父进程并没有执行上述操作,或者即使父进程执行了上面的操作,在某些特殊情况下,子进程执行速度过快,很快就结束了,父进程还没来及去回收子进程的资源。那么,子进程就成为了僵尸进程。

三僵尸进程的本质

僵尸进程本质上讲,就是子进程已经执行结束了,但是父进程并没有回收它的资源。这样,子进程就成了僵尸进程。

四僵尸进程的危害

系统中的僵尸进程越来越多,会导致file descriptor以及pid系统资源不能及时被回收而导致浪费,最终导致这些资源不足而带来的问题,比如pid不够,进而导致系统中不能创建或者启动新的进程。

五如何查看僵尸进程

除了前面我们可以通过top命令的输出来查看到僵尸进程的信息,还可以通过

  • ps -ef|grep defunct来查看僵尸进程:
[root@node-1 ~]# ps -ef|grep defunct|more
root     20047 20016  0 15:06 pts/5    00:00:02 [app] <defunct>
root     20048 20016  0 15:06 pts/5    00:00:02 [app] <defunct>
root     20064 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20065 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20082 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20083 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20098 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20099 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20115 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20116 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20139 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20140 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20156 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20157 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20172 20016  0 15:07 pts/5    00:00:01 [app] <defunct>
...
  • ps -e -o pid,ppid,stat|grep Z
[root@node-1 ~]# ps -e -o pid,ppid,stat|grep Z|more
20047 20016 Z+
20048 20016 Z+
20064 20016 Z+
20065 20016 Z+
20082 20016 Z+
20083 20016 Z+
20098 20016 Z+
20099 20016 Z+
20115 20016 Z+
20116 20016 Z+
20139 20016 Z+
20140 20016 Z+
20156 20016 Z+
20157 20016 Z+
20172 20016 Z+
20173 20016 Z+
20190 20016 Z+
...

这里的-o选项中的pid和ppid分别是进程的pid和父进程的pid;stat表示进程的状态,其中Z表示进程是僵尸进程,+号表示进程组的信息。这里,我们看到系统中的僵尸进程都是pid为20016这个进程的子进程。

随着时间的推移,我这里的当前系统中的僵尸进程会越来越多,通过该命令进行统计的僵尸进程和top看到的僵尸进程数吻合:

#terminal 1:
[root@node-1 ~]# ps -ef|grep defunct|grep -v grep |wc -l
1040
[root@node-1 ~]# ps -e -o pid,ppid,stat|grep Z|wc -l
1040
[root@node-1 ~]# 
​
#terminal 2:
[root@node-1 ~]# top
top - 15:49:48 up 31 days,  3:12,  2 users,  load average: 0.13, 0.13, 0.17
Tasks: 1209 total,   1 running, 168 sleeping,   0 stopped, 1040 zombie
%Cpu0  :  4.3 us, 15.4 sy,  0.0 ni, 79.9 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
%Cpu1  :  4.7 us, 19.3 sy,  0.0 ni, 75.4 id,  0.0 wa,  0.0 hi,  0.7 si,  0.0 st
KiB Mem :  3881352 total,   619232 free,  1325160 used,  1936960 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  1850244 avail Mem 
​
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                  
29297 root      20   0       0      0      0 Z  13.3  0.0   0:01.84 app                                                                                                                                                      
29296 root      20   0       0      0      0 Z  11.4  0.0   0:01.83 app                                                                                                                                                      
 1039 root      20   0 1748860  72784  18140 S   6.5  1.9   2093:45 kubelet                                                                                                                                                  
 1032 root      20   0 1164324  56992  10684 S   1.6  1.5 769:22.21 dockerd-current                                                                                                                                          
21594 root      20   0  163152   3312   1540 R   1.0  0.1   0:19.42 top          

六如何处理僵尸进程

通常,系统中某个进程出现异常,我们会通过kill -9 pid来杀死进程。但是,这对于僵尸进程来说是无效的,如下:

[root@node-1 ~]# ps -ef|grep defunct|head
root     20047 20016  0 15:06 pts/5    00:00:02 [app] <defunct>
root     20048 20016  0 15:06 pts/5    00:00:02 [app] <defunct>
root     20064 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20065 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20082 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20083 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20098 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20099 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20115 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
root     20116 20016  0 15:06 pts/5    00:00:01 [app] <defunct>
[root@node-1 ~]# ps -eo pid,ppid,stat|grep Z|head
20047 20016 Z+
20048 20016 Z+
20064 20016 Z+
20065 20016 Z+
20082 20016 Z+
20083 20016 Z+
20098 20016 Z+
20099 20016 Z+
20115 20016 Z+
20116 20016 Z+
[root@node-1 ~]# ps -ef|grep 20047
root       350 19268  0 16:06 pts/0    00:00:00 grep --color=auto 20047
root     20047 20016  0 15:06 pts/5    00:00:02 [app] <defunct>
[root@node-1 ~]# 

系统中,当前pid=20047的进程是僵尸进程,我们尝试kill它:

[root@node-1 ~]# ps -ef|grep 20047
root       350 19268  0 16:06 pts/0    00:00:00 grep --color=auto 20047
root     20047 20016  0 15:06 pts/5    00:00:02 [app] <defunct>
[root@node-1 ~]# kill -9 20047
[root@node-1 ~]# ps -ef|grep 20047
root       459 19268  0 16:06 pts/0    00:00:00 grep --color=auto 20047
root     20047 20016  0 15:06 pts/5    00:00:02 [app] <defunct>
[root@node-1 ~]# 

为什么呢?这就要说回到僵尸进程的本质了,僵尸进程其实是一个已经死掉的进程,但是由于某些原因它的父进程没有回收它的资源,即它已经脱离了父进程的管控范围。我们这里再次给它发送SIGKILL的信号,可是它还是不会被它的父进程管控啊,它的资源依然不会被回收哇

正确的方式是,找到它的父进程,并杀死它的父进程,这样当它的父进程被杀死之后,之前由这个父进程创建出来的所有子进程都将被系统1号进程init接管,最终由1号进程回收这些僵尸进程的系统资源。

大白话来讲就是,不是管不了你这些僵尸进程嘛(“死孩子”调皮捣蛋),你们的父进程(“亲爹”不管不顾)管不了你,那我把你们的父进程干掉,你们这些无爹看管的“野死孩子”被过继给”老祖宗”(系统的1号进程init或者是systemd)来接管。

[root@node-1 ~]# ps -ef|grep defunct|grep -v grep |wc -l
1734
[root@node-1 ~]# kill -9 20016
[root@node-1 ~]# ps -ef|grep defunct|grep -v grep |wc -l
0
[root@node-1 ~]# 

至此,系统中的僵尸进程全部被清理了。

七模拟僵尸进程的docker容器信息

docker run --privileged --name=app -itd feisky/app:iowait-fix1

案例场景来源于倪朋飞老师的Linux 性能优化专栏第8篇,08 | 案例篇:系统中出现大量不可中断进程和僵尸进程怎么办?(下)。

构建僵尸进程的C语言源代码:https://github.com/feiskyer/linux-perf-examples/blob/master/high-iowait-process/app-fix1.c

八参考链接

https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/

留言