BUG 分析: 大量 D 进程卡在 shrink_inactive_list 导( 二 )


D 进程就是被设置了进程状态,不可中断的睡眠状态 。不可中断,指的并不是 CPU 不响应外部硬件的中断,而是指进程不响应异步信号,信号只会挂到信号队列,而没有机会去立即执行 。它不占用 cpu,也不能被杀掉,很直观的现象就是,kill -9 一个 D 进程,是没有效果的,只有等进程获得资源被唤醒才处理信号,才处理。
static noinline_for_stack unsigned longshrink_inactive_list(unsigned long nr_to_scan, struct lruvec *lruvec,struct scan_control *sc, enum lru_list lru){......while (unlikely(too_many_isolated(pgdat, file, sc, stalled))) {if (stalled)return 0;/* wait a bit for the reclaimer. */msleep(100); // 卡在这里stalled = true;/* We are about to die and free our memory. Return now. */if (fatal_signal_pending(current))return SWAP_CLUSTER_MAX;}......
初步定位:
该函数已经有跳出功能,不会一直卡住,最多 2 次就会退出去 。
说明是大量的进程疯狂地调用又被阻塞了一下子,又退出去,又掉进来 。所以,不是一直卡死,而是性能瓶颈拥堵在这个地方,.
从上层也能看到,很有规律的大概 110ms 一段的 D 状态,一个进程甚至可以持续几十秒 。
说明隔离页面过多,sleep 100ms,猜测目的是 1. 给时间处理隔离页面,回写文件页到磁盘 2. 是控制并发,也许另一个 cpu 也在同样的回收流程导致隔离页这在时刻变大 。
所以初步定了两个方向,疑点:
一是内存回收瓶颈,内存回收不及时,内存需求量巨大,而 LMK 没触发,内存有很多匿名页,都在回收和回写文件页等 。
二是 io 读写瓶颈,io 速率慢,某个时间段速率变慢,ufs 频率低,上层读写大量数据,io 占用率过高等 。
需要澄清这些疑点 。
插播一些背景知识
page cache
导致这个情况的原因是:进程在申请内存的时候,发现该 zone 的上已经没有足够的内存可用,所以不得不去从该 zone 的 LRU 链表里回收的page,这种情况就是(直接回收) 。会比较消耗时间的原因是,如果回收的是 dirty page,就会触发磁盘 IO 的操作,它会首先把 dirty page 里面的内容给回写到磁盘作同步,再去把该 page 给放到里 。
下图来看下,page cache,Disk I/O 的关系 。
举个简单的例子,比如我们 open 一个文件时,如果没有使用这个flag,那就是 File I/O, 所有对磁盘文件的访问都要经过内存,内存会把这部分数据给缓存起来;但是如果使用了这个flag,那就是I/O, 它会绕过内存而去直接访问磁盘,访问的这部分数据也不会被缓存起来,自然性能上会降低很多 。
page
在直观上,我们有一个认知,我们现在读了一个文件,它会被缓存到内存里面,如果接下来的一个月我们一直都不会再次访问它,而且我们这一个月都不会关闭或者重启机器,那么在这一个月之后该文件就不应该再在内存里头了 。这就是内核对 page cache 的管理策略:LRU(最近最少使用) 。即把最近最少使用的 page cache 给回收为 free pages 。(页框回收算法 PFRA 远没有这么简单)
内核的页回收机制有两种:后台周期性回收和直接回收 。
后台回收是有一个内核线程来做,当内存里 free 的 pages 低于一个水位()时,就会唤醒该内核线程,然后它从 LRU 链表里回收 page cache 到内存的里头,它会一直回收直至 free 的 pages 达到另外一个水位才停止. 如下图所示,
直接回收则是,在发生 page fault/alloc时,没有足够可用的内存,于是线程就自己直接去回收内存,它一次性的会回收 32 个 pages 。逻辑过程如下图所示,
所以,在内存优化上,1. 抬高可以间接减少内存回收的并发量,减轻卡在 . 2. 提高回收效率,如 LMK 的效率 。