设为首页 收藏本站
查看: 1212|回复: 0

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

[复制链接]

尚未签到

发表于 2017-6-25 08:53:16 | 显示全部楼层 |阅读模式
转自:http://blog.csdn.net/vanbreaker/article/details/7955713
版权声明:本文为博主原创文章,未经博主允许不得转载。

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



[cpp] view plain copy

  • static inline int handle_pte_fault(struct mm_struct *mm,  
  •         struct vm_area_struct *vma, unsigned long address,  
  •         pte_t *pte, pmd_t *pmd, unsigned int flags)  
  • {
  •     pte_t entry;
  •     spinlock_t *ptl;

  •     entry = *pte;

  •     ...
  •     ...
  •     ...
  •     /********页在主存中的情况***********/  

  •     ptl = pte_lockptr(mm, pmd);
  •     spin_lock(ptl);
  •     if (unlikely(!pte_same(*pte, entry)))  
  •         goto unlock;  
  •     if (flags & FAULT_FLAG_WRITE) {//异常由写访问触发  
  •         if (!pte_write(entry))//而对应的页是不可写的  
  •             return do_wp_page(mm, vma, address, //此时必须进行写时复制的操作  
  •                     pte, pmd, ptl, entry);
  •         entry = pte_mkdirty(entry);
  •     }
  •     entry = pte_mkyoung(entry);
  •     if (ptep_set_access_flags(vma, address, pte, entry, flags & FAULT_FLAG_WRITE)) {  
  •         update_mmu_cache(vma, address, entry);
  •     } else {  
  •         /*
  •          * This is needed only for protection faults but the arch code
  •          * is not yet telling us if this is a protection fault or not.
  •          * This still avoids useless tlb flushes for .text page faults
  •          * with threads.
  •          */  
  •         if (flags & FAULT_FLAG_WRITE)  
  •             flush_tlb_page(vma, address);
  •     }
  • unlock:
  •     pte_unmap_unlock(pte, ptl);
  •     return 0;  
  • }
  可以看到,hand_pte_fault()函数处理页存在于主存中的情况的关键操作都集中在do_wp_page()函数上。该函数是用来处理COW的,不过在COW之前先要做一些检查,比如说,如果对应的页只有一个进程使用,那么便可以直接修改页的权限为可读可写,而不进行COW。总之,不到不得以的情况下是不会进行COW的。



