linux内核源码分析之缺页异常

目录

一、什么是缺页异常?

二、处理器特定部分

2.1生成页错误异常

2.2处理页错误异常

三、匿名页的缺页异常

四、文件的缺页异常

4.1 处理文件页错误,具体处理读文件页错误的方法

4.2 文件写私有文件页错误的方法

4.3文件写共享文件页错误的方法如下

五、写时复制

六、内核模式页错误异常

ARM64架构下内核发送的页错误异常处理


想分析内存缺页相关的程序,先看一下基础知识。有了基础知识,才能很好的理解程序。

一、什么是缺页异常?

        在取指令或数据的时候,处理器的内存管理单元需要把虚拟地址转换成物理地址。如果虚拟也没有找到物理页时,或者没有访问权限,处理器将生成页错误异常。通常情况下称为缺页异常

大概有以下情况:

  1. 访问用户栈的时候,超出了当前用户栈的范围,需要扩大用户栈
  2. 当进程申请虚拟内存区域的时候,通常没有分配物理页,进程第一次访问的时候触发页错误异常。
  3. 内存不足的时候,内核把进程的匿名页换出到交换区
  4. 一个文件页被映射到进程的虚拟地址空间,内存不足时,内核回收这个文件页,在进程的页表中删除这个文件的映射
  5. 程序错误,访问没有分配给进程的虚拟内存区域,将会发出SIGSEGV信号将进程杀死。

没有访问权限,有以下两种情况:

  1. 可能是软件有意造成的,如写时复制:子进程和父进程以只读的方式共享私有的匿名页和文件页。当其中一个进程试图写只读页时,触发页错误异常,页错误异常处理程序分配新的物理页,把旧的物理页数据复制到新的物理页,然后把虚拟页映射到新的物理页。
  2. 程序错误,如试图写只读的代码段所在的物理页。

        不同处理器架构的页面错误异常不同,页错误异常处理程的前面一部分是各处理器架构自定义部分,后面函数handle_mm_fault开始是共同架构。

二、处理器特定部分

2.1生成页错误异常

ARM64处理器在取指令或数据的时候,需要把虚拟地址换成物理地址,分为两种情况:

1)如果虚拟地址的高16位不全是1或全0,是非法地址,生成页错误异常。

2,处理页错误异常高16位全是1或全0,内存管理单元根据关键字{地址空间标识符,虚拟地址}查找TLB。

如果命中了TLB选项,从TLB表项读取访问权限,检查访问权限,如果没有访问权限则生成页错误异常。

如果没有命中TLB选项,内存管理单元将会查询内存中的页表,称为转换遍历。

  • 如果虚拟地址的高16位全是1,说明是内核虚拟地址,应该查询内核的页表,从寄存器TTBR1_EL1取内核的页全局目录的物理地址。
  •  如果虚拟地址的高16位全是0,说明是用户虚拟地址,应该查询进程的页表,从寄存器TTBR0_L1取进程的页全局目录的物理地址。

2.2处理页错误异常

在ARM64架构中,用户程序在异常级别0运行,内核在异常级别1运行。

ARM64定义了一个异常向量表,起始地址是vectors(arch/arm64/kernel/entry.S)每个异常向量表的长度是128字节,但是在linux内核中每个异常只存在一条指令,跳转到对应的处理程序。异常向量表的虚拟地址放在异常级别1的向量基准地址寄存器(VBAR_EL1)中。


三、匿名页的缺页异常

发生情况

  1. 函数的局部变量比较大,或者函数调用层次比较深,导致当前栈不够用,需要扩大栈;
  2. 进程调用malloc,从堆申请了内存块,只分配虚拟内存区域,还没有映射到物理页,第一次访问时触发缺页异常
  3. 进程直接调用mmap,创建匿名的内存映射,只分配了虚拟内存区域,还没有映射到物理页,第一次访问时触发缺页异常。

缺页异常函数 do_anonymous_page处理私有匿名的缺页异常。

