用户空间缺页异常pte_handle_fault()分析--(下)--写时复制【转】

转自:http://blog.csdn.net/vanbreaker/article/details/7955713

版权声明:本文为博主原创文章,未经博主允许不得转载。

       在pte_handle_fault()中,如果触发异常的页存在于主存中,那么该异常往往是由写了一个只读页触发的,此时需要进行COW(写时复制操作)。如当一个父进程通过fork()创建了一个子进程时,子进程将会共享父进程的页框。之后,无论是父进程还是子进程要对相应的内存进行写操作,都要进行COW,也就是为自己重新分配一个页框,并把之前的数据复制到页框中去,再写。

[cpp] view plain copy

 

  1. static inline int handle_pte_fault(struct mm_struct *mm,  
  2.         struct vm_area_struct *vma, unsigned long address,  
  3.         pte_t *pte, pmd_t *pmd, unsigned int flags)  
  4. {  
  5.     pte_t entry;  
  6.     spinlock_t *ptl;  
  7.   
  8.     entry = *pte;  
  9.   
  10.     ...  
  11.     ...  
  12.     ...  
  13.     /********页在主存中的情况***********/  
  14.       
  15.     ptl = pte_lockptr(mm, pmd);  
  16.     spin_lock(ptl);  
  17.     if (unlikely(!pte_same(*pte, entry)))  
  18.         goto unlock;  
  19.     if (flags & FAULT_FLAG_WRITE) {//异常由写访问触发  
  20.         if (!pte_write(entry))//而对应的页是不可写的  
  21.             return do_wp_page(mm, vma, address, //此时必须进行写时复制的操作  
  22.                     pte, pmd, ptl, entry);  
  23.         entry = pte_mkdirty(entry);  
  24.     }  
  25.     entry = pte_mkyoung(entry);  
  26.     if (ptep_set_access_flags(vma, address, pte, entry, flags & FAULT_FLAG_WRITE)) {  
  27.         update_mmu_cache(vma, address, entry);  
  28.     } else {  
  29.         /* 
  30.          * This is needed only for protection faults but the arch code 
  31.          * is not yet telling us if this is a protection fault or not. 
  32.          * This still avoids useless tlb flushes for .text page faults 
  33.          * with threads. 
  34.          */  
  35.         if (flags & FAULT_FLAG_WRITE)  
  36.             flush_tlb_page(vma, address);  
  37.     }  
  38. unlock:  
  39.     pte_unmap_unlock(pte, ptl);  
  40.     return 0;  
  41. }  

可以看到,hand_pte_fault()函数处理页存在于主存中的情况的关键操作都集中在do_wp_page()函数上。该函数是用来处理COW的,不过在COW之前先要做一些检查,比如说,如果对应的页只有一个进程使用,那么便可以直接修改页的权限为可读可写,而不进行COW。总之,不到不得以的情况下是不会进行COW的。

