_count和_mapcount是struct page数据结构中非常重要的两个引用计数,且都是atomic_t类型的变量,其中,_count表示内核中引用该页面的次数。当_count的值为0时,表示该page页面为空闲或即将要被释放的页面。当_count的值大于0时,表示该page页面已经被分配且内核正在使用,暂时不会被释放。
_count
内核中常用的加减_count引用计数的API为get_page()和put_page()。
[include/linux/mm.h]
static inline void get_page(struct page *page)
{
if (unlikely(PageTail(page)))
if (likely(__get_page_tail(page)))
return;
/*
* Getting a normal page or the head of a compound page
* requires to already have an elevated page->_count.
*/
/*首先利用VM_BUG_ON_PAGE来判断页面的_count的值不能小于等于0,这是因为页面伙伴分配系统分配好的页面初始值为1,
然后直接使用aotmic_inc()函数原子地增加引用计数。*/
VM_BUG_ON_PAGE(atomic_read(&page->_count) <= 0, page);
atomic_inc(&page->_count);
}
/*
* Drop a ref, return true if the refcount fell to zero (the page has no users)
*/
static inline int put_page_testzero(struct page *page)
{
VM_BUG_ON_PAGE(atomic_read(&page->_count) == 0, page);
return atomic_dec_and_test(&page->_count);
}
[mm/swap.c]
void put_page(struct page *page)
{
/*put_page首先也会使用VM_BUG_ON_PAGE判断_count不能为0,如果为0,说明这个页面已经被释放了。如果_count计数
减1之后等于0,就会调用__put_signle_page()来释放这个页面*/
if (unlikely(PageCompound(page)))
put_compound_page(page);
else if (put_page_testzero(page))
__put_single_page(page);
}
内核还有一堆常用的变种宏,如下
[include/linux/pagemap.h]
#define page_cache_get(page) get_page(page)
#define page_cache_release(page) put_page(page)
_count引用计数通常在内核宏用于跟踪page页面的使用情况。常见的用法归纳如下:
(1) 分配页面时_count引用计数会变成1。分配页面函数alloc_page()在成功分配页面后,_count引用计数为1,见set_page_count()函数。
[alloc_pages()->alloc_pages_node()->__alloc_pages()->__alloc_pages_nodemask()->get_page_from_freelist()->prep_new_page()->set_page_refcounted()]
/*
* Turn a non-refcounted page (->_count == 0) into refcounted with
* a count of one.
*/
static inline void set_page_refcounted(struct page *page)
{
VM_BUG_ON_PAGE(PageTail(page), page);
VM_BUG_ON_PAGE(atomic_read(&page->_count), page);
set_page_count(page, 1);
}
(2) 加入LRU链表时,page页面会被kswapd内核线程使用,因此_count引用计数会加1.以malloc为用户程序分配内存为例,发生缺页中断后do_anonymous_page()函数成功分配出来一个页面,在设置硬件pte表项之前,调用lru_cache_add()函数把这个匿名页面添加到LRU链表中,在这个过程中,使用page_cache_get()宏来增加_count引用计数。
[发生缺页中断->do_page_fault()->__do_page_fault()->handle_mm_fault()->__handle_mm_fault()->
handle_pte_fault()->do_anonymous_page()->lru_cache_add_active_or_unevictable()->
lru_cache_add()->__lru_cache_add()]
static void __lru_cache_add(struct page *page)
{
struct pagevec *pvec = &get_cpu_var(lru_add_pvec);
page_cache_get(page);
if (!pagevec_space(pvec))
__pagevec_lru_add(pvec);
pagevec_add(pvec, page);
put_cpu_var(lru_add_pvec);
}
void lru_cache_add_active_or_unevictable(struct page *page,
struct vm_area_struct *vma)
{
VM_BUG_ON_PAGE(PageLRU(page), page);
if (likely((vma->vm_flags & (VM_LOCKED | VM_SPECIAL)) != VM_LOCKED)) {
SetPageActive(page);
lru_cache_add(page);
return;
}
......
}
(3) 被映射到其他用户进程pte时,_count引用计数会加1。例如,子进程在被创建时共享父进程的地址空间,设置父进程的pte页表项内容到子进程中并增加该页面的_count计数,见
[do_fork()->copy_process()->copy_mm()->dup_mm()->dup_mmap()->
copy_page_range->copy_pud_range()->copy_pmd_range()->copy_pte_range()->copy_one_pte()]
(4) 页面的private中有私有数据
-
对于PG_swapable的页面,__add_to_swap_cache()函数增加_count引用计数。
-
对于PG_private的页面,主要在block模块的buffer_head中使用,例如buffer_migrate_page()函数中会增加_count引用计数。
(5) 内核对页面进行操作等关键路径上也会使用_count引用计数加1。例如内核的follow_page()函数和get_user_pages()函数。以follow_page()为例,调用者通常需要设置FOLL_GET标志来使其增加_count引用计数。例如KSM中获取可合并的页面函数get_mergeable_page(),另一个例子是Direct IO
static struct page *get_mergeable_page(struct rmap_item *rmap_item)
{
struct mm_struct *mm = rmap_item->mm;
unsigned long addr = rmap_item->address;
struct vm_area_struct *vma;
struct page *page;
down_read(&mm->mmap_sem);
vma = find_mergeable_vma(mm, addr);
if (!vma)
goto out;
page = follow_page(vma, addr, FOLL_GET);
if (IS_ERR_OR_NULL(page))
goto out;
if (PageAnon(page) || page_trans_compound_anon(page)) {
flush_anon_page(vma, page, addr);
flush_dcache_page(page);
} else {
put_page(page);
out: page = NULL;
}
up_read(&mm->mmap_sem);
return page;
}
_mapcount
_mapcount引用计数表示这个页面被进程映射的个数,即已经映射了多少个用户pte页表。在32位linux内核中,每个用户进程都拥有3GB的虚拟空间和一份独立的页表,所以有可能出现多个用户进程地址空间同时映射到一个物理页面的情况,RMAP反向映射系统就是利用这个特性来实现的._mapcount引用计数主要用于RMAP反向映射系统中。
-
_mapcount == -1,表示没有pte映射到页面中。
-
_mapcount == 0, 表示只有父进程映射了页面。匿名页面刚分配时,_mapcount引用计数初始化为0。例如do_anonymous_page()产生的匿名页面通过page_add_new_anon_rmap()添加到反向映射rmap系统中时,会设置_mapcount为0,表明匿名页面当前只有父进程的pte映射了页面。
[发生缺页中断do_page_fault()->__do_page_fault()->handle_mm_fault()->
__handle_mm_fault()->handle_pte_fault()->do_anonymous_page()->page_add_new_anon_rmap()]
void page_add_new_anon_rmap(struct page *page,
struct vm_area_struct *vma, unsigned long address)
{
VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
SetPageSwapBacked(page);
atomic_set(&page->_mapcount, 0); /* increment count (starts at -1) */
if (PageTransHuge(page))
__inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
__mod_zone_page_state(page_zone(page), NR_ANON_PAGES,
hpage_nr_pages(page));
__page_set_anon_rmap(page, vma, address, 1);
}
-
_mapcount > 0, 表示除了父进程外还有其他进程映射了这个页面。同样以子进程被创建时共享父进程地址空间为例,设置父进程pte页面项内容到子进程中并增加该页面的_mapcount计数。见[do_fork()->copy_process()->copy_mm()->dup_mm()->dup_mmap()->
copy_page_range->copy_pud_range()->copy_pmd_range()->copy_pte_range()->copy_one_pte()]
static inline unsigned long
copy_one_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,
pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *vma,
unsigned long addr, int *rss)
{
......
page = vm_normal_page(vma, addr, pte);
if (page) {
get_page(page);/*增加_count计数*/
page_dup_rmap(page); /*增加_mapcount计数*/
if (PageAnon(page))
rss[MM_ANONPAGES]++;
else
rss[MM_FILEPAGES]++;
}
out_set_pte:
set_pte_at(dst_mm, addr, dst_pte, pte);
return 0;
}