Linux内存管理(三十一):页面回收总结
源码基于:Linux5.4
0. 前言
在 页面回收简介和 kswapd详解(1)一文中简单列举了Linux 内核中触发页面回收的机制,详细剖析了 kswapd 内核线程的初始化和唤醒过程,了解了唤醒 kswapd 内核线程的 3 种方式。
图1 是直接内存回收唤醒 kswapd 的大致流程。
在 kswapd详解(2) 一文中详细地剖析了唤醒 kswapd 内核线程后,线程核心处理函数 kswapd() 的执行过程和注意点。
图2 是唤醒kswapd 和 kswapd() 处理的大致流程。
在 shrink_node 详解 一文中详细地剖析了内存回收的核心函数 shrink_node() 的处理过程,并详细分析以节点为单位的回收函数 shrink_lruvec() 和可回收页面数量的 get_scan_count()。
图3 是shrink_node() 处理的大致流程。
在 shrink_list 详解(1)一文中,继续前一篇博文 shrink_node 详解,详细地剖析了内存回收重要两个函数之一 shrink_active_list() 的处理过程和回收过程。
图4 是 shrink_active_list() 处理的大致流程。
在 shrink_list 详解(2) 一文中,继续前一篇博文 shrink_list 详解(1),详细地剖析内存回收的另一个重要函数 shrink_inactive_list() 的处理过程和内存回收过程。
图5 是shrink_inactive_list() 处理的大致流程。
本文在之前的基础上对内存回收过程进行总结,简单描述各个处理时期的注意点。
1. 唤醒kswapd的3种方式
- 出现内存碎片时唤醒 kswapd
- 内存严重短缺时唤醒 kswapd
- 直接回收内存中唤醒 kswapd
第 1 种出现在快速分配内存时,如果在当前的 migrate type 中没有找到空闲内存转而去其他 migrate type 偷页面时,我们认为出现了内存外碎片化,需要根据需要唤醒 kswapd 进行内存规整。详细可以查看:buddy 系统分配器之快速分配(3)
第 2 种出现在慢速分配内存时,进入此种场景时说明内存已经出现了紧缺,通过唤醒 kswapd 来内存规整,此时并不会阻塞等待 kswapd 的完成,而是继续做其他的尝试。
第 3 种出现还是慢速分配时,此时通过规整和kswapd 后台规整还没有获取有效内存,那么会进入直接内存回收 __alloc_pages_direct_reclaim(),这里再次唤醒 kswapd,并设定等待队列 pfmemalloc_wait 等待kswapd 的完成。详细过程见下图。
在 allow_direct_reclaim() 函数中等待 kswapd,直至回收完成。
2. kswapd 执行过程
注意,
- kswapd 初始化时设置的线程 flags;
- kswapd_try_to_sleep() 不一定能够睡眠成功, 如果无法睡眠成功,会继续进入下一次的balance_pgdat() 函数进行页面回收;
- scan_control.nr 基本是在 shrink_inactive_list() 中统计;
- 内存回收之前确认是否对匿名页进行老化;
- kswpad 内存回收的核心函数是 kswapd_shrink_node();
3. shrink_node 执行过程
注意,
- sc->may_deactivate、sc->cache_trim_mode、sc->file_is_tiny 控制内存回收方向的变量都是在这里确定的;
-
inactive_is_low() 来确定 inactive list 中页面是否比较少,与active list 是保持一定的比例;
-
内存回收是两个核心函数 shrink_lruvec() 和 shrink_slab();
- 回收完成后,为下一轮扫描设定的属性也是在本函数中;
- should_continue_reclaim() 来确定是否继续回收;
4. shrink_active_list 执行过程
注意,
- 在kswapd 内核线程回收内存之前 ,会确认是否需要对匿名页进行老化,此时也是通过shrink_active_list;
- lru_add_drain() 会将pagevec 回归到 LRU list 中,准备回收;
- isolate_lru_pages() 是扫描、回收页面的关键函数,将所有需要的页面进行隔离;
- page_referenced() 用以确定页面被引用的次数,在shrink_inactive_list() 中page_check_references() 时也会调用;
- move_pages_to_lru() 是分派页面的处理函数,将不同的page 放置到不同的list 中,没有引用的page 会被回收到 buddy 中;
- free_unref_page_list() 用以释放页面,详细看:buddy 系统分配器之页面释放
5. shrink_inactive_list 执行过程
注意,
- too_many_isolated() 针对直接内存回收,确认隔离页是否过多;
- shrink_page_list() 是shrink_inactive_list() 核心处理函数;
- page_check_references() 是二次机会法的核心check 函数;
- 通过 shrink_inactive_list() 可以确认匿名页或文件页的详细回收过程;
- 隔离时,都会给page->refcount 加1,为了防止并发释放,当然后面都会相应地减 1。详细查看 shrink_active_list() 第 2.5 节;
6. 页面回收总流程
其实,在 LRU 第二次机会法一文中,已经提前说明 LRU 经典算法,后面也是在这个LRU 经典算法中演变出 LRU 第二次机会法。
本图是总结了LRU 算法中页面回收的流程,详细可以查看:shrink_inactive_list()一文。