[cpp] view plain copy

 

  1. static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,  
  2.         unsigned long address, pte_t *page_table, pmd_t *pmd,  
  3.         spinlock_t *ptl, pte_t orig_pte)  
  4. {  
  5.     struct page *old_page, *new_page;  
  6.     pte_t entry;  
  7.     int reuse = 0, ret = 0;  
  8.     int page_mkwrite = 0;  
  9.     struct page *dirty_page = NULL;  
  10.   
  11.     old_page = vm_normal_page(vma, address, orig_pte);//获取共享页  
  12.     if (!old_page) {//获取共享页失败  
  13.         /* 
  14.          * VM_MIXEDMAP !pfn_valid() case 
  15.          * 
  16.          * We should not cow pages in a shared writeable mapping. 
  17.          * Just mark the pages writable as we can't do any dirty 
  18.          * accounting on raw pfn maps. 
  19.          */  
  20.          /*如果vma的映射本来就是共享且可写的,则跳转至reuse直接使用orig_pte对应的页*/  
  21.         if ((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==  
  22.                      (VM_WRITE|VM_SHARED))  
  23.             goto reuse;  
  24.         /*否则跳转至gotten分配一个页*/  
  25.         goto gotten;  
  26.     }  
  27.   
  28.     /* 
  29.      * Take out anonymous pages first, anonymous shared vmas are 
  30.      * not dirty accountable. 
  31.      */  
  32.      /*下面首先判断匿名页的情况,如果old_page是匿名页,并且只有一个进程使用它(reuse为1),则 
  33.         则直接使用该页*/  
  34.     if (PageAnon(old_page) && !PageKsm(old_page)) {  
  35.         /*这里先判断是否有其他进程竞争,修改了页表*/  
  36.         if (!trylock_page(old_page)) {  
  37.             page_cache_get(old_page);  
  38.             pte_unmap_unlock(page_table, ptl);  
  39.             lock_page(old_page);  
  40.             page_table = pte_offset_map_lock(mm, pmd, address,  
  41.                              &ptl);  
  42.             if (!pte_same(*page_table, orig_pte)) {  
  43.                 unlock_page(old_page);  
  44.                 page_cache_release(old_page);  
  45.                 goto unlock;  
  46.             }  
  47.             page_cache_release(old_page);  
  48.         }  
  49.         /*确定没有其他进程竞争,则进行reuse判断,通过reuse_swap_page()函数判断 
  50.          old_page的_mapcount字段是否为0,是的话则表明只有一个进程使用该匿名页*/  
  51.         reuse = reuse_swap_page(old_page);  
  52.         unlock_page(old_page);  
  53.     } else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==  
  54.                     (VM_WRITE|VM_SHARED))) {//如果vma的映射本来就是共享且可写的  
  55.         /* 
  56.          * Only catch write-faults on shared writable pages, 
  57.          * read-only shared pages can get COWed by 
  58.          * get_user_pages(.write=1, .force=1). 
  59.          */  
  60.         if (vma->vm_ops && vma->vm_ops->page_mkwrite) {  
  61.             struct vm_fault vmf;  
  62.             int tmp;  
  63.   
  64.             vmf.virtual_address = (void __user *)(address &  
  65.                                 PAGE_MASK);  
  66.             vmf.pgoff = old_page->index;  
  67.             vmf.flags = FAULT_FLAG_WRITE|FAULT_FLAG_MKWRITE;  
  68.             vmf.page = old_page;  
  69.   
  70.             /* 
  71.              * Notify the address space that the page is about to 
  72.              * become writable so that it can prohibit this or wait 
  73.              * for the page to get into an appropriate state. 
  74.              * 
  75.              * We do this without the lock held, so that it can 
  76.              * sleep if it needs to. 
  77.              */  
  78.             page_cache_get(old_page);//增加old_page的引用计数作为保护  
  79.             pte_unmap_unlock(page_table, ptl);  
  80.   
  81.             /*这里通知即将修改页的权限*/  
  82.             tmp = vma->vm_ops->page_mkwrite(vma, &vmf);  
  83.   
  84.             /*如果无法修改的话,则跳转到unwritable_page*/  
  85.             if (unlikely(tmp &  
  86.                     (VM_FAULT_ERROR | VM_FAULT_NOPAGE))) {  
  87.                 ret = tmp;  
  88.                 goto unwritable_page;  
  89.             }  
  90.             if (unlikely(!(tmp & VM_FAULT_LOCKED))) {  
  91.                 lock_page(old_page);  
  92.                 if (!old_page->mapping) {  
  93.                     ret = 0; /* retry the fault */  
  94.                     unlock_page(old_page);  
  95.                     goto unwritable_page;  
  96.                 }  
  97.             } else  
  98.                 VM_BUG_ON(!PageLocked(old_page));  
  99.   
  100.             /* 
  101.              * Since we dropped the lock we need to revalidate 
  102.              * the PTE as someone else may have changed it.  If 
  103.              * they did, we just return, as we can count on the 
  104.              * MMU to tell us if they didn't also make it writable. 
  105.              */  
  106.              /*走到这里表示已经成功修改了页的权限了,这里同样重新获取页表,判断是否和之前一致*/  
  107.             page_table = pte_offset_map_lock(mm, pmd, address,  
  108.                              &ptl);  
  109.             if (!pte_same(*page_table, orig_pte)) {  
  110.                 unlock_page(old_page);  
  111.                 page_cache_release(old_page);  
  112.                 goto unlock;  
  113.             }  
  114.   
  115.             page_mkwrite = 1;  
  116.         }  
  117.         dirty_page = old_page;  
  118.         get_page(dirty_page);  
  119.         reuse = 1;  
  120.     }  
  121.   
  122.     if (reuse) {//reuse处理,也就是说不进行COW,可以直接在old_page上进行写操作  
  123. reuse:  
  124.         flush_cache_page(vma, address, pte_pfn(orig_pte));  
  125.         entry = pte_mkyoung(orig_pte);//标记_PAGE_ACCESSED位  
  126.         entry = maybe_mkwrite(pte_mkdirty(entry), vma);//将页的权限修改为可读可写,并且标记为脏页  
  127.         if (ptep_set_access_flags(vma, address, page_table, entry,1))  
  128.             update_mmu_cache(vma, address, entry);  
  129.         ret |= VM_FAULT_WRITE;  
  130.         goto unlock;  
  131.     }  
  132.   
  133.     /* 
  134.      * Ok, we need to copy. Oh, well.. 
  135.      */  
  136.      /***************终于走到了不得已的一步了,下面只好进行COW了********************/  
  137.     page_cache_get(old_page);  
  138. gotten:  
  139.     pte_unmap_unlock(page_table, ptl);  
  140.   
  141.     if (unlikely(anon_vma_prepare(vma)))  
  142.         goto oom;  
  143.   
  144.     if (is_zero_pfn(pte_pfn(orig_pte))) {  
  145.         new_page = alloc_zeroed_user_highpage_movable(vma, address);//分配一个零页面  
  146.         if (!new_page)  
  147.             goto oom;  
  148.     } else {  
  149.         new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, address);//分配一个非零页面  
  150.         if (!new_page)  
  151.             goto oom;  
  152.         cow_user_page(new_page, old_page, address, vma);//将old_page中的数据拷贝到new_page  
  153.     }  
  154.     __SetPageUptodate(new_page);  
  155.   
  156.     /* 
  157.      * Don't let another task, with possibly unlocked vma, 
  158.      * keep the mlocked page. 
  159.      */  
  160.     if ((vma->vm_flags & VM_LOCKED) && old_page) {  
  161.         lock_page(old_page);    /* for LRU manipulation */  
  162.         clear_page_mlock(old_page);  
  163.         unlock_page(old_page);  
  164.     }  
  165.   
  166.     if (mem_cgroup_newpage_charge(new_page, mm, GFP_KERNEL))  
  167.         goto oom_free_new;  
  168.   
  169.     /* 
  170.      * Re-check the pte - we dropped the lock 
  171.      */  
  172.     page_table = pte_offset_map_lock(mm, pmd, address, &ptl);  
  173.     if (likely(pte_same(*page_table, orig_pte))) {  
  174.         if (old_page) {  
  175.             if (!PageAnon(old_page)) {  
  176.                 dec_mm_counter(mm, file_rss);  
  177.                 inc_mm_counter(mm, anon_rss);  
  178.             }  
  179.         } else  
  180.             inc_mm_counter(mm, anon_rss);  
  181.         flush_cache_page(vma, address, pte_pfn(orig_pte));  
  182.         entry = mk_pte(new_page, vma->vm_page_prot);//获取new_page的pte  
  183.         entry = maybe_mkwrite(pte_mkdirty(entry), vma);//修改new_page的权限  
  184.         /* 
  185.          * Clear the pte entry and flush it first, before updating the 
  186.          * pte with the new entry. This will avoid a race condition 
  187.          * seen in the presence of one thread doing SMC and another 
  188.          * thread doing COW. 
  189.          */  
  190.         ptep_clear_flush(vma, address, page_table);  
  191.         page_add_new_anon_rmap(new_page, vma, address);  
  192.         /* 
  193.          * We call the notify macro here because, when using secondary 
  194.          * mmu page tables (such as kvm shadow page tables), we want the 
  195.          * new page to be mapped directly into the secondary page table. 
  196.          */  
  197.         set_pte_at_notify(mm, address, page_table, entry);  
  198.         update_mmu_cache(vma, address, entry);  
  199.         if (old_page) {  
  200.             /* 
  201.              * Only after switching the pte to the new page may 
  202.              * we remove the mapcount here. Otherwise another 
  203.              * process may come and find the rmap count decremented 
  204.              * before the pte is switched to the new page, and 
  205.              * "reuse" the old page writing into it while our pte 
  206.              * here still points into it and can be read by other 
  207.              * threads. 
  208.              * 
  209.              * The critical issue is to order this 
  210.              * page_remove_rmap with the ptp_clear_flush above. 
  211.              * Those stores are ordered by (if nothing else,) 
  212.              * the barrier present in the atomic_add_negative 
  213.              * in page_remove_rmap. 
  214.              * 
  215.              * Then the TLB flush in ptep_clear_flush ensures that 
  216.              * no process can access the old page before the 
  217.              * decremented mapcount is visible. And the old page 
  218.              * cannot be reused until after the decremented 
  219.              * mapcount is visible. So transitively, TLBs to 
  220.              * old page will be flushed before it can be reused. 
  221.              */  
  222.             page_remove_rmap(old_page);  
  223.         }  
  224.   
  225.         /* Free the old page.. */  
  226.         new_page = old_page;  
  227.         ret |= VM_FAULT_WRITE;  
  228.     } else  
  229.         mem_cgroup_uncharge_page(new_page);  
  230.   
  231.     if (new_page)  
  232.         page_cache_release(new_page);  
  233.     if (old_page)  
  234.         page_cache_release(old_page);  
  235. unlock:  
  236.     pte_unmap_unlock(page_table, ptl);  
  237.     if (dirty_page) {  
  238.         /* 
  239.          * Yes, Virginia, this is actually required to prevent a race 
  240.          * with clear_page_dirty_for_io() from clearing the page dirty 
  241.          * bit after it clear all dirty ptes, but before a racing 
  242.          * do_wp_page installs a dirty pte. 
  243.          * 
  244.          * do_no_page is protected similarly. 
  245.          */  
  246.         if (!page_mkwrite) {  
  247.             wait_on_page_locked(dirty_page);  
  248.             set_page_dirty_balance(dirty_page, page_mkwrite);  
  249.         }  
  250.         put_page(dirty_page);  
  251.         if (page_mkwrite) {  
  252.             struct address_space *mapping = dirty_page->mapping;  
  253.   
  254.             set_page_dirty(dirty_page);  
  255.             unlock_page(dirty_page);  
  256.             page_cache_release(dirty_page);  
  257.             if (mapping)    {  
  258.                 /* 
  259.                  * Some device drivers do not set page.mapping 
  260.                  * but still dirty their pages 
  261.                  */  
  262.                 balance_dirty_pages_ratelimited(mapping);  
  263.             }  
  264.         }  
  265.   
  266.         /* file_update_time outside page_lock */  
  267.         if (vma->vm_file)  
  268.             file_update_time(vma->vm_file);  
  269.     }  
  270.     return ret;  
  271. oom_free_new:  
  272.     page_cache_release(new_page);  
  273. oom:  
  274.     if (old_page) {  
  275.         if (page_mkwrite) {  
  276.             unlock_page(old_page);  
  277.             page_cache_release(old_page);  
  278.         }  
  279.         page_cache_release(old_page);  
  280.     }  
  281.     return VM_FAULT_OOM;  
  282.   
  283. unwritable_page:  
  284.     page_cache_release(old_page);  
  285.     return ret;  
  286. }  
