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

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

前面简单的分析了内核处理用户空间缺页异常的流程,进入到了handle_mm_fault()函数,该函数为触发缺页异常的地址address分配各级的页目录,也就是说现在已经拥有了一个和address配对的pte了,但是这个pte如何去映射物理页框,内核又得根据pte的状态进行分类和判断,而这个过程又会牵扯出一些其他的概念……这也是初读linux内核源码的最大障碍吧,在一些复杂的处理中,一个点往往可以延伸出一个面,容易让人迷失方向……因此后面打算分几次将这个函数分析完,自己也没有完全理解透,所以不到位的地方欢迎大家指出,一起交流~

  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. if (!pte_present(entry)) {//如果页不在主存中
  9. if (pte_none(entry)) {//页表项内容为0,表明进程未访问过该页
  10. /*如果vm_ops字段和fault字段都不为空,则说明这是一个基于文件的映射*/
  11. if (vma->vm_ops) {
  12. if (likely(vma->vm_ops->fault))
  13. return do_linear_fault(mm, vma, address,
  14. pte, pmd, flags, entry);
  15. }
  16. /*否则分配匿名页*/
  17. return do_anonymous_page(mm, vma, address,
  18. pte, pmd, flags);
  19. }
  20. /*属于非线性文件映射且已被换出*/
  21. if (pte_file(entry))
  22. return do_nonlinear_fault(mm, vma, address,
  23. pte, pmd, flags, entry);
  24. /*页不在主存中,但是页表项保存了相关信息,则表明该页被内核换出,则要进行换入操作*/
  25. return do_swap_page(mm, vma, address,
  26. pte, pmd, flags, entry);
  27. }
  28. ...
  29. ...
  30. }

首先要确定的一点就是pte对应的页是否驻留在主存中,因为pte有可能之前映射了页,但是该页被换出了。上面的代码给出了pte对应的页没有驻留在主存中的情况。如果pte对应的页没有驻留在主存中,且没有映射任何页,即pte_present()返回0,pte_none()返回0,则要判断要分配一个匿名页还是一个映射页。在Linux虚拟内存中,如果页对应的vma映射的是文件,则称为映射页,如果不是映射的文件,则称为匿名页。两者最大的区别体现在页和vma的组织上,因为在页框回收处理时要通过页来逆向搜索映射了该页的vma。对于匿名页的逆映射,vma都是通过vma结构体中的vma_anon_node(链表节点)和anon_vma(链表头)组织起来,再把该链表头的信息保存在页描述符中;而映射页和vma的组织是通过vma中的优先树节点和页描述符中的mapping->i_mmap优先树树根进行组织的,具体可以参看ULK3。

来看基于文件的映射的处理:

  1. static int do_linear_fault(struct mm_struct *mm, struct vm_area_struct *vma,
  2. unsigned long address, pte_t *page_table, pmd_t *pmd,
  3. unsigned int flags, pte_t orig_pte)
  4. {
  5. pgoff_t pgoff = (((address & PAGE_MASK)
  6. - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;
  7. pte_unmap(page_table);//如果page_table之前用来建立了临时内核映射,则释放该映射
  8. return __do_fault(mm, vma, address, pmd, pgoff, flags, orig_pte);
  9. }

关键函数__do_fault():

  1. static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma,
  2. unsigned long address, pmd_t *pmd,
  3. pgoff_t pgoff, unsigned int flags, pte_t orig_pte)
  4. {
  5. pte_t *page_table;
  6. spinlock_t *ptl;
  7. struct page *page;
  8. pte_t entry;
  9. int anon = 0;
  10. int charged = 0;
  11. struct page *dirty_page = NULL;
  12. struct vm_fault vmf;
  13. int ret;
  14. int page_mkwrite = 0;
  15. vmf.virtual_address = (void __user *)(address & PAGE_MASK);
  16. vmf.pgoff = pgoff;
  17. vmf.flags = flags;
  18. vmf.page = NULL;
  19. ret = vma->vm_ops->fault(vma, &vmf);//调用定义好的fault函数,确保将所需的文件数据读入到映射页
  20. if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE)))
  21. return ret;
  22. if (unlikely(PageHWPoison(vmf.page))) {
  23. if (ret & VM_FAULT_LOCKED)
  24. unlock_page(vmf.page);
  25. return VM_FAULT_HWPOISON;
  26. }
  27. /*
  28. * For consistency in subsequent calls, make the faulted page always
  29. * locked.
  30. */
  31. if (unlikely(!(ret & VM_FAULT_LOCKED)))
  32. lock_page(vmf.page);
  33. else
  34. VM_BUG_ON(!PageLocked(vmf.page));
  35. /*
  36. * Should we do an early C-O-W break?
  37. */
  38. page = vmf.page;
  39. if (flags & FAULT_FLAG_WRITE) {//写访问
  40. if (!(vma->vm_flags & VM_SHARED)) {//私有映射,则要创建一个副本进行写时复制
  41. anon = 1;// 标记为一个匿名映射
  42. if (unlikely(anon_vma_prepare(vma))) {//创建一个anon_vma实例给vma
  43. ret = VM_FAULT_OOM;
  44. goto out;
  45. }
  46. page = alloc_page_vma(GFP_HIGHUSER_MOVABLE,//分配一个页
  47. vma, address);
  48. if (!page) {
  49. ret = VM_FAULT_OOM;
  50. goto out;
  51. }
  52. if (mem_cgroup_newpage_charge(page, mm, GFP_KERNEL)) {
  53. ret = VM_FAULT_OOM;
  54. page_cache_release(page);
  55. goto out;
  56. }
  57. charged = 1;
  58. /*
  59. * Don't let another task, with possibly unlocked vma,
  60. * keep the mlocked page.
  61. */
  62. if (vma->vm_flags & VM_LOCKED)
  63. clear_page_mlock(vmf.page);
  64. /*创建数据的副本,将数据拷贝到新分配的页*/
  65. copy_user_highpage(page, vmf.page, address, vma);
  66. __SetPageUptodate(page);
  67. } else {
  68. /*
  69. * If the page will be shareable, see if the backing
  70. * address space wants to know that the page is about
  71. * to become writable
  72. */
  73. if (vma->vm_ops->page_mkwrite) {
  74. int tmp;
  75. unlock_page(page);
  76. vmf.flags = FAULT_FLAG_WRITE|FAULT_FLAG_MKWRITE;
  77. tmp = vma->vm_ops->page_mkwrite(vma, &vmf);
  78. if (unlikely(tmp &
  79. (VM_FAULT_ERROR | VM_FAULT_NOPAGE))) {
  80. ret = tmp;
  81. goto unwritable_page;
  82. }
  83. if (unlikely(!(tmp & VM_FAULT_LOCKED))) {
  84. lock_page(page);
  85. if (!page->mapping) {
  86. ret = 0; /* retry the fault */
  87. unlock_page(page);
  88. goto unwritable_page;
  89. }
  90. } else
  91. VM_BUG_ON(!PageLocked(page));
  92. page_mkwrite = 1;
  93. }
  94. }
  95. }
  96. page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
  97. /*
  98. * This silly early PAGE_DIRTY setting removes a race
  99. * due to the bad i386 page protection. But it's valid
  100. * for other architectures too.
  101. *
  102. * Note that if FAULT_FLAG_WRITE is set, we either now have
  103. * an exclusive copy of the page, or this is a shared mapping,
  104. * so we can make it writable and dirty to avoid having to
  105. * handle that later.
  106. */
  107. /* Only go through if we didn't race with anybody else... */
  108. if (likely(pte_same(*page_table, orig_pte))) {//确定没有竞争,也就是页表项中的内容和之前是一样的
  109. flush_icache_page(vma, page);
  110. entry = mk_pte(page, vma->vm_page_prot);//页表项指向对应的物理页
  111. /*如果是写操作,则将页的访问权限置为RW*/
  112. if (flags & FAULT_FLAG_WRITE)
  113. entry = maybe_mkwrite(pte_mkdirty(entry), vma);
  114. /*如果之前生成的页是匿名的,则将其集成到逆向映射当中*/
  115. if (anon) {
  116. inc_mm_counter(mm, anon_rss);
  117. page_add_new_anon_rmap(page, vma, address);//建立匿名页与第一个vma的逆向映射
  118. } else {
  119. inc_mm_counter(mm, file_rss);
  120. page_add_file_rmap(page);//建立页与vma的普通映射
  121. if (flags & FAULT_FLAG_WRITE) {
  122. dirty_page = page;
  123. get_page(dirty_page);
  124. }
  125. }
  126. set_pte_at(mm, address, page_table, entry);//修改page_table使其指向entry对应的页框
  127. /* no need to invalidate: a not-present page won't be cached */
  128. update_mmu_cache(vma, address, entry);
  129. } else {
  130. if (charged)
  131. mem_cgroup_uncharge_page(page);
  132. if (anon)
  133. page_cache_release(page);
  134. else
  135. anon = 1; /* no anon but release faulted_page */
  136. }
  137. pte_unmap_unlock(page_table, ptl);
  138. out:
  139. if (dirty_page) {
  140. struct address_space *mapping = page->mapping;
  141. if (set_page_dirty(dirty_page))
  142. page_mkwrite = 1;
  143. unlock_page(dirty_page);
  144. put_page(dirty_page);
  145. if (page_mkwrite && mapping) {
  146. /*
  147. * Some device drivers do not set page.mapping but still
  148. * dirty their pages
  149. */
  150. balance_dirty_pages_ratelimited(mapping);
  151. }
  152. /* file_update_time outside page_lock */
  153. if (vma->vm_file)
  154. file_update_time(vma->vm_file);
  155. } else {
  156. unlock_page(vmf.page);
  157. if (anon)
  158. page_cache_release(vmf.page);
  159. }
  160. return ret;
  161. unwritable_page:
  162. page_cache_release(page);
  163. return ret;
  164. }

首先要做的就是调用vma->vm_ops中定义好的fault()函数,将所需的数据从文件读入到映射页中,该函数还会将vma插入到映射页的mapping->i_mmap优先树中。

文件一般以共享的方式进行映射,接下来就要判断触发异常的操作是否包含写操作,如果是写操作并且该vma不是以共享的方式映射该页,则要进行写时复制,也就是创建一个新的页来供该vma读写,此时会申请一个匿名页,并将数据拷贝到该匿名页中。

接下来就要计算出page对应的pte值是多少,并将page_table指向的pte以该值进行填充,这样就完成了页表项到物理页的映射

再来看分配匿名页的处理

  1. static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
  2. unsigned long address, pte_t *page_table, pmd_t *pmd,
  3. unsigned int flags)
  4. {
  5. struct page *page;
  6. spinlock_t *ptl;
  7. pte_t entry;
  8. pte_unmap(page_table);
  9. /* Check if we need to add a guard page to the stack */
  10. if (check_stack_guard_page(vma, address) < 0)
  11. return VM_FAULT_SIGBUS;
  12. /* Use the zero-page for reads */
  13. /*如果是读操作,那么就让entry指向一个已有的填充为0的现有页,因为进程是第一次访问该页,
  14. 所以页中的内容是什么并不重要,这样进一步推迟了新页的分配*/
  15. if (!(flags & FAULT_FLAG_WRITE)) {
  16. entry = pte_mkspecial(pfn_pte(my_zero_pfn(address),
  17. vma->vm_page_prot));
  18. page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
  19. if (!pte_none(*page_table))
  20. goto unlock;
  21. goto setpte;
  22. }
  23. /*如果是写操作,则要分配一个新的页*/
  24. /* Allocate our own private page. */
  25. if (unlikely(anon_vma_prepare(vma)))//分配一个anon_vma实例
  26. goto oom;
  27. /*分配一个被0填充的页*/
  28. page = alloc_zeroed_user_highpage_movable(vma, address);
  29. if (!page)
  30. goto oom;
  31. __SetPageUptodate(page);
  32. if (mem_cgroup_newpage_charge(page, mm, GFP_KERNEL))
  33. goto oom_free_page;
  34. /*获取页对应的PTE内容*/
  35. entry = mk_pte(page, vma->vm_page_prot);
  36. /*如果是写操作则将页的权限设为读写并设置为脏页*/
  37. if (vma->vm_flags & VM_WRITE)
  38. entry = pte_mkwrite(pte_mkdirty(entry));
  39. page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
  40. if (!pte_none(*page_table))
  41. goto release;
  42. inc_mm_counter(mm, anon_rss);
  43. page_add_new_anon_rmap(page, vma, address);//建立线性区和匿名页的反向映射
  44. setpte:
  45. set_pte_at(mm, address, page_table, entry);//设置page_table对应的pte
  46. /* No need to invalidate - it was non-present before */
  47. update_mmu_cache(vma, address, entry);//更新MMU缓存
  48. unlock:
  49. pte_unmap_unlock(page_table, ptl);
  50. return 0;
  51. release:
  52. mem_cgroup_uncharge_page(page);
  53. page_cache_release(page);
  54. goto unlock;
  55. oom_free_page:
  56. page_cache_release(page);
  57. oom:
  58. return VM_FAULT_OOM;
  59. }

匿名页分配的工作和__do_fault()中分配匿名页差不多,只不过前面多了一个读写的判断,如果是读的话,不会分配匿名页,而是让pte指向一个被0填充的页,这样就进一步推迟了页的分配。也许你会觉得奇怪,既然要读数据怎么可以分配一个事先准备好的全0的页,其实仔细想想就会明白,缺页异常处理进行到这里,一定是第一次访问相应的内存时才会触发,匿名页对应的一般都是堆,栈这些区域,对这些区域的访问一定先是写而不是读,所以对于这种操作本身就不正常,分配一个被0填充的页使用户进程读出来的都是0也许会更安全一些。

如果不是这两种情况的话,也就是说pte_none()返回的是0,那就说明pte之前映射过页,只是该页已被换出

如果该页之前是用来进行非线性文件映射的话,其处理的主体函数就是上面介绍过的__do_fault()

  1. static int do_nonlinear_fault(struct mm_struct *mm, struct vm_area_struct *vma,
  2. unsigned long address, pte_t *page_table, pmd_t *pmd,
  3. unsigned int flags, pte_t orig_pte)
  4. {
  5. pgoff_t pgoff;
  6. flags |= FAULT_FLAG_NONLINEAR;
  7. if (!pte_unmap_same(mm, pmd, page_table, orig_pte))
  8. return 0;
  9. if (unlikely(!(vma->vm_flags & VM_NONLINEAR))) {//确保vma具有非线性映射属性
  10. /*
  11. * Page table corrupted: show pte and kill process.
  12. */
  13. print_bad_pte(vma, address, orig_pte, NULL);
  14. return VM_FAULT_SIGBUS;
  15. }
  16. pgoff = pte_to_pgoff(orig_pte);//获取映射的文件偏移
  17. return __do_fault(mm, vma, address, pmd, pgoff, flags, orig_pte);
  18. }

pte_to_pgoff()这个函数是和pgoff_to_pte()相对的一组操作。在非线性文件映射的页被换出时,其映射文件的偏移会以PAGE_SIZE为单位进行编码,存储到其pte中,所以当要重新换入该页时,要进行相应的解码计算出pgoff,再由__do_fault()进行处理!

对于页没有驻留在主存的情况中的最后一种处理方式,do_swap_page(),留在下次再做分析!

用户空间缺页异常pte_handle_fault()分析--(上)【转】的更多相关文章

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

    转自:http://blog.csdn.net/vanbreaker/article/details/7955713 版权声明:本文为博主原创文章,未经博主允许不得转载. 在pte_handle_fa ...

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

    转自:http://blog.csdn.net/vanbreaker/article/details/7870769 版权声明:本文为博主原创文章,未经博主允许不得转载. 用户空间的缺页异常可以分为两 ...

  3. linux内存(三)内核与用户空间交互

    来自网址http://www.kerneltravel.net/jiaoliu/005.htm 用户程序和内核的信息交换是双向的,也就是说既可以主动从用户空间向内核空间发送信息,也可以从内核空间向用户 ...

  4. [置顶] Linux Malloc分析-从用户空间到内核空间【转】

    转自:http://blog.csdn.net/ordeder/article/details/41654509 版权声明:本文为博主(http://blog.csdn.net/ordeder)原创文 ...

  5. Linux Malloc分析-从用户空间到内核空间【转】

    转自:http://blog.csdn.net/ordeder/article/details/41654509 版权声明:本文为博主(http://blog.csdn.net/ordeder)原创文 ...

  6. 用户空间和内核空间通讯之【Netlink 上】

    原文地址:用户空间和内核空间通讯之[Netlink 上] 作者:wjlkoorey258 引言 Alan Cox在内核1.3版本的开发阶段最先引入了Netlink,刚开始时Netlink是以字符驱动接 ...

  7. CK:User mode Bus Error(用户空间操作内核地址导致的异常)

    关键词:VEC_ACCESS.coredump.LR.PC等. CK中存在一种VEC_ACCESS异常,可能原因是用户空间访问了内核空间,还有一种是内核访问不存在的总线地址. 下面简单构造VEC_AC ...

  8. linux 用户态和内核态以及进程上下文、中断上下文 内核空间用户空间理解

    1.特权级         Intel x86架构的cpu一共有0-4四个特权级,0级最高,3级最低,ARM架构也有不同的特权级,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查.硬件已经提 ...

  9. cpu的用户态和内核态和内存的用户空间内核空间

    谈到CPU的这两个工作状态,也就是处理器的这两个工作状态,那我们有必要说一下为什么搞出这两个鬼玩意出来.       用过电脑的娃娃们肯定知道在一个系统中既有操作系统的程序,也由普通用户的程序.但那么 ...

随机推荐

  1. C17K:Lying Island

    链接 题意: 有n个人,每个人可能会说: 第x个人是好人/坏人 如果第x个人是好人/坏人,则第y个人是好人/坏人 思路: 状压dp,首先每个人所说的人只能是他前面10个人,所以对于第i个人记录下,他前 ...

  2. java前台传参json,后台用map或者实体对象接收

    (一)前台传js对象,里面包含数组,后台用map接收 (1)第一种情况:数组里不包含js对象 var param ={}: param.id=id; param.name=name; var scor ...

  3. laravel5.5服务提供器

    目录 1. 编写服务提供器 1.1 注册方法 register 1.1.1 简单绑定 1.1.2 绑定单例 1.1.3 绑定实例 1.1.4 绑定初始数据 1.2 引导方法 boot 2. 注册服务提 ...

  4. Installing Nginx With PHP5 (And PHP-FPM) And MySQL Support (LEMP) On Ubuntu 12.04 LTS [repost]

    from : http://www.howtoforge.com/installing-nginx-with-php5-and-php-fpm-and-mysql-support-lemp-on-ub ...

  5. webstrom11 vue插件配置

    直接上图 1. 安装vue插件 2.添加模板 3.指定模板类型 最新的是插件 是 vue.js 创建完 Vue File 文件后 需要在 下面这里关联一下

  6. day02-python基础2

    操作 列表是用来存储一组数据,可以实现对列表中元素的增删改查等操作. 转换: list(string):把字符串转为列表 声明: 列表使用方括号 查询: 根据元素下标获取列表中元素的值 切片: [0: ...

  7. http协议--留

    1.http消息结构 *http客户端,即web浏览器,链接到服务器,向服务器发送一个http请求的目的 *http服务器,即web服务,接受请求,并向客户端发送http响应数据 http统一资源标识 ...

  8. diskimage-builder

    Supported Distributions Distributions which are supported as a build host: Centos 6, 7 Debian 8 (“je ...

  9. XGBoost——机器学习--周振洋

    XGBoost——机器学习(理论+图解+安装方法+python代码) 目录 一.集成算法思想 二.XGBoost基本思想 三.MacOS安装XGBoost 四.用python实现XGBoost算法 在 ...

  10. PHP如何实现第三方分享

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...