页式存储管理机制通过页面目录和页面表将每个线性地址转换成物理地址,当遇到下面几种情况就会使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. 30天自制操作系统(二)汇编语言学习和Makefile入门

    我们继续学习操作系统的相关内容. ; hello-os ; TAB=4 ORG 0x7c00 ; このプログラムがどこに読み込まれるのか ; 以下は標準的なFAT12フォーマットフロッピーディスクのた ...

  2. php去除html标签

    function cutstr_html($string){ $string = strip_tags($string); $string = preg_replace(["\t" ...

  3. 华为云计算IE面试笔记-eBackup有哪几种备份组网方式,各备份组网方式主要的应用场景及备份流程?

    应用场景: LAN-Base一般用于备份数据量小,且对备份窗口没有特殊要求的场景,此类场景下备份服务器和备份代理一般是虚拟机部署. LAN-Free一般用于备份数据量较大,且对备份窗口要求比较严格的场 ...

  4. P3352-[ZJOI2016]线段树【dp】

    正题 题目链接:https://www.luogu.com.cn/problem/P3352 题目大意 \(n\)个数字的一个序列,每次随机选择一个区间让这个区间所有数等于这个区间的最大值,重复\(q ...

  5. 深入浅出WPF-11.Template(模板)03

    模板 如果把WPF窗体看做一个舞台的话,窗体上的控件就是演员,他们的职责就是在用户界面上按照业务逻辑的需呀哦扮演自己的角色.为了让同一个控件担当起不同的角色,程序员就要为他们设计多种外观样式和行为动作 ...

  6. python OSError: [Errno 22] Invalid argument: '\u202aF://text

    windows10 python3 读文件的时候报的错误 原因不明时好时坏很头疼但又没办法不知道怎么解决,网上的说法都不能解决,

  7. CSP初赛考点汇总

    qwq 为SCP初赛选手(我)收集的各种定理qwq 更新: 1.为了初赛都能用,不限于定理了 2.主旨为在短时间内复习各算法,备初赛 3.请确定你学习(学懂了)了 \(\texttt{oi}\) 的基 ...

  8. IDEA Web渲染插件开发(二)— 自定义JsDialog

    <IDEA Web渲染插件开发(一)>中,我们了解到了如何编写一款用于显示网页的插件,所需要的核心知识点就是IDEA插件开发和JCEF,在本文中,我们将继续插件的开发,为该插件的JS Di ...

  9. SpringPlugin-Core在业务中的应用

    前言 一直负责部门的订单模块,从php转到Java也是如此,换了一种语言来实现订单相关功能.那么Spring里有很多已经搭建好基础模块的设计模式来帮助我们解耦实际业务中的逻辑,用起来非常的方便!就比如 ...

  10. Java初步学习——2021.10.09每日总结,第五周周六

    (1)今天做了什么: (2)明天准备做什么? (3)遇到的问题,如何解决? 今天学习了菜鸟教程实例部分 一.字符串 1.字符串比较--compareTo方法 public class Main { p ...