linux fork COW机制分析

在linux系统中通过系统调用fork/clone来创建一个新的进程,创建进程的过程中根据clone flags会选择复制资源还是公用一份资源,通常资源包括:打开的文件files_struct,文件系统fs_struct,信号处理signal,内存资源mm_struct,其中mm_struct不仅代表虚拟地址空间而且还有关联的物理内存。

早期linux中fork会复制父进程中所有内存的资源:mm_struct,VMA和页表项,物理内存,但是大部分子进程不执行exec加载新的程序,父子进程共享大部分地址空间和数据,另外复制过程的代价相当昂贵,物理内存资源紧张,后来发明了COW机制:fork的时候只复制mm_struct和vma,但是不申请新的物理内存和复制内存内容,也就是他们的页表项中指向同一份物理资源。这样就有个问题,进程中有数据段,如果一直共用一份物理内存这样就不能实现进程间地址空间隔离,所以需要对他们私有的数据进行分离,也就是可能发生写操作的区域,分离的时机取决于写操作发生的时候,这就是Copy On Write操作。为了实现这样COW机制,它利用MMU的缺页异常,将父子进程的页表项都置为只读,当写操作发生的时候因为权限异常触发MMU异常,CPU在尝试修复异常的时候,发现如下特征就会当做COW来修复,此时实现物理内存资源的分离。

  1. 页表项存在,但是是只读,发生的错误为写:PF_WRITE
  2. 发生的错误地址位于进程的虚拟地址空间vma中,且区间的权限允许写操作

所以COW机制分为两部分:fork时资源复制时复制页表项并且将其都设置为只读,缺页异常中识别COW引发的错误并实际分配资源实现地址空间隔离。

fork时资源复制

fork过程中复制内存资源,首先分配并且复制mm_struct内容,之后将其中统计字段清零。之后遍历mm_struct->mmap链表,复制其中每一个区域,重点在于虚拟内存区间对应的页表项。复制页表项的过程中可以看到从最顶级的pgd->pud->pmd->pte逐级遍历,如果对应的页表项存在的话就向下逐级遍历直到pte级别。如果页表只有两级pgd和pte时,拷贝pud和pmd的过程基本上是空的。
在拷贝每一项pte时内核会检查当前是否是COW区域:1.vma区域可写,2.vma不能是共享的,3.vma是匿名映射。

do_fork()
	copy_process()
		copy_mm()
			dup_mm()
				allocate_mm()    /* 分配新的mm_struct */
				dup_mmap(mm, oldmm) /* 复制vma和页表 */
				
dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm) {
	for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {	/* 遍历所有的vm_area_struct */
		tmp = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
		*tmp = *mpnt;
		copy_page_range(mm, oldmm, mpnt);	/* 复制父进程中所有的页表项 */
	}
}

copy_page_range()
	copy_pud_range()
		copy_pmd_range()
			copy_pte_range()
				copy_one_pte()

copy_one_pte() {
	if (is_cow_mapping(vm_flags)) {                                             
        ptep_set_wrprotect(src_mm, addr, src_pte);      /* 如果需要COW,则将父进程中pte设置写保护位,即~_PAGE_BIT_RW */                        
        pte = pte_wrprotect(pte);                                               
    }
    set_pte_at(dst_mm, addr, dst_pte, pte);	/* 设置子进程中pte写保护位:~_PAGE_BIT_RW*/
}

缺页异常中修复

对pte中标记只读的页进行写操作会触发MMU缺页异常,缺页异常修复中探测触发异常的地址是否是合法的:地址位于vma区间中并且vma区间是可写的,此时就认为是COW的页,下面就是修复异常。缺页异常修复返回修复类型:如果是major/minor类型,统计到进程中的maj_fault,min_fault;如果是异常则发送信号SIGBUS给进程;如果修复失败则返回oom错误并发送SIGKILL信号杀死进程。

  1. 重新分配物理页并拷贝原始页内容到新的物理页中
  2. 修正新的页表项属性
  3. 修正旧的页表项属性
do_page_fault()
	handle_mm_fault()
		__handle_mm_fault()
			handle_pte_fault()
				do_wp_page()	/* wp:write protect */

do_wp_page() {
	new_page = alloc_page_vma(GFP_HIGHUSER, vma, address);	//分配新的物理页
	cow_user_page(new_page, old_page, address, vma);		//拷贝页内容
	entry = mk_pte(new_page, vma->vm_page_prot);
	entry = maybe_mkwrite(pte_mkdirty(entry), vma);			//设置写属性
	set_pte_at(mm, address, page_table, entry);				//设置新的pte项
}

遗留问题:上面只是第一个进程触发COW机制的下半部,还有另一个的进程如何实现COW的下半部呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值