时间: 2024-09-22 19:24:53

用户空间缺页异常pte_handle_fault()分析--(下)--写时复制【转】的相关文章

用户空间缺页异常pte_handle_fault()分析--(上)【转】

转自:http://blog.csdn.net/vanbreaker/article/details/7881206 版权声明:本文为博主原创文章,未经博主允许不得转载.        前面简单的分析了内核处理用户空间缺页异常的流程,进入到了handle_mm_fault()函数,该函数为触发缺页异常的地址address分配各级的页目录,也就是说现在已经拥有了一个和address配对的pte了,但是这个pte如何去映射物理页框,内核又得根据pte的状态进行分类和判断,而这个过程又会牵扯出一些其他

reference counting:PHP源码分析-变量的引用计数、写时复制(Reference counting & Copy-on-Write)

PHP语法中有两种赋值方式:引用赋值.非引用赋值.<?php$a = 1;$b = $a; // 非引用赋值$c = &$b; // 引用赋值从表面看,通常会这样认为:"引用赋值就是两个变量对应同一个变量(在C中其实就是一个zval),非引用赋值则是直接产生的一个新的变量(zval),同时将值copy过来".这种认为在大部分情况下都是可以想通的.(#1)但有些情况下则会显得非常低效,例如:(#2)<?phpfunction print_arr($arr){//非引用

