[转帖]Linux系统:page fault

linux,系统,page,fault · 浏览次数 : 0

小编点评

**程序访问异常处理** ```c if (unlikely(access_error(error_code, vma))) { bad_area_access_error(regs, error_code, address); return; } ``` **排版** ```c if (unlikely(expand_stack(vma, address))) { bad_area_access_error(regs, error_code, address); return; } ``` **其他** * `up_read()`函数用于读取内存中的数据。 * `flags`变量用于控制异常的处理。 * ` Fault_FLAG_ALLOW_RETRY`常数用于指示是否允许重试。

正文

Linux进程如何访问内存

        Linux下,进程并不是直接访问物理内存,而是通过内存管理单元(MMU)来访问内存资源,原因后面会讲到。

为什么需要虚拟内存地址空间

       假设某个进程需要4MB的空间,内存假设是1MB的,如果进程直接使用物理地址,这个进程会因为内存不足跑不起来。既然进程不是直接访问物理内存,那么进程中涉及的内存地址当然也不是物理内存地址。而是虚拟的内存地址,虚拟的内存地址和物理的内存地址之间保持一种映射关系,这种关系由MMU进行管理。每个进程都有自己独立的虚拟地址空间。

什么是MMU

MMU_principle_updated
        MMU 全称是内存管理单元,它将物理内存分割成多个pages,MMU管理进程的虚拟地址空间中的PAGE和物理内存中的PAGE之间的映射关系。
       因为是映射,所以随时都可能发生变化,例如某个进程虚拟内存空间中的PAGE1,在不同的时间点,可能出现在物理内存中的不同位置(当发生了页交换时)。

什么是page fault

       当进程访问它的虚拟地址空间中的PAGE时,如果这个PAGE目前还不在物理内存中,此时CPU是不能干活的,Linux会产生一个hard page fault中断。系统需要从慢速设备(如磁盘)将对应的数据PAGE读入物理内存,并建立物理内存地址与虚拟地址空间PAGE的映射关系。然后进程才能访问这部分虚拟地址空间的内存。

page fault 又分为几种,major page fault、 minor page fault、 invalid(segment fault)。

major page fault 也称为 hard page fault, 指需要访问的内存不在虚拟地址空间,也不在物理内存中,需要从慢速设备载入。从swap 回到物理内存也是 hard page fault。

minor page fault 也称为 soft page fault, 指需要访问的内存不在虚拟地址空间,但是在物理内存中,只需要MMU建立物理内存和虚拟地址空间的映射关系即可。

  1. 当一个进程在调用 malloc 获取虚拟空间地址后,首次访问该地址会发生一次soft  page fault。
  2. 通常是多个进程访问同一个共享内存中的数据,可能某些进程还没有建立起映射关系,所以访问时会出现soft page fault

invalid fault 也称为 segment fault,指进程需要访问的内存地址不在它的虚拟地址空间范围内,属于越界访问,内核会报 segment fault错误。

源码解析:

mm_struct和vm_area_struct
mm_struct:
mm_structtask_struct的一个成员变量,是对整个进程用户空间的描述。
mm_struct收集一系列vm_area_struct信息。

vm_area_struct:
Linux内核将内存划分为不同的区域(vm_area_struct),每个区域是一系列连续的有相同保护和分页属性的页面,vm_area_struct是内存管理的最小单元。

page fault
参考代码:kernel/4.1.18/linux-4.1.18.y/arch/arm64/mm/fault.c    static int __do_page_fault()

page fault出现的原因:
a). 页表中找不到对应虚拟地址的PTE(无效地址/有效地址但是没有载入主存);
b). 对应虚拟地址的PTE拒绝访问。

page fault在哪里进行处理
page fault被CPU捕获,跳转到 page_fault_handler 进行处理。

page fault的处理方式
page fault -> 访问地址是否合法
a. 无效地址:segment fault,返回(用户地址杀死进程、内核地址杀死内核)
b. 有效地址:
1). page第一次被访问: demand_page_faults (demanding pages,请求调页)
检查页表中是否存在该PTE    pmd_none, pte_none
分配新的页帧,初始化(从磁盘读入内存)
2). page被交换到swap分区
检查present标志位,如果该位为0表示不在主存中。
分配新的页帧,从磁盘重新读入内存。
3). COW(Copy-On-Write)
vm_area_struct允许写,但是对应的PTE禁止写操作。