[cpp] view plain copy

  • static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,  
  •         unsigned long address, pte_t *page_table, pmd_t *pmd,  
  •         spinlock_t *ptl, pte_t orig_pte)
  • {
  •     struct page *old_page, *new_page;  
  •     pte_t entry;
  •     int reuse = 0, ret = 0;  
  •     int page_mkwrite = 0;  
  •     struct page *dirty_page = NULL;  

  •     old_page = vm_normal_page(vma, address, orig_pte);//获取共享页  
  •     if (!old_page) {//获取共享页失败  
  •         /*
  •          * VM_MIXEDMAP !pfn_valid() case
  •          *
  •          * We should not cow pages in a shared writeable mapping.
  •          * Just mark the pages writable as we can't do any dirty
  •          * accounting on raw pfn maps.
  •          */  
  •          /*如果vma的映射本来就是共享且可写的,则跳转至reuse直接使用orig_pte对应的页*/  
  •         if ((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==  
  •                      (VM_WRITE|VM_SHARED))
  •             goto reuse;  
  •         /*否则跳转至gotten分配一个页*/  
  •         goto gotten;  
  •     }

  •     /*
  •      * Take out anonymous pages first, anonymous shared vmas are
  •      * not dirty accountable.
  •      */  
  •      /*下面首先判断匿名页的情况,如果old_page是匿名页,并且只有一个进程使用它(reuse为1),则
  •         则直接使用该页*/  
  •     if (PageAnon(old_page) && !PageKsm(old_page)) {  
  •         /*这里先判断是否有其他进程竞争,修改了页表*/  
  •         if (!trylock_page(old_page)) {  
  •             page_cache_get(old_page);
  •             pte_unmap_unlock(page_table, ptl);
  •             lock_page(old_page);
  •             page_table = pte_offset_map_lock(mm, pmd, address,
  •                              &ptl);
  •             if (!pte_same(*page_table, orig_pte)) {  
  •                 unlock_page(old_page);
  •                 page_cache_release(old_page);
  •                 goto unlock;  
  •             }
  •             page_cache_release(old_page);
  •         }
  •         /*确定没有其他进程竞争,则进行reuse判断,通过reuse_swap_page()函数判断
  •          old_page的_mapcount字段是否为0,是的话则表明只有一个进程使用该匿名页*/  
  •         reuse = reuse_swap_page(old_page);
  •         unlock_page(old_page);
  •     } else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) ==  
  •                     (VM_WRITE|VM_SHARED))) {//如果vma的映射本来就是共享且可写的  
  •         /*
  •          * Only catch write-faults on shared writable pages,
  •          * read-only shared pages can get COWed by
  •          * get_user_pages(.write=1, .force=1).
  •          */  
  •         if (vma->vm_ops && vma->vm_ops->page_mkwrite) {  
  •             struct vm_fault vmf;  
  •             int tmp;  

  •             vmf.virtual_address = (void __user *)(address &  
  •                                 PAGE_MASK);
  •             vmf.pgoff = old_page->index;
  •             vmf.flags = FAULT_FLAG_WRITE|FAULT_FLAG_MKWRITE;
  •             vmf.page = old_page;

  •             /*
  •              * Notify the address space that the page is about to
  •              * become writable so that it can prohibit this or wait
  •              * for the page to get into an appropriate state.
  •              *
  •              * We do this without the lock held, so that it can
  •              * sleep if it needs to.
  •              */  
  •             page_cache_get(old_page);//增加old_page的引用计数作为保护  
  •             pte_unmap_unlock(page_table, ptl);

  •             /*这里通知即将修改页的权限*/  
  •             tmp = vma->vm_ops->page_mkwrite(vma, &vmf);

  •             /*如果无法修改的话,则跳转到unwritable_page*/  
  •             if (unlikely(tmp &  
  •                     (VM_FAULT_ERROR | VM_FAULT_NOPAGE))) {
  •                 ret = tmp;
  •                 goto unwritable_page;  
  •             }
  •             if (unlikely(!(tmp & VM_FAULT_LOCKED))) {  
  •                 lock_page(old_page);
  •                 if (!old_page->mapping) {  
  •                     ret = 0; /* retry the fault */  
  •                     unlock_page(old_page);
  •                     goto unwritable_page;  
  •                 }
  •             } else  
  •                 VM_BUG_ON(!PageLocked(old_page));

  •             /*
  •              * Since we dropped the lock we need to revalidate
  •              * the PTE as someone else may have changed it.  If
  •              * they did, we just return, as we can count on the
  •              * MMU to tell us if they didn't also make it writable.
  •              */  
  •              /*走到这里表示已经成功修改了页的权限了,这里同样重新获取页表,判断是否和之前一致*/  
  •             page_table = pte_offset_map_lock(mm, pmd, address,
  •                              &ptl);
  •             if (!pte_same(*page_table, orig_pte)) {  
  •                 unlock_page(old_page);
  •                 page_cache_release(old_page);
  •                 goto unlock;  
  •             }

  •             page_mkwrite = 1;
  •         }
  •         dirty_page = old_page;
  •         get_page(dirty_page);
  •         reuse = 1;
  •     }

  •     if (reuse) {//reuse处理,也就是说不进行COW,可以直接在old_page上进行写操作  
  • reuse:
  •         flush_cache_page(vma, address, pte_pfn(orig_pte));
  •         entry = pte_mkyoung(orig_pte);//标记_PAGE_ACCESSED位  
  •         entry = maybe_mkwrite(pte_mkdirty(entry), vma);//将页的权限修改为可读可写,并且标记为脏页  
  •         if (ptep_set_access_flags(vma, address, page_table, entry,1))  
  •             update_mmu_cache(vma, address, entry);
  •         ret |= VM_FAULT_WRITE;
  •         goto unlock;  
  •     }

  •     /*
  •      * Ok, we need to copy. Oh, well..
  •      */  
  •      /***************终于走到了不得已的一步了,下面只好进行COW了********************/  
  •     page_cache_get(old_page);
  • gotten:
  •     pte_unmap_unlock(page_table, ptl);

  •     if (unlikely(anon_vma_prepare(vma)))  
  •         goto oom;  

  •     if (is_zero_pfn(pte_pfn(orig_pte))) {  
  •         new_page = alloc_zeroed_user_highpage_movable(vma, address);//分配一个零页面  
  •         if (!new_page)  
  •             goto oom;  
  •     } else {  
  •         new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, address);//分配一个非零页面  
  •         if (!new_page)  
  •             goto oom;  
  •         cow_user_page(new_page, old_page, address, vma);//将old_page中的数据拷贝到new_page  
  •     }
  •     __SetPageUptodate(new_page);

  •     /*
  •      * Don't let another task, with possibly unlocked vma,
  •      * keep the mlocked page.
  •      */  
  •     if ((vma->vm_flags & VM_LOCKED) && old_page) {  
  •         lock_page(old_page);    /* for LRU manipulation */  
  •         clear_page_mlock(old_page);
  •         unlock_page(old_page);
  •     }

  •     if (mem_cgroup_newpage_charge(new_page, mm, GFP_KERNEL))  
  •         goto oom_free_new;  

  •     /*
  •      * Re-check the pte - we dropped the lock
  •      */  
  •     page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
  •     if (likely(pte_same(*page_table, orig_pte))) {  
  •         if (old_page) {  
  •             if (!PageAnon(old_page)) {  
  •                 dec_mm_counter(mm, file_rss);
  •                 inc_mm_counter(mm, anon_rss);
  •             }
  •         } else  
  •             inc_mm_counter(mm, anon_rss);
  •         flush_cache_page(vma, address, pte_pfn(orig_pte));
  •         entry = mk_pte(new_page, vma->vm_page_prot);//获取new_page的pte  
  •         entry = maybe_mkwrite(pte_mkdirty(entry), vma);//修改new_page的权限  
  •         /*
  •          * Clear the pte entry and flush it first, before updating the
  •          * pte with the new entry. This will avoid a race condition
  •          * seen in the presence of one thread doing SMC and another
  •          * thread doing COW.
  •          */  
  •         ptep_clear_flush(vma, address, page_table);
  •         page_add_new_anon_rmap(new_page, vma, address);
  •         /*
  •          * We call the notify macro here because, when using secondary
  •          * mmu page tables (such as kvm shadow page tables), we want the
  •          * new page to be mapped directly into the secondary page table.
  •          */  
  •         set_pte_at_notify(mm, address, page_table, entry);
  •         update_mmu_cache(vma, address, entry);
  •         if (old_page) {  
  •             /*
  •              * Only after switching the pte to the new page may
  •              * we remove the mapcount here. Otherwise another
  •              * process may come and find the rmap count decremented
  •              * before the pte is switched to the new page, and
  •              * "reuse" the old page writing into it while our pte
  •              * here still points into it and can be read by other
  •              * threads.
  •              *
  •              * The critical issue is to order this
  •              * page_remove_rmap with the ptp_clear_flush above.
  •              * Those stores are ordered by (if nothing else,)
  •              * the barrier present in the atomic_add_negative
  •              * in page_remove_rmap.
  •              *
  •              * Then the TLB flush in ptep_clear_flush ensures that
  •              * no process can access the old page before the
  •              * decremented mapcount is visible. And the old page
  •              * cannot be reused until after the decremented
  •              * mapcount is visible. So transitively, TLBs to
  •              * old page will be flushed before it can be reused.
  •              */  
  •             page_remove_rmap(old_page);
  •         }

  •         /* Free the old page.. */  
  •         new_page = old_page;
  •         ret |= VM_FAULT_WRITE;
  •     } else  
  •         mem_cgroup_uncharge_page(new_page);

  •     if (new_page)  
  •         page_cache_release(new_page);
  •     if (old_page)  
  •         page_cache_release(old_page);
  • unlock:
  •     pte_unmap_unlock(page_table, ptl);
  •     if (dirty_page) {  
  •         /*
  •          * Yes, Virginia, this is actually required to prevent a race
  •          * with clear_page_dirty_for_io() from clearing the page dirty
  •          * bit after it clear all dirty ptes, but before a racing
  •          * do_wp_page installs a dirty pte.
  •          *
  •          * do_no_page is protected similarly.
  •          */  
  •         if (!page_mkwrite) {  
  •             wait_on_page_locked(dirty_page);
  •             set_page_dirty_balance(dirty_page, page_mkwrite);
  •         }
  •         put_page(dirty_page);
  •         if (page_mkwrite) {  
  •             struct address_space *mapping = dirty_page->mapping;  

  •             set_page_dirty(dirty_page);
  •             unlock_page(dirty_page);
  •             page_cache_release(dirty_page);
  •             if (mapping)    {  
  •                 /*
  •                  * Some device drivers do not set page.mapping
  •                  * but still dirty their pages
  •                  */  
  •                 balance_dirty_pages_ratelimited(mapping);
  •             }
  •         }

  •         /* file_update_time outside page_lock */  
  •         if (vma->vm_file)  
  •             file_update_time(vma->vm_file);
  •     }
  •     return ret;  
  • oom_free_new:
  •     page_cache_release(new_page);
  • oom:
  •     if (old_page) {  
  •         if (page_mkwrite) {  
  •             unlock_page(old_page);
  •             page_cache_release(old_page);
  •         }
  •         page_cache_release(old_page);
  •     }
  •     return VM_FAULT_OOM;  

  • unwritable_page:
  •     page_cache_release(old_page);
  •     return ret;  
  • }


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-387827-1-1.html 上篇帖子: 虚拟机硬盘不足 下篇帖子: Java虚拟机1:什么是Java
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表