copy on write(COW)特性主要用于在forck时节省内存空间,可以防止在fork时将父进程中的所有数据全局复制一份出来。可以想象如果fork是将所有父进程中的数据全部复制一份给子进程会造成严重副作用:

  • 复制大量不必要的数据会造成占有大量物理内存
  • fork时造成大量数据会比较耗时操作性能严重损耗。

当一个程序在复制之后调用exec,则会造成更严重性能浪费。意味着fork时复制的数据时完全没有用。内核在解决次问题时,fork并不会复制父进程整个地址空间,也是只复制其page table,则这样子进程继承父进程的虚拟地址和物理地址映射关系,子进程的虚拟地址和父进程的虚拟地址指向相同的物理地址(通过rmap 管理一对多关系),此时父子进程中的page table PTE中的读权限被关闭清除,当父子子进程对该页进行写操作时,将会发生page fault,重新申请新的物理页给该进程使用,同时将内容进行copy,实现物理内存按需分配,减少不必要的复制操作。

The kernel uses the copy-on-write technique (COW) to prevent all data of the parent process from being copied when fork is executed. This technique exploits the fact that processes normally use only a fraction of their pages in memory.8 When fork is called, the kernel would usually create an identical copy of each memory page of the parent process for the child process. This has two very negative effects:

  1. A large amount of RAM, a scarce resource, is used.
  2. The copy operation takes a long time

The negative impact is even greater if the application loads a new program using exec immediately after process duplication. This means, in effect, that the preceding copy operation was totally superfluous as the process address space is reinitialized and the data copied are no longer needed.
The kernel can get around this problem by using a trick. Not the entire address space of the process but only its page tables are copied.

Of course, parent and child processes must not be allowed to modify each other’s pages,9 which is why the page tables of both processes indicate that only read access is allowed to the pages — even thoughthey could be written to in normal circumstances. 

Providing that both processes have only read access to their pages in memory, data sharing between the two is not a problem because no changes can be made.
As soon as one of the processes attempts to write to the copied pages, the processor reports an access error to the kernel (errors of this kind are called page faults). The kernel then references additional memory management data structures (see Chapter 4) to check whether the page can be accessed in Read and Write mode or in Read mode only — if the latter is true,a segmentation fault must be reported to the process
The condition in which a page table entry indicates that a page is ‘‘Read Only’’ although normally it would be writable allows the kernel to recognize that the page is, in fact, a COW page. It therefore creates a copy of the page that is assigned exclusively to the process — and may therefore also be used for write operations

The COW mechanism enables the kernel to delay copying of memory pages for as long as possible and — more importantly — to make copying unnecessary in many cases. This saves a great deal of time 

fork 复制PTE

fork子进程是,子进程除了复制父进程的虚拟空间vma,还会复制page table表,fork主要处理主要调用函数:

  • copy_mm为复制父进程内存管理信息,将current->mm中的数据复制到tsk->mm中。
  • dup_mm为具体实现。

dup_mm

dup_mm 复制mm具体实现:

static struct mm_struct *dup_mm(struct task_struct *tsk,struct mm_struct *oldmm)
{struct mm_struct *mm;int err;mm = allocate_mm();if (!mm)goto fail_nomem;memcpy(mm, oldmm, sizeof(*mm));if (!mm_init(mm, tsk, mm->user_ns))goto fail_nomem;err = dup_mmap(mm, oldmm);if (err)goto free_pt;mm->hiwater_rss = get_mm_rss(mm);mm->hiwater_vm = mm->total_vm;if (mm->binfmt && !try_module_get(mm->binfmt->module))goto free_pt;return mm;free_pt:/* don't put binfmt in mmput, we haven't got module yet */mm->binfmt = NULL;mm_init_owner(mm, NULL);mmput(mm);fail_nomem:return NULL;
}
  • allocate_mm:为子进程申请一个新的mm_struct。
  • memcpy(mm, oldmm, sizeof(*mm)):将父进程中的mm_struct copy到子进程中。
  • mm_init(mm, tsk, mm->user_ns):初始化里面mm其他内容。
  • dup_mmap(mm, oldmm):复制vma 信息以及对应page table。
  • 复制mm->hiwater_rss和mm->hiwater_vm 。

dup_mmap

dup_mmap处理较长,主要做了两件事情:复制父进程所有vma 到子进程中以及所有表项:

