转自:http://blog.csdn.net/bullbat/article/details/7108402

linux缺页异常程序必须能够区分由编程引起的异常以及由引用属于进程地址空间但还尚未分配物理页框的页所引起的异常。在x86-ia32体系上由do_page_fault函数处理,每个版本有所差异,现分析的版本为2.6.32

  1. /*
  2. regs:该结构包含当异常发生时的微处理器寄存器的值
  3. 3位的error_code,当异常发生时由控制单元压入栈中
  4. -如果第0位被清0,则异常由访问一个不存在的页所
  5. 引起,否则,则异常由无效的访问权限所引起;
  6. -如果第1位被清0,表示异常由读访问或者执行访问
  7. 所引起,反之,异常由写访问引起;
  8. -如果第2位被清0,则异常发生在处理器处于内核态
  9. 时,否则,异常发生在处理器处于用户态时
  10. -如果3位为1表示检测到使用了保留位。4位为1表示
  11. 1表示缺页异常是在取指令的时候出现的
  12. */
  13. dotraplinkage void __kprobes
  14. do_page_fault(struct pt_regs *regs, unsigned long error_code)
  15. {
  16. struct vm_area_struct *vma;
  17. struct task_struct *tsk;
  18. unsigned long address;
  19. struct mm_struct *mm;
  20. int write;
  21. int fault;
  22. /*获取当前cpu正在运行的进程的进程描述符
  23. 然后获取该进程的内存描述符*/
  24. tsk = current;
  25. mm = tsk->mm;
  26. /* Get the faulting address: */
  27. /*获取出错的地址*/
  28. address = read_cr2();
  29. /*
  30. * Detect and handle instructions that would cause a page fault for
  31. * both a tracked kernel page and a userspace page.
  32. */
  33. if (kmemcheck_active(regs))
  34. kmemcheck_hide(regs);
  35. prefetchw(&mm->mmap_sem);
  36. if (unlikely(kmmio_fault(regs, address)))
  37. return;
  38. /*
  39. * We fault-in kernel-space virtual memory on-demand. The
  40. * 'reference' page table is init_mm.pgd.
  41. *
  42. * NOTE! We MUST NOT take any locks for this case. We may
  43. * be in an interrupt or a critical region, and should
  44. * only copy the information from the master page table,
  45. * nothing more.
  46. *
  47. * This verifies that the fault happens in kernel space
  48. * (error_code & 4) == 0, and that the fault was not a
  49. * protection error (error_code & 9) == 0.
  50. */
  51. /*页访问出错地址address在内核空间*/
  52. if (unlikely(fault_in_kernel_space(address))) {
  53. /*检查标志位确定访问发生在"内核态"*/
  54. if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {
  55. /*如果是内核空间"非连续内存"的访问,
  56. 则直接拷贝"内核页表项"到"用户页表项"
  57. 如果"内核页表项"为null,说明内核有BUG,返回-1
  58. 这里就是把init_mm中addr对应的项拷贝到本进程
  59. 的相应页表,防止缺页中断
  60. */
  61. if (vmalloc_fault(address) >= 0)
  62. return;
  63. /*关于kmemcheck的操作需要设置宏,这个版本
  64. 没有设置,可以不看;
  65. 检查不能为vm86模式以及读写权限是否正确*/
  66. if (kmemcheck_fault(regs, address, error_code))
  67. return;
  68. }
  69. /* Can handle a stale RO->RW TLB: */
  70. /*内核空间的地址,检查页表对应项的写、执行权限*/
  71. if (spurious_fault(error_code, address))
  72. return;
  73. /* kprobes don't want to hook the spurious faults: */
  74. if (notify_page_fault(regs))
  75. return;
  76. /*
  77. * Don't take the mm semaphore here. If we fixup a prefetch
  78. * fault we could otherwise deadlock:
  79. */
  80. /*如果上面的检查不能搞定直接进入"非法访问"处理函数*/
  81. bad_area_nosemaphore(regs, error_code, address);
  82. return;
  83. }
  84. /* kprobes don't want to hook the spurious faults: */
  85. if (unlikely(notify_page_fault(regs)))
  86. return;
  87. /*
  88. * It's safe to allow irq's after cr2 has been saved and the
  89. * vmalloc fault has been handled.
  90. *
  91. * User-mode registers count as a user access even for any
  92. * potential system fault or CPU buglet:
  93. */
  94. if (user_mode_vm(regs)) {
  95. local_irq_enable();
  96. error_code |= PF_USER;
  97. } else {
  98. if (regs->flags & X86_EFLAGS_IF)
  99. local_irq_enable();
  100. }
  101. if (unlikely(error_code & PF_RSVD))/*使用了保留位*/
  102. /*CPU寄存器和内核态堆栈的全部转储打印到控制台,
  103. 以及页表的相关信息,并输出到一个系统消息缓冲
  104. 区,然后调用函数do_exit()杀死当前进程*/
  105. pgtable_bad(regs, error_code, address);
  106. perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, address);
  107. /*
  108. * If we're in an interrupt, have no user context or are running
  109. * in an atomic region then we must not take the fault:
  110. */
  111. /*如果运行在中断环境中,没有用户上下文
  112. 或运行在临界区中*/
  113. if (unlikely(in_atomic() || !mm)) {
  114. bad_area_nosemaphore(regs, error_code, address);
  115. return;
  116. }
  117. /*
  118. * When running in the kernel we expect faults to occur only to
  119. * addresses in user space.  All other faults represent errors in
  120. * the kernel and should generate an OOPS.  Unfortunately, in the
  121. * case of an erroneous fault occurring in a code path which already
  122. * holds mmap_sem we will deadlock attempting to validate the fault
  123. * against the address space.  Luckily the kernel only validly
  124. * references user space from well defined areas of code, which are
  125. * listed in the exceptions table.
  126. *
  127. * As the vast majority of faults will be valid we will only perform
  128. * the source reference check when there is a possibility of a
  129. * deadlock. Attempt to lock the address space, if we cannot we then
  130. * validate the source. If this is invalid we can skip the address
  131. * space check, thus avoiding the deadlock:
  132. */
  133. /*此时可以确定出错addr在用户空间*/
  134. if (unlikely(!down_read_trylock(&mm->mmap_sem))) {
  135. /*错误发生在"内核态",查看异常表
  136. 如果在内核态引起缺页,则引起缺页的
  137. "指令地址"一定在"异常表"中
  138. 如果"异常表"中返回指令地址
  139. ,则说明可能是"请求调页",也可能是"非法访问"
  140. 如果"异常表"中无地址,则肯定是内核错误
  141. */
  142. if ((error_code & PF_USER) == 0 &&
  143. !search_exception_tables(regs->ip)) {
  144. bad_area_nosemaphore(regs, error_code, address);
  145. return;
  146. }
  147. down_read(&mm->mmap_sem);
  148. } else {
  149. /*
  150. * The above down_read_trylock() might have succeeded in
  151. * which case we'll have missed the might_sleep() from
  152. * down_read():
  153. */
  154. might_sleep();
  155. }
  156. /*寻找address所在的vma*/
  157. vma = find_vma(mm, address);
  158. /*如果address之后无vma,则肯定是非法访问*/
  159. if (unlikely(!vma)) {
  160. bad_area(regs, error_code, address);
  161. return;
  162. }
  163. /*1 如果vma->start_address<=address,则直接跳到 "合法访问"阶段
  164. 2 如果vma->start_address>address,则也有可能是用户的"入栈行为"导致缺页*/
  165. if (likely(vma->vm_start <= address))
  166. goto good_area;
  167. /* "入栈"操作,则该vma的标志为 "向下增长"*/
  168. if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
  169. bad_area(regs, error_code, address);
  170. return;
  171. }
  172. /*确定缺页发生在"用户态"*/
  173. if (error_code & PF_USER) {
  174. /*
  175. * Accessing the stack below %sp is always a bug.
  176. * The large cushion allows instructions like enter
  177. * and pusha to work. ("enter $65535, $31" pushes
  178. * 32 pointers and then decrements %sp by 65535.)
  179. */
  180. /*验证缺页address和栈顶sp的关系*/
  181. if (unlikely(address + 65536 + 32 * sizeof(unsigned long) < regs->sp)) {
  182. bad_area(regs, error_code, address);
  183. return;
  184. }
  185. }/*扩展栈*/
  186. if (unlikely(expand_stack(vma, address))) {
  187. bad_area(regs, error_code, address);
  188. return;
  189. }
  190. /*
  191. * Ok, we have a good vm_area for this memory access, so
  192. * we can handle it..
  193. */
  194. good_area:
  195. write = error_code & PF_WRITE;
  196. /*再次验证"权限"*/
  197. if (unlikely(access_error(error_code, write, vma))) {
  198. bad_area_access_error(regs, error_code, address);
  199. return;
  200. }
  201. /*
  202. * If for any reason at all we couldn't handle the fault,
  203. * make sure we exit gracefully rather than endlessly redo
  204. * the fault:
  205. */
  206. /*分配新"页框"*/
  207. fault = handle_mm_fault(mm, vma, address, write ? FAULT_FLAG_WRITE : 0);
  208. if (unlikely(fault & VM_FAULT_ERROR)) {
  209. mm_fault_error(regs, error_code, address, fault);
  210. return;
  211. }
  212. if (fault & VM_FAULT_MAJOR) {
  213. tsk->maj_flt++;
  214. perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, 0,
  215. regs, address);
  216. } else {
  217. tsk->min_flt++;
  218. perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, 0,
  219. regs, address);
  220. }
  221. check_v8086_mode(regs, address, tsk);
  222. up_read(&mm->mmap_sem);
  223. }