如何判断访问地址是否合法?如果地址合法有什么操作?
判断地址合法的方式:
static int __do_page_fault()函数 vma = find_vma(mm, addr);
根据传入的地址addr查找对应的vm_area_struct,如果没有找到证明该地址访问无效,返回segment fault。
 

kernel 3.10内核源码分析--缺页异常(page fault)处理流程

基本原理
1、page fault由硬件产生,是一种“异常”。产生条件为:当CPU访问某线性地址,而该线性地址还没有对应的页表项,即还没有分配相应的物理内存并进行映射时,自动产生异常。
2、page fault基本流程:
从cr2中获取发生异常的地址
  缺页地址位于内核态
    位于vmalloc区?->从主内核页表同步数据到进程页表
    非vmalloc区 ->不应该产生page fault->oops
  缺页地址位于用户态
     缺页上下文发生在内核态
        exception table中有相应的处理项? ->进行修正
        没有 ->oops
     查找vma
        找到?-> 是否expand stack?->堆栈扩展
                       不是->正常的缺页处理:handle_mm_fault                    
        没找到->bad_area    

  1. /*
  2. *缺页异常主处理函数。
  3. *regs:异常时的寄存器信息;
  4. *error_code-当异常发生时,硬件压入栈中的错误代码。
  5. * 当第0位被清0时,则异常是由一个不存在的页所引起的。否则是由无效的访问权限引起的。
  6. * 如果第1位被清0,则异常由读访问或者执行访问所引起,如果被设置,则异常由写访问引起。
  7. * 如果第2位被清0,则异常发生在内核态,否则异常发生在用户态。
  8. */
  9. static void __kprobes
  10. __do_page_fault(struct pt_regs *regs, unsigned long error_code)
  11. {
  12. struct vm_area_struct *vma;
  13. struct task_struct *tsk;
  14. unsigned long address;
  15. struct mm_struct *mm;
  16. int fault;
  17. int write = error_code & PF_WRITE;
  18. unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE |
  19. (write ? FAULT_FLAG_WRITE : 0);
  20. tsk = current;
  21. mm = tsk->mm;
  22. /* Get the faulting address: */
  23. //缺页异常的地址默认存放于CR2寄存器中,x86硬件决定
  24. address = read_cr2();
  25. /*
  26. * Detect and handle instructions that would cause a page fault for
  27. * both a tracked kernel page and a userspace page.
  28. */
  29. if (kmemcheck_active(regs))
  30. kmemcheck_hide(regs);
  31. prefetchw(&mm->mmap_sem);
  32. // mmio不应该发生缺页,通常都会ioremap到vmalloc区,然后进行访问
  33. if (unlikely(kmmio_fault(regs, address)))
  34. return;
  35. /*
  36. * We fault-in kernel-space virtual memory on-demand. The
  37. * 'reference' page table is init_mm.pgd.
  38. *
  39. * We MUST NOT take any locks for this case. We may
  40. * be in an interrupt or a critical region, and should
  41. * only copy the information from the master page table,
  42. * nothing more.
  43. *
  44. * This verifies that the fault happens in kernel space
  45. * (error_code & 4) == 0, and that the fault was not a
  46. * protection error (error_code & 9) == 0.
  47. */
  48. /*
  49. * 缺页地址位于内核空间。并不代表异常发生于内核空间,有可能是用户
  50. * 态访问了内核空间的地址。
  51. */
  52. if (unlikely(fault_in_kernel_space(address))) {
  53. if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {
  54. /*
  55. * 检查发生缺页的地址是否在vmalloc区,是则进行相应的处理
  56. * 主要是从内核主页表向进程页表同步数据
  57. */
  58. if (vmalloc_fault(address) >= 0)
  59. return;
  60. if (kmemcheck_fault(regs, address, error_code))
  61. return;
  62. }
  63. /* Can handle a stale RO->RW TLB: */
  64. /*
  65. * 检查是否是由于陈旧的TLB导致的假的pagefault(由于TLB的延迟flush导致,
  66. * 因为提前flush会有比较大的性能代价)。
  67. */
  68. if (spurious_fault(error_code, address))
  69. return;
  70. /* kprobes don't want to hook the spurious faults: */
  71. if (notify_page_fault(regs))
  72. return;
  73. /*
  74. * Don't take the mm semaphore here. If we fixup a prefetch
  75. * fault we could otherwise deadlock:
  76. */
  77. /*
  78. * 有问题了: 由于异常地址位于内核态,触发内核异常,因为vmalloc
  79. * 区的缺页异常前面已经处理过了,内核态的缺页异常只能发生在
  80. * vmalloc区,如果不是,那就是内核异常了。
  81. */
  82. bad_area_nosemaphore(regs, error_code, address);
  83. return;
  84. }
  85. // 进入到这里,说明异常地址位于用户态
  86. /* kprobes don't want to hook the spurious faults: */
  87. if (unlikely(notify_page_fault(regs)))
  88. return;
  89. /*
  90. * It's safe to allow irq's after cr2 has been saved and the
  91. * vmalloc fault has been handled.
  92. *
  93. * User-mode registers count as a user access even for any
  94. * potential system fault or CPU buglet:
  95. */
  96. /*
  97. * 开中断,这种情况下,是安全的,可以缩短因缺页异常导致的关中断时长。
  98. * 老内核版本中(2.6.11)没有这样的操作
  99. */
  100. if (user_mode_vm(regs)) {
  101. local_irq_enable();
  102. error_code |= PF_USER;
  103. } else {
  104. if (regs->flags & X86_EFLAGS_IF)
  105. local_irq_enable();
  106. }
  107. if (unlikely(error_code & PF_RSVD))
  108. pgtable_bad(regs, error_code, address);
  109. if (static_cpu_has(X86_FEATURE_SMAP)) {
  110. if (unlikely(smap_violation(error_code, regs))) {
  111. bad_area_nosemaphore(regs, error_code, address);
  112. return;
  113. }
  114. }
  115. perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
  116. /*
  117. * If we're in an interrupt, have no user context or are running
  118. * in an atomic region then we must not take the fault:
  119. */
  120. /*
  121. * 当缺页异常发生于中断或其它atomic上下文中时,则产生异常。
  122. * 这种情况下,不应该再产生page fault
  123. */
  124. if (unlikely(in_atomic() || !mm)) {
  125. bad_area_nosemaphore(regs, error_code, address);
  126. return;
  127. }
  128. /*
  129. * When running in the kernel we expect faults to occur only to
  130. * addresses in user space. All other faults represent errors in
  131. * the kernel and should generate an OOPS. Unfortunately, in the
  132. * case of an erroneous fault occurring in a code path which already
  133. * holds mmap_sem we will deadlock attempting to validate the fault
  134. * against the address space. Luckily the kernel only validly
  135. * references user space from well defined areas of code, which are
  136. * listed in the exceptions table.
  137. *
  138. * As the vast majority of faults will be valid we will only perform
  139. * the source reference check when there is a possibility of a
  140. * deadlock. Attempt to lock the address space, if we cannot we then
  141. * validate the source. If this is invalid we can skip the address
  142. * space check, thus avoiding the deadlock:
  143. */
  144. if (unlikely(!down_read_trylock(&mm->mmap_sem))) {
  145. /*
  146. * 缺页发生在内核上下文,这种情况发生缺页的地址只能位于用户态地址空间
  147. * 这种情况下,也只能为exceptions table中预先定义好的异常,如果exceptions
  148. * table中没有预先定义的处理,或者缺页的地址位于内核态地址空间,则表示
  149. * 错误,进入oops流程。
  150. */
  151. if ((error_code & PF_USER) == 0 &&
  152. !search_exception_tables(regs->ip)) {
  153. bad_area_nosemaphore(regs, error_code, address);
  154. return;
  155. }
  156. retry:
  157. // 如果发生在用户态或者有exception table,说明不是内核异常
  158. down_read(&mm->mmap_sem);
  159. } else {
  160. /*
  161. * The above down_read_trylock() might have succeeded in
  162. * which case we'll have missed the might_sleep() from
  163. * down_read():
  164. */
  165. might_sleep();
  166. }
  167. // 在当前进程的地址空间中寻找发生异常的地址对应的VMA。
  168. vma = find_vma(mm, address);
  169. // 如果没找到VMA,则释放mem_sem信号量后,进入__bad_area_nosemaphore处理。
  170. if (unlikely(!vma)) {
  171. bad_area(regs, error_code, address);
  172. return;
  173. }
  174. /* 找到VMA,且发生异常的虚拟地址位于vma的有效范围内,则为正常的缺页
  175. * 异常,请求调页,分配物理内存 */
  176. if (likely(vma->vm_start <= address))
  177. goto good_area;
  178. /* 如果异常地址不是位于紧挨着堆栈区的那个区域,同时又没有相应VMA,则
  179. * 进程访问了非法地址,进入bad_area处理
  180. */
  181. if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
  182. bad_area(regs, error_code, address);
  183. return;
  184. }
  185. if (error_code & PF_USER) {
  186. /*
  187. * Accessing the stack below %sp is always a bug.
  188. * The large cushion allows instructions like enter
  189. * and pusha to work. ("enter $65535, $31" pushes
  190. * 32 pointers and then decrements %sp by 65535.)
  191. */
  192. /*
  193. * 压栈操作时,操作的地址最大的偏移为65536+32*sizeof(unsigned long),
  194. * 该操作由pusha命令触发(老版本中,pusha命令最大只能操作32字节,即
  195. * 同时压栈8个寄存器)。如果访问的地址距栈顶的距离超过了,则肯定是非法
  196. * 地址访问了。
  197. */
  198. if (unlikely(address + 65536 + 32 * sizeof(unsigned long) < regs->sp)) {
  199. bad_area(regs, error_code, address);
  200. return;
  201. }
  202. }
  203. /*
  204. * 运行到这里,说明设置了VM_GROWSDOWN标记,表示缺页异常地址位于堆栈区
  205. * 需要扩展堆栈。说明: 堆栈区的虚拟地址空间也是动态分配和扩展的,不是
  206. * 一开始就分配好的。
  207. */
  208. if (unlikely(expand_stack(vma, address))) {
  209. bad_area(regs, error_code, address);
  210. return;
  211. }
  212. /*
  213. * Ok, we have a good vm_area for this memory access, so
  214. * we can handle it..
  215. */
  216. /*
  217. * 运行到这里,说明是正常的缺页异常,则进行请求调页,分配物理内存
  218. */
  219. good_area:
  220. if (unlikely(access_error(error_code, vma))) {
  221. bad_area_access_error(regs, error_code, address);
  222. return;
  223. }
  224. /*
  225. * If for any reason at all we couldn't handle the fault,
  226. * make sure we exit gracefully rather than endlessly redo
  227. * the fault:
  228. */
  229. /*
  230. * 分配物理内存,缺页异常的正常处理主函数
  231. * 可能的情况有:1、请求调页/按需分配;2、COW;3、缺的页位于交换分区,
  232. * 需要换入。
  233. */
  234. fault = handle_mm_fault(mm, vma, address, flags);
  235. if (unlikely(fault & (VM_FAULT_RETRY|VM_FAULT_ERROR))) {
  236. if (mm_fault_error(regs, error_code, address, fault))
  237. return;
  238. }
  239. /*
  240. * Major/minor page fault accounting is only done on the
  241. * initial attempt. If we go through a retry, it is extremely
  242. * likely that the page will be found in page cache at that point.
  243. */
  244. if (flags & FAULT_FLAG_ALLOW_RETRY) {
  245. if (fault & VM_FAULT_MAJOR) {
  246. tsk->maj_flt++;
  247. perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1,
  248. regs, address);
  249. } else {
  250. tsk->min_flt++;
  251. perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1,
  252. regs, address);
  253. }
  254. if (fault & VM_FAULT_RETRY) {
  255. /* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk
  256. * of starvation. */
  257. flags &= ~FAULT_FLAG_ALLOW_RETRY;
  258. flags |= FAULT_FLAG_TRIED;
  259. goto retry;
  260. }
  261. }
  262. // VM86模式(兼容老环境)相关检查
  263. check_v8086_mode(regs, address, tsk);
  264. up_read(&mm->mmap_sem);
  265. }

 

 

参考文献:

https://blog.csdn.net/h549570564/article/details/90760407

https://blog.csdn.net/vanquishedzxl/article/details/47029805?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

http://blog.chinaunix.net/uid-29813277-id-4425529.html

 

 

与[转帖]Linux系统:page fault相似的内容:

[转帖]Linux系统:page fault

Linux进程如何访问内存 Linux下,进程并不是直接访问物理内存,而是通过内存管理单元(MMU)来访问内存资源,原因后面会讲到。 为什么需要虚拟内存地址空间 假设某个进程需要4MB的空间,内存假设是1MB的,如果进程直接使用物理地址,这个进程会因为内存不足跑不起来。既然进程不是直接访问物理内存,

[转帖]Linux系统中的Page cache和Buffer cache

Free命令显示内存 首先,我们来了解下内存的使用情况: Mem:表示物理内存统计 total:表示物理内存总量(total = used + free) used:表示总计分配给缓存(包含buffers 与cache )使用的数量,但其中可能部分缓存并未实际使用。 free:未被分配的内存。 sh

[转帖]Linux:页表中PGD、PUD、PMD、TLB等概念介绍

1、PGD: Page Global Directory Linux系统中每个进程对应用户空间的pgd是不一样的,但是linux内核 的pgd是一样的。当创建一个新的进程时,都要为新进程创建一个新的页面目录PGD,并从内核的页面目录swapper_pg_dir中复制内核区间页面目录项至新建进程页面目

[转帖]Linux中的Page cache和Buffer cache详解

1、内存情况 在讲解Linux内存管理时已经提到,当你在Linux下频繁存取文件后,即使系统上没有运行许多程序,也会占用大量的物理内存。这是因为当你读写文件的时候,Linux内核为了提高读写的性能和速度,会将文件在内存中进行缓存,这部分内存就是Cache Memory(缓存内存)。即使你的程序运行结

[转帖]Linux系统多网卡环境下的路由配置

https://www.cnblogs.com/connect/p/linux-static-route.html Linux下路由配置命令 1. 添加主机路由 route add -host 192.168.1.11 dev eth0 route add -host 192.168.1.12 gw

[转帖]linux 系统 UDP 丢包问题分析思路

https://cizixs.com/2018/01/13/linux-udp-packet-drop-debug/ 最近工作中遇到某个服务器应用程序 UDP 丢包,在排查过程中查阅了很多资料,总结出来这篇文章,供更多人参考。 在开始之前,我们先用一张图解释 linux 系统接收网络报文的过程。 首

[转帖]Linux系统IO基准测试方法

https://www.cnblogs.com/wangzhen3798/p/13631848.html 顺序读写测试 主要关注磁盘的吞吐量,即每秒能够读入或者写出多少数据。普通单块机械磁盘顺序写在100MB/s左右,普通单块SSD的顺序写在500MB/s左右。该指标对MQ、ES等以append方式

[转帖]Linux系统指令 top 之 %si 占用高,分析实例

https://www.coonote.com/linux-note/linux-top-si-high-instance.html 续“top %wa 高的问题”之后,又遇到top之%si过高(高峰时段超过95%)的问题。 %wa高,说明磁盘忙。譬如磁盘读写次数非常高。 %si高,是否说明软中断忙

[转帖]Linux系统中的tar命令

https://www.cnblogs.com/PatrickLiu/p/9761988.html 时间一长什么东西都容易忘记,尤其是一些不常用的东西忘记的更快,所以避免忘记,就记录下来,可以方面使用的时候查询。Tar命令在linux系统中算是一个比较重要的命令,今天就针对该命令进行总结一下。 1.

[转帖]Linux 系统TCP连接内存大小限制 调优

https://www.cnblogs.com/liujunjun/p/12496677.html 系统TCP连接内存大小限制 TCP的每一个连接请求,读写都需要占用系统内存资源,可根据系统配置,对TCP连接数,内存大小,限制调优。 查看系统内存资源 记录内存 详情:cat /proc/meminf