linux内核源码分析之缺页异常
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核源码分析之缺页异常相关的知识,希望对你有一定的参考价值。
目录
想分析内存缺页相关的程序,先看一下基础知识。有了基础知识,才能很好的理解程序。
一、什么是缺页异常?
在取指令或数据的时候,处理器的内存管理单元需要把虚拟地址转换成物理地址。如果虚拟也没有找到物理页时,或者没有访问权限,处理器将生成页错误异常。通常情况下称为缺页异常
大概有以下情况:
- 访问用户栈的时候,超出了当前用户栈的范围,需要扩大用户栈
- 当进程申请虚拟内存区域的时候,通常没有分配物理页,进程第一次访问的时候触发页错误异常。
- 内存不足的时候,内核把进程的匿名页换出到交换区
- 一个文件页被映射到进程的虚拟地址空间,内存不足时,内核回收这个文件页,在进程的页表中删除这个文件的映射
- 程序错误,访问没有分配给进程的虚拟内存区域,将会发出SIGSEGV信号将进程杀死。
没有访问权限,有以下两种情况:
- 可能是软件有意造成的,如写时复制:子进程和父进程以只读的方式共享私有的匿名页和文件页。当其中一个进程试图写只读页时,触发页错误异常,页错误异常处理程序分配新的物理页,把旧的物理页数据复制到新的物理页,然后把虚拟页映射到新的物理页。
- 程序错误,如试图写只读的代码段所在的物理页。
不同处理器架构的页面错误异常不同,页错误异常处理程的前面一部分是各处理器架构自定义部分,后面函数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)中。
三、匿名页的缺页异常
发生情况
- 函数的局部变量比较大,或者函数调用层次比较深,导致当前栈不够用,需要扩大栈;
- 进程调用malloc,从堆申请了内存块,只分配虚拟内存区域,还没有映射到物理页,第一次访问时触发缺页异常
- 进程直接调用mmap,创建匿名的内存映射,只分配了虚拟内存区域,还没有映射到物理页,第一次访问时触发缺页异常。
缺页异常函数 do_anonymous_page处理私有匿名的缺页异常。
四、文件的缺页异常
发生情况
- 启动程序的时候,内核为程序的代码段和数据段创建私有文件映射,映射到进程的虚拟地址空间,第一次访问的时候,触发问你件页的缺页异常。
- 进程使用mmap创建文件映射,把文件的一个区间映射到进程的虚拟地址空间,第一次访问的时候,触发文件页的缺页异常。
处理函数 __do_fault()
4.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,执行流程及源码分析
4.2 文件写私有文件页错误的方法
- 把文件页从存储设备上的文件系统读到文件的页缓存中;
- 执行写时复制,为文件的页缓存中的物理页创建一个副本,这个副本是进程的私有匿名页和文件脱离系统,修改副本不会导致文件变化;
- 设备进程的页表项,把虚拟页映射到副本;
函数do_cow_falut处理写私有文件页错误
4.3文件写共享文件页错误的方法如下
- 把文件页从存储设备上的文件系统读到文件的也缓存中
- 设备进程的页表项,把虚拟地址映射到文件的页缓存中的物理页
函数do_shared_fault处理写共享文件页错误
五、写时复制
有两种情况会执行写时复制
- 进程创建子进程的时候,为了避免复制物理页,子进程和父进程以只读方式共享私有的匿名页和文件页。当其中一个进程试图写只读页时,触发页错误异常,页错误异常的处理程序分配新的物理页,把旧的物理页的数据复制到新的物理页,然后把虚拟页映射到新的物理页。
- 进程创建私有的文件映射,然后读访问,触发页错误异常,异常处理程序把文件读到页缓存,然后以只读模式把虚拟页映射到文件的页缓存中的物理页。接着执行写访问,触发页错误异常,异常处理程序执行写时复制,把文件的页缓存中的物理页创建一个副本,把虚拟页映射到副本。这个副本是进程的私有匿名页,和文件脱离关系,修改副本不会导致文件变化。
六、内核模式页错误异常
内核访问虚拟地址时,内核使用线性映射,正常情况下不会出现没有映射到的情况。如果虚拟页没有映射到物理页,一定会出现内核崩溃。
内核运行时可能使用vmalloc()函数从vmalloc区域分配虚拟内存区域,vmalloc函数会分配并且映射到物理内页。
内核可能访问用户虚拟地址。进程通过系统调用进入内核模式,有些系统调用会传入用户空间缓冲区。如果出现页错误异常,页错误异常处理程序发现用户虚拟地址没有被分配给进程,就会在异常表中(uaccess.h 专用函数访问用户空间缓冲区)查找指令地址对应的异常修正程序,如果找到了,修复异常,避免内核崩溃。
ARM64架构下内核发送的页错误异常处理
1)如果不允许内核执行用户空间指令,那么进程在内核模式下试图执行用户空间的质量时,内核崩溃。
2)如果进程在内核模式下访问用户虚拟地址,那么先使用函数__do_page_fault处理,如果处理失败,最后使用__do_kernel_fault处理
3)其他情况使用函数__do_kernel_fault处理。
参考链接
以上是关于linux内核源码分析之缺页异常的主要内容,如果未能解决你的问题,请参考以下文章