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

发布于:2022-07-26 ⋅ 阅读:(4528) ⋅ 点赞:(4)

目录

什么是缺页异常?

处理器特定部分

生成页错误异常

处理页错误异常

匿名页的缺页异常

文件的缺页异常

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

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

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

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


什么是缺页异常?

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

大概有以下情况:

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

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

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

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

处理器特定部分

 

生成页错误异常

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

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

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

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

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

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

处理页错误异常

在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()

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

  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,执行流程及源码分析

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

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

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

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

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

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

 参考链接

https://course.0voice.com/v1/course/intro?courseId=2&agentId=0