CPU 使用率的类型。除了上一节提到的用户 CPU 之外,它还包括系统 CPU(比如上下文切换)、等待 I/O 的 CPU(比如等待磁盘的响应)以及中断 CPU(包 括软中断和硬中断)等。
等待 I/O 的 CPU 使用率(以下简称为 iowait)升高,也是最常见的一个服务器性能问题。
进程状态
当 iowait 升高时,进程很可能因为得不到硬件的响应,而长时间处于不可中断状态。从 ps 或者 top 命令的输出中,你可以发现它们都处于 D 状态,也就是不可中断状态 (Uninterruptible Sleep)。
top 和 ps 是最常用的查看进程状态的工具,我们就从 top 的输出开始。下面是一个 top 命令输出的示例,S 列(也就是 Status 列)表示进程的状态。从这个示例里,你可以看到 R、D、Z、S、I 等几个状态,它们分别是什么意思呢?
|
|
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的使用情况
|
|
从 dstat 的输出,我们可以看到,每当 iowait 升高(wai)时,磁盘的读请求(read)都 会很大。这说明 iowait 的升高跟磁盘的读请求有关,很可能就是磁盘读导致的。
我们继续在刚才的终端中,运行 top 命令,观察 D 状态的进程,找出D状态进程的PID。
接着,我们查看这些进程的磁盘读写情况。对了,别忘了工具是什么。一般要查看某一个进程的资源使用情况,都可以用我们的老朋友 pidstat,不过这次记得加上 -d 参数,以便 输出 I/O 使用情况。
也可以使用 pidstat观察所有进程的I/O使用情况。
|
|
从上面中的命令中,找出读写磁盘大的进行分析,下面使用stress进行分析,
然后在终端中运行 strace 命令,并用 -p 参数指定 PID 号
|
|
已经是root用户了,为什么还提示权限不足呢,一般遇到这种问题时,我会先检查一下进程的状态是否正常。比如,继续在终端中运行 ps 命令,并使用 grep 找出刚才的 6082 号进程:
|
|
果然,进程 6082 已经变成了 Z 状态,也就是僵尸进程。僵尸进程都是已经退出的进程, 所以就没法儿继续分析它的系统调用。
在终端中运行 perf record,持续一会儿(例如 15 秒),然后按 Ctrl+C 退出,再运行 perf report 查看报告:
|
|
接着,找到我们关注的 app 进程,按回车键展开调用栈,
这个图里的 swapper 是内核中的调度进程,你可以先忽略掉。
app 的确在通过系统调用 sys_read() 读取数据。并且 从 new_sync_read 和 blkdev_direct_IO 能看出,进程正在对磁盘进行直接读,也就是绕 过了系统缓存,每个读请求都会从磁盘直接读,这就可以解释我们观察到的 iowait 升高 了。
僵尸进程
既然僵尸进程是因为父进程没有回收子进程的资 源而出现的,那么,要解决掉它们,就要找到它们的根儿,也就是找出父进程,然后在父进程里解决。
可以使用 pstree -aps $pid进行排查,-a 表示输出命令行选项 , s 表示指定进程的父进程。