转自: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. Lighting System Design UVA - 11400 动态规划

    题目:题目链接 思路:简单的动态规划问题,先把灯泡按照电压从小到大排序.设s[i]为前i种灯泡的总数量(即L值之和),d[i]为灯 泡1-i的最小开销,则d[i] = min{d[j] + (s[i] ...

  2. Aizu:2170-Marked Ancestor

    Marked Ancestor Time limit 8000 ms Memory limit 131072 kB Problem Description You are given a tree T ...

  3. PHP代码审计6-实战漏洞挖掘-xdcms用户注册页面漏洞

    xdcms 源码:xdcms v2.0.8 1.配置 [一直下一步(仅为测试)] #数据库账号root,密码为空:管理员账号/密码:xdcms/xdcms #登录后台 2.查看后台登录页面的配置项[x ...

  4. Alter the structure of web pages with JavaScript

    Most of the DOM methods you've seen so far are useful for identifying elements. Both getElementById ...

  5. TopCoder SRM 710 Div2 Hard MinMaxMax Floyd最短路变形

    题意: 有一个无向连通图,没有重边没有自环,并给出顶点的权值和边的权值 定义一条路径\(difficulty\)值为该路径上最大的点权乘上最大的边权 定义函数\(d(i,j)\)为\(i,j\)之间的 ...

  6. Android stadio Switch repository Android stadio切换仓库

    Android stadio 有时候,有很多module. 这些module 都有自己的仓库.也就是不在一块.那么,Android stadio 默认管理的就是根git. 如图,画对号的就是默认的. ...

  7. java 1.7 新io 实践 NIO2

    Files 类使用 package com.xinyu.test; import java.io.IOException; import java.nio.ByteBuffer; import jav ...

  8. django之python3.4及以上连接mysql的一些问题记录

    首先,祭出大杀器whl https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient django1.x与django2.x 在项目的写法上有一些区别 ...

  9. react书写规范小记

    1.对齐方式 //如果没有属性,在自闭和标签前添加一个空格: <Footer /> //如果可以放在一行,放在一行上即可: <Footer bar="bar" / ...

  10. gulp相关

    'use strict'; var gulp = require('gulp'), webserver = require('gulp-webserver'), //gulp服务器 connect = ...