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

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

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

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

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

php变量之写时复制机制(copy on write)

编程思想虽然可以共用,不过语言间的差异还是比较明显的,只是使用者之间没有意识到而己,而了解其中的差异对于编写程序以及把握性能还是有好处的.下面我们来介绍下PHP的一个很重要的机制copy on write,我们先以最简单的变量来介绍这个机制,在说这个之前,笔者先来介绍下弱类型是怎么实现的. 大家都知道,PHP是由C实现的,可是C是强类型语言,PHP怎么做到弱类型语言.一起来看下,PHP变量在C语言低层中的代码, typedef struct _zval_struct zval; typedef

Ring3下绕过Windows写时复制机制实现全局EAT钩子

在注入到某进程中对Ntdll下EAT钩子的时候作用域仅仅只是当前进程,可是明明所有进程的Ntdll模块全是映射的同一个啊.原来Windows支持一种机制,允许两个或两个以上的进程共享同一块存储器.不过操作系统会给共享的存储页指定写时复制属性,当有个进程想修改一个共享页面时,操作系统会从内存中找到一个闲置页面并将修改的页面内容复制到这个闲置页面上,再将虚拟地址空间和这个新的页面映射上.那么只需在下钩前先想办法消掉页面的写时复制属性,再hook ntdll的时候就能在Ring3下实现类似Ring0钩

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

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

PHP写时复制

原文:http://www.huamanshu.com/blog/2014-05-18.html 起源 写时复制英文名字叫“copy-on-write”,简称“cow”,又名“靠” 今天查了下这个"cow",原来起源于*nix内存管理机制,后来广泛应用在其它开发语言上,C++的STL,还有PHP,相信很多人都了解这个写时复制,经常跟别人侃得甚欢. $foo = 'foo'; $bar = $foo; 不会 初写这样的PHP代码时,常会问,这样的话会不会因为复制而占用更多内存,旁边会有人

fork()和写时复制

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

再谈QVector与QByteArray——Qt的写时复制(copy on write)技术

Qt作为一个优秀的跨平台开源C++框架,如果我们只停留在使用它的基础上而不深挖其实现手法,实在是浪费这个知识宝库了~我们在之前的博文QVector的内存分配策略与再谈QVector与std::vector--使用装饰者让std::vector支持连续赋值中简单聊了聊QVector内存分配和赋值方面的一点东西,今天接着从QVector展开谈谈Qt的写时复制技术.老实说,"隐式共享,引用计数,写时复制"也是老调重弹的话题了,不过也是QTL与STL最大的区别之一,这篇博文不详谈"写

php变量的引用计数器和写时复制

众所周知,PHP是不支持指针的,但是如果希望两个变量同时指向同一内存块怎么办呢?为了解决这个问题,PHP内核里使用了引用计数器. 上篇博文介绍了PHP变量在内核中的存储方式了,zval结构中下面两个成员变量用于引用计数器: is_ref BOOL值,标识变量是否是引用集合. refcount 计算指向引用集合的变量个数. 看下面的php代码 <?php $a = "this is a"; ?> 一个zval结构的实体称为zval容器.在php语言层创建一个变量就会相应地在p

47-引用计数与写时复制

47-引用计数与写时复制 对于PHP这种需要同时处理多个请求的程序来说,申请和释放内存的时候应该慎之又慎,一不小心便会酿成大错.另一方面,除了要安全申请和释放内存外,还应该做到内存的最小化使用,因为它可能要处理每秒钟数以千计的请求,为了提高系统整体的性能,每一次操作都应该只使用最少的内存,对于不必要的相同数据的复制则应该能免则免.我们来看下面这段PHP代码: <?php $a = 'Hello NowaMagic!'; $b = $a; unset($a); ?> 第一条语句执行后,PHP创建