系统中出现大量不可中断进程和僵尸进程怎么办?
进程状态
通过top命令,我们可以看到进程的状态(S列)
top
top - 19:27:57 up 365 days, 25 min, 0 users, load average: 0.06, 0.05, 0.01
Tasks: 134 total, 1 running, 90 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.5 sy, 0.0 ni, 99.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 3514764 total, 137136 free, 1193824 used, 2183804 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1965284 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
11399 root 20 0 1114904 150016 19564 S 1.0 4.3 537:19.31 YDService
9951 ubuntu 20 0 1142180 261916 36424 S 0.7 7.5 0:44.05 node
15178 ubuntu 20 0 41144 3700 3032 R 0.3 0.1 0:00.06 top
30256 root 20 0 64552 11232 3632 S 0.3 0.3 41:52.93 barad_agent
30257 root 20 0 588648 20356 4840 S 0.3 0.6 316:58.21 barad_agent
1 root 20 0 225544 7596 4920 S 0.0 0.2 19:35.77 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:13.93 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
进程的状态共有以下几种:
-
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 的缩写,表示进程处于暂停或者跟踪状态。向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再向它发送 SIGCONT 信号,进程又会恢复运行。而当你用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程的运行。
-
X,也就是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。
不可中断状态
先看不可中断状态,这其实是为了保证进程数据与硬件状态一致,并且正常情况下,不可中断状态在很短时间内就会结束。所以,短时的不可中断状态进程,我们一般可以忽略。
但如果系统或硬件发生了故障,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时,你就得注意下,系统是不是出现了 I/O 等性能问题。
僵尸进程
僵尸进程,这是多进程应用很容易碰到的问题。正常情况下,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册 SIGCHLD 信号的处理函数,异步回收资源。
如果父进程没这么做,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。
通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。
大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建,所以这种情况一定要避免。
iowait 分析
我们先用dstat分析一下数据
# 间隔1秒输出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
0 0 99 0 0|3880B 47k| 0 0 | 0 0 | 379 1240
1 2 98 0 0| 0 0 |1332B 2039B| 0 0 | 884 1683
2 3 95 0 0| 0 0 |1158B 3065B| 0 0 |1066 2113
2 1 96 1 0| 0 384k|2548B 3323B| 0 0 |1018 1991
0 0 100 0 0| 0 0 | 364B 380B| 0 0 | 616 1166
1 0 99 0 0| 0 0 | 986B 1016B| 0 0 | 739 1432
1 1 98 0 0| 0 0 |1304B 5305B| 0 0 | 775 1659
1 1 98 0 0| 0 0 |1950B 909B| 0 0 | 764 1450
1 1 98 0 0| 0 68k|2898B 4832B| 0 0 | 799 1423
1 1 98 0 0| 0 0 | 856B 1088B| 0 0 | 667 1382
pidstat 查看IO
# -d 展示 I/O 统计数据,-p 指定进程号,间隔 1 秒输出 10 组数据
pidstat -d -p 9951 1 10
Linux 4.15.0-180-generic (VM-0-11-ubuntu) 08/04/2023 _x86_64_ (2 CPU)
07:40:23 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
07:40:24 PM 500 9951 0.00 0.00 0.00 0 node
07:40:25 PM 500 9951 0.00 0.00 0.00 0 node
07:40:26 PM 500 9951 0.00 0.00 0.00 0 node
07:40:27 PM 500 9951 0.00 0.00 0.00 0 node
07:40:28 PM 500 9951 0.00 0.00 0.00 0 node
07:40:29 PM 500 9951 0.00 0.00 0.00 0 node
07:40:30 PM 500 9951 0.00 0.00 0.00 0 node
07:40:31 PM 500 9951 0.00 0.00 0.00 0 node
07:40:32 PM 500 9951 0.00 0.00 0.00 0 node
07:40:33 PM 500 9951 0.00 0.00 0.00 0 node
Average: 500 9951 0.00 0.00 0.00 0 node
如果无法定位,可以去掉进程号看所有的进程,观察现象
# 间隔 1 秒输出多组数据 (这里是 20 组)
pidstat -d 1 20
通过 strace 进行跟踪
上一步拿到 pid 后可以用 strace 进行跟踪
sudo strace -p 11399
strace: Process 11399 attached
epoll_wait(11, [{EPOLLIN, {u32=40982992, u64=40982992}}], 16, 120000) = 1
read(13, ".\0\0\0(\0\0\0X\10\20\20\1j 1a44f465a3554d4f9"..., 262144) = 138
read(13, 0x7fff7523a380, 262144) = -1 EAGAIN (Resource temporarily unavailable)
nanosleep({tv_sec=0, tv_nsec=10000000}, NULL) = 0
epoll_wait(11, [{EPOLLIN, {u32=40982992, u64=40982992}}], 16, 120000) = 1
read(13, ".\0\0\0(\0\0\0X\10\20\20\1j 2b7e857880da41f8b"..., 262144) = 138
read(13, 0x7fff7523a380, 262144) = -1 EAGAIN (Resource temporarily unavailable)
nanosleep({tv_sec=0, tv_nsec=10000000}, NULL) = 0
epoll_wait(11,
ps检查进程状态
ps aux | grep 11399
root 11399 0.7 4.2 1114904 149348 ? Sl Jun13 537:27 /usr/local/qcloud/YunJing/YDEyes/YDService
ubuntu 23898 0.0 0.0 13776 1104 pts/4 S+ 19:46 0:00 grep --color=auto 11399
如果以上方案都不行,就要用基于事件记录的动态追踪工具。
perf top 或 perf record
pref top -g
pref record -g
pref report
处理僵尸进程
找僵尸进程父进程
# -a 表示输出命令行选项
# p表PID
# s表示指定进程的父进程
pstree -aps 11399
systemd,1 --switched-root --system --deserialize 32
└─YDLive,7059
└─YDService,11399
├─sh,11521 -c sleep 100
│ ├─{sh},11523
│ ├─{sh},11524
│ ├─{sh},11525
│ ├─{sh},11526
│ ├─{sh},11527
│ ├─{sh},11528
│ ├─{sh},11530
│ └─{sh},9989
├─{YDService},11400
├─{YDService},11401
├─{YDService},11402
├─{YDService},11403
├─{YDService},11404
├─{YDService},11405
├─{YDService},11406
├─{YDService},11407
├─{YDService},11408
├─{YDService},11428
├─{YDService},11434
├─{YDService},11450
├─{YDService},11451
├─{YDService},11455
├─{YDService},11456
├─{YDService},11457
├─{YDService},11466
├─{YDService},11467
├─{YDService},11468
├─{YDService},11483
├─{YDService},11485
├─{YDService},11522
└─{YDService},4319
再处理父进程就可以了。
小结
iowait 高不一定代表 I/O 有性能瓶颈。当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度。
因此,碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再找是哪些进程导致了 I/O。
等待 I/O 的进程一般是不可中断状态,所以用 ps 命令找到的 D 状态(即不可中断状态)的进程,多为可疑进程。有时在 I/O 操作后,进程又变成了僵尸进程,所以不能用 strace 直接分析这个进程的系统调用。这种情况下,需要用 perf 工具,来分析系统的 CPU 时钟事件,最终发现是直接 I/O 导致的问题。这时,再检查源码中对应位置的问题,就很轻松了。而僵尸进程的问题相对容易排查,使用 pstree 找出父进程后,去查看父进程的代码,检查 wait() / waitpid() 的调用,或是 SIGCHLD 信号处理函数的注册就行了。