linux kernel内存回收机制
无论计算机上有多少内存都是不够的,因而linux kernel需要回收一些很少使用的内存页面来保证系统持续有内存使用。页面回收的方式有页回写、页交换和页丢弃三种方式:如果一个很少使用的页的后备存储器是一个块设备(例如文件映射),则可以将内存直接同步到块设备,腾出的页面可以被重用;如果页面没有后备存储器,则可以交换到特定swap分区,再次被访问时再交换回内存;如果页面的后备存储器是一个文件,但文件内容在内存不能被修改(例如可执行文件),那么在当前不需要的情况下可直接丢弃。
1 回收的时机

2 哪些内存可以回收
2.1 页框的回收
LRU(Least Recently Used),近期最少使用链表,是按照近期的使用情况排列的,最少使用的存在链表末尾,通过以下宏定义即可看出:
#define lru_to_page(_head) (list_entry((_head)->prev, struct page, lru))
每个zone有5个LRU链表用以存放各种最近使用状态的页面。
enum lru_list {
LRU_INACTIVE_ANON = LRU_BASE,
LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
LRU_UNEVICTABLE,
NR_LRU_LISTS
};
其中INACTIVE_ANON、ACTIVE_ANON、INACTIVE_FILE、ACTIVE_FILE 4个链表中的页面是可以回收的。ANON代表匿名映射,没有后备存储器;FILE代表文件映射。
页面回收时,会优先回收INACTIVE的页面,只有当INACTIVE页面很少时,才会考虑回收ACTIVE页面。
为了评估页的活动程度,kernel引入了PG_referend和PG_active两个标志位。为什么需要两个位呢?假定只使用一个PG_active来标识页是否活动,在页被访问时,设置该位,但是何时清楚呢?为此需要维护大量的内核定时器,这种方法注定是要失败的。
使用两个标志,可以实现一种更精巧的方法,其核心思想是:一个表示当前活动程度,一个表示最近是否被引用过,下图说明了基本算法。

基本上有以下步骤:
(1)如果页是活动的,设置PG_active位,并保存在ACTIVE LRU链表;反之在INACTIVE;
(2)每次访问页时,设置PG_referenced位,负责该工作的是mark_page_accessed函数;
(3)PG_referenced以及由逆向映射提供的信息用来确定页面活动程度,每次清除该位时,都会检测页面活动程度,page_referenced函数实现了该行为;
(4)再次进入mark_page_accessed。如果发现PG_referenced已被置位,意味着page_referenced没有执行检查,因而对于mark_page_accessed的调用比page_referenced更频繁,这意味着页面经常被访问。如果该页位于INACTIVE链表,将其移动到ACTIVE,此外还会设置PG_active标志位,清除PG_referenced;
(5)反向的转移也是有可能的,在页面活动程度减少时,可能连续调用两次page_referenced而中间没有mark_page_accessed。
如果对内存页的访问是稳定的,那么对page_referenced和mark_page_accessed的调用在本质上是均衡的,因而页面保持在当前LRU链表。这种方案同时确保了内存页不会再ACTIVE与INACTIVE链表间快速跳跃。
2.2 slab****缓存回收
slab缓存回收相对比较灵活,所有注册到shrinker_list中的方法都会被执行。
内核默认针对每个文件系统都注册了prune_super方法,这个函数用来回收文件系统中不再使用的dentry和inode缓存;
android的lowmemorykiller机制注册了选择性杀死进程的方法,回收进程使用的内存。
3****怎样回收页框

其中shrink_page_list是真正回收页面的过程

4****周期性回收的频率
4.1 kswapd
kswapd是内核为每个内存node创建的内存回收线程,为什么有了紧缺回收机制还需要周期性回收呢?因为有些内存分配是不允许阻塞等待回收的,比如中断和异常处理程序中的内存分配;还有些内存分配不允许激活I/O访问的。只有少数情况的内存紧缺可以完整执行回收过程,所以利用系统空闲时间回收内存非常必要。
该函数记录了上一次均衡操作时所用的分配order,如果kswapd_max_order大于上一次的值,或者classzone_idx小于上一次的值,则调用balance_pgdat再次均衡该内存域,否则可以进行短暂休眠,休眠的时间是HZ/10,对于arm(HZ=100)来说,休眠的时间就是1ms。
balance_pgdat均衡操作直到该内存域的zone_wartermark_ok为止。
4.2 cache_reap
cache_reap用来回收slab中的空闲对象,如果空闲对象可以还原成一个页面,则释放回buddy system。每次调用cache_reap会把所有的slab_caches遍历一遍,之后休眠2*HZ,对于arm(HZ=100)来说,周期就是20ms。
5 参考文献
(1)《understanding the linux kernel》
(2)《professional linux kernel architecture》
继续讨论
新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。
评论(37 条,只读存档)
以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

linuxer
itrocker
linuxer
wowo
wowo
itrocker
Daniel Shieh
itrocker
ericzhou
郭健
qkhhyga
linuxer
qkhhyga
linuxer
qkhhyga
linuxer
qkhhyga
andy01011501
itrocker
andy01011501
keiviw
wowo
keiviw
wowo
linux小白
bsp
zhaoge.zhang
linux小白
davidlamb
tiger
bsp
zhenhua
tigerbear
firo
Second2None
bsp
Second2None