[转帖]《Linux性能优化实战》笔记(五)—— 不可中断进程与僵尸进程

linux,性能,优化,实战,笔记,不可,中断,进程,僵尸 · 浏览次数 : 0

小编点评

**iowait 高可能代表 I/O 有性能瓶颈** 当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度。因此,碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再找是哪些进程导致了 I/O。等待 I/O 的进程一般是不可中断状态,所以用 ps 命令找到的 D 状态的进程,多为可疑进程。但这个案例中,在 I/O 操作后,进程又变成了僵尸进程,所以不能用 strace 直接分析这个进程的系统调用。这种情况下,我们用了 perf 工具,来分析系统的 CPU 时钟事件,最终发现是直接 I/O 导致的问题。 **iowait 高可能代表 I/O 有性能瓶颈** 当系统中只有 I/O 类型的进程在运行时,iowait 也会很高,但实际上,磁盘的读写远没有达到性能瓶颈的程度。因此,碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再找是哪些进程导致了 I/O。等待 I/O 的进程一般是不可中断状态,所以用 ps 命令找到的 D 状态的进程,多为可疑进程。但这个案例中,在 I/O 操作后,进程又变成了僵尸进程,所以不能用 strace 直接分析这个进程的系统调用。这种情况下,我们用了 perf 工具,来分析系统的 CPU 时钟事件,最终发现是直接 I/O 导致的问题。

正文

一、 进程状态

1. 状态含义

从 ps或者 top 命令的输出中,可以看到处于不同状态的进程

  • R:Running 或 Runnable,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行
  • D:Disk Sleep,不可中断状态睡眠(Uninterruptible Sleep)
  • S:Interruptible Sleep,可中断状态睡眠,表示进程因为等待某个资源而被系统挂起,等到之后,会被唤醒并进入 R 状态
  • I:Idle,空闲状态
  • Z:Zombie ,僵尸进程,子进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)
  • T 或t:即 Stopped 或 Traced,表示进程处于暂停或者跟踪状态。

向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped),再向它发送 SIGCONT 信号,进程又会恢复运行。当你用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程的运行。

  • X:Dead,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。

 

2. 进程组与会话

有时用ps还能看到以下状态的进程

小写的s和+是什么意思呢?不知道也没关系,查一下 man ps 就可以。s 表示这个进程是一个会话的领导进程,而 + 表示前台进程组。这里又出现了两个新概念,进程组和会话,它们用来管理一组相互关联的进程。

  • 进程组表示一组相互关联的进程,比如每个子进程都是父进程所在组的成员
  • 会话是指共享同一个控制终端的一个或多个进程组。

比如,我们通过 SSH 登录服务器,就会打开一个控制终端(TTY),这个控制终端就对应一个会话。而我们在终端中运行的命令以及它们的子进程,就构成了一个个的进程组,其中,在后台运行的命令,构成后台进程组;在前台运行的命令,构成前台进程组。

 

二、 不可中断状态

不可中断状态其实是一种保护机制,是为了保证进程数据与硬件状态一致。如果进程在进行IO操作时被随意中断,很有可能出现数据不一致问题。正常情况下,不可中断状态在很短时间内就会结束,短时间的不可中断状态进程一般可以忽略。

但如果系统或硬件发生了故障,或者 iowait 明显升高,进程可能会在不可中断状态保持很久,甚至导致系统中出现大量不可中断进程。这时,你就得注意下,系统是不是出现了 I/O 等性能问题。

 

三、 僵尸进程

通常,当一个进程创建了子进程后,它应该通过系统调用 wait() 或者 waitpid() 等待子进程结束,回收子进程的资源;而子进程在结束时,会向它的父进程发送 SIGCHLD 信号,所以,父进程还可以注册SIGCHLD 信号的处理函数,异步回收资源。

如果父进程没这么做,或是子进程执行太快,父进程还没来得及处理子进程状态,子进程就已经提前退出,那这时的子进程就会变成僵尸进程。通常,僵尸进程持续的时间都比较短,在父进程回收它的资源后就会消亡;或者在父进程退出后,由 init 进程回收后也会消亡。

一旦父进程没有处理子进程的终止,还一直保持运行状态,那么子进程就会一直处于僵尸状态。大量的僵尸进程会用尽 PID 进程号,导致新进程不能创建,所以这种情况一定要避免。
 

四、 案例

来看这个top,有发现什么奇怪的地方吗?

  • 2个CPU,平均负载为2,说明此时系统已经满载,且根据时间来看有上升的趋势
  • 115个僵尸进程
  • CPU使用率不高但 iowait 很高,有超过90%
  • 有两个app进程状态为D

根据本节及之前的知识可以推断:

  • IO负载过高导致了平均负载升高,可能达到了性能瓶颈
  • 僵尸进程多,说明有子进程在退出时没被清理