static __latent_entropy int dup_mmap(struct mm_struct *mm,struct mm_struct *oldmm)
{... ...for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {... ...tmp = vm_area_dup(mpnt);... ...__vma_link_rb(mm, tmp, rb_link, rb_parent);rb_link = &tmp->vm_rb.rb_right;rb_parent = &tmp->vm_rb;mm->map_count++;if (!(tmp->vm_flags & VM_WIPEONFORK))retval = copy_page_range(mm, oldmm, mpnt);... ...}... ...
}
  • for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) :遍历该进程所有vma。
  • vm_area_dup:复制父进程中的一个vma 到一个新的vma tmp中
  • 将新的tmp vma插入到子进程中。
  • tmp->vm_flags & VM_WIPEONFORK:如果该vma 标记为VM_WIPEONFORK,即可以子进程可以继承该vma表项,则会调用copy_page_range进行page table 复制处理。

copy_page_range

copy_page_range 复制对应一个page 的page table,其处理流程就是按照page table中每个级别PUD、P4D等分别进行处理:

复制过程毕竟枯燥不在详细分析,主要是设计最后PTE复制中与COW相关部分:


/** copy one vm_area from one task to the other. Assumes the page tables* already present in the new task to be cleared in the whole range* covered by this vma.*/static inline unsigned long
copy_one_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,pte_t *dst_pte, pte_t *src_pte, struct vm_area_struct *vma,unsigned long addr, int *rss)
{... ...if (is_cow_mapping(vm_flags) && pte_write(pte)) {ptep_set_wrprotect(src_mm, addr, src_pte);pte = pte_wrprotect(pte);}... ...if (vm_flags & VM_SHARED)pte = pte_mkclean(pte);pte = pte_mkold(pte);... ...page = vm_normal_page(vma, addr, pte);if (page) {get_page(page);page_dup_rmap(page, false);rss[mm_counter(page)]++;}out_set_pte:set_pte_at(dst_mm, addr, dst_pte, pte);return 0;
}

复制PTE几个比较关键的地方:

  • pte_wrprotect: 如果是针对COW情况,且用于写权限会将其pte中 W/R中的写权限清除,其中虚拟地址标记为VM_SHARED 共享 不会将保护位清除,只会针对私有且写权限才会将写权限清除,这样后续子进程对该页进行写将会触发page fault,重新申请内存
  • ptep_set_wrprotect:即清除父进程中的PTE 写权限,这样子进程和父进程都将清除写权限,先写者将会触发page table.
  • pte_mkclean: 如果是该也是该页是共享的,则将该页中的dirty标记位清除,防止造成不一致性问题。
  • vm_normal_page:获取该PTE对应物理page。
  • get_page:page计数+1
  • page_dup_rmap:因为该页被两个进程使用,需要加入到反向映射中,便于后续管理。

COW Page Fault

fork之后 父子共对应一个page 的私有映射,对应的PTE都被进行写保护,当其中任何一个进程先对该页进行写操作时将触发COW page fault:

static vm_fault_t handle_pte_fault(struct vm_fault *vmf)
{... ...//COW page faultif (vmf->flags & FAULT_FLAG_WRITE) {if (!pte_write(entry))return do_wp_page(vmf);entry = pte_mkdirty(entry);}... ...
}
  • 处理page fault时,当vma中flag标记位可写,但是PTE没有写权限时,就被认为时触发COW page fault,调用do_wp_page处理。

do_wp_page

do_wp_page主要处理page 不同页面使用场景的处理:

static vm_fault_t do_wp_page(struct vm_fault *vmf)__releases(vmf->ptl)
{struct vm_area_struct *vma = vmf->vma;if (userfaultfd_pte_wp(vma, *vmf->pte)) {pte_unmap_unlock(vmf->pte, vmf->ptl);return handle_userfault(vmf, VM_UFFD_WP);}//获取对应页面vmf->page = vm_normal_page(vma, vmf->address, vmf->orig_pte);if (!vmf->page) {//特殊页面 COWif ((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==(VM_WRITE|VM_SHARED))return wp_pfn_shared(vmf);pte_unmap_unlock(vmf->pte, vmf->ptl);return wp_page_copy(vmf);}//匿名页if (PageAnon(vmf->page)) {//ksm 页面复用if (PageKsm(vmf->page)) {bool reused = reuse_ksm_page(vmf->page, vmf->vma,vmf->address);unlock_page(vmf->page);if (!reused)goto copy;wp_page_reuse(vmf);return VM_FAULT_WRITE;}//页面是否只有一个进程映射,如果只映射一个进程,不用申请新的页面,直接修改PTE即可if (reuse_swap_page(vmf->page, &total_map_swapcount)) {if (total_map_swapcount == 1) {page_move_anon_rmap(vmf->page, vma);}unlock_page(vmf->page);wp_page_reuse(vmf);return VM_FAULT_WRITE;}unlock_page(vmf->page);} else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==(VM_WRITE|VM_SHARED))) {//共享页面处理return wp_page_shared(vmf);}get_page(vmf->page);pte_unmap_unlock(vmf->pte, vmf->ptl);//执行COW,申请一个新的页面并copyreturn wp_page_copy(vmf);
}

