彩色科技|linux内核写时复制机制源代码解读

转自Linux阅码场
写时复制技术(以下简称COW)是linux内核比较重要的一种机制 , 我们都知道:父进程fork子进程的时候 , 子进程会和父进程会以只读的方式共享所有私有的可写页 , 当有一方将要写的时候会发生COW缺页异常 。 那么究竟COW在linux内核中是如何触发?又是如何处理的呢?我们将在本文中以源代码情景分析的方式来解读神秘的写时COW , 从源代码级别的角度彻底理解它 。
需要说明的是:本文中所分析的内核源码时linux-5.0版本内核 , 使用arm64处理器架构 , 当然此文章发布时linux内核已经是linux-5.8.x , 当你查看最新的内核源码的时候会发现变化并不是很大 。 本文主要会从下面几个方面去分析讨论写时复制:

  1. fork子进程时内核为COW做了哪些准备
  2. COW进程是如何触发的
  3. 内核时怎样处理COW这种缺页异常的
  4. 匿名页的reuse
一 , 从fork说起我们都知道 , 进程是通过fork进行创建的 , fork创建子进程的时候会和父进程共享资源 , 如fs,file,mm等等 , 其中内存资源的共享是以下路径:
kernel/fork.c_do_fork->copy_process->copy_mm当然本文中讨论的是COW , 暂时不详解其他资源共享以及内存资源共享的其他部分(后面的相关文章我们会讨论) , copy_mm总体来说所作的工作是:分配mm_struct结构实例mm , 拷贝父进程的old_mm到mm,创建自己的pgd页全局目录 , 然后会遍历父进程的vma链表为子进程建立vma链表(如代码段 , 数据段等等) , 然后就是比较关键的页的共享 , linux内核为了效率考虑并不是拷贝父进程的所有物理页内容 , 而是通过复制页表来共享这些页 。 而在复制页表的时候 , 内核会判断这个页表条目是完全复制还是修改为只读来为COW缺页做准备 。
共享父进程内存资源处理如下:
彩色科技|linux内核写时复制机制源代码解读以下我们主要分析copy_one_pte 拷贝页表条目的这一函数:
首先会处理一些页表项不为空但物理页不在内存中的情况(!pte_present(pte)分支)如被swap到交换分区中的页 , 接下来处理物理页在内存中的情况:
773/*774|* If it's a COW mapping, write protect it both775|* in the parent and the child776|*/777if (is_cow_mapping(vm_flags) //设置父进程页表项为只读779pte = pte_wrprotect(pte); //为子进程设置只读的页表项值780}781上面的代码块是判断当前页所在的vma是否是私有可写的属性而且父进程页表项是可写:
247 static inline bool is_cow_mapping(vm_flags_t flags)248 {249return (flags250 }如果判断成立说明是COW的映射 , 则需要将父子进程页表修改为只读:
ptep_set_wrprotect(src_mm, addr, src_pte)将父进程的页表项修改为只读 ,pte = pte_wrprotect(pte)将子进程的即将写入的页表项值修改为只读(注意:修改之前pte为父进程原来的pte值 , 修改之后子进程pte还没有写入到对应的页表项条目中!)
修改页表项为只读的核心函数为:
152 static inline pte_t pte_wrprotect(pte_t pte)153 {154pte = clear_pte_bit(pte, __pgprot(PTE_WRITE));//清可写位155pte = set_pte_bit(pte, __pgprot(PTE_RDONLY));//置位只读位156return pte;157 再次回到copy_one_pte函数往下分析:
上面我们已经修改了父进程的页表项 , 也获得了子进程即将写入的页表项值pte(注意:现在还没有写入到子进程的页表项中 , 因为此时子进程的页表项值还没有被完全拼接号好) , 接下来我们将要拼接子进程的页表项的值:
782/*783|* If it's a shared mapping, mark it clean in784|* the child785|*/786if (vm_flags //设置页表项值为clean788pte = pte_mkold(pte); //设置页表项值为未被访问过即是清PTE_AF789790page = vm_normal_page(vma, addr, pte); //获得pte对应的page结构(即是和父进程共享的页描述符)791if (page) {792get_page(page);//增进page结构的引用计数793page_dup_rmap(page, false);//注意:不是拷贝rmap 而是增加page->_mapcount计数(页被映射计数)794rss[mm_counter(page)]++;795} else if (pte_devmap(pte)) {796page = pte_page(pte);797798/*799|* Cache coherent device memory behave like regular page and800|* not like persistent memory page. For more informations see801|* MEMORY_DEVICE_CACHE_COHERENT in memory_hotplug.h802|*/803if (is_device_public_page(page)) {804get_page(page);805page_dup_rmap(page, false);806rss[mm_counter(page)]++;807}808}809810 out_set_pte:811set_pte_at(dst_mm, addr, dst_pte, pte);//将拼接的页表项值写入到子进程的页表项中812return 0;


推荐阅读