在取指令或数据时,处理器的内存管理单元需要把虚拟地址转换成物理地址。如果虚拟页没有映射到物理页,或者没有访问权限,处理器将生成页错误异常。
虚拟页没有映射到物理页,这种情况通常成为缺页异常。
生成页错误异常
ARM64处理器在取指令或者数据的时候,需要把虚拟地址转换成物理地址,分两种情况:
(1)如果虚拟地址的高16位不是全0或者全1(假设使用48位虚拟地址),是非法地址,生成页错误异常。
(2)如果虚拟地址的高16位是全1或者全0,内存管理单元根据关键字{地址空间标识符,虚拟地址}查找TLB。
如果命中了TLB选项,从TLB表项读取访问权限,检查访问权限,如果没有访问权限则生成页错误异常。
如果没有命中TLB选项,内存管理单元将会查询内存中的页表,称为转换遍历。
如果虚拟地址的高16位全是1,说明是内核虚拟地址,应该查询内核的页表,从寄存器TTBR1_EL1取内核的页全局目录的物理地址。
如果虚拟地址的高16位全是0,说明是用户虚拟地址,应该查询进程的页表,从寄存器TTBR0_L1取进程的页全局目录的物理地址。
内存管理单元访问内存中的页表,根据页表的类型进行处理。
(1)如果是无效描述符,生成页错误异常
(2)如果是块描述符或页描述符,把表项复制到TLB
(3)如果是表描述符,从表项读取下一级表的物理地址,继续访问下一级页表。
处理错误异常
对于ARM64处理器,异常级别1的异常症状(ESR_EL1)用来存放异常的症状信息,下图是异常级别1的异常症状寄存器
EC:异常类别,表示引起异常的原因。
ISS:指令特定症状,每种异常类别独立定义这个字段。
函数do_mem_abort
该函数根据异常症状寄存器的指令特定症状字段的指令错误状态码(第0~5位),调用数组fault_info中的处理函数,下表是指令错误状态码和处理函数的对应关系
匿名页的缺页异常
发生情况
函数的局部变量比较大,或者函数调用层次比较深,导致当前栈不够用,需要扩大栈;
进程调用malloc,从堆申请了内存块,只分配虚拟内存区域,还没有映射到物理页,第一次访问时触发缺页异常
进程直接调用mmap,创建匿名的内存映射,只分配了虚拟内存区域,还没有映射到物理页,第一次访问时触发缺页异常。
缺页异常函数 do_anonymous_page处理私有匿名的缺页异常。
文件的缺页异常
发生情况
启动程序的时候,内核为程序的代码段和数据段创建私有文件映射,映射到进程的虚拟地址空间,第一次访问的时候,触发问你件页的缺页异常。
进程使用mmap创建文件映射,把文件的一个区间映射到进程的虚拟地址空间,第一次访问的时候,触发文件页的缺页异常。
处理函数 __do_fault()
(1)处理文件页错误,具体处理读文件页错误的方法
把文件页从存储设备上的文件系统读到文件缓存(每个文件有一个缓存,因为以页为单位,所以称为页缓存)中设置检测的页表项,把虚拟页映射到文件页缓存的物理页。
函数do_read_fault()
给定一个虚拟内存区域vma,函数filemap_fault读文件页的方法如下
根据vma->vm_file得到文件的打开实例file
根据file->f_mapping得到文件的地址空间mapping
使用地址空间操作集合中的readpage方法(mapping->a_ops->readpage)把文件页读到内存中
函数finish_fault 负责设备项页表,把主要工作委托给函数alloc_set_pte,执行流程及源码分析
(2)文件写私有文件页错误的方法
把文件页从存储设备上的文件系统读到文件的页缓存中;
执行写时复制,为文件的页缓存中的物理页创建一个副本,这个副本是进程的私有匿名页和文件脱离系统,修改副本不会导致文件变化;
设备进程的页表项,把虚拟页映射到副本;
函数do_cow_falut处理写私有文件页错误
(3)文件写共享文件页错误的方法如下
把文件页从存储设备上的文件系统读到文件的也缓存中
设备进程的页表项,把虚拟地址映射到文件的页缓存中的物理页
函数do_shared_fault处理写共享文件页错误
写时复制
有两种情况会执行写时复制
进程创建子进程的时候,为了避免复制物理页,子进程和父进程以只读方式共享私有的匿名页和文件页。当其中一个进程试图写只读页时,触发页错误异常,页错误异常的处理程序分配新的物理页,把旧的物理页的数据复制到新的物理页,然后把虚拟页映射到新的物理页。
进程创建私有的文件映射,然后读访问,触发页错误异常,异常处理程序把文件读到页缓存,然后以只读模式把虚拟页映射到文件的页缓存中的物理页。接着执行写访问,触发页错误异常,异常处理程序执行写时复制,把文件的页缓存中的物理页创建一个副本,把虚拟页映射到副本。这个副本是进程的私有匿名页,和文件脱离关系,修改副本不会导致文件变化。
内核模式页错误异常
内核访问虚拟地址时,内核使用线性映射,正常情况下不会出现没有映射到的情况。如果虚拟页没有映射到物理页,一定会出现内核崩溃。
内核运行时可能使用vmalloc()函数从vmalloc区域分配虚拟内存区域,vmalloc函数会分配并且映射到物理内页。
内核可能访问用户虚拟地址。进程通过系统调用进入内核模式,有些系统调用会传入用户空间缓冲区。如果出现页错误异常,页错误异常处理程序发现用户虚拟地址没有被分配给进程,就会在异常表(uaccess.h 专用函数访问用户空间缓冲区)查找指令地址对应的异常修正程序,如果找到了,修复异常,避免内核崩溃。
ARM64架构下内核发送的页错误异常处理
1)如果不允许内核执行用户空间指令,那么进程在内核模式下试图执行用户空间的质量时,内核崩溃。
2)如果进程在内核模式下访问用户虚拟地址,那么先使用函数__do_page_fault处理,如果处理失败,最后使用__do_kernel_fault处理
3)其他情况使用函数__do_kernel_fault处理。