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

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

缺页异常被触发通常有两种情况——

1.程序设计的不当导致访问了非法的地址

2.访问的地址是合法的,但是该地址还未分配物理页框

下面解释一下第二种情况,这是虚拟内存管理的一个特性。尽管每个进程独立拥有3GB的可访问地址空间,但是这些资源都是内核开出的空头支票,也就是说进程手握着和自己相关的一个个虚拟内存区域(vma),但是这些虚拟内存区域并不会在创建的时候就和物理页框挂钩,由于程序的局部性原理,程序在一定时间内所访问的内存往往是有限的,因此内核只会在进程确确实实需要访问物理内存时才会将相应的虚拟内存区域与物理内存进行关联(为相应的地址分配页表项,并将页表项映射到物理内存),也就是说这种缺页异常是正常的,而第一种缺页异常是不正常的,内核要采取各种可行的手段将这种异常带来的破坏减到最小。

缺页异常的处理函数为do_page_fault(),该函数是和体系结构相关的一个函数,缺页异常的来源可分为两种,一种是内核空间(访问了线性地址空间的第4个GB),一种是用户空间(访问了线性地址空间的0~3GB),以X86架构为例,先来看内核空间异常的处理。

  1. dotraplinkage void __kprobes
  2. do_page_fault(struct pt_regs *regs, unsigned long error_code)
  3. {
  4. struct vm_area_struct *vma;
  5. struct task_struct *tsk;
  6. unsigned long address;
  7. struct mm_struct *mm;
  8. int write;
  9. int fault;
  10. tsk = current; //获取当前进程
  11. mm = tsk->mm;  //获取当前进程的地址空间
  12. /* Get the faulting address: */
  13. address = read_cr2(); //读取CR2寄存器获取触发异常的访问地址
  14. ...
  15. ...
  16. if (unlikely(fault_in_kernel_space(address))) { //判断address是否处于内核线性地址空间
  17. if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {//判断是否处于内核态
  18. if (vmalloc_fault(address) >= 0)//处理vmalloc异常
  19. return;
  20. if (kmemcheck_fault(regs, address, error_code))
  21. return;
  22. }
  23. /* Can handle a stale RO->RW TLB: */
  24. /*异常发生在内核地址空间但不属于上面的情况或上面的方式无法修正,
  25. 则检查相应的页表项是否存在,权限是否足够*/
  26. if (spurious_fault(error_code, address))
  27. return;
  28. /* kprobes don't want to hook the spurious faults: */
  29. if (notify_page_fault(regs))
  30. return;
  31. /*
  32. * Don't take the mm semaphore here. If we fixup a prefetch
  33. * fault we could otherwise deadlock:
  34. */
  35. bad_area_nosemaphore(regs, error_code, address);
  36. return;
  37. }
  38. ...
  39. ...
  40. }

该函数传递进来的两个参数--

regs包含了各个寄存器的值

error_code是触发异常的错误类型,它的含义如下

  1. /*
  2. * Page fault error code bits:
  3. *
  4. *   bit 0 ==    0: no page found   1: protection fault
  5. *   bit 1 ==    0: read access     1: write access
  6. *   bit 2 ==    0: kernel-mode access  1: user-mode access
  7. *   bit 3 ==               1: use of reserved bit detected
  8. *   bit 4 ==               1: fault was an instruction fetch
  9. */
  10. enum x86_pf_error_code {
  11. PF_PROT     =       1 << 0,
  12. PF_WRITE    =       1 << 1,
  13. PF_USER     =       1 << 2,
  14. PF_RSVD     =       1 << 3,
  15. PF_INSTR    =       1 << 4,
  16. };

