数据结构
AV & AVC & VMA
struct anon_vma { //AV是per VMA的
struct anon_vma *root; //指向祖宗(root)进程的anon_vma
struct anon_vma *parent; //指向父进程的anon_vma
struct rw_semaphore rwsem; /* W: modification, R: walking the list */
atomic_t refcount; //引用计数
unsigned degree; //Count of child anon_vmas and VMAs which points to this anon_vma.
struct rb_root rb_root; /* Interval tree of private "related" vmas */
}
struct anon_vma_chain {
struct vm_area_struct *vma; //指向VMA
struct anon_vma *anon_vma; //指向AV
struct list_head same_vma; //把自己挂在VMA->anon_vma_chain链表上
struct rb_node rb; //把自己挂在anon_vma->rb_root红黑树上
unsigned long rb_subtree_last;
};
struct vm_area_struct {
struct list_head anon_vma_chain; //VMA和AVC是一对多,把AVC穿成链表
struct anon_vma *anon_vma; //VMA和AV是一对一的关系
}
映射产生的过程
父进程产生匿名页面
用户态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)
{
/* 保证vma有一个AV对应它,common case是已经有了,但没有的话就要分配并建立AVC VMA AV三者关系 */
anon_vma_prepare(vma);
/* 申请页面的动作 */
page = alloc_zeroed_user_highpage_movable(vma, address);
/* 设置该页面的匿名映射 */
page_add_new_anon_rmap(page, vma, address);
}
准备VMA的AV和AVC
int anon_vma_prepare(struct vm_area_struct *vma) {
struct anon_vma *anon_vma = vma->anon_vma;
struct anon_vma_chain *avc;
if (unlikely(!anon_vma)) {
avc = anon_vma_chain_alloc(GFP_KERNEL);
anon_vma = find_mergeable_anon_vma(vma);
anon_vma = anon_vma_alloc();//如果find_mergeable_av失败
vma->anon_vma = anon_vma; //建立三者关系
anon_vma_chain_link(vma, avc, anon_vma);
anon_vma->degree++; //Count of child anon_vmas and VMAs which points to this anon_vma.
}
}
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);
}
设置该页面的匿名映射
/**
* 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
*/
void page_add_new_anon_rmap(struct page *page,
struct vm_area_struct *vma, unsigned long address)
{
/* 设置这个页可以交换到磁盘 */
SetPageSwapBacked(page);
/* 省略…… */
/* 设置这个页面为匿名映射 */
__page_set_anon_rmap(page, vma, address, 1);
}
/**
* @exclusive: the page is exclusively owned by the current process
page被当前进程独占
*/
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;
/* 如果已经是匿名页 *//* 不理解 */
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 = (void *) anon_vma + PAGE_MAPPING_ANON;
page->mapping = (struct address_space *) anon_vma;
/* 计算address在vma中的第几个页面 */
page->index = linear_page_index(vma, address);
}
父进程产生匿名页面时的状态
总结:父进程产生匿名页时的反向映射操作,首先申请了AV和AVC,构建VMA、AV、AVC三者的关系,然后申请一个页面,将AV的地址(利用最低位表示该page是一个anon_page)设置到page->mapping中,将address在VMA中的第几个页面的信息设置在page->index中。
此时因为父进程还没有子进程,或者说之前fork的时候该page还没有分配,就不必处理父子进程之间复杂的AVC指针问题。
父进程fork子进程
父进程在fork()创建子进程时,子进程会复制父进程的VMA数据结构内容,并且复制父进程的PTE内容到子进程的页表中,实现父子进程共享页表。多个不同子进程的虚拟页面会同时映射到同一个物理页面。
另外,多个不相干的进程的虚拟页面可以通过KSM机制映射到同一个物理页面中,暂不讨论该技术