父进程为自己的进程地址空间VMA分配物理内存时,通常会产生匿名页面。例如do_anonymous_page()会分配匿名页面,do_wp_page()发生写时复制COW时会生成一个新的匿名页面。以do_anonymous_page()分配一个新的匿名页面为例:
[用户态malloc()分配内存->写入该内存->内核缺页中断->do_anonymous_page()]
static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd,
unsigned int flags)
{
......
/* Allocate our own private page. */
if (unlikely(anon_vma_prepare(vma)))
goto oom;
page = alloc_zeroed_user_highpage_movable(vma, address);
if (!page)
goto oom;
......
page_add_new_anon_rmap(page, vma, address);
......
}
在分配匿名页面时,调用RMAP反向映射系统的两个API接口来完成初始化,一个是anon_vma_prepare()函数,另一个page_add_new_anon_rmap()函数,下面查看anon_vma_prepare()函数实现
[do_anonymous_page()->anon_vma_prepare()]
int anon_vma_prepare(struct vm_area_struct *vma)
{
/*VMA数据结构中有一个成员anon_vma用于指向anon_vma数据结构,如果VMA还没有分配过匿名页面,
那么vma->anon_vma为NULL,则会分配一个anon_vma数据结构和一个anon_vma_chain数据结构*/
struct anon_vma *anon_vma = vma->anon_vma;
struct anon_vma_chain *avc;
might_sleep();
if (unlikely(!anon_vma)) {
struct mm_struct *mm = vma->vm_mm;
struct anon_vma *allocated;
/*分配一个struct anon_vma_chain数据结构avc*/
avc = anon_vma_chain_alloc(GFP_KERNEL);
if (!avc)
goto out_enomem;
/*检查是否可以复用当前vma的前继者near_vma和后继者pre_vma的anon_vma。能复用的判断条件
比较苛刻,例如两个VMA必须相邻,VMA的内存policy也必须相同,有相同的vm_file等,具体可以看
anon_vma_compatible()函数。如果相邻的VMA无法复用anon_vma,那么重新分配一个anon_vma
数据结构*/
anon_vma = find_mergeable_anon_vma(vma);
allocated = NULL;
if (!anon_vma) {
anon_vma = anon_vma_alloc();
if (unlikely(!anon_vma))
goto out_enomem_free_avc;
allocated = anon_vma;
}
anon_vma_lock_write(anon_vma);
/* page_table_lock to protect against threads */
spin_lock(&mm->page_table_lock);
if (likely(!vma->anon_vma)) {
/*把vma->anon_vma指向到刚才分配的anon_vma*/
vma->anon_vma = anon_vma;
/*将刚才分配的avc添加到vma的anon_vma_chian链表中,另外把avc添加到
anon_vma->rb_root红黑树中。anon_vma数据结构中有一个读写信号量
rwsem,此操作需要获取写者锁anon_vma_lock_write()。下面查看
anon_vma_chain_link()函数的实现*/
anon_vma_chain_link(vma, avc, anon_vma);
/* vma reference or self-parent link for new root */
anon_vma->degree++;
allocated = NULL;
avc = NULL;
}
spin_unlock(&mm->page_table_lock);
anon_vma_unlock_write(anon_vma);
if (unlikely(allocated))
put_anon_vma(allocated);
if (unlikely(avc))
anon_vma_chain_free(avc);
}
return 0;
out_enomem_free_avc:
anon_vma_chain_free(avc);
out_enomem:
return -ENOMEM;
}
anon_vma_prepare()函数主要为进程地址空间VMA准备struct anon_vma数据结构和一些管理用的链表。RMAP反向映射系统中有两个重要的数据结构,一个是anon_vma,简称AV;另一个是anon_vma_chain,简称AVC。struct anon_vma数据结构定义如下:
struct anon_vma {
struct anon_vma *root; /* 指向anon_vma数据结构中的根节点 */
struct rw_semaphore rwsem; /* 保护anon_vma中链表的读写信号量*/
atomic_t refcount; /*引用计数*/
/*
* Count of child anon_vmas and VMAs which points to this anon_vma.
*
* This counter is used for making decision about reusing anon_vma
* instead of forking new one. See comments in function anon_vma_clone.
*/
unsigned degree;
struct anon_vma *parent; /* 指向父anon_vma数据结构 */
/*
* NOTE: the LSB of the rb_root.rb_node is set by
* mm_take_all_locks() _after_ taking the above lock. So the
* rb_root must only be read/written after taking the above lock
* to be sure to see a valid next pointer. The LSB bit itself
* is serialized by a system wide lock only visible to
* mm_take_all_locks() (mm_all_locks_mutex).
*/
struct rb_root rb_root; /* 红黑树根节点。anon_vma内部有一颗红黑树*/
};
struct anon_vma_chain数据结构是连接父子进程中的枢纽,定义如下:
struct anon_vma_chain {
struct vm_area_struct *vma; /*指向VMA,可以指向父进程的VMA,也可以指向子进程的VMA,具体情况需要具体分析*/
struct anon_vma *anon_vma; /*anon_vma数据结构,可以指向父进程的anon_vma数据结构,也可以指向子进程的anon_vma,
具体情况具体分析*/
struct list_head same_vma; /*链表节点,通常把anon_vma_chain添加到vma->anon_vma_chain链表中*/
struct rb_node rb; /*红黑树节点,通常把anon_vma_chain添加到anon_vma->rb_root的红黑树 */
unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
unsigned long cached_vma_start, cached_vma_last;
#endif
};
回到anon_vma_prepare函数中
anon_vma_chain_link()函数实现:把avc添加到vma的anon_vma_chain链表中,另外把avc添加到anon_vma->rb_root红黑树中。
[mm/rmap.c]
[do_anonymous_page()->anon_vma_prepare()->anon_vma_chain_link()]
static void anon_vma_chain_link(struct vm_area_struct *vma,
struct anon_vma_chain *avc,
struct anon_vma *anon_vma)
{
avc->vma = vma;
avc->anon_vma = anon_vma;
list_add(&avc->same_vma, &vma->anon_vma_chain);
anon_vma_interval_tree_insert(avc, &anon_vma->rb_root);
}回到anon_vma_prepare()函数
接下来看另外一个重要的API函数:page_add_new_anon_rmap()
do_anonymous_page()->page_add_new_anon_rmap()
/**
* page_add_new_anon_rmap - add pte mapping to a new anonymous page
* @page: the page to add the mapping to
* @vma: the vm area in which the mapping is added
* @address: the user virtual address mapped
*
* Same as page_add_anon_rmap but must only be called on *new* pages.
* This means the inc-and-test can be bypassed.
* Page does not have to be locked.
*/
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);
/*设置page的标志位PG_SwapBacked,表示这个页面可以swap到磁盘*/
SetPageSwapBacked(page);
/*设置page的_mapcount引用计数为0,_mapcount的初始化值为-1.*/
atomic_set(&page->_mapcount, 0); /* increment count (starts at -1) */
if (PageTransHuge(page))
__inc_zone_page_state(page, NR_ANON_TRANSPARENT_HUGEPAGES);
/*增加页面所在的zone的匿名页面的计数,匿名页面计数类型NR_ANON_PAGES*/
__mod_zone_page_state(page_zone(page), NR_ANON_PAGES,
hpage_nr_pages(page));
/*设置这个页面为匿名映射,下面查看此函数实现*/
__page_set_anon_rmap(page, vma, address, 1);
}
__page_set_anon_rmap()函数实现:
page_add_new_anon_rmap()->__page_set_anon_rmap()
/**
* __page_set_anon_rmap - set up new anonymous rmap
* @page: Page to add to rmap
* @vma: VM area to add page to.
* @address: User virtual address of the mapping
* @exclusive: the page is exclusively owned by the current process
*/
static void __page_set_anon_rmap(struct page *page,
struct vm_area_struct *vma, unsigned long address, int exclusive)
{
struct anon_vma *anon_vma = vma->anon_vma;
BUG_ON(!anon_vma);
if (PageAnon(page))
return;
/*
* If the page isn't exclusively mapped into this vma,
* we must use the _oldest_ possible anon_vma for the
* page mapping!
*/
if (!exclusive)
anon_vma = anon_vma->root;
/*将anon_vma的指针的值加上PAGE_MAPPING_ANON,然后把指针赋值给page->mapping。
struct page数据结构中的mapping成员用于指定页面所在的地址空间。内核中所谓的
地址空间通常有两个不同的地址空间,一个用于文件映射页面,另一个用于匿名映射。
mapping指针的最低两位用于判断是否指向匿名映射或KSM页面的地址空间,如果
mapping指针最低1位不为0,那么mapping指向匿名页面的地址空间数据结构
struct anon_vma。内核提供一个函数PageAnon()函数,用于判断一个页面是否为
匿名页面。下面给出*/
anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;
page->mapping = (struct address_space *) anon_vma;
/*计算当前地址address是在VMA中的第几个页面,然后把offset值赋值到page->index中。
对于匿名页面来说,vma->vm_pgoff这个成员的值是0或者vm_addr/PAGE_SIZE,比如mmap中
MAP_SHARED映射时vm_pgoff为0,MAP_PRIVATE映射时为vm_addr/PAGE_SIZE。vm_addr/PAGE_SIZE
表示匿名页面在整个进程地址空间中的offset。vm_pgoff这个值在匿名页面的生命周期中只有RMAP
反映射时才用到。对于匿名页面来说把vma->vm_pgoff看成0可能更好的体现出page->index的
含义来,把page->index看成是一个VMA里面的offset,而不是整个进程地址空间的offset,也许更贴切些*/
page->index = linear_page_index(vma, address);
}
static inline pgoff_t linear_page_index(struct vm_area_struct *vma,
unsigned long address)
{
pgoff_t pgoff;
if (unlikely(is_vm_hugetlb_page(vma)))
return linear_hugepage_index(vma, address);
pgoff = (address - vma->vm_start) >> PAGE_SHIFT;
pgoff += vma->vm_pgoff;
return pgoff >> (PAGE_CACHE_SHIFT - PAGE_SHIFT);
}
补充:
通过struct page中的mapping成员我们可以获得该page映射的相关信息:
(1) 当mapping等于NULL,表示该page frame不再内存中,而是被swap out到磁盘中去了。
(2) 如果不等于NULL,并且least signification bit等于2,表示page frame是KSM页面
(3) 如果不等于NULL,并且least signification bit等于1,表示page frame是匿名映射页面,mapping指向了一个anon_vma的数据结构。
(4) 如果不等于NULL,并且least signification bit等于0,表示page frame是文件映射页面,mapping指向了一个该文件的address_space数据结构。
#define PAGE_MAPPING_ANON 1
#define PAGE_MAPPING_KSM 2
#define PAGE_MAPPING_FLAGS (PAGE_MAPPING_ANON | PAGE_MAPPING_KSM)
static inline int PageAnon(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}
补充:
page->index和vma->vm_pgoff的含义:
(1) 文件映射的情况
对于文件映射,page->index表示的是page映射到文件内的偏移值(单位是Byte),而vma->vm_gpoff表示的是该VMA映射到文件内的偏移(page为单位),因此,通过vma->vm_pgoff和page->index可以得到该page frame在VMA中的地址偏移,再加上vma->vm_start就可以得到该page_frame的虚拟地址。有了虚拟地址和地址空间(vma->vm_mm),我们就可以通过各级页面找到page对应的pte entry。
(2) 匿名映射的情况
其中匿名映射又要分为share anonymous mapping和private anonymous mapping.
-
share anonymous mapping(MAP_SHARED): vma->vm_pgoff等于0,page->index表示将整个虚拟地址空间作为偏移,
-
share anonymous mapping(私有匿名映射):page->index表示作为vma内的地址偏移
-
private anonymous mapping(MAP_PRIVATE): vma->vm_pgoff 等于vma->vm_start >> PAGE_SHIFT, page->index 等于(vma(虚拟地址) - vma->vm_start) >> PAGE_SHIFT + vma->vm_pgoff;所以page->index为整个进程虚拟地址空间的offset 即vm_addr>>PAGE_SHIFT
父进程的匿名页面:

-
父进程的每个VMA中有一个anon_vma数据结构(下文用AVp来表示),vma->anon_vma指向AVp。
-
和VMAp相关的物理页面page->mapping都指向AVp。
-
有一个anon_vma_chain数据结构AVC,其中avc->vma指向VMA,avc->av指向AVp。
-
AVC添加到VMAp->anon_vma_chain
总结:每个vma只有一个anon_vma数据结构,vma中的每个page的mapping都指向anon_vma数据结构。
本文详细解析了Linux内核中匿名页面的分配过程,包括do_anonymous_page()函数如何调用anon_vma_prepare()和page_add_new_anon_rmap()来初始化和添加新的匿名页面。anon_vma_prepare()负责创建或复用anon_vma数据结构,而page_add_new_anon_rmap()则将新页面添加到反向映射系统。此外,还介绍了anon_vma和anon_vma_chain数据结构在匿名页面管理中的作用,以及page->mapping成员的含义和用途。

1041

被折叠的 条评论
为什么被折叠?



