系统中出现大量不可中断进程和僵尸进程怎么办?

进程状态

通过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 信号处理函数的注册就行了。