匿名页反向映射得建立

参考链接: https://www.cnblogs.com/tolimit/p/5398552.html

http://www.wowotech.net/memory_management/reverse_mapping.html
链接

1.匿名页创建

建立匿名线性区有两种情况,一种是通过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);//文件页和共享匿名页
	}
  省略部分代码...

}

2.1 私有匿名页映射

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);
}

 

2.2 写时复制

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);

.....
}

3父子进程的匿名页

 

创建子任务的函数调用关系
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);
}

 

你可能感兴趣的:(linux,android)