1. 概述
在我的职业生涯中,确实还没有碰到过僵尸进程。不过该学习的还是要学一下,万一以后碰到了呢?
重温一下常见的进程状态:
top命令如下
top - 13:06:10 up 12:02, 2 users, load average: 0.00, 0.06, 0.76
Tasks: 109 total, 1 running, 59 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8070116 total, 711064 free, 6883448 used, 475604 buff/cache
KiB Swap: 4194300 total, 4175856 free, 18444 used. 938196 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 159956 6940 4784 S 0.0 0.1 0:04.48 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
7 root 20 0 0 0 0 S 0.0 0.0 0:05.34 ksoftirqd/0
8 root 20 0 0 0 0 I 0.0 0.0 0:02.04 rcu_sched
9 root 20 0 0 0 0 I 0.0 0.0 0:00.00 rcu_bh
10 root rt 0 0 0 0 S 0.0 0.0 0:00.28 migration/0
11 root rt 0 0 0 0 S 0.0 0.0 0:00.06 watchdog/0
12 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/0
- R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
- D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。
- Z 是 Zombie 的缩写,如果你玩过“植物大战僵尸”这款游戏,应该知道它的意思。它表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
- S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
- I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。
另外还有两种状态
- T 或者 t,也就是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。
在 Unix 系统中,"T" 或 "t" 代表进程处于暂停或跟踪状态。发送 SIGSTOP 信号给进程,它会进入暂停状态(Stopped);发送 SIGCONT 信号,它会恢复运行。如果是用调试器(如 gdb)调试,进程在触发断点后会进入跟踪状态,这也是一种暂停状态,但允许使用调试器进行控制和跟踪进程。
- X,也就是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。
不可中断一般是在等待I/O的情况较多,大部分场景下因为I/O速度远比内存慢。
再看僵尸进程,这是多进程应用很容易碰到的问题。正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。
一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建,所以这种情况一定要避免。
2. 案例
环境准备:
- 操作系统:Ubuntu 18.04
- 机器配置:2 CPU,8GB 内存
- 预先安装 docker、sysstat、dstat 等工具,如 apt -y install docker.io dstat sysstat
这里,dstat 是一个新的性能工具,它吸收了 vmstat、iostat、ifstat 等几种工具的优点,可以同时观察系统的 CPU、磁盘 I/O、网络以及内存使用情况。
执行案例
$ docker run --privileged --name=app -itd feisky/app:iowait
检查启动是否成功
root@calvin:~# ps aux | grep /app
root 32333 0.0 0.0 4512 1492 pts/0 Ss+ 13:50 0:00 /app
root 32414 0.0 0.8 70052 65840 pts/0 D+ 13:51 0:00 /app
root 32415 0.0 0.8 70052 65840 pts/0 D+ 13:51 0:00 /app
root 32417 0.0 0.0 13140 1088 pts/0 S+ 13:51 0:00 grep --color=auto /app
在这个界面中,多个 app 进程的状态分别是 "Ss+" 和 "D+"。其中:
- S 表示可中断睡眠状态,D 表示不可中断睡眠状态。
- s 表示进程是会话的领导进程。
- + 表示该进程属于前台进程组。
进程组是一组相互关联的进程,会话则是共享同一控制终端的一个或多个进程组。例如,通过 SSH 登录时,会打开一个控制终端(TTY),这个控制终端就是一个会话,里面的进程组根据是否在前台或后台运行而不同。
明白了这些,我们再用 top 看一下系统的资源使用情况(top之后按一次键盘 1):
root@calvin:~# top
top - 14:06:27 up 13:02, 2 users, load average: 2.00, 1.47, 0.70
Tasks: 494 total, 1 running, 61 sleeping, 0 stopped, 384 zombie
%Cpu0 : 0.0 us, 1.7 sy, 0.0 ni, 78.1 id, 20.2 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 2.6 sy, 0.0 ni, 93.7 id, 3.6 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8070116 total, 622640 free, 6660536 used, 786940 buff/cache
KiB Swap: 4194300 total, 4175856 free, 18444 used. 1156660 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
307 root 20 0 0 0 0 Z 1.7 0.0 0:00.05 app
308 root 20 0 0 0 0 Z 1.7 0.0 0:00.05 app
7 root 20 0 0 0 0 S 0.3 0.0 0:05.44 ksoftirqd/0
1 root 20 0 159956 6940 4784 S 0.0 0.1 0:04.50 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
8 root 20 0 0 0 0 I 0.0 0.0 0:02.09 rcu_sched
9 root 20 0 0 0 0 I 0.0 0.0 0:00.00 rcu_bh
10 root rt 0 0 0 0 S 0.0 0.0 0:00.28 migration/0
11 root rt 0 0 0 0 S 0.0 0.0 0:00.07 watchdog/0
- 先看第一行的平均负载( Load Average),过去 1 分钟、5 分钟和 15 分钟内的平均负载在依次减小,说明平均负载正在升高;而 1 分钟内的平均负载已经达到系统的 CPU 个数,说明系统很可能已经有了性能瓶颈。
- 再看第二行的 Tasks,有 1 个正在运行的进程,但僵尸进程比较多,而且还在不停增加,说明有子进程在退出时没被清理。
- 接下来看两个 CPU 的使用率情况,用户 CPU 和系统 CPU 都不高,但 iowait 分别是 20.2% 和 3.6%,好像有点儿不正常。
- 最后再看每个进程的情况, CPU 使用率最高的进程只有 1.7%,看起来并不高;但有两个进程处于 Z(前一秒还是D) 状态,它们可能在等待 I/O,但光凭这里并不能确定是它们导致了 iowait 升高。
到此我们可以得出两个结论:
- iowait 太高了,导致系统的平均负载升高,甚至达到了系统 CPU 的个数。
- 僵尸进程在不断增多,说明有程序没能正确清理子进程的资源。
未完待续。。。