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/