Linux 内核的异常(2)

举报
云舒云卷 发表于 2020/05/18 11:54:11 2020/05/18
【摘要】 上一期中我们介绍了 Linux 4.19 内核的异常向量表,这一期我们将介绍 Linux 4.19 内核在 ARM64 处理器上的异常处理。

一、Linux 4.19 内核在 ARM64 处理器上的异常处理

当异常发生时,处理器需要调用异常处理程序来处理异常,该调用过程可以粗略地分为保存处理器当前状态、调用异常处理程序和恢复异常发生前的处理器状态三步,具体说来内核中异常处理的流程是[1]:

  1. 保存处理器当前状态。将当前处理器状态 PSTATE 保存在 SPSR_EL1 寄存器中,将返回地址保存在 ELR_EL1 寄存器中,这两个寄存器中的变量会在返回时被 eret 指令用于恢复处理器状态。通过设置处理器状态中的调试掩码位D、系统错误掩码位A、中断掩码位I和快速中断掩码位F禁止调试异常、系统错误异常、外部中断和快速中断。将发生错误的原因保存在 ESR_EL1 寄存器中,将同步异常的错误地址保存在 FAR_EL1 寄存器中。如果处理器处于异常级别 EL0 则将异常级别提升到 EL1。根据异常向量表基地址、生成异常的异常级别和异常类型计算出异常向量的位置,通过异常向量跳转到异常处理程序的入口。异常向量表的基地址是保存在 VBAR_EL1 寄存器中的。

  2. 调用异常处理程序。以异常级别 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


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。