Linux进程管理——fork()和写时复制

写时复制技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建:当发出fork(  )系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程.这种行为是非常耗时的,因为它需要:   ·      为子进程的页表分配页面 ·      为子进程的页分配页面 ·      初始化子进程的页表 ·      把父进程的页复制到子进程相应的页中   创建一个地址空间的这种方法涉及许多内存访问,消耗许多CPU周期,并且完全破坏了高速缓存中的内容.在大多数情况下,这样做常常是毫无意义的,

关于linux写时复制的一个疑问,C程序

问题描述 关于linux写时复制的一个疑问,C程序 linux写时复制的机制就是,当fork出一个子进程的时候,子进程并不立刻复制数据段,而是当子进程要修改数据时才分配相应内存给变量.按照这样的原理,我用C写了一个程序,声明一个全局变量并初始化,然后在进程里fork出一个进程,这时在父子进程输出全局变量的地址都是一样的,我在子进程修改变量值后,同样在父子进程输出变量地址,却地址还是一样的!这到底是为什么?写的时候不是要分配内存的吗?怎么地址值还是一样!路过的朋友帮我解解惑ˊ_>ˋ 解决方案 你这

《C++面向对象高效编程(第2版)》——4.10 “写时复制”的概念

4.10 "写时复制"的概念 C++面向对象高效编程(第2版) 通过以上的讨论可知,TString类相当易懂和易实现.如果经常使用该类的对象作为函数参数和按值返回的值,会出现什么情况?因为TString类使用了深复制语义,如果TString 图4-12 类对象中的字符数目很多,将花费很长的时间来复制字符和删除动态分配内存.这也意味着,创建对象和销毁对象的开销很大.我们设计TString类的初衷,就是希望客户在使用字符串的地方,都能使用TString类对象.但是,如果创建.复制.赋值和销