四、文件的缺页异常

发生情况

  1. 启动程序的时候,内核为程序的代码段和数据段创建私有文件映射,映射到进程的虚拟地址空间,第一次访问的时候,触发问文件页的缺页异常。
  2. 进程使用mmap创建文件映射,把文件的一个区间映射到进程的虚拟地址空间,第一次访问的时候,触发文件页的缺页异常。

处理函数 __do_fault()

4.1 处理文件页错误,具体处理读文件页错误的方法

  1. 把文件页从存储设备上的文件系统读到文件缓存(每个文件有一个缓存,因为以页为单位,所以称为页缓存)中
  2. 设置检测的页表项,把虚拟页映射到文件页缓存的物理页。

函数do_read_fault()

给定一个虚拟内存区域vma,函数filemap_fault读文件页的方法如下

  1. 根据vma->vm_file得到文件的打开实例file
  2. 根据file->f_mapping得到文件的地址空间mapping
  3. 使用地址空间操作集合中的readpage方法(mapping->a_ops->readpage)把文件页读到内存中
  4. 函数finish_fault 负责设备项页表,把主要工作委托给函数alloc_set_pte,执行流程及源码分析

4.2 文件写私有文件页错误的方法

  1. 把文件页从存储设备上的文件系统读到文件的页缓存中;
  2. 执行写时复制,为文件的页缓存中的物理页创建一个副本,这个副本是进程的私有匿名页和文件脱离系统,修改副本不会导致文件变化;
  3. 设备进程的页表项,把虚拟页映射到副本;

函数do_cow_falut处理写私有文件页错误

4.3文件写共享文件页错误的方法如下

  1. 把文件页从存储设备上的文件系统读到文件的也缓存中
  2. 设备进程的页表项,把虚拟地址映射到文件的页缓存中的物理页

函数do_shared_fault处理写共享文件页错误

五、写时复制

有两种情况会执行写时复制

  1. 进程创建子进程的时候,为了避免复制物理页,子进程和父进程以只读方式共享私有的匿名页和文件页。当其中一个进程试图写只读页时,触发页错误异常,页错误异常的处理程序分配新的物理页,把旧的物理页的数据复制到新的物理页,然后把虚拟页映射到新的物理页。
  2. 进程创建私有的文件映射,然后读访问,触发页错误异常,异常处理程序把文件读到页缓存,然后以只读模式把虚拟页映射到文件的页缓存中的物理页。接着执行写访问,触发页错误异常,异常处理程序执行写时复制,把文件的页缓存中的物理页创建一个副本,把虚拟页映射到副本。这个副本是进程的私有匿名页,和文件脱离关系,修改副本不会导致文件变化。

六、内核模式页错误异常

        内核访问虚拟地址时,内核使用线性映射,正常情况下不会出现没有映射到的情况。如果虚拟页没有映射到物理页,一定会出现内核崩溃。

        内核运行时可能使用vmalloc()函数从vmalloc区域分配虚拟内存区域,vmalloc函数会分配并且映射到物理内页。

        内核可能访问用户虚拟地址。进程通过系统调用进入内核模式,有些系统调用会传入用户空间缓冲区。如果出现页错误异常,页错误异常处理程序发现用户虚拟地址没有被分配给进程,就会在异常表中(uaccess.h 专用函数访问用户空间缓冲区)查找指令地址对应的异常修正程序,如果找到了,修复异常,避免内核崩溃。

ARM64架构下内核发送的页错误异常处理

1)如果不允许内核执行用户空间指令,那么进程在内核模式下试图执行用户空间的质量时,内核崩溃。

2)如果进程在内核模式下访问用户虚拟地址,那么先使用函数__do_page_fault处理,如果处理失败,最后使用__do_kernel_fault处理

3)其他情况使用函数__do_kernel_fault处理。


参考:
《深入理解linux内核》
《Linux内核深度解析》
《linux内核深度解析 (余华兵) 》

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值