参考链接: https://www.cnblogs.com/tolimit/p/5398552.html
http://www.wowotech.net/memory_management/reverse_mapping.html
链接
建立匿名线性区有两种情况,一种是通过mmap建立私有匿名线性区,另一种是fork时子进程克隆了父进程的匿名线性区,这两种方式有所区别,首先mmap建立私有匿名线性区时,应用层调用mmap时传入的参数fd必须为-1,即不指定映射的文件,参数flags必须有MAP_ANONYMOUS和MAP_PRIVATE。如果是参数是MAP_ANONYMOUS和MAP_SHARED,创建的就不是匿名线性区了,而是使用shmem的共享内存线性区,这种shmem的共享内存线性区会使用打开/dev/zero的fd。而mmap使用MAP_ANONYMOUS和MAP_PRIVATE创建,可以得到一个空闲的匿名线性区,由于mmap代码中更多的是涉及文件映射线性区的创建,这里就先不给代码,当创建好一个匿名线性区后,anon_vma和anon_vma_chain都为空,并且此线性区对应的线性地址区间的页表项也都为空,但是此vma已经创建完成,之后进程访问此vma的地址区间时合理的,我们知道,内核在创建vma时并不会马上对整个vma的地址进行页表的处理,只有在进程访问此vma的某个地址时,会产生一个缺页异常,在缺页异常中判断此地址属于进程的vma并且合理,才会分配一个页用于页表映射,之后进程就可以顺利读写这个地址所在的页框。也就是说,我一个匿名线性区vma,开始地址是0,结束地址是8K,当我访问6k这个地址时,内核会做好4K~8K地址的映射(正好是一个页大小,四级页表中一个页表项映射的大小),而此匿名线性区0~4k的地址是没有进行映射的。只有在第一次访问的时候才会进行映射。
对于匿名线性区,还需要注意vma的vm_start和vm_pgoff,vm_start保存的是此vma开始的线性地址,而vm_pgoff保存的是vma的开始线性地址对应的虚拟页框号,比如vma的开始线性地址是10K,那么这个vm_pgoff就等于3(起始线性地址属于第3个页框的范围)。之后会说到这个有什么用作 (来自ttps://www.cnblogs.com/tolimit/p/5398552.html)
当程序触发缺页中断时,如果是匿名映射会分配匿名页。 缺页中断得具体过程链接 写得比较清楚,在缺页过程中,会检查vma 是否存在,如果vma 存在,会调用函数handle_mm_fault。
2.缺页异常
int handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
unsigned int flags)
{
省略部分代码...
if (unlikely(is_vm_hugetlb_page(vma))) 判断是不是大页
ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
else
ret = __handle_mm_fault(vma, address, flags); //不是大页会走这里
省略部分代码....
}
__handle_mm_fault->handle_pte_fault
static int handle_pte_fault(struct vm_fault *vmf)
{
省略部分代码....
if (!vmf->pte) { 判断pte 中是否存在此页
if (vma_is_anonymous(vmf->vma)) 判断是不是私有匿名页
return do_anonymous_page(vmf);
else
return do_fault(vmf);//文件页和共享匿名页
}
省略部分代码...
}
do_anonymous_page 函数大部分注释来自 https://www.cnblogs.com/tolimit/p/5398552.html
//创建匿名页并且建立反向映射
static int do_anonymous_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct mem_cgroup *memcg;
struct page *page;
int ret = 0;
pte_t entry;
/* File mapping without ->vm_ops ? */
//共享得页面不需要创建匿名页
if (vma->vm_flags & VM_SHARED)
return VM_FAULT_SIGBUS;
if (pte_alloc(vma->vm_mm, vmf->pmd, vmf->address))
return VM_FAULT_OOM;
/* See the comment in pte_alloc_one_map() */
if (unlikely(pmd_trans_unstable(vmf->pmd)))
return 0;
/* Use the zero-page for reads */
/* vma中的页是只读的的情况,因为是匿名页,又是只读的,不会是代码段,这里执行成功则直接设置页表项pte,不会进行反向映射 */
if (!(vmf->flags & FAULT_FLAG_WRITE) &&
!mm_forbids_zeropage(vma->vm_mm)) {
/* 创建pte页表项,这个pte会指向内核中一个默认的全是0的页框,并且会有vma->vm_page_prot * 中的标志,最后会加上_PAGE_SPECIAL标志 */
entry = pte_mkspecial(pfn_pte(my_zero_pfn(vmf->address),
vma->vm_page_prot));
/* 创建pte页表项,这个pte会指向内核中一个默认的全是0的页框,并且会有vma->vm_page_prot中的标志,最后会加上_PAGE_SPECIAL标志 */
vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd,
vmf->address, &vmf->ptl);
/* 如果页表项不为空,则说明这页曾经被该进程访问过,可能其他核上更改了此页表项 */
if (!pte_none(*vmf->pte))
goto unlock;
ret = check_stable_address_space(vma->vm_mm);
if (ret)
goto unlock;
/* Deliver the page fault to userland, check inside PT lock */
if (userfaultfd_missing(vma)) {
pte_unmap_unlock(vmf->pte, vmf->ptl);
return handle_userfault(vmf, VM_UFFD_MISSING);
}
goto setpte;
}
/* Allocate our own private page. */
/* 为vma准备反向映射条件
* 检查此vma能与前后的vma进行合并吗,如果可以,则使用能够合并的那个vma的anon_vma,如果不能够合并,则申请一个空闲的anon_vma
* 新建一个anon_vma_chain
* 将avc->anon_vma指向获得的vma(这个vma可能是新申请的空闲的anon_vma,也可能是获取到的可以合并的vma的anon_vma),avc->vma指向vma,并把avc加入到vma的anon_vma_chain中
*/
if (unlikely(anon_vma_prepare(vma)))
goto oom;
//分配一个物理页面
page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
if (!page)
goto oom;
if (mem_cgroup_try_charge(page, vma->vm_mm, GFP_KERNEL, &memcg, false))
goto oom_free_page;
/* 设置此页的PG_uptodate标志,表示此页是最新的 */
__SetPageUptodate(page);
/* 根据vma的页参数,创建一个页表项 */
entry = mk_pte(page, vma->vm_page_prot);
/* 如果vma区是可写的,则给页表项添加允许写标志 */
if (vma->vm_flags & VM_WRITE)
entry = pte_mkwrite(pte_mkdirty(entry));
vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address,
&vmf->ptl);
if (!pte_none(*vmf->pte))
goto release;
ret = check_stable_address_space(vma->vm_mm);
if (ret)
goto release;
/* Deliver the page fault to userland, check inside PT lock */
if (userfaultfd_missing(vma)) {
pte_unmap_unlock(vmf->pte, vmf->ptl);
mem_cgroup_cancel_charge(page, memcg, false);
put_page(page);
return handle_userfault(vmf, VM_UFFD_MISSING);
}
/* 增加mm_struct中匿名页的统计计数 */
inc_mm_counter_fast(vma->vm_mm, MM_ANONPAGES);
/* 对这个新页进行反向映射
* 主要工作是:
* 设置此页的_mapcount = 0,说明此页正在使用,但是是非共享的(>0是共享)
* 统计
* 设置page->mapping最低位为1
* page->mapping指向此vma->anon_vma
* page->index存放此page在vma中的第几页
*/
page_add_new_anon_rmap(page, vma, vmf->address, false);
mem_cgroup_commit_charge(page, memcg, false, false);
/* 通过判断,将页加入到活动lru缓存或者不能换出页的lru链表 *
lru_cache_add_active_or_unevictable(page, vma);
setpte:
/* 将上面配置好的页表项写入页表 */
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);
/* No need to invalidate - it was non-present before */
/* 让mmu更新页表项,应该会清除tlb */
update_mmu_cache(vma, vmf->address, vmf->pte);
unlock:
pte_unmap_unlock(vmf->pte, vmf->ptl);
return ret;
release:
/* 将此页释放到每CPU页高速缓存中 */
mem_cgroup_cancel_charge(page, memcg, false);
put_page(page);
goto unlock;
oom_free_page:
put_page(page);
oom:
return VM_FAULT_OOM;
}
anon_vma_prepare 从函数名可以看出是做准备
static inline int anon_vma_prepare(struct vm_area_struct *vma)
{
// vma 刚创建得时候 anon_vma 是空的,如果之前已经分配过了,不需要再次分配
if (likely(vma->anon_vma))
return 0;
return __anon_vma_prepare(vma);
}
int __anon_vma_prepare(struct vm_area_struct *vma)
{
struct mm_struct *mm = vma->vm_mm;
struct anon_vma *anon_vma, *allocated;
struct anon_vma_chain *avc;
might_sleep(); //可能需要睡眠
//分配一个anon_vma_chain, 每个anon_vma 和avc 是对应的,肯定就没有avc
avc = anon_vma_chain_alloc(GFP_KERNEL);
if (!avc)
goto out_enomem;
/* 检查vma能否与其前/后vma进行合并,如果可以,则返回能够合并的那个vma的anon_vma,如果不可以,返回NULL
* 主要检查vma前后的vma是否连在一起(vma->vm_end == 前/后vma->vm_start)
* vma->vm_policy和前/后vma->vm_policy
* 是否都为文件映射,除了(VM_READ|VM_WRITE|VM_EXEC|VM_SOFTDIRTY)其他标志位是否相同,如果为文件映射,前/后vma映射的文件位置是否正好等于vma映射的文件 + vma的长度
* 这里有个疑问,为什么匿名线性区会有vm_file不为空的时候,我也没找到原因
* 可以合并,则返回可合并的线性区的anon_vma
*/
anon_vma = find_mergeable_anon_vma(vma);
allocated = NULL;
if (!anon_vma) {
anon_vma = anon_vma_alloc(); //如果找不到前后不合适的anon_vma 需要从新分配
if (unlikely(!anon_vma))
goto out_enomem_free_avc;
allocated = anon_vma;
}
//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;//将anon_vma 赋值给vma
//将avc 和anon_vma vma 链接起来,链接关系http://www.wowotech.net/memory_management/reverse_mapping.html 讲的比较清楚
anon_vma_chain_link(vma, avc, anon_vma);
/* vma reference or self-parent link for new root */
//anon_vma 是有个root 指向它的父anon_vma,有子anon_vma ,那么父的degree就需要+1
// 在刚非陪anon_vma 时 degree =1
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 和 anon_vma_chain 之间的联系就是通过下面这个函数实现的
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);
}
avc 有两个指针,分别联系vma 和anon_vma. 下面时 avc 结构体指针
struct anon_vma_chain {
struct vm_area_struct *vma; //指向vma
struct anon_vma *anon_vma; //指向anon_vma
struct list_head same_vma; /* locked by mmap_sem & page_table_lock */
//加入到anon_vma 的红黑树中
struct rb_node rb; /* locked by anon_vma->rwsem */
unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
unsigned long cached_vma_start, cached_vma_last;
#endif
};
anon_vma 建立好后需要和物理页联系起来 ,将page 中的mapping 指向anon_vma ,也就是建立反向影射,page_add_new_anon_rmap ->__page_set_anon_rmap函数完成这个事情
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分配时要2字节对齐, 低位都是0, 在这里要给+1 .这样在page 中直接判断
mmapping 中最低位是不是1,判断是否是匿名映射。
anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;
page->mapping = (struct address_space *) anon_vma;
// 物理地址page 在vma中相对start 地址的偏移。
page->index = linear_page_index(vma, address);
}
tatic int do_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
.......
} else if (!(vmf->flags & FAULT_FLAG_WRITE)) //只读文件页
ret = do_read_fault(vmf);
else if (!(vma->vm_flags & VM_SHARED)) // 写时复制缺页
ret = do_cow_fault(vmf);
else
ret = do_shared_fault(vmf); //共享页缺页
/* preallocated pagetable is unused: free it */
if (vmf->prealloc_pte) {
pte_free(vma->vm_mm, vmf->prealloc_pte);
vmf->prealloc_pte = NULL;
}
return ret;
}
//前面分析过,查看是否vma 已经有anon_vma 直接返回,如果没有需要查看相邻的vma 是否有
//没有需要新分配一个anon_vma
static vm_fault_t do_cow_fault(struct vm_fault *vmf)
{
if (unlikely(anon_vma_prepare(vma))) // 查看是否有anon_vma
return VM_FAULT_OOM;
// 分配一个物理页面
vmf->cow_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vmf->address);
if (!vmf->cow_page)
return VM_FAULT_OOM;
.....
ret = __do_fault(vmf);
if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY)))
goto uncharge_out;
if (ret & VM_FAULT_DONE_COW)
return ret;
copy_user_highpage(vmf->cow_page, vmf->page, vmf->address, vma);
__SetPageUptodate(vmf->cow_page);
ret |= finish_fault(vmf);
.....
}
创建子任务的函数调用关系
do_fork->copy_process->copy_mm->dup_mm
static struct mm_struct *dup_mm(struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm = current->mm;
int err;
mm = allocate_mm(); //分配一个mm_struct
if (!mm)
goto fail_nomem;
memcpy(mm, oldmm, sizeof(*mm));//copy 老的mm_struct, 这里是浅拷贝
if (!mm_init(mm, tsk, mm->user_ns))
goto fail_nomem;
err = dup_mmap(mm, oldmm);//深度拷贝
if (err)
goto free_pt;
......
}
内容基本来自https://www.cnblogs.com/tolimit/p/5398552.html
static __latent_entropy int dup_mmap(struct mm_struct *mm,
struct mm_struct *oldmm)
{
struct vm_area_struct *mpnt, *tmp, *prev, **pprev;
struct rb_node **rb_link, *rb_parent;
int retval;
unsigned long charge;
LIST_HEAD(uf);
/* 获取每CPU的dup_mmap_sem这个读写信号量的读锁 *
uprobe_start_dup_mmap();
if (down_write_killable(&oldmm->mmap_sem)) {
retval = -EINTR;
goto fail_uprobe_end;
}
//刷新缓存
flush_cache_dup_mm(oldmm);
/* 如果父进程的mm_struct的flags设置了MMF_HAS_UPROBES,则子进程的mm_struct的flags设置MMF_HAS_UPROBES和MMF_RECALC_UPROBES */
uprobe_dup_mmap(oldmm, mm);
/*
* Not linked in yet - no deadlock potential:
*/
/* 获取子进程的mmap_sem,后面SINGLE_DEPTH_NESTING意思需要查查 */
down_write_nested(&mm->mmap_sem, SINGLE_DEPTH_NESTING);
/* No ordering required: file already has been exposed. */
RCU_INIT_POINTER(mm->exe_file, get_mm_exe_file(oldmm));
/* 复制父进程进程地址空间的大小(页框数)到子进程的mm */
mm->total_vm = oldmm->total_vm;
/* 数据段的内存页数 */
mm->data_vm = oldmm->data_vm;
/* 复制父进程可执行内存映射中的页数量到子进程的mm */
mm->exec_vm = oldmm->exec_vm;
/* 复制父进程用户态堆栈的页数量到子进程的mm */
mm->stack_vm = oldmm->stack_vm;
/* 子进程vma红黑树的根结点,保存到rb_link中 */
rb_link = &mm->mm_rb.rb_node;
rb_parent = NULL;
pprev = &mm->mmap;
retval = ksm_fork(mm, oldmm);
if (retval)
goto out;
retval = khugepaged_fork(mm, oldmm);
if (retval)
goto out;
prev = NULL;
/* 遍历父进程所有vma,通过mm->mmap链表遍历 */
for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {
struct file *file;
// 有过标记了不能复制标志,就不复制vma
if (mpnt->vm_flags & VM_DONTCOPY) {
/* 做统计,因为上面把父进程的total_vm、shared_vm、exec_vm、stack_vm都复制过来了,这些等于父进程所有vma的页的总和,这里这个vma不复制,要相应减掉此vma的页数量 */
vm_stat_account(mm, mpnt->vm_flags, -vma_pages(mpnt));
continue;
}
charge = 0;
if (mpnt->vm_flags & VM_ACCOUNT) {
unsigned long len = vma_pages(mpnt);
if (security_vm_enough_memory_mm(oldmm, len)) /* sic */
goto fail_nomem;
charge = len;
}
/* 分配一个vma结构体用于子进程使用 */
tmp = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
if (!tmp)
goto fail_nomem;
/* 直接复制父进程的vma的数据到子进程的vma */
*tmp = *mpnt;
/* 初始化子进程新的vma的anon_vma_chain为空 */
INIT_LIST_HEAD(&tmp->anon_vma_chain);
/* 视情况复制父进程vma的权限,非vm_flags */
retval = vma_dup_policy(mpnt, tmp);
if (retval)
goto fail_nomem_policy;
/* 将子进程新的vma的有一个vm_mm 需要指向mm_struct*/
tmp->vm_mm = mm;
retval = dup_userfaultfd(tmp, &uf);
if (retval)
goto fail_nomem_anon_vma_fork;
//清除子项中的VMA内容
if (tmp->vm_flags & VM_WIPEONFORK) {
/* VM_WIPEONFORK gets a clean slate in the child. */
tmp->anon_vma = NULL;
if (anon_vma_prepare(tmp))
goto fail_nomem_anon_vma_fork;
/* 对父子进程的anon_vma和anon_vma_chain进行处理
* 如果父进程的此vma没有anon_vma,直接返回,vma用于映射文件应该会没有anon_vma
*/
} else if (anon_vma_fork(tmp, mpnt))
goto fail_nomem_anon_vma_fork;
tmp->vm_flags &= ~(VM_LOCKED | VM_LOCKONFAULT);
tmp->vm_next = tmp->vm_prev = NULL;
/* 获取vma所映射的文件,如果是匿名映射区,则为空 */
file = tmp->vm_file;
if (file) {
struct inode *inode = file_inode(file);
struct address_space *mapping = file->f_mapping;
get_file(file);
if (tmp->vm_flags & VM_DENYWRITE)
atomic_dec(&inode->i_writecount);
i_mmap_lock_write(mapping);
if (tmp->vm_flags & VM_SHARED)
atomic_inc(&mapping->i_mmap_writable);
flush_dcache_mmap_lock(mapping);
/* insert tmp into the share list, just after mpnt */
vma_interval_tree_insert_after(tmp, mpnt,
&mapping->i_mmap);
flush_dcache_mmap_unlock(mapping);
i_mmap_unlock_write(mapping);
}
/*
* Clear hugetlb-related page reserves for children. This only
* affects MAP_PRIVATE mappings. Faults generated by the child
* are not guaranteed to succeed, even if read-only
*/
if (is_vm_hugetlb_page(tmp))
reset_vma_resv_huge_pages(tmp);
/*
* Link in the new vma and copy the page table entries.
*/
*pprev = tmp;
pprev = &tmp->vm_next;
tmp->vm_prev = prev;
prev = tmp;
__vma_link_rb(mm, tmp, rb_link, rb_parent);
rb_link = &tmp->vm_rb.rb_right;
rb_parent = &tmp->vm_rb;
mm->map_count++;
/* 做页表的复制
* 将父进程的vma对应的开始地址到结束地址这段地址的页表复制到子进程中
* 如果这段vma有可能会进行写时复制(vma可写,并且不是共享的VM_SHARED),那就会对子进程和父进程的页表项都设置为映射的页是只读的(vma中权限是可写),这样写时会发生缺页异常,在缺页异常中做写时复制
*/
// VM_WIPEONFORK :Wipe VMA contents in child.
if (!(tmp->vm_flags & VM_WIPEONFORK))
retval = copy_page_range(mm, oldmm, mpnt);
if (tmp->vm_ops && tmp->vm_ops->open)
tmp->vm_ops->open(tmp);
if (retval)
goto out;
}
/* a new mm has just been created */
retval = arch_dup_mmap(oldmm, mm);
out:
up_write(&mm->mmap_sem);
flush_tlb_mm(oldmm);
up_write(&oldmm->mmap_sem);
dup_userfaultfd_complete(&uf);
fail_uprobe_end:
uprobe_end_dup_mmap();
return retval;
fail_nomem_anon_vma_fork:
mpol_put(vma_policy(tmp));
fail_nomem_policy:
kmem_cache_free(vm_area_cachep, tmp);
fail_nomem:
retval = -ENOMEM;
vm_unacct_memory(charge);
goto out;
}
anon_vma_fork 函数是将父进程的vma anon_vma和子进程的vma 中的avc链接起来
int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma)
{
struct anon_vma_chain *avc;
struct anon_vma *anon_vma;
int error;
//检查父进程是否有anon_vma 没有直接退出
if (!pvma->anon_vma)
return 0;
/* Drop inherited anon_vma, we'll reuse existing or allocate new. */
vma->anon_vma = NULL;
// 遍历父进程的avc, 遍历到一个重新分配一个avc 加入到子进程的avc链表中。
//新分配的avc 需要将自己的anon_vma 指针指向父进程avc 对应的anon_vma.同时将子进程
//中的avc 中的红黑树节点加入到父进程anon_vma 的红黑树中。 这样在父进程查找时能够根据红黑树
//快速查找到当前有多少子进程关联到了这个anon_vma
error = anon_vma_clone(vma, pvma);
if (error)
return error;
/* An existing anon_vma has been reused, all done then. */
if (vma->anon_vma) // 如果子进程存在anon_vma 就直接返回
return 0;
//不存在需要重新分配一个anon_vma, avc
//前面分配的avc 都是为了和父进程关联,不是从父进程来继承来的页需要自己的avc
//比如如果父进程当时一个页面因为没写那么物理页面不会分配,如果子进程写了这个页面
//需要分配物理页,这时需要自己的anon_vma 和 avc。 父进程和这个物理页没什么联系
anon_vma = anon_vma_alloc();
if (!anon_vma)
goto out_error;
avc = anon_vma_chain_alloc(GFP_KERNEL);
if (!avc)
goto out_error_free_anon_vma;
//root 是根, parent是父,区别在于父进程可能是继承它的父进程,
// root 是指向最开始哪个进程
anon_vma->root = pvma->anon_vma->root;
anon_vma->parent = pvma->anon_vma;
//这个函数很简单,就是更新最开始的哪个anonv_vma 中的refcount 索引值加1
//这个root 和parent 概念是不一样的。parent 不代表就是最初始的哪个,因为父进程也可能是父进程的父进程创建的。
get_anon_vma(anon_vma->root);
/* Mark this anon_vma as the one where our new (COWed) pages go. */
vma->anon_vma = anon_vma;
anon_vma_lock_write(anon_vma);
anon_vma_chain_link(vma, avc, anon_vma);
// 这里父进程的degree ++
anon_vma->parent->degree++;
anon_vma_unlock_write(anon_vma);
return 0;
out_error_free_anon_vma:
put_anon_vma(anon_vma);
out_error:
unlink_anon_vmas(vma);
return -ENOMEM;
}
static void anon_vma_chain_link(struct vm_area_struct *vma, //子进程的vma
struct anon_vma_chain *avc,//重新分配的avc
struct anon_vma *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);
}