linux内核异步内存回收的另一个思路:基于冷热文件的冷热区域精准的回收冷文件页( 二 )


1:一些page在某个时间点被频繁访问后被移入 lru链表,但是之后很长一段时间就不再被访问了 。接着,有些page被少量访问了而移动到 lru链表 。如果此时发生内存回收,是应该回收 lru链表长时间不被访问的page,还是回收 lru链表刚访问过的page?正常情况回收前者更合理,但是内核是回收后者 。当然,内核也可以实时把 lru链表长时间不被访问的page移动到 lru链表,但是需要从 lru链表找到这种page,这个过程需要pgdat->或->加锁,损耗性能会较大 。回收page的标准应该是它最近一段时间的访问频率,而不是它在处于哪个链表 。
2:遍历 lru链表尾上的page,在内存回收这些page后,很快又被访问了,此时发生了现象 。内核改善措施之一是增大 lru链表长度,增加 page在 lru链表停留的时间,防止再被回收掉 。而更好点的方法是,对发生的page做个标记,后续内存回收尽量不回收这种page 。
2:内存回收方案的改进
目前的异步内存回收方案都修改了内核,能否把异步内存回收做成一个ko,这样不用修改内核了,灵活很多 。除了这点之外,有如下改进会更好:
如下图所示,这是一个4k*12大小的文件读写后产生的示意图,page0是文件地址0~4k文件数据对应的,就是索引是0的文件页page,其他类推 。page0、page1、page3、page8、、访问的很频繁,是热page 。剩下的page很少访问,是冷page 。
这种情况其实挺常见的,如前文所说,把 page0~page3、page4~page7、page8~分别作为3个内存page单元,一起参与内存回收比较合适 。分组效果如下:
每个page被访问一次则内存page单元的访问计数加1 。内存回收时先扫描这3个内存page单元,根据访问计数大小判断冷热程度,然后再回收冷的内存page单元的page,这样的内存回收效率比较高 。
好的,有了以上铺垫,继续深化 。在“不修改内核,做成内核ko”的前提下,本文介绍的异步内存回收主要思路如下:
1:为每个文件分配一个数据结构,管理每个文件的所有 。并且把每个文件的均分成若干个区域(每个区域包含4个索引连续的page),每个区域作为一个内存page单元 。并为每个内存page单元分配一个数据结构,主要反应这个内存page单元page的冷热,如下图所示:
2:创建一个内核线程,负责异步内存回收工作,默认每1分钟运行一次,每次运行时先令全局age计数加1(这个age的思路参考了MGLRU的方案,作用一致) 。同时,每个文件的每个内存page单元,也有一个age(注意,内存page单元的age和访问计数,都反应的冷热,但是作用有差异,3.2.3节详细介绍) 。当某个内存page单元对应的文件页page被访问了,则该的age就要更新为全局age 。的age就是它的文件页page最近一次被访问时的全局age!如果一个对应的文件页page长时间不被访问,它的age就很小,称为冷,内存回收时就要回收这些冷对应的文件页page 。举个例子,如下图所示:
现在全局age是10,异步内存回收线程已经运行了10次(每个周期1分钟) 。和的age是10,说明二者对应的文件页page最近一个周期被访问过,被判定是热 。的age是2,说明已经至少有8个周期(8分钟)内其对应的文件页page没有被访问过,被判定是冷,内存回收时优先回收的4个文件页page 。
3:针对消耗多的文件,内存回收时优先扫描这些文件的内存page单元,大概率能回收很多冷page 。这点第3节再介绍 。
每个文件的内存page单元在代码里是怎么组织起来呢?用一个内核双向链表即可:
如图,展示的一个文件的简易组织情况 。这个文件的共有page0~这些文件页page(索引 0~23) 。示意图中表示一个内存page单元,它包含4个索引连续的文件页page 。比如 对应的是索引是0、1、2、3的文件页page0、page1、page2、page3,其他同理,这种形式下文会经常遇到 。一共有6个,最初全局age是0,每个的age都是0 。假设在第5个周期,全局age增加为5,此时和对应的page被访问了,则把全局age赋值给这些自己的age,如下: