linux page有两个非常重要的引用计数字段_refcount和_mapcount,都是atomic_t类型,其中,_refcount表示内核中应用该page的次数。当_refcount = 0时,表示该page为空闲或者将要被释放。当_refcount > 0,表示该page页面已经被分配且内核正在使用,暂时不会被释放。
_refcount
内核中常用的加减_refcount应用计数的API:get_page()和put_page()
static inline void get_page(struct page *page)
{
page = compound_head(page);
/*
* Getting a normal page or the head of a compound page
* requires to already have an elevated page->_refcount.
*/
VM_BUG_ON_PAGE(page_ref_count(page) <= 0, page);
page_ref_inc(page);
}
static inline void put_page(struct page *page)
{
page = compound_head(page);
/*
* For private device pages we need to catch refcount transition from
* 2 to 1, when refcount reach one it means the private device page is
* free and we need to inform the device driver through callback. See
* include/linux/memremap.h and HMM for details.
*/
if (IS_HMM_ENABLED && unlikely(is_device_private_page(page) ||
unlikely(is_device_public_page(page)))) {
put_zone_device_private_or_public_page(page);
return;
}
if (put_page_testzero(page))
__put_page(page);
}
增加_refcount场景
1)alloc_pages 分配成功_refcount = 1
2)加入到lru链表
3)加入到address_space
上面三个路径可以通过write写数据场景结合源码分析,我们知道write系统调用可以使用page cache加速写性能,我们就以该场景看下page->_refcount引用计数的变化情况。write写数据场景的page cache创建是mm/filemap.c : grab_cache_page_write_begin
/*
* Find or create a page at the given pagecache position. Return the locked
* page. This function is specifically for buffered writes.
*/
struct page *grab_cache_page_write_begin(struct address_space *mapping,
pgoff_t index, unsigned flags)
{
struct page *page;
int fgp_flags = FGP_LOCK|FGP_WRITE|FGP_CREAT;
...
no_page:
if (!page && (fgp_flags & FGP_CREAT)) {
..
page = __page_cache_alloc(gfp_mask);
if (!page)
return NULL;
if (WARN_ON_ONCE(!(fgp_flags & (FGP_LOCK | FGP_FOR_MMAP))))
fgp_flags |= FGP_LOCK;
/* Init accessed so avoid atomic mark_page_accessed later */
if (fgp_flags & FGP_ACCESSED)
__SetPageReferenced(page);
err = add_to_page_cache_lru(page, mapping, index, gfp_mask);
if (unlikely(err)) {
put_page(page);
page = NULL;
if (err == -EEXIST)
goto repeat;
}
/*
* add_to_page_cache_lru locks the page, and for mmap we expect
* an unlocked page.
*/
if (page && (fgp_flags & FGP_FOR_MMAP))
unlock_page(page);
}
return page;
}
- __page_cache_alloc通过alloc_page创建页面,page刚刚创建_refcount = 1
-
add_to_page_cache_lru将page分别加入lru链表和address_space,这两个步骤中都会增加_refcount,该函数返回后_refcount = 3
add_to_page_cache_lru
int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
pgoff_t offset, gfp_t gfp_mask)
{
void *shadow = NULL;
int ret;
__SetPageLocked(page);
ret = __add_to_page_cache_locked(page, mapping, offset,
gfp_mask, &shadow);
if (unlikely(ret))
__ClearPageLocked(page);
else {
...
if (!(gfp_mask & __GFP_WRITE) && shadow)
workingset_refault(page, shadow);
lru_cache_add(page);
}
return ret;
}
static int __add_to_page_cache_locked(struct page *page,
struct address_space *mapping,
pgoff_t offset, gfp_t gfp_mask,
void **shadowp)
{
...
get_page(page);
...
error:
page->mapping = NULL;
/* Leave page->index set: truncation relies upon it */
put_page(page);
return error;
}
void lru_cache_add(struct page *page)
{
...
get_page(page);
...
}
_add_to_page_cache_locked和lru_cache_add都会增加_refcount引用计数。
4)page被映射到其他用户进程pte时,_refcount引用技数会加1。
例如子进程创建时共享父进程的地址空间,设置父进程的pte页表项内容到子进程中并增加该页的_refcount计数:
do_fork->copy_process->copy_mm->dup_mmap->copy_page_range->...->copy_pte_range->copy_one_pte函数。
5) 对于PG_swapable的页面,_add_to_swap_cache会增加_refcount引用计数
6)内核对页面进程操作的一些关键路径上也会增加_refcount。比如内核的follow_page和get_user_pages
_mapcount
_mapcount表示这个页面被进程映射的个数,即已经映射了多少个用户pte页表。
- _mapcount = -1,表示没有pte映射到该页面中
- _mapcount = 0,表示只有父进程映射了页面,匿名页面刚分配时,_mapcount = 0
- _mapcount > 0,表示除了父进程外还有其他进程映射了这个页面,同样以子进程创建共享父进程地址空间为例,设置父进程的pte页表项到子进程中并增加该页面的_mapcount。
get_page增加_refcount,page_dump_rmap增加_mapcount