下面分别来看应该如何分析及处理

 

五、 iowait分析

什么工具可以查询系统的 I/O 情况呢?首先可以使用 dstat ,它可以同时查看 CPU 和 I/O 的使用情况,便于对比分析。

从 dstat 的输出,我们可以看到,每当 iowait 升高(wai)时,磁盘的读请求(read)都会很大。这说明 iowait 的升高跟磁盘的读请求有关,很可能就是磁盘读导致的。

运行 top 命令观察,发现有两个 D 状态的进程,PID 分别是 4344 和 4345。

接下来可以使用pidstat -d 观察指定进程 I/O 使用情况,或者直接观察所有进程的 I/O 使用情况(观察久一点)

  1. # -d 展示 I/O 统计数据,-p 指定进程号,间隔 1 秒,输出 3 组数据
  2. pidstat -d -p 4344 1 3

kB_rd 表示每秒读的 KB 数, kB_wr 表示每秒写的 KB 数,iodelay 表示I/O 的延迟(单位是时钟周期)。

可以发现,的确是 app 进程在进行磁盘读,并且每秒读的数据有 32 MB,看来就是 app 的问题。不过,app 进程到底在执行啥 I/O 操作呢?这里,我们需要回顾一下进程用户态和内核态的区别。进程想要访问磁盘,就必须使用系统调用,所以接下来,重点就是找出 app 进程的系统调用了。

strace 正是最常用的跟踪进程系统调用的工具。我们从 pidstat 的输出中拿到进程的 PID 号,比如 6082,然后在终端中运行 strace 命令,并用 -p 参数指定 PID 号:

  1. $ strace -p 6082
  2. strace: attach: ptrace(PTRACE_SEIZE, 6082): Operation not permitted

这儿出现了一个奇怪的错误,strace 命令居然失败了,并且命令报出的错误是没有权限。按理来说,我们所有操作都已经是以 root 用户运行了,为什么还会没有权限呢?

一般遇到这种问题时,我会先检查一下进程的状态是否正常。比如,继续在终端中运行 ps命令,并使用 grep 找出刚才的 6082 号进程:

  1. ps aux | grep 6082
  2. root 6082 0.0 0.0 0 0 pts/0 Z+ 13:43 0:00 [app] <defunct

果然,进程 6082 已经变成了 Z 状态,也就是僵尸进程。僵尸进程都是已经退出的进程,也就没法继续分析它的系统调用。

到这一步,你应该注意到了,系统 iowait 的问题还在继续,但是 top、pidstat 这类工具已经不能给出更多的信息了。这时,我们就应该求助那些基于事件记录的动态追踪工具了。

你可以用 perf top 看看有没有新发现,或者在终端中运行 perf record,持续一会儿(例如 15 秒),然后按 Ctrl+C 退出,再运行 perf report 查看报告:

  1. perf record -g
  2. perf report

找到我们关注的 app 进程,按回车键展开调用栈,你就会得到下面这张调用关系图

这个图里的 swapper 是内核中的调度进程,你可以先忽略掉。我们来看其他信息,你可以发现, app 的确在通过系统调用 sys_read() 读取数据。并且从new_sync_read 和 blkdev_direct_IO 能看出,进程正在对磁盘进行直接读,也就是绕过了系统缓存,每个读请求都会从磁盘直接读,这就可以解释我们观察到的 iowait 升高了。

看来,罪魁祸首是 app 内部进行了磁盘的直接 I/O 啊!

我们接下来应该从代码层面分析,究竟是哪里出现了直接读请求。查看源码文件你会发现它果然使用了 O_DIRECT 选项打开磁盘,绕过了系统缓存,直接对磁盘进行读写。在大部分情况下,我们最好还是通过系统缓存来优化磁盘I/O,换句话说,解决方法,删除 O_DIRECT 这个选项就是了。

open(disk, O_RDONLY|O_DIRECT|O_LARGEFILE, 0755)

 

六、 僵尸进程分析

既然僵尸进程是因为父进程没有回收子进程的资源而出现的,那么,要解决掉它们,就要找到它们的根,也就是找出父进程,然后在父进程里解决。最简单的就是运行 pstree 命令

  1. # -a表示输出命令行选项,p表示PID,s表示指定进程的父进程
  2. pstree -aps 3084

你会发现 3084 号进程的父进程是 4009,也就是 app 应用。我们接着查看 app 应用程序的代码,看看子进程结束的处理是否正确,比如有没有调用 wait() 或 waitpid() ,或者有没有注册 SIGCHLD 信号的处理函数。

找到代码中子进程的创建和清理的地方:

循环语句本来就容易出错,你能找到这里的问题吗?这段代码虽然看起来调用了 wait() 函数等待子进程结束,但却错误地把 wait() 放到了 for 死循环的外面,也就是说,wait() 函数实际上并没被调用到,我们把它挪到 for 循环的里面就可以了。
 

七、 小结

  • 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 信号处理函数的注册就行了
文章知识点与官方知识档案匹配,可进一步学习相关知识
CS入门技能树Linux入门初识Linux32621 人正在系统学习中

与[转帖]《Linux性能优化实战》笔记(五)—— 不可中断进程与僵尸进程相似的内容:

[转帖]《Linux性能优化实战》笔记(五)—— 不可中断进程与僵尸进程

一、 进程状态 1. 状态含义 从 ps或者 top 命令的输出中,可以看到处于不同状态的进程 R:Running 或 Runnable,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行D:Disk Sleep,不可中断状态睡眠(Uninterruptible Sleep)S:Interr

[转帖]《Linux性能优化实战》笔记(一)—— 平均负载

最近在看极客时间的《Linux性能优化实战》课程,记录下学习内容。 一、 平均负载(Load Average) 1. 概念 我们都知道uptime命令的最后三列分别是过去 1 分钟、5 分钟、15 分钟系统的平均负载,到底平均负载是什么? 简单来说,平均负载是指单位时间内,系统处于可运行状态和不可中

[转帖]《Linux性能优化实战》笔记(六)—— Linux 软中断与对应故障分析方法

中断是系统用来响应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。 一、 为什么要有中断 举个生活中的例子,让你感受一下中断的魅力。比如说你订了一份外卖,但是不确定外卖什么时候送到,也没有别的方法了解外卖的进度,但是,配送员送外卖是不等人的,到了你

[转帖]《Linux性能优化实战》笔记(21)—— 网络性能优化思路

一、 确定优化目标 优化前,我会先问问自己,网络性能优化的目标是什么?实际上,虽然网络性能优化的整体目标,是降低网络延迟(如 RTT)和提高吞吐量(如BPS 和 PPS),但具体到不同应用中,每个指标的优化标准可能会不同,优先级顺序也大相径庭。 拿NAT 网关来说,由于其直接影响整个数据中心的网络出

[转帖]《Linux性能优化实战》笔记(24)—— 动态追踪 DTrace

使用 perf 对系统内核线程进行分析时,内核线程依然还在正常运行中,所以这种方法也被称为动态追踪技术。动态追踪技术通过探针机制来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码就获得丰富的信息,帮你分析、定位想要排查的问题。 以往,在排查和调试性能问题时,我们往往需要先为应用程

[转帖]《Linux性能优化实战》笔记(十七)—— Linux网络基础与性能指标

一、 网络模型 1. OSI 网络模型(七层) 为了解决网络互联中异构设备的兼容性问题,并解耦复杂的网络包处理流程,OSI 模型把网络互联的框架分为七层,每个层负责不同的功能。其中, 应用层,负责为应用程序提供统一的接口。表示层,负责把数据转换成兼容接收系统的格式。会话层,负责维护计算机之间的通信连

[转帖]《Linux性能优化实战》笔记(25)—— 总结:Linux 性能工具速查

一、 性能工具速查 在梳理性能工具之前,首先给你提一个问题,那就是,在什么情况下,我们才需要去查找、挑选性能工具呢? 其实在我看来,只有当你想了解某个性能指标,却不知道该怎么办的时候,才会想到,“要是有一个性能工具速查表就好了”这个问题。如果已知一个性能工具可用,我们更多会去查看这个工具的手册,找出

[转帖]《Linux性能优化实战》笔记(十五)—— 磁盘IO的工作原理

前一篇介绍了文件系统的工作原理,这一篇来看看磁盘IO的工作原理 一、 磁盘 1. 按存储介质分类 磁盘是可以持久化存储的设备,根据存储介质的不同,常见磁盘可以分为两类:机械磁盘和固态磁盘。 机械磁盘,也称为硬盘驱动器(Hard Disk Driver,HDD),主要由盘片和读写磁头组成,数据存储在盘

[转帖]《Linux性能优化实战》笔记(二)—— CPU 上下文切换(上)

上一篇的最后一个例子,在多个进程竞争CPU时,我们看到每个进程实际上%usr部分只有20%多,70%多是在wait,但是load远远高于单个进程使用CPU达到100%。 这让我想到之前看的RWP公开课,里面有一篇连接池管理。为什么相同的业务量,起6千个连接(进程)远远要慢于200个连接,因为绝大多数

[转帖]《Linux性能优化实战》笔记(八)—— 内存是怎么工作的

一、 内存映射 我们通常所说的内存容量,指的是物理内存。物理内存也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM)。只有内核才可以直接访问物理内存。那么,进程要访问内存时,该怎么办呢? Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以