PHP中copy on write写时复制机制介绍_php技巧

什么是写时复制(Copy On Write)? 答:在复制一个对象的时候并不是真正的把原先的对象复制到内存的另外一个位置上,而是在新对象的内存映射表中设置一个指针,指向源对象的位置,并把那块内存的Copy-On-Write位设置为1.这样,在对新的对象执行读操作的时候,内存数据不发生任何变动,直接执行读操作:而在对新的对象执行写操作时,将真正的对象复制到新的内存地址中,并修改新对象的内存映射表指向这个新的位置,并在新的内存位置上执行写操作. 这个技术需要跟虚拟内存和分页同时使用,好处就是在执行复

linux缺页异常处理--用户空间【转】

转自:http://blog.csdn.net/vanbreaker/article/details/7870769 版权声明:本文为博主原创文章,未经博主允许不得转载. 用户空间的缺页异常可以分为两种情况-- 1.触发异常的线性地址处于用户空间的vma中,但还未分配物理页,如果访问权限OK的话内核就给进程分配相应的物理页了 2.触发异常的线性地址不处于用户空间的vma中,这种情况得判断是不是因为用户进程的栈空间消耗完而触发的缺页异常,如果是的话则在用户空间对栈区域进行扩展,并且分配相应的物理页

《C++多线程编程实战》——2.9 在用户空间实现线程

2.9 在用户空间实现线程 既可以在用户空间也可以在内核中实现线程包.具体选择在哪里实现还存在一些争议,在一些实现中可能会混合使用内核线程和用户线程. 我们将讨论在不同地方实现线程包的方法及优缺点.第1种方法是,把整个线程包放进用户空间,内核完全不知道.就内核而言,它管理着普通的单线程进程.这种方法的优点和最显著的优势是,可以在不支持线程的操作系统中实现用户级线程包. 过去,传统的操作系统就采用这种方法,甚至沿用至今.用这种方法,线程可以通过库来实现.所有这些实现都具有相同的通用结构.线程运行在

五个 Linux 下用户空间的调试工具

五个 Linux 下用户空间的调试工具 根据定义,调试工具是那些那些使我们能够监测.控制和纠正其他程序的程序.我们为什么应该用调试工具呢? 在有些情况下,运行一些程序的时候我们会被卡住,我们需要明白究竟发生了什么. 例如,我们正在运行应用程序,它产生了一些错误消息.要修复这些错误,我们应该先找出为什么产生这些错误的消息和这些错误消息从哪里产生的. 一个应用程序可能突然挂起,我们必须了解其他什么进程同时在运行.我们可能还必须弄清楚某个进程挂起的时候在做什么.为了剖析这些细节, 我们需要调试工具的帮