zoukankan      html  css  js  c++  java
  • 用户空间缺页异常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. }  
  • 相关阅读:
    僵尸进程
    理论整理
    SQLServer相关概念
    存储过程
    我的简书地址
    swift pragma mark
    苹果iOS开发中如何直接跳转到App Store页面
    使用cocoadPod updating local specs repositories 卡主
    iOS 代码格式化插件Clang-Format
    iOS错误:AFNetworking Error Domain=NSURLErrorDomain Code=-999
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/5663397.html
Copyright © 2011-2022 走看看