一 LRU回收算法
LRU算法,假定最近最少使用的页,在较短的时间内页不会使用,所以这些页成为回收的候选者.
内存回收的核心是围绕LRU链表来进行操作,Linux内核实现了5种LRU链表类型
1. 不活跃匿名页表链表(LRU_INACTIVE_ANON)//shmem
2. 活跃匿名页表链表(LRU_ACTIVE_ANON)//
3. 不活跃文件映射页表链表(LRU_INACTIVE_FILE)
4. 活跃文件映射页表链表(LRU_ACTIVE_FILE)
5. 不可回收页表链表(LRU_UNEVICTABL)
内核宏定义:
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
};
LRU算法遵循先进先出(FIFO)原则
1.新分配的page链接到不活跃或活跃链表头部。
2. 从活跃链表的尾部摘取页面链接到不活跃链表头部。
3. 从不活跃链表尾部摘取页表进行内存回收.
每个zone都有一个lruvec结构体
struct lruvec {
struct list_head lists[NR_LRU_LISTS];//LRU链表
struct zone_reclaim_stat reclaim_stat;
};
整个内存回收,就是让页面在活跃链表/不活跃链表和Buddy system之间流动.
匿名页面回收时,如果没有使用者,页面直接被释放,如果有进程使用,先交换到swap分区,然后再释放,
文件映射页面只能回写磁盘,然后被释放.
二,第二次机会法
单纯基于LRU算法,INACTIVE LIST表头的page页面最容易被回收,这样可能存在频繁使用的page被换出去,为了防止这种情况,内核开发者提交了第二次机会法补丁,也就是标记页面是否频繁被引用/访问,如果频繁被使用,会多一次机会停留在不活跃链表,甚至还有机会迁移到活跃链表. 为了实现第二次机会法,内核定义了三个标志位
PG_active, PG_referenced,L_PTE_YOUNG,以及相关函数
mark_page_accessed: 标记页面访问
page_referenced: 获取page引用计数
page_check_references:跟page引用pte计数和访问计数,决定页面是否可被回收.
void mark_page_accessed(struct page *page)
{
if (!PageActive(page) && !PageUnevictable(page) &&
PageReferenced(page)) {
if (PageLRU(page))
activate_page(page);
else
__lru_cache_activate_page(page);
ClearPageReferenced(page);
} else if (!PageReferenced(page)) {
SetPageReferenced(page);
}
}
规则如下:
inactive,unreferenced -> inactive,referenced
inactive,referenced -> active,unreferenced
active,unreferenced -> active,referenced
page_referenced(page)主要通过RMAP机制,计算引用了这个page,且最近被cpu访问过的pte数量(L_PTE_YOUNG置1),然后清除L_PTE_YOUNG,当pte再次被访问时,会产生缺页异常,handle_pte_fault调用pte_mkyoung再次标记L_PTE_YOUNG标志位
page_check_references:决定页面是否可以被回收.
调用关系:shrink_inactive_list->shrink_page_list->page_check_references:
static enum page_references page_check_references(struct page *page,
struct scan_control *sc)
{
int referenced_ptes, referenced_page;
/*计算最近被访问过pte数量 */
referenced_ptes = page_referenced(page, 1, sc->target_mem_cgroup,
&vm_flags);
/*读取page引用标志位,并清楚标志位 */
referenced_page = TestClearPageReferenced(page);
/*如果有引用pte */
if (referenced_ptes) {
/*匿名页面,重新链接到active list */
if (PageSwapBacked(page))
return PAGEREF_ACTIVATE;
/*设置Referenced标志位 */
SetPageReferenced(page);
/*有引用标志位,或者共享的page cache,重新链接到active list */
/*referenced_ptes > 1,这里可以过滤大量只读一次的文件页面迁移到active list */
if (referenced_page || referenced_ptes > 1)
return PAGEREF_ACTIVATE;
/*可执行文件页面,重新连接到active list */
if (vm_flags & VM_EXEC)
return PAGEREF_ACTIVATE;
/*如果只有引用pte,则继续保留在inactive list ,这就是所谓的第二次机会法??? */
return PAGEREF_KEEP;
}
/*页面没有引用pte,则可以尝试回收 */
if (referenced_page && !PageSwapBacked(page))
return PAGEREF_RECLAIM_CLEAN;
/*可回收最佳对象page */
return PAGEREF_RECLAIM;
}
page_check_references函数总结如下:
1. 有引用pte时
1.1 匿名页面直接迁移回活跃链表
1.2 可执行文件页面,直接迁移回活跃链表
1.3 共享page cache,以及最近第二次访问的page cache迁移回活跃链表
1.4 只有引用pte,则保留在inactive list,且清PTE_YOUNG,设置PG_referenced
2.没有引用pte,则是回收的最佳候选者.
三, 第二次机会法举例
3.1 直接读取文件(read)
用户进程直接read文件时,内核调用了vfs_read,
第一次读: vfs_read->do_generic_file_read->page_cache_sync_readhead(预读文件)->read_pages(分配page)->add_to_page_cache_list(清PG_active,添加到inactive list),
do_generic_file_read->mark_page_accessed(置标志位PG_referenced)
第一次read,返回后,page cache处于inactive list,且PG_referenced=1,PG_active=0,因为是直接读取,这个page没有对应的用户空间pte(也就是没有进行映射),如果此时页面回收对这个page调用page_check_references进行检查,会返回PAGEREF_RECLAIM_CLEAN
第二次读: vfs_read->do_generic_file_read->mark_page_accessed,因为PG_referenced=1,则标记PG_active,并添加到active list (这里体现第二次机会法)
3.2 mmap方式读取文件(ext4)
第一次读时,建立mmap映射->ext4_file_mmap->filemap_fault->ext4_readpages->add_to_page_cache_lru(清PG_active,并添加到inactive list )
第二次读写: 后面读取,就跟操作内存一样,
内存回收第一次对page调用page_check_references时,发现有引用pte,PG_referenced=0,则置PG_referenced=1(并清PTE_YOUNG标志),返回PAGEREF_KEEP,继续保留在不活跃链表(这里也体现第二次机会法).
内存回收第二次对page调用page_check_references时,
1. 如果第一次调用page_check_references后,又通过pte访问了对应的page(L_PTE_YOUNG为1),则直接添加到ACTIVE LIST(这里体现了第二次机会法)
2. 如果第一次调用page_check_references后,没有在对page进行读取,则返回PAGEREF_RECLAIM_CLEAN
这里也可以看出mark_page_accessed函数是用于直接读取文件场景,而page_references用于mmap场景.
四 匿名/文件页面的产生
通过添加到LRU链表操作,可以知道哪些情况下会产生匿名页面和文件映射页面
4.1 匿名页面的产生
swap read和shmem通信时,产生不活跃匿名页面,调用lru_cache_add_anon添加到inactive lru
下面几种情况产生活跃页面,调用lru_cache_add_active_or_unevictable添加到active lru
do_wp_page:写时复制缺页异常
do_swap_page: KSM缺页异常
do_anonymous_page:malloc/mmap匿名缺页异常
4.2 文件映射页面产生
文件页面产生时,都是添加到不活跃LRU,主要通过文件直接read,和mmap操作产生page cache
add_to_page_cache_lru:文件系统预读时调用
lru_cache_add_file:cifs文件系统条用
五,page 的lru链表迁移
总的迁移图如下,
5.1 邻居子系统->活跃链表->临时链表->不活跃链表
产生逻辑: 用户空间malloc分配内存,产生匿名缺页中断(do_anonymous_page)进入邻居子系统分配内存(alloc_page)->添加到活跃链表(lru_cache_add_active_or_unevictable),当kswap线程扫描活跃链表时(shrink_active_list),会把页面从活跃链表分离到临时链表(isolate_lru_pages),然后把临时链表的page,迁移到不活跃链表.
5.2 邻居子系统->活跃链表->临时链表->邻居子系统
当活跃链表的page迁移到临时链表后,从临时链表迁移到不活跃链表会减少page引用计数,如果page计数为0,则直接释放到邻居子系统.
5.3 邻居子系统->不活跃链表->临时链表->活跃链表
用户空间mmap一个文件读操作时,产生缺页异常,从邻居子系统分配内存,然后添加到不活跃链表(add_to_page_cache_lru),
kswap线程扫描不活跃链表时(shrink_inactive_list),先分离页面到临时链表(isolate_lru_pages),然后通过page_check_references函数,判断页面是否要迁移到活跃链表(有pte引用计数,PG_refeferencs)
5.4 邻居子系统->不活跃链表->临时链表->swap/磁盘->邻居子系统
kswap线程扫描不活跃链表时(shrink_inactive_list),先分离页面到临时链表(isolate_lru_pages),然后通过page_check_references函数,判断页面是否能够被回收,如果能够被回收,先把page的数据回写writeback,或swap出去.然后再释放page到邻居子系统
6 页面回收条件
6.1 活跃链表迁移到不活跃链表
file list :当active file大于inactive file时,会把cache从活跃链表迁移到不活跃链表
ano list : 当inactive file *rate < active时,会把cache从活跃链表迁移到不活跃链表
根据是否满足迁移模式(dirty ,write back,mapcount)来分离页面. 然后迁移到不越活链表.
6.2 怎么确认回收哪个链表?
1. 关闭swap时,只回收FILE
2. swappiness为0,只回收FILE
3. 不活跃文件页面大于活跃页面,只回收FILE
3. zone free page + zone file 小于规定的高水位,则回收匿名链表
4. 其他情况根据swappiness比例来回收.
6.3 什么时候退出回收
1. 回收页面大于2<<order
2. 回收线程应该从退出.
3. 没有回收时.
6.4 怎么判读一个zone已经平衡
1. 满足最高水位,且能分配order内存
2. 当系统中的处于平衡状态的页面,大于 管理总页面数/2时,系统达到平衡
6.5 对zone每次回收多少个页面
nr_to_reclaim = max(SWAP_CLUSTER_MAX, high_wmark_pages(zone))
6.6 扫描优先级
默认优先级为DEF_PRIORITY=12,每次回收失败时减1.
当每次scan的页面大于nr_to_reclaim时,不需要增加优先级.
6.7 vm_swappiness
【0,100】,越高,表示回收更多的匿名页面.