内存管理基础学习笔记 - 4.1 缺页中断处理 - 概述

1. 前言

本专题我们开始学习内存管理部分,本文为缺页中断处理相关学习笔记。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。
Linux的缺页异常(page fault)必须区分两种情况:由编程错误引发的异常;由引用属于进程地址空间但尚未分配物理页框的页引起的异常。具体发生page fault的情况:

1.如果写地址落在VMA区域,且权限合法
访问地址落在VMA区域且权限为R+W,页表权限为R,则第一次写时会发生page fault ,内核会修改页表权限为R+W,并为之申请一页内存;
2.如果访问地址落在VMA区域,但是访问权限不合法
如果访问地址没有写权限,写入会引发page fault, 且内核发出SIGV
3.如果访问地址落在非法区域
如果访问地址落在空白区域,即不在任何一个VMA,则引发page fault且发出SEGV,杀死进程

在之前介绍brk系统调用和mmap系统调用时,它们只是建立了进程地址空间(VMA),用户空间可以看到虚拟内存,但是没有建立虚拟内存与物理内存之间的映射,即没有创建相应的页表项。当进程访问这些还没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常(也称缺页中断),Linux会处理此异常,它依赖于处理器架构,缺页异常底层的处理流程在内核代码中特定体系架构的部分。

kernel版本:5.10
平台:arm64

2. page fault的底层处理

ESR_EL1
esr_el1为arm64异常综合信息寄存器,寄存器结构如上,其中bit31-26为异常类型(EC), bit24-0为具体的异常指令编码(ISS),不同的异常类型ISS有不同的编码方式

可以根据esr.EC简单判断异常的类型,ESR支持几十种不同的异常类型,page fault为数据异常,此处以发生在EL1的数据异常为例

对于esr寄存器不同的异常类型EC有不同的ISS表(bit24-0),本例中对于数据异常种类,ISS表结构如下:
在这里插入图片描述
其中DFSC为Data Fault Status Code的缩写,指示了访问权限错误或页表转换错误等。Linux内核定义了fault_info结构体,指示了不同的DFSC对应了不同的回调函数。

FAR_ELL1
far寄存器是Fault Address Register的缩写,这个寄存器保存了发生异常的虚拟地址

综上,arm64处理数据异常的底层逻辑如下:

  1. arm64异常分为同步异常和异步异常,当数据异常发生时,会跳转到arm64同步异常向量处理;
  2. 通过读取esr寄存器,进一步通过esr.ec来确定异常分类,确定分类处理函数;
  3. 不同的异常分类EC对应不同的ISS结构,对于数据异常也对应特定的结构,此时ISS的DFSC描述了不同的数据异常编码;
  4. arm64针对不同的DFSC编码,定义了fault_info表,表中针对每种数据异常,都定义了回调函数。

3. el1_sync

page fault作为arm64的一种同步异常而被处理,其主要的异常处理函数位于arch/arm64/kernel/entry.S, 其异常处理向量为el1_sync->el1_sync_handler

el1_sync
    |--el1_sync_handler
           |--esr = read_sysreg(esr_el1)
           |--switch (ESR_ELx_EC(esr)) {
              case ESR_ELx_EC_DABT_CUR:
              case ESR_ELx_EC_IABT_CUR:
                  el1_abort(regs, esr);
                      |--far = read_sysreg(far_el1)
                             |--far = untagged_addr(far)
                             |--do_mem_abort(far, esr, regs)
                  break;
              ...
              }

esr_el1:为arm64异常综合信息寄存器, esr为读取的寄存器的值

ESR_ELx_EC(esr): ESR_ELx_EC(esr)可以根据esr.EC简单判断异常的类型,ESR支持几十种不同的异常类型,此处以发生在EL1的数据异常ESR_ELx_EC_DABT_CUR(EC为0x25)为例,处理函数为el1_abort

el1_abort:读取far寄存器,far寄存器是Fault Address Register的缩写,这个寄存器保存了发生异常的虚拟地址

do_mem_abort:根据发生异常的虚拟地址addr进行处理

|- -do_mem_abort

do_mem_abort的参数far为读取的far_el1寄存器,保存了发生异常的虚拟地址,esr为esr_el1寄存器,为异常综合信息寄存器,regs为发生异常时的pt_regs指针,对于esr寄存器不同的异常类型EC有不同的ISS表(bit24-0),本例中对于数据异常种类,其中DFSC为Data Fault Status Code的缩写,指示了访问权限错误或页表转换错误等。Linux内核定义了fault_info结构体,指示了不同的DFSC对应了不同的回调函数。

do_mem_abort(far, esr, regs)
	|--const struct fault_info *inf = esr_to_fault_info(esr);
	|--inf->fn(addr, esr, regs)

esr_to_fault_info:当发生数据异常时,根据esr寄存器的ISS字段获取到DFSC字段,对于DFSC的每一个数据异常码都对应了一个struct fault_info结构体变量,所有的struct fault_info结构体变量形成一个fault_info表,通过DFSC就可以获取到对应的struct fault_info,从而得到相应的回调函数。

inf->fn(addr, esr, regs):针对缺页异常有常见的几种修复方案,fault_info中相应的回调函数主要包括:

#arch/arm64/mm/fault.c
static const struct fault_info fault_info[] = { 
	.......
	{ do_bad,               SIGKILL, SI_KERNEL,     "level 1 address size fault"    },
	{ do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 1 translation fault"     },
	{ do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 1 access flag fault"     },
	{ do_alignment_fault,   SIGBUS,  BUS_ADRALN,    "alignment fault"               }, 
	{ do_sea,               SIGKILL, SI_KERNEL,     "level 1 synchronous parity error (translation table walk)"     },      // Reserved when RAS is implemented
  • do_translation_fault:处理页表转换相关的异常错误
  • do_page_fault:处理页表访问或权限相关的异常错误
  • do_alignment_fault:处理与对齐相关的异常错误
  • do_bad:处理与位置的错误或硬件相关的错误,如TLB冲突等
  • do_sea:

后续将以do_page_fault为例,说明缺页异常的主要过程。

参考文档

  1. 奔跑吧,Linux内核
  2. ARM TRM
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值