首先要检查该异常的触发地址是不是位于内核地址空间 也就是address>=TASK_SIZE_MAX,一般为3GB。然后要检查触发异常时是否处于内核态,满足这两个条件就尝试通过vmalloc_fault()来解决这个异常。由于使用vmalloc申请内存时,内核只会更新主内核页表,所以当前使用的进程页表就有可能因为未与主内核页表同步导致这次异常的触发,因此该函数试图将address对应的页表项与主内核页表进行同步

  1. static noinline int vmalloc_fault(unsigned long address)
  2. {
  3. unsigned long pgd_paddr;
  4. pmd_t *pmd_k;
  5. pte_t *pte_k;
  6. /* 确定触发异常的地址是否处于VMALLOC区域*/
  7. if (!(address >= VMALLOC_START && address < VMALLOC_END))
  8. return -1;
  9. /*
  10. * Synchronize this task's top level page-table
  11. * with the 'reference' page table.
  12. *
  13. * Do _not_ use "current" here. We might be inside
  14. * an interrupt in the middle of a task switch..
  15. */
  16. pgd_paddr = read_cr3();//获取当前的PGD地址
  17. pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);//将当前使用的页表和内核页表同步
  18. if (!pmd_k)
  19. return -1;
  20. /*到这里已经获取了内核页表对应于address的pmd,并且将该值设置给了当前使用页表的pmd,
  21. 最后一步就是判断pmd对应的pte项是否存在*/
  22. pte_k = pte_offset_kernel(pmd_k, address);//获取pmd对应address的pte项
  23. if (!pte_present(*pte_k))//判断pte项是否存在,不存在则失败
  24. return -1;
  25. return 0;
  26. }

同步处理:

  1. static inline pmd_t *vmalloc_sync_one(pgd_t *pgd, unsigned long address)
  2. {
  3. unsigned index = pgd_index(address);
  4. pgd_t *pgd_k;
  5. pud_t *pud, *pud_k;
  6. pmd_t *pmd, *pmd_k;
  7. pgd += index; //记录当前页表pgd对应address的偏移
  8. pgd_k = init_mm.pgd + index;//记录内核页表对应address的偏移
  9. if (!pgd_present(*pgd_k))//内核PGD页表对应的项不存在,则无法进行下一步,返回NULL
  10. return NULL;
  11. /*
  12. * set_pgd(pgd, *pgd_k); here would be useless on PAE
  13. * and redundant with the set_pmd() on non-PAE. As would
  14. * set_pud.
  15. */
  16. /*获取当前页表对应address的PUD地址和内核页表对应address的地址,并判断pud_k对应的项是否存在*/
  17. pud = pud_offset(pgd, address);
  18. pud_k = pud_offset(pgd_k, address);
  19. if (!pud_present(*pud_k))
  20. return NULL;
  21. /*对pmd进行和上面类似的操作*/
  22. pmd = pmd_offset(pud, address);
  23. pmd_k = pmd_offset(pud_k, address);
  24. if (!pmd_present(*pmd_k))
  25. return NULL;
  26. if (!pmd_present(*pmd))//当前使用页表对应的pmd项不存在,则修正pmd项使其和内核页表的pmd_k项相同
  27. set_pmd(pmd, *pmd_k);
  28. else
  29. BUG_ON(pmd_page(*pmd) != pmd_page(*pmd_k));
  30. return pmd_k;
  31. }

如果do_page_fault()函数执行到了bad_area_nosemaphore(),那么就表明这次异常是由于对非法的地址访问造成的。在内核中产生这样的结果的情况一般有两种:

1.内核通过用户空间传递的系统调用参数,访问了无效的地址

2.内核的程序设计缺陷

第一种情况内核尚且能通过异常修正机制来进行修复,而第二种情况就会导致OOPS错误了,内核将强制用SIGKILL结束当前进程。

内核态的bad_area_nosemaphore()的实际处理函数为bad_area_nosemaphore()-->__bad_area_nosemaphore()-->no_context()

  1. <span style="font-size:12px;">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. /*fixup_exception()用于搜索异常表,并试图找到一个对应该异常的例程来进行修正,
  11. 这个例程在fixup_exception()返回后执行*/
  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. /* 走到这里就说明异常确实是由于内核的程序设计缺陷导致的了,内核将
  34. 产生一个oops,下面的工作就是打印CPU寄存器和内核态堆栈的信息到控制台并
  35. 终结当前的进程*/
  36. flags = oops_begin();
  37. show_fault_oops(regs, error_code, address);
  38. stackend = end_of_stack(tsk);
  39. if (*stackend != STACK_END_MAGIC)
  40. printk(KERN_ALERT "Thread overran stack, or stack corrupted\n");
  41. tsk->thread.cr2      = address;
  42. tsk->thread.trap_no  = 14;
  43. tsk->thread.error_code   = error_code;
  44. sig = SIGKILL;
  45. if (__die("Oops", regs, error_code))
  46. sig = 0;
  47. /* Executive summary in case the body of the oops scrolled away */
  48. printk(KERN_EMERG "CR2: %016lx\n", address);
  49. oops_end(flags, regs, sig);
  50. }
  51. </span>

linux缺页异常处理--内核空间【转】的更多相关文章

  1. linux缺页异常处理--内核空间

    缺页异常被触发通常有两种情况-- 程序设计的不当导致访问了非法的地址 访问的地址是合法的,但是该地址还未分配物理页框. 下面解释一下第二种情况,这是虚拟内存管理的一个特性.尽管每个进程独立拥有3GB的 ...

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

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

  3. linux进程用户内存空间和内核空间

    When a process running in user mode requests additional memory, pages are allocated from the list of ...

  4. Linux用户空间与内核空间

    源:http://blog.csdn.net/f22jay/article/details/7925531 Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针 ...

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

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

  6. 裸板中中断异常处理,linux中断异常处理 ,linux系统中断处理的API,中断处理函数的要求,内核中登记底半部的方式

    1.linux系统中的中断处理  1.0裸板中中断异常是如何处理的?     以s5p6818+按键为例          1)按键中断的触发        中断源级配置            管脚功 ...

  7. 在Linux用户空间做内核空间做的事情

    导读 我相信,Linux 最好也是最坏的事情,就是内核空间(kernel space)和用户空间(user space)之间的巨大差别.如果没有这个区别,Linux 可能也不会成为世界上影响力最大的操 ...

  8. Linux用户空间与内核空间(理解高端内存)

    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...

  9. linux内核空间与用户空间信息交互方法

    linux内核空间与用户空间信息交互方法     本文作者: 康华:计算机硕士,主要从事Linux操作系统内核.Linux技术标准.计算机安全.软件测试等领域的研究与开发工作,现就职于信息产业部软件与 ...

随机推荐

  1. poj 2579 中位数问题 查找第K大的值

    题意:对列数X计算∣Xi – Xj∣组成新数列的中位数. 思路:双重二分搜索 对x排序 如果某数大于 mid+xi 说明在mid后面,这些数的个数小于 n/2 的话说明这个中位数 mid 太大 反之太 ...

  2. PHP.35-TP框架商城应用实例-后台11-商品分类-删除分类(2种方法)、添加、修改

    删除分类 删除一个分类的同时,其所有子分类都删除 在控制器CategoryCtroller.class.php中添加删除函数(delete) 在分类模型中添加钩子函数_before_delete()[ ...

  3. Altium Designer多原理图、PCB更新处理

    问题扫述: 一般一个同程有多个原理图.PCB.但是AD默认从原理图更新到PCB会把全部原理图都更新过去.因此需要稍加设置. 一.

  4. MySQL 5.7.18 压缩包版配置记录

    1.解压到一个目录(建议根目录),比如:D:\mysql2.在系统Path中添加 D:\mysql\bin3.这个版本不带my-default.ini,需要自己写,放在D:\mysql\my.ini, ...

  5. 剑指Offer - 九度1515 - 打印1到最大的N位数

    剑指Offer - 九度1515 - 打印1到最大的N位数2013-11-30 01:11 题目描述: 给定一个数字N,打印从1到最大的N位数. 输入: 每个输入文件仅包含一组测试样例.对于每个测试案 ...

  6. 剑指Offer - 九度1372 - 最大子向量和(连续子数组的最大和)

    剑指Offer - 九度1372 - 最大子向量和(连续子数组的最大和)2013-11-23 16:25 题目描述: HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天JOBDU测试组开完会后, ...

  7. 《Cracking the Coding Interview》——第5章:位操作——题目5

    2014-03-19 06:22 题目:将整数A变成整数B,每次只能变一个二进制位,要变多少次呢. 解法:异或,然后求‘1’的个数. 代码: // 5.5 Determine the number o ...

  8. 【Kernal Support Vector Machine】林轩田机器学习技术

    考虑dual SVM 问题:如果对原输入变量做了non-linear transform,那么在二次规划计算Q矩阵的时候,就面临着:先做转换,再做内积:如果转换后的项数很多(如100次多项式转换),那 ...

  9. Python 字符串格式化输出方式

    字符串格式化有两种方式:百分号方式.format方式. 其中,百分号方式比较老,而format方式是比较先进的,企图替代古老的方式,目前两者共存. 1.百分号方式 格式:%[(name)][flags ...

  10. Linux - Shell - 常用方法 - 备忘录

    $? 上一个指令的返回值 =成功,=失败 dmesg 检测系统开机启动信息 $() 对命令的替换,同`` ${} 对变量的替换,同$var $(()) 对内部内容进行整数运算 i= grep AAA ...