主要针对几种场景:

  • wp_pfn_shared:特殊共享页面(包括VM_MIXEDMAP或 VM_PFNMAP)执行操作。
  • wp_page_reuse: 可以复用页面。
  • pag只对应一个进程,因此不需要申请新的page,用来刷新PTE。
  • wp_page_shared:处理可写并且共享的普通映射页面。
  • wp_page_copy:处理一个正常页面,即COW场景,页面属于私有,需要申请一个新的page,并copy刷新对应PTE。

wp_page_copy

wp_page_copy 主要功能是新申请一个page,并将旧页内存cop到新页,并刷新PTE:

static vm_fault_t wp_page_copy(struct vm_fault *vmf)
{// 从buddy中申请一个新页if (is_zero_pfn(pte_pfn(vmf->orig_pte))) {//旧页内容全部为0,重新申请一个全0的页面,由于旧页也为0,因此不需要copynew_page = alloc_zeroed_user_highpage_movable(vma,vmf->address);if (!new_page)goto oom;} else {//旧页内容不为0,新申请一个pagenew_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma,vmf->address);if (!new_page)goto oom;//将old page中的内容copy到新页中        if (!cow_user_page(new_page, old_page, vmf)) {/** COW failed, if the fault was solved by other,* it's fine. If not, userspace would re-fault on* the same address and we will handle the fault* from the second attempt.*/put_page(new_page);if (old_page)put_page(old_page);return 0;}}if (mem_cgroup_charge(new_page, mm, GFP_KERNEL))goto oom_free_new;cgroup_throttle_swaprate(new_page, GFP_KERNEL);//刷新page flag:PG_uptodate__SetPageUptodate(new_page);mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, mm,vmf->address & PAGE_MASK,(vmf->address & PAGE_MASK) + PAGE_SIZE);mmu_notifier_invalidate_range_start(&range);/** Re-check the pte - we dropped the lock*/vmf->pte = pte_offset_map_lock(mm, vmf->pmd, vmf->address, &vmf->ptl);if (likely(pte_same(*vmf->pte, vmf->orig_pte))) {//刷新pte cacheflush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte));//组装PTE entryentry = mk_pte(new_page, vma->vm_page_prot);entry = pte_sw_mkyoung(entry);entry = maybe_mkwrite(pte_mkdirty(entry), vma);//清除PTE中内容ptep_clear_flush_notify(vma, vmf->address, vmf->pte);//添加page 到反向映射中page_add_new_anon_rmap(new_page, vma, vmf->address, false);//添加到active LRU中lru_cache_add_active_or_unevictable(new_page, vma);//通知PTE更新 set_pte_at_notify(mm, vmf->address, vmf->pte, entry);//刷新MMUupdate_mmu_cache(vma, vmf->address, vmf->pte);... ...        } else {update_mmu_tlb(vma, vmf->address, vmf->pte);}... ...
}

主要处理流程代表了一个对PTE操作的基本步骤:

  • 从buddy中申请新的page,如果旧page内容全部为0,则申请一个全0的页面。
  • 如果旧page内容不为0,则调用cow_user_page,将旧page中的内容copy到新页。
  • __SetPageUptodate: 更新page flag PG_uptodate。
  • flush_cache_page:刷新page table cache
  • 组装entry,pte标记为刚访问过
  • ptep_clear_flush_notify:清除PTE中内容。
  • page_add_new_anon_rmap:添加匿名反向映射。
  • lru_cache_add_active_or_unevictable:添加active LRU。
  • set_pte_at_notify:刷新PTE
  • update_mmu_cache:刷新MMU。

