页式存储管理机制通过页面目录和页面表将每个线性地址转换成物理地址,当遇到下面几种情况就会使CPU产生一次缺页中断,从而执行预定的页面异常处理程序:

  ① 相应的页面目录或页表项为空,也就是该线性地址与物理地址的关系还没建立或者已经撤销

  ② 相应的物理页面不在内存中

  ③ 指令规定的访问方式与页面的权限不符

  我们假设一个情景,当我们的用户程序将一个打开的文件通过mmap()映射到内存,然后又通过munmap()撤销映射。在撤销一个映射区间时,常常会在虚存地址空间中留下一个空洞,而相应的地址则不应继续使用了,但是,程序中可能会有错误继续访问这个已经撤销的区域,这时候,一次因越界访问一个无效地址而引起映射失败,从而产生了一次页面出错异常。 我们假设CPU已经运行到页面异常服务程序的主体函数do_page_fault()的入口处

 1 asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
2 {
3 struct task_struct *tsk;
4 struct mm_struct *mm;
5 struct vm_area_struct * vma;
6 unsigned long address;
7 unsigned long page;
8 unsigned long fixup;
9 int write;
10 siginfo_t info;
11
12 /* get the address */
13 __asm__("movl %%cr2,%0":"=r" (address));
14
15 tsk = current;
16
17 /*
18 * We fault-in kernel-space virtual memory on-demand. The
19 * 'reference' page table is init_mm.pgd.
20 *
21 * NOTE! We MUST NOT take any locks for this case. We may
22 * be in an interrupt or a critical region, and should
23 * only copy the information from the master page table,
24 * nothing more.
25 */
26 if (address >= TASK_SIZE)
27 goto vmalloc_fault;
28
29 mm = tsk->mm;
30 info.si_code = SEGV_MAPERR;
31
32 /*
33 * If we're in an interrupt or have no user
34 * context, we must not take the fault..
35 */
36 if (in_interrupt() || !mm)
37 goto no_context;
38
39 down(&mm->mmap_sem);
40
41 vma = find_vma(mm, address);
42 if (!vma)
43 goto bad_area;
44 if (vma->vm_start <= address)
45 goto good_area;
46 if (!(vma->vm_flags & VM_GROWSDOWN))
47 goto bad_area;

  此函数参数,一个是pt_regs 结构指针regs,它指向异常发生前夕 CPU 中各寄存器内容的一份副本,这是由内核的中断响应机制保存下来的“现场”,另一个参数error_code则指明映射失败的具体原因。

  首先是一行汇编代码,引文当i386 CPU 产生“页面错”异常时,CPU 将导致映射失败的线性地址放在控制寄存器CR2中,这行代码的作用就是读取CR2的内容到变量address。

  然后是通过宏操作current来取得当前进程的task_struct结构的地址。

  接下来,需要检测两个特殊情况,一个特殊情况是in_interrupt()返回非0,说明映射的失败发生在某个中断服务程序中,因而与当前进程毫无关系,另一个特殊情况是当前进程的mm指针为空,也就是该进程尚未建立映射,也就不可能与当前进程有关。若mm指针为空,且in_interrupt返回非0,,那这次异常发生在什么地方呢?其实还是在某个中断/异常的服务程序中,只不过不在in_interrupt()能检测到的范围而已。

  以下有互斥的要求了,从down()返回后就不会有别的进程来打扰了。

  在知道了发生映射失败的地址以及所属的进程以后,接下来应该要搞清楚的是这个地址是否落在某个已建立起映射的区间,或者进一步具体指出在哪个区间。事实正是这样,这就是find_vma()所做的事。以前讲过,find_vma()试图在一个虚存空间中找出结束地址大于给定地址的第一个区间,如果找不到的话,那么本次页面异常就必定是因越界访问而引起,那么,在什么情况下会找不到呢?回忆一下内核对用户虚存空间的使用,堆栈在用户区的顶部,从上而下扩展,而进程的代码和数据都是自底向上分配空间。如果没有一个区间的结束地址高于给定的地址,那就说明这个地址是在堆栈之上,也就是3G字节以上了,要从用户空间访问内核空间,当然是越界了,然后就转向bad_area,发生映射失败的地址对应下图的①

  如果找到了这么一个区间,而且其起始地址又不高于给定的地址,那就说明给定的地址恰好落在这个区间,这样,映射肯定已经建立,所以就转向good_area去进一步检查失败的原因,发生映射失败的地址对应下图的②或⑤

  剩下的情况就是给定地址正好落在两个区间当中的空洞里,也就是该地址所在页面的映射尚未建立或已经撤销,在用户虚存空间中,可能有两种不同的空洞,第一种空洞只能有一个,那就是堆栈区以下的那个大空洞,它代表着工动态分配而仍未分配出去的空间。但是怎样知道这个地址是落在这个空洞里呢?我们知道,堆栈是向下扩展的,如果find_vma()找到的区间是堆栈区间,那么它的vm_flags中应该有标志位VM_GROWSDOWN。要是该标志位为0的话,那就说明空洞上方的区间并非堆栈区,说明这个空洞是因为一个映射区间被撤销而留下的,或者是在建立映射时跳过了一块地址,发生映射失败的地址对应下图的④

下面,我们就随着goto语句转向bad_area,

【do_page_fault】

 1 bad_area:
2 up(&mm->mmap_sem);
3
4 bad_area_nosemaphore:
5 /* User mode accesses just cause a SIGSEGV */
6 if (error_code & 4) {
7 tsk->thread.cr2 = address;
8 tsk->thread.error_code = error_code;
9 tsk->thread.trap_no = 14;
10 info.si_signo = SIGSEGV;
11 info.si_errno = 0;
12 /* info.si_code has been set above */
13 info.si_addr = (void *)address;
14 force_sig_info(SIGSEGV, &info, tsk);
15 return;
16 }

  当error_code的bit2为1时,表示失败是当CPU处于用户模式时发生的,这与我们的情景相符,所以控制将进入第6行,在那里,对当前进程的task_struct结构内的一些成员进行一些设置以后,就向该进程发出一个强制的“信号”SIGSEGV,至此,本次例外服务程序就结束了。

  每次中断/异常返回之前,都要检查当前进程是否有悬而未决的信号需要处理,然后,内核根据这些待处理的信号的性质以及进程本身的选择决定怎么办,而对于SIGSEGV的处理结果是在该进程的显示屏上显示“Segment Fault”提示,然后结束进程

linux 内核源代码情景分析——越界访问的更多相关文章

  1. Linux内核源代码情景分析系列

    http://blog.sina.com.cn/s/blog_6b94d5680101vfqv.html Linux内核源代码情景分析---第五章 文件系统  5.1 概述 构成一个操作系统最重要的就 ...

  2. Linux内核源代码情景分析-fork()

    父进程fork子进程: child = fork() fork经过系统调用.来到了sys_fork.具体过程请參考Linux内核源码情景分析-系统调用. asmlinkage int sys_fork ...

  3. linux 内核源代码情景分析——用户堆栈的扩展

    上一节中,我们浏览了一次因越界访问而造成映射失败从而引起进程流产的过程,不过有时候,越界访问时正常的.现在我们就来看看当用户堆栈过小,但是因越界访问而"因祸得福"得以伸展的情景. ...

  4. linux 内核源代码情景分析——地址映射的全过程

    linux 内核采用页式存储管理.虚拟地址空间划分成固定大小的"页面",由MMU在运行时将虚拟地址映射成某个物理内存页面中的地址.页式内存管理比段式内存管理有很多好处,但是由于In ...

  5. linux 内核源代码情景分析——linux 内存管理的基本框架

    386 CPU中的页式存管的基本思路是:通过页面目录和页面表分两个层次实现从线性地址到物理地址的映射.这种映射模式在大多数情况下可以节省页面表所占用的空间.因为大多数进程不会用到整个虚存空间,在虚存空 ...

  6. linux 内核源代码情景分析——linux 内核源代码中的C语言代码

    linux 内核的主体是以GNU的C语言编写的,GNU为此提供了编译工具gcc.GNU对C语言本身作了不少扩充. 1) gcc 从 C++ 语言中吸收了"inline"和" ...

  7. Linux内核源代码情景分析-中断半

    一.中断初始化 1.中断向量表IDT初始化 void __init init_IRQ(void) { int i; #ifndef CONFIG_X86_VISWS_APIC init_ISA_irq ...

  8. linux 内核源代码情景分析——几个重要的数据结构和函数

    页面目录PGD.中间目录PMD和页面表PT分别是由表项pgd_t.pmd_t和pte_t构成的数组,而这些表项都是数据结构 1 /* 2 * These are used to make use of ...

  9. linux 内核源代码情景分析——linux 内核源码中的汇编语言代码

    1. 用汇编语言编写部分核心代码的原因: ① 操作系统内核中的底层程序直接与硬件打交道,需要用到一些专用的指令,而这些指令在C语言中并无对应的语言成分: ② CPU中的一些特殊指令也没有对应的C语言成 ...

随机推荐

  1. scrum项目冲刺_day06总结

    摘要:今日完成任务. 1.服务器部署完成 2.由于将数据库放到了服务器上,搜索功能需要修改 总任务: 一.appUI页面(已完成) 二.首页功能: 1.图像识别功能(已完成) 2.语音识别功能(已完成 ...

  2. 学习PHP中统计扩展函数的使用

    做统计相关系统的朋友一定都会学习过什么正态分布.方差.标准差之类的概念,在 PHP 中,也有相应的扩展函数是专门为这些统计相关的功能所开发的.我们今天要学习的 stats 扩展函数库就是这类操作函数. ...

  3. JavaScript进阶面向对象ES6

    类和对象 对象:万物皆对象,对象是一个具体的事物,看得见摸得着的实物 对象是由属性和方法组成的: 属性:事物的特征,再对象中用属性来表示(常用名词) 方法:事物的行为,再对象中用方法来表示(常用动词) ...

  4. 替代jquery中的几个函数

    // https://open.alipay.com/developmentAccess/developmentAccess.htm var $ = window.jQuery; (function( ...

  5. 接口Mock测试

    什么是Mock测试? Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取的比较复杂的对象(如 JDB ...

  6. 【C++ Primer Plus】编程练习答案——第9章

    1 // chapter09_golf.h 2 3 #ifndef LEARN_CPP_CHAPTER09_GOLF_H 4 #define LEARN_CPP_CHAPTER09_GOLF_H 5 ...

  7. WPF之资源专题

    1.一般程序的资源可以分为四个等级: 数据库中的数据相当于放在仓库里 资源文件里的数据相当于放在旅行箱里 WPF对象资源里的数据相当于携带在背包里 变量中的数据相当于拿在手里 2.资源的查找顺序是沿着 ...

  8. PAT (Basic Level) Practice (中文)1061 判断题 (15分)

    1061 判断题 (15分) 判断题的评判很简单,本题就要求你写个简单的程序帮助老师判题并统计学生们判断题的得分. 输入格式: 输入在第一行给出两个不超过 100 的正整数 N 和 M,分别是学生人数 ...

  9. python和js分别在多行字符串中插入一行字符串

    问题 一个多行字符串,"asfdb;\nwesfpjoing;\nwbfliqwbefpwqufn\nasfdwe\nsafewt\nqwern\nvar\ntgwtg\n\nftwg\n& ...

  10. 【转载-Andrew_qian】stm32中断学习

    [转载]stm32中断学习 中断对于开发嵌入式系统来讲的地位绝对是毋庸置疑的,在C51单片机时代,一共只有5个中断,其中2个外部中断,2个定时/计数器中断和一个串口中断,但是在STM32中,中断数量大 ...