五、页面在active链表和inactive链表之间来回移动涉及的函数
1.mark_page_accessed
voidmark_page_accessed(struct page *page)
{
if(!PageActive(page) && !PageUnevictable(page)
&&
PageReferenced(page)&& PageLRU(page)) {
activate_page(page);
ClearPageReferenced(page);
}else if (!PageReferenced(page)) {
SetPageReferenced(page);
}
}
该函数实现了如下表所示的状态迁移:
初始状态
目标状态
inactive,unreferenced
inactive,referenced
inactive,referenced
active,unreferenced
active,unreferenced
active,referenced
2.activate_page
voidactivate_page(struct page *page)
{
if(PageLRU(page) && !PageActive(page) &&
!PageUnevictable(page)){
structpagevec *pvec = &get_cpu_var(activate_page_pvecs);
page_cache_get(page);
if(!pagevec_add(pvec, page))
pagevec_lru_move_fn(pvec,__activate_page, NULL);
put_cpu_var(activate_page_pvecs);
}
}
activate_page函数的目的是将页从非活动链表中移动到活动链表,首先检查page是不是lru链表中的,然后判断页是不是在非活动链表中。如果在,就使用pagevec_add将该页添加到特定于CPU的页向量pvec中。pagevec_add返回的是添加之后页向量中仍然空闲的项数目,返回0表示pvec满了,这时pvec结构中的所有页面才会被一次性地移动到相应的链表上去。其中get_cpu_var的作用是阻止CPU处理中断。
3.page_referenced
intpage_referenced(struct page *page,
int
is_locked,
struct
mem_cgroup *memcg,
unsigned
long *vm_flags)
{
intreferenced = 0;
intwe_locked = 0;
*vm_flags= 0;
if(page_mapped(page) && page_rmapping(page)) {
if(!is_locked && (!PageAnon(page) || PageKsm(page))) {
we_locked= trylock_page(page);
if(!we_locked) {
referenced++;
gotoout;
}
}
if
(unlikely(PageKsm(page))//内核同页合并,具有写保护的页。
referenced +=
page_referenced_ksm(page,memcg,vm_flags);//计算访问该KSM页的次数
elseif (PageAnon(page))
referenced +=
page_referenced_anon(page,memcg,vm_flags);//计算该访问匿名映射页的次数
elseif (page->mapping)
referenced +=
page_referenced_file(page,memcg,vm_flags);//计算访问该文件映射页的次数
if(we_locked)
unlock_page(page);
//该函数在X86处理器上为空,那么PG_referenced在哪里复位呢?
if(page_test_and_clear_young(page_to_pfn(page)))
referenced++;
}
out:
returnreferenced;
}
释放page时候,通过
page_referenced函数来实现page的二次机会释放。在页框回收算法中,对每个页的扫描都会调用该函数,函数返回最近引用该页的次数。
具体实现是,page_referenced通过反向映射找到所有的pte并检查其access位,如果有最近刚访问的pte话(access位设置),这个page不会立刻释放,而是重新加入到lru中,并且清除所有pte的access标志。下次再释放该page的时候,没有最近访问了的pte了。这个时候真正的释放,从而实现二次机会。
其中清除pte的access标志位的函数调用路径是:
page_referenced()
->page_referenced_ksm()/page_referenced_anon()/page_referenced_file()
->page_referenced_one()
->ptep_clear_flush_young()
->ptep_test_and_clear_young()
->test_and_clear_bit(_PAGE_BIT_ACCESSED,(unsignedlong *)
&ptep->pte);