linux那些事之copy on write(COW)相关推荐

  1. linux那些事之page fault(AMD64架构)(user space)(2)

    do_user_addr_fault 用户空间地址处理是page fault主要处理流程,x86 64位系统主要是do_user_addr_fault()函数 该处理部分是x86架构特有部分 即与架构 ...

  2. linux那些事之follow_page

    follow_page()函数是内核中用于根据虚拟地址查找对应的物理页函数,函数定义如下: struct page *follow_page(struct vm_area_struct *vma, u ...

  3. R语言ggplot2可视化绘制一头奶牛、Linux下使用cowsay打印奶牛(cow)

    R语言ggplot2可视化绘制一头奶牛.Linux下使用cowsay打印奶牛(cow) 目录 R语言ggplot2可视化绘制一头奶牛.Linux下使用cowsay打印奶牛

  4. linux那些事之LRU(3)

    继续承接<linux那些事之LRU(2)>,shrink_active_list()函数中需要将调用isolate_lru_pages函数将active LRU中从链表尾部隔离出nr_to ...

  5. linux那些事之early pape fault

    由linux那些事之中断与异常(AMD64架构)_2>分析可知,在kernel启动过程中首先安装的early中断dt_setup_early_handler中,主要是对page fault中断支 ...

  6. linux那些事之TLB(Translation-Lookaside Buffer)无效操作

    TLB 为了加速虚拟地址转换物理地址过程,CPU内部一般都集成TLB硬件单元,通过缓存存取虚拟地址与物理地址映射关系,避免再通过MMU 通过多级查表引入多次内存开销,直接将映射关系存储到硬件单元中,本 ...

  7. linux那些事之pin memory相关API

    内核中为pin memory 用户空间申请物理内存除了get_user_pages() API函数之外,还有其他相关一系列函数,主要位于mm\gup.c 主要都是针对get_user_pages进行的 ...

  8. 网吧软件正版化,别拿Linux说事

    <!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } --> 5月15日,媒体盛传微软开始对东莞市网吧行业"下手" ...

  9. Linux内核RCU(Read Copy Update)锁简析

    在非常早曾经,大概是2009年的时候.写过一篇关于Linux RCU锁的文章<RCU锁在linux内核的演变>,如今我承认.那个时候我尽管懂了RCU锁,可是我没有能力用一种非常easy的描 ...

  10. linux那些事之LRU(4)

    当物理内存实际比较紧张时,内存水位处于较低water level时,会触发间接回收内存kswapd或者直接回收内存,从inactive LRU list中将不常用的内存 置换到内存中,整个调用过程大概 ...

最新文章

  1. 实战 Prometheus 搭建监控系统
  2. OpenCV4 C++学习 必备基础语法知识二
  3. 赢在CSDN——如何在CSDN赚到一桶金
  4. 是单片机高手还是菜鸟?看看你的程序框架就知道了
  5. SpringBoot之AOP之基本使用
  6. 模板方法(Template)模式
  7. MySQL命名、设计及使用规范--------来自标点符的《MySQL命名、设计及使用规范》...
  8. android优化中国风应用、完整NBA客户端、动态积分效果、文件传输、小说阅读器等源码...
  9. iis 创建应用程序池的方法与分析第3/3页
  10. linux系统如何启动rpcbind,在Linux系统上关闭rpcbind、postfix、rpc.statd、hpiod服务的方法...
  11. 绿坝花季护航,为何如此吸引眼球?
  12. Python打印2018年的日历(【问题描述】 打印2018年的日历 【输入形式】 【输出形式】 【样例输入】 【样例输出】)
  13. 一些学习网址,centos镜像下载地址
  14. 最网最全python框架--scrapy(体系学习,爬取全站校花图片),学完显著提高爬虫能力(附源代码),突破各种反爬
  15. php 获取月份的周数,PHP获取当前月份的周数只能使用php
  16. Pyinstaller Pmw
  17. Ubuntu1804安装及基本配置
  18. upper_bound和lower_bound用法(史上最全)
  19. 利用html表单制作个人简历
  20. 项目笔记(一):实验——用神经网络实现midi音乐旋律音轨的确定

热门文章

  1. 无线射频识别技术与条形码的比较
  2. 匀速运动小车卡尔曼_平衡小车卡尔曼滤波算法使用心得
  3. 如何将360浏览器兼容IE8、IE7
  4. java date 最小值_Java_Math/Date
  5. Kubernetes API的流编解码器StreamSerializer
  6. 设计文档应该怎么写?
  7. Qt实现类似QQ好友列表
  8. 传奇DBC2000安装及配置图文详细教程
  9. 1分钟教会python代码实现电影下载
  10. 一个轻量级多功能免费开源web聊天室