Linux 内核的异常(2)
一、Linux 4.19 内核在 ARM64 处理器上的异常处理
当异常发生时,处理器需要调用异常处理程序来处理异常,该调用过程可以粗略地分为保存处理器当前状态、调用异常处理程序和恢复异常发生前的处理器状态三步,具体说来内核中异常处理的流程是[1]:
保存处理器当前状态。将当前处理器状态 PSTATE 保存在 SPSR_EL1 寄存器中,将返回地址保存在 ELR_EL1 寄存器中,这两个寄存器中的变量会在返回时被 eret 指令用于恢复处理器状态。通过设置处理器状态中的调试掩码位D、系统错误掩码位A、中断掩码位I和快速中断掩码位F禁止调试异常、系统错误异常、外部中断和快速中断。将发生错误的原因保存在 ESR_EL1 寄存器中,将同步异常的错误地址保存在 FAR_EL1 寄存器中。如果处理器处于异常级别 EL0 则将异常级别提升到 EL1。根据异常向量表基地址、生成异常的异常级别和异常类型计算出异常向量的位置,通过异常向量跳转到异常处理程序的入口。异常向量表的基地址是保存在 VBAR_EL1 寄存器中的。
调用异常处理程序。以异常级别 EL0 下64位应用程序发生的同步异常为例,其异常处理程序入口为 sync,异常向量表通过 kernel_ventry 宏跳转到了该入口(同步异常和异步异常的概念见第27期)。kernel_ventry 在跳转的时候会为将异常级别加到跳转入口之前,所以这时实际跳转到的入口是 el0_sync,其汇编代码在 openeuler/kernel/blob/kernel-4.19/arch/arm64/kernel/entry.S 文件中可以找到:
这段代码首先通过 kernel_entry 宏保存了异常处理前的通用寄存器状态,然后从 ESR_EL1 寄存器中读取了错误的原因,移位之后和各种异常的标志值相比较,若与某一异常的标志值相同则跳转到该异常的处理程序。kernel_entry 宏会将通用寄存器的值保存在当前进程的内核栈中。其汇编代码比较复杂,可以在同一个文件中找到:
跳转到处理程序后将会进行异常处理,以异常级别 EL0 下的数据中止异常为例,其处理程序的汇编代码在同一个文件中可以找到:
在这段代码中,处理器从 FAR_EL1 寄存器中读取了数据中止发生的虚拟地址,然后调用了C程序处理函数 do_mem_abort 并通过寄存器 X0 和 X1 传递了两个参数,其中 x0 中保存了错误发生的虚拟地址,X1 中保存了错误发生的原因(el0_sync 宏中从 ELR_EL1 寄存器读到了 X25 中)。
3. 恢复处理器状态,继续执行程序。异常处理程序执行完之后跳转到了ret_to_user,其汇编代码在同一个文件中可以找到:
这段代码在最后调用了 kernel_exit 宏,其作用是将之前 kernel_entry 中保存的通用寄存器恢复,并使用 eret 指令返回异常发生前的程序执行位置,该位置的地址是被事先保存在 ELR_EL1 寄存器中的,其取值情况为[1]:
对于系统调用,返回地址为系统调用指令后面第一条指令的地址;
对于系统调用外的同步异常,返回地址为生成异常的指令,因为该指令需要被重新执行;
对于异步异常,返回地址为没执行的第一条指令。
kernel_exit 宏的汇编代码可以在同一个文件中找到:
二、结语
本期我们考察了 Linux 4.19 内核的异常处理,下一期我们将介绍 Linux 内核中的中断处理流程。
[1]《Linux内核深度解析》,余华兵著,2019
- 点赞
- 收藏
- 关注作者
评论(0)