大致流程中分为:

地址为内核空间:

1,当地址为内核地址空间并且在内核中访问时,如果是非连续内存地址,将init_mm中对应的项复制到本进程对应的页表项做修正;

2,地址为内核空间时,检查页表的访问权限;

3,如果1,2没搞定,跳到非法访问处理(在后面详细分析这个);

地址为用户空间:

4,如果使用了保留位,打印信息,杀死当前进程;

5,如果在中断上下文中火临界区中时,直接跳到非法访问;

6,如果出错在内核空间中,查看异常表,进行相应的处理;

7,查找地址对应的vma,如果找不到,直接跳到非法访问处,如果找到正常,跳到good_area;

8,如果vma->start_address>address,可能是栈太小,对齐进行扩展;

9,good_area处,再次检查权限;

10,权限正确后分配新页框,页表等;

对于缺页中断的非法访问由函数bad_area执行,该函数的执行情况分为:

1,如果在用户空间访问,直接发送SEGSEGV信号;

2,如果在内核空间访问分为两种情况:

1)地址是一个错误的系统调用参数,修正码(典型是发送SIGSEGV信号);

2)反之,杀死进程并显示内核的OOPS信息;

  1. static void
  2. __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,
  3. unsigned long address, int si_code)
  4. {
  5. struct task_struct *tsk = current;
  6. /* User mode accesses just cause a SIGSEGV */
  7. /*如果用户态*/
  8. if (error_code & PF_USER) {
  9. /*
  10. * It's possible to have interrupts off here:
  11. */
  12. local_irq_enable();
  13. /*
  14. * Valid to do another page fault here because this one came
  15. * from user space:
  16. */
  17. if (is_prefetch(regs, error_code, address))
  18. return;
  19. if (is_errata100(regs, address))
  20. return;
  21. if (unlikely(show_unhandled_signals))
  22. show_signal_msg(regs, error_code, address, tsk);
  23. /* Kernel addresses are always protection faults: */
  24. tsk->thread.cr2      = address;
  25. tsk->thread.error_code   = error_code | (address >= TASK_SIZE);
  26. tsk->thread.trap_no  = 14;
  27. /*发送SIGSEGV信号*/
  28. force_sig_info_fault(SIGSEGV, si_code, address, tsk);
  29. return;
  30. }
  31. if (is_f00f_bug(regs, address))
  32. return;
  33. /*内核态访问*/
  34. no_context(regs, error_code, address);
  35. }

内核访问时

  1. static noinline void
  2. no_context(struct pt_regs *regs, unsigned long error_code,
  3. unsigned long address)
  4. {
  5. struct task_struct *tsk = current;
  6. unsigned long *stackend;
  7. unsigned long flags;
  8. int sig;
  9. /* Are we prepared to handle this kernel fault? */
  10. /*地址是一个系统调用参数,"修正码",典型情况是发送
  11. SIGSEGV信号*/
  12. if (fixup_exception(regs))
  13. return;
  14. /*
  15. * 32-bit:
  16. *
  17. *   Valid to do another page fault here, because if this fault
  18. *   had been triggered by is_prefetch fixup_exception would have
  19. *   handled it.
  20. *
  21. * 64-bit:
  22. *
  23. *   Hall of shame of CPU/BIOS bugs.
  24. */
  25. if (is_prefetch(regs, error_code, address))
  26. return;
  27. if (is_errata93(regs, address))
  28. return;
  29. /*
  30. * Oops. The kernel tried to access some bad page. We'll have to
  31. * terminate things with extreme prejudice:
  32. */
  33. /*下面代码用于oops信息的显示和杀死当前
  34. 进程*/
  35. flags = oops_begin();
  36. show_fault_oops(regs, error_code, address);
  37. stackend = end_of_stack(tsk);
  38. if (*stackend != STACK_END_MAGIC)
  39. printk(KERN_ALERT "Thread overran stack, or stack corrupted\n");
  40. tsk->thread.cr2      = address;
  41. tsk->thread.trap_no  = 14;
  42. tsk->thread.error_code   = error_code;
  43. sig = SIGKILL;
  44. if (__die("Oops", regs, error_code))
  45. sig = 0;
  46. /* Executive summary in case the body of the oops scrolled away */
  47. printk(KERN_EMERG "CR2: %016lx\n", address);
  48. oops_end(flags, regs, sig);
  49. }

linux内核分析之缺页中断【转】的更多相关文章

  1. linux内核分析作业8:理解进程调度时机跟踪分析进程调度与进程切换的过程

    1. 实验目的 选择一个系统调用(13号系统调用time除外),系统调用列表,使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 分析汇编代码调用系统调用的工作过程,特别是参数的传递的方 ...

  2. Linux内核分析作业7:Linux内核如何装载和启动一个可执行程序

            1.可执行文件的格式 在 Linux 平台下主要有以下三种可执行文件格式: 1.a.out(assembler and link editor output 汇编器和链接编辑器的输出) ...

  3. linux内核分析作业6:分析Linux内核创建一个新进程的过程

    task_struct结构: struct task_struct {   volatile long state;进程状态  void *stack; 堆栈  pid_t pid; 进程标识符  u ...

  4. linux内核分析作业5:分析system_call中断处理过程

    1.增加 Menu 内核命令行 调试系统调用. 步骤:删除menu git clone        (tab) make rootfs 这就是我们将 fork 函数写入 Menu 系统内核后的效果, ...

  5. linux内核分析作业:以一简单C程序为例,分析汇编代码理解计算机如何工作

    一.实验 使用gcc –S –o main.s main.c -m32 命令编译成汇编代码,如下代码中的数字请自行修改以防与他人雷同 int g(int x) { return x + 3; } in ...

  6. linux内核分析作业:操作系统是如何工作的进行:完成一个简单的时间片轮转多道程序内核代码

    计算机如何工作 三个法宝:存储程序计算机.函数调用堆栈.中断机制. 堆栈 函数调用框架 传递参数 保存返回地址 提供局部变量空间 堆栈相关的寄存器 Esp 堆栈指针  (stack pointer) ...

  7. linux内核分析作业3:跟踪分析Linux内核的启动过程

    内核源码目录 1. arch:录下x86重点关注 2. init:目录下main.c中的start_kernel是启动内核的起点 3. ipc:进程间通信的目录 实验 使用实验楼的虚拟机打开shell ...

  8. linux内核分析作业4:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    系统调用:库函数封装了系统调用,通过库函数和系统调用打交道 用户态:低级别执行状态,代码的掌控范围会受到限制. 内核态:高执行级别,代码可移植性特权指令,访问任意物理地址 为什么划分级别:如果全部特权 ...

  9. 《Linux内核分析》期末总结

    Linux内核设计期中总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 前八周博客汇总及总结 Linux内核设计第一周——从汇编语言出发理解计算机工作原理 我们学习了汇编语言的基础知识,这一 ...

随机推荐

  1. javascript实现在textarea光标位置插入文字并移动光标到文字末尾

    1.背景:实现在textarea光标位置插入文字并移动光标到文字末尾 如果每次通过val("ss")赋值的形式插入文字到textarea中,会将上一次赋的值覆盖掉. 2.思路: & ...

  2. Java 泛型 二

    一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...

  3. 剑指Offer - 九度1524 - 复杂链表的复制

    剑指Offer - 九度1524 - 复杂链表的复制2014-02-07 01:30 题目描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点 ...

  4. Python lambda介绍

    在学习python的过程中,lambda的语法时常会使人感到困惑,lambda是什么,为什么要使用lambda,是不是必须使用lambda? 下面就上面的问题进行一下解答. 1.lambda是什么? ...

  5. 一个初学者的辛酸路程-jQuery

    前言: 主要概要: 1.HTML+CSS补充 2.DOM事件 3.jQuery示例 内容概要: 1.布局 代码如下 <!DOCTYPE html> <html lang=" ...

  6. Oracle 学习----:Oracle删除表时报错:表或视图不存在

    表明明存在,但是删除时却报错:表或视图不存在. 可能的原因之一是表名包含了小写,可以用双引号包含表名通过drop命令来删除, 如下所示: drop table "tmp_ST" ; ...

  7. java 继承小结

    [code=java] //多态的经典例子 //向上转型后,父类只能调用子类和父类的共同方法和的重写方法(方法名相同,参数也相同),不能调用重载方法(方法名相同,但参数不同) class A { pu ...

  8. ssh.sh_for_ubuntu1604

    #!/bin/bash sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config ...

  9. 【已解决】UBuntu16.04软件中心更新后只有桌面

    出现问题之前 安装好系统之后,成功启动系统,根据软件中心提示,升级电脑安装的软件,如下图所示.点击install(安装)开始更新,之后无任何提示,其实这个时候已经出现了升级异常. 呈现问题 操作步骤一 ...

  10. 【志银】NYOJ《题目860》又见01背包

    题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=860 方法一:不用滚动数组(方法二为用滚动数组,为方法一的简化) 动态规划分析:最少要拿总 ...