Linux|浅谈 Linux 高负载的系统化分析( 二 )


CPU user 和 sys 时间的分布通常能帮助人们快速定位与用户态进程有关 , 还是与内核有关 。 另外 , CPU 的 run queue 长度和调度等待时间 , 非主动的上下文切换 (nonvoluntary context switch) 次数都能帮助大致理解问题的场景 。
因此 , 如果要将问题的场景关联到相关的代码 , 通常需要使用 perf , systemtap ftrace 这种动态的跟踪工具 。
关联到代码路径后 , 接下来的代码时间分析过程中 , 代码中的一些无效的运行时间也是分析中首要关注的 , 例如用户态和内核态中的自旋锁 (Spin Lock) 。
【Linux|浅谈 Linux 高负载的系统化分析】当然 , 如果 CPU 上运行的都是有非常意义 , 非常有效率的代码 , 那唯一要考虑的就是 , 是不是负载真得太大了 。
D 状态任务增多
根据 Linux 内核的设计 ,D 状态任务本质上是 TASK_UNINTERRUPTIBLE 引发的主动睡眠 , 因此其可能性非常多 。 但是由于 Linux 内核 CPU 空闲时间上对 IO 栈引发的睡眠做了特殊的定义 , 即 iowait , 因此iowait 成为 D 状态分类里定位是否 Load 高是由 IO 引发的一个重要参考 。
当然 , 如前所述 ,/proc/stat 中的 procs_blocked 的变化趋势也可以是一个非常好的判定因 iowait引发的 Load 高的一个参考 。
CPU iowait 高
很多人通常都对 CPU iowait 有一个误解 , 以为 iowait 高是因为这时的 CPU 正在忙于做 IO 操作 。 其实恰恰相反 ,iowait 高的时候 , CPU 正处于空闲状态 , 没有任何任务可以运行 。 只是因为此时存在已经发出的磁盘 IO , 因此这时的空闲状态被标识成了 iowait, 而不是 idle 。
但此时 , 如果用 perf probe 命令 , 我们可以清楚得看到 , 在 iowait 状态的 CPU , 实际上是运行在 pid 为 0 的 idle 线程上:

相关的 idle 线程的循环如何分别对 CPU iowait 和 idle 计数的代码 , 如下所示:

而 Linux IO 栈和文件系统的代码则会调用 io_schedule , 等待磁盘 IO 的完成 。 这时候 , 对 CPU 时间被记为 iowait 起关键计数的原子变量 rq-nr_iowait 则会在睡眠前被增加 。 注意 , io_schedule 在被调用前 , 通常 caller 会先将任务显式地设置成 TASK_UNINTERRUPTIBLE 状态:

CPU idle 高
如前所述 , 有相当多的内核的阻塞 , 即 TASK_UNINTERRUPTIBLE 的睡眠 , 实际上与等待磁盘 IO 无关 , 如内核中的锁竞争 , 再如内存直接页回收的睡眠 , 又如内核中一些代码路径上的主动阻塞 , 等待资源 。
Brendan Gregg 在最近的博客 [Linux Load Averages: Solving the Mystery
(http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html)中 , 使用 perf 命令产生的 TASK_UNINTERRUPTIBLE 的睡眠的火焰图 , 很好的展示了引起 CPU idle 高的多样性 。 本文不在赘述 。
因此 , CPU idle 高的分析 , 实质上就是分析内核的代码路径引起阻塞的主因是什么 。 通常 , 我们可以使用 perf inject 对 perf record 记录的上下文切换的事件进行处理 , 关联出进程从 CPU 切出 (swtich out) 和再次切入 (switch in) 的内核代码路径 , 生成一个所谓的 Off CPU 火焰图.
当然 , 类似于锁竞争这样的比较简单的问题 , Off CPU 火焰图足以一步定位出问题 。 但是对于更加复杂的因 D 状态而阻塞的延迟问题 , 可能 Off CPU 火焰图只能给我们一个调查的起点 。
例如 , 当我们看到 , Off CPU 火焰图的主要睡眠时间是因为 epoll_wait 等待引发的 。 那么 , 我们继续要排查的应该是网络栈的延迟 , 即本文大图中的 Net Delay 这部分 。
至此 , 你也许会发现 , CPU iowait 和 idle 高的性能分析的实质就是 延迟分析 。 这就是大图按照内核中资源管理的大方向 , 将延迟分析细化成了六大延迟分析: