top - 11:10:02 up 4 days, 17:47, 1 user, load average: 1.16, 0.93, 1.31
Tasks: 377 total, 1 running, 318 sleeping, 0 stopped, 0 zombie
%Cpu(s): 11.6 us, 7.1 sy, 0.0 ni, 81.1 id, 0.1 wa, 0.0 hi, 0.2 si, 0.0 st
KiB Mem : 8071876 total, 932000 free, 2877168 used, 4262708 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 5102952 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4040 root 20 0 1942132 149508 14712 S 17.6 1.9 1035:11 kubelet
2425 root 20 0 2542804 92828 6644 S 11.8 1.2 362:36.93 dockerd
25414 tux 20 0 51448 4224 3356 R 11.8 0.1 0:00.03 top
3593 root 20 0 615192 340592 18524 S 5.9 4.2 381:13.83 kube-apiserver
3870 root 20 0 10.099g 72136 13912 S 5.9 0.9 150:27.33 etcd
R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正 在等待运行。
D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般 表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。
Z 是 Zombie 的缩写,如果你玩过“植物大战僵尸”这款游戏,应该知道它的意思。它 表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件 而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件 交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有 任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升 高, I 状态的进程却不会。
当然了,上面的示例并没有包括进程的所有状态。除了以上 5 个状态,进程还包括下面这 2 个状态。
第一个是 T 或者 t,也就是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。
向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再 向它发送 SIGCONT 信号,进程又会恢复运行(如果进程是终端里直接启动的,则需要你 用 fg 命令,恢复到前台运行)。
而当你用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪 状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程 的运行。
另一个是 X,也就是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令 中看到它。
不可中断状态
看不可中断状态,这其实是为了保证进程数据与 硬件状态一致,并且正常情况下,不可中断状态在很短时间内就会结束。所以,短时的不 可中断状态进程,我们一般可以忽略。
但如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出 现大量不可中断进程。这时,你就得注意下,系统是不是出现了 I/O 等性能问题。
僵尸进程,这是多进程应用很容易碰到的问题。正常情况下,当一个进程创建了子进 程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源; 而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。
如果父进程没这么做,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。
通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。
一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建,所以这种情况一定要避免。
不可中断状态,表示进程正在跟硬件交互,为了保护进程数据和硬件的一致性,系统不 允许其他进程或中断打断这个进程。进程长时间处于不可中断状态,通常表示系统有 I/O 性能问题。
僵尸进程表示进程已经退出,但它的父进程还没有回收子进程占用的资源。短暂的僵尸 状态我们通常不必理会,但进程长时间处于僵尸状态,就应该注意了,可能有应用程序 没有正常处理子进程的退出。
iowait 分析
一提到 iowait 升高,你首先会想要查询系统的 I/O 情况,这里我们使用dstat命令,观察cpu和I/O的使用情况
# 间隔1s输出10组数据
$ dstat 1 10
You did not select any stats, using -cdngy by default.
--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai stl| read writ| recv send| in out | int csw
12 7 81 0 0| 49k 637k| 0 0 | 0 0 |5928 21k
11 7 82 0 0| 0 0 | 17k 10k| 0 0 |5108 19k
22 15 64 0 0| 0 240k| 48k 56k| 0 0 |8880 37k
6 3 90 0 0| 0 0 | 104k 105k| 0 0 |3598 12k
8 2 90 0 0| 0 7376k| 105k 69k| 0 0 |4601 12k
8 2 90 0 0| 0 24k| 17k 219k| 0 0 |4091 13k
19 14 68 0 0| 0 168k| 48k 65k| 0 0 |8177 32k
11 7 82 0 0| 0 12k|9923B 33k| 0 0 |5259 19k
7 3 90 0 0| 0 56k| 23k 32k| 0 0 |3815 13k
13 4 83 0 0| 0 2112k| 121k 840k| 0 0 |4505 17k
从 dstat 的输出,我们可以看到,每当 iowait 升高(wai)时,磁盘的读请求(read)都 会很大。这说明 iowait 的升高跟磁盘的读请求有关,很可能就是磁盘读导致的。
我们继续在刚才的终端中,运行 top 命令,观察 D 状态的进程,找出D状态进程的PID。
接着,我们查看这些进程的磁盘读写情况。对了,别忘了工具是什么。一般要查看某一个进程的资源使用情况,都可以用我们的老朋友 pidstat,不过这次记得加上 -d 参数,以便 输出 I/O 使用情况。
也可以使用 pidstat观察所有进程的I/O使用情况。
# 间隔1s输出多组数据
$ pidstat -d 1 20
11:34:32 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
11:34:33 AM 0 220 -1.00 -1.00 -1.00 1 jbd2/sda7-8
11:34:33 AM 0 6080 32768.00 0.00 0.00 170 app
从上面中的命令中,找出读写磁盘大的进行分析,下面使用stress进行分析,
然后在终端中运行 strace 命令,并用 -p 参数指定 PID 号
$ strace -p 6082
strace: attach: ptrace(PTRACE_SEIZE, 6082): Operation not permitted
已经是root用户了,为什么还提示权限不足呢,一般遇到这种问题时,我会先检查一下进程的状态是否正常。比如,继续在终端中运行 ps 命令,并使用 grep 找出刚才的 6082 号进程:
$ ps aux | grep 6082
root 6082 0.0 0.0 0 0 pts/0 Z+ 13:43 0:00 [app] <defunct>
果然,进程 6082 已经变成了 Z 状态,也就是僵尸进程。僵尸进程都是已经退出的进程, 所以就没法儿继续分析它的系统调用。
在终端中运行 perf record,持续一会儿(例如 15 秒),然后按 Ctrl+C 退出,再运行 perf report 查看报告:
$ perf record -g
$ perf report
接着,找到我们关注的 app 进程,按回车键展开调用栈,
这个图里的 swapper 是内核中的调度进程,你可以先忽略掉。
app 的确在通过系统调用 sys_read() 读取数据。并且 从 new_sync_read 和 blkdev_direct_IO 能看出,进程正在对磁盘进行直接读,也就是绕 过了系统缓存,每个读请求都会从磁盘直接读,这就可以解释我们观察到的 iowait 升高 了。
既然僵尸进程是因为父进程没有回收子进程的资 源而出现的,那么,要解决掉它们,就要找到它们的根儿,也就是找出父进程,然后在父进程里解决。
可以使用 pstree -aps $pid进行排查,-a 表示输出命令行选项 , s 表示指定进程的父进程。