概述
内核mm子系统中有一个workingset.c实现了refault distance算法,发现网络上介绍该算法的文章主要是复制自奔跑吧内核一书中的内容,均比较雷同,讲述的角度比较难以理解,我第一看到的时候琢磨了2天才明白,本文希望从更容易理解的角度来分析何为refault distance算法,以及内核引入该算法的原因,这就要从内核回收page面临的挑战说起。本文源码:v5.9
page回收的问题
以read/write读取文件场景来举例,我们知道内核会生成page cache加速读写过程,而这些缓存有可能是一次性的,也有可能是不断重复使用,具体要看应用程序的代码时怎么实现的,所以内核很难预测页面的使用趋势,毕竟内核也不能未卜先知,在这个前提下,那么这种page cache新创建的时候到底是加入active lru还是inactive lru呢?内核引入workingset refault distance算法后,新一点内核(比如5.9及以后)都将page先加入inactive lru列表中,因为贸然加入active lru中,大量的page产生之后,就很容易把真正hot的page挤到inactive lru当中,这个很明显是不合理。
那么放入Inactive lru链表中岂不是很容易回收误伤?
没有workingset refault distance算法签确实如此,所以低版本内核对于anon page新产生时加入的是active lru列表。但是引入refault distance算法后,如上所述都是加入inactive lru列表中,如果page被内存回收,而应用程序再次访问,触发的缺页叫:refault。那么问题来了,refault之后这个page到底是放入active lru还是inactive lru呢?
要想解决这个问题我们要看下加入active lru和inactive lru的利弊:
- 放入inactive lru:这个page相对更容易被回收掉,可能造成性能损失。
- 放入active lru,相对更不容回收,如果这个page后续访问频率依然很高,这样很有利,但是如果page后续不再访问,放入active就亏了。还有一种情况是,如果后续继续访问,但是访问的间隔很长,放入active lru也是没用的....
所以上面到底决定是加入哪个lru链表,本质上是无法“精准”预测的,只能根据已经的使用情况来评估此次refault到底是将page放入哪个lru链表,那么怎么实现呢,这就是“refault distance"算法要做的。
我们知道知道放入active比inactive更不容回收,原因是因为内核的LRU算法的老化方法决定,inactive被回收之后,发现active:inactive比例失调,inactive链表的page比较多时候,就会将active lru中的page move到inactive链表中,所以如果放入active lru当中,page抵抗更久的内存回收。
如果将时间线拉长来看,从inactive链表上移出回收的页面数量与从inactive链表上激活升级到active链表的页面数量的总和等于inactive链表的长度NR_inactive;
T0时刻页面A第一次被访问时加入到inactive链表,T1时刻页面A第一次从inactive链表移出回收,T2时刻页面A再次被访问到,T2-T1时刻inactive链表上移出回收的页面数量和升级到active的页面数量和被称为refault distance。在T2时刻,如果想页面一直保持在LRU链表中, refault distance加上inactive链表的页面数量要小于内存的大小,即
refault_distance + NR_inactive <= total_memory (NR_active + NR_inactive)
refault_distance <= NR_active