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

       在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. }  
【作者】张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
上一篇:Spring Boot使用过滤器和拦截器分别实现REST接口简易安全认证


下一篇:[PeterDLax著泛函分析习题参考解答]第6章 Hilbert 空间