内核稳定性问题复杂多样,最常见的莫过于“kernel panic”,意为“内核恐慌,不知所措”。这种情况下系统自然无法正常运转,只能自我结束生命,留下死亡信息。诸如:
“Unable to handle kernel XXX at virtual address XXX”
“undefined instruction XXX”
“Bad mode in Error handler detected on CPUX, code 0xbe000011 -- SError”
......
这些死亡信息是系统在什么状态下产生?如何产生?以及如何处理?本文主要从这三个方面介绍ARMv8架构下CPU的异常处理流程。
一、ARMv8异常简介
1.异常级别
不同于Armv7架构采用CPU模式切换的方式进行异常处理,Armv8架构定义了一组全新的异常级别进行异常处理,即EL0至EL3,有如下特性:
-
如果ELn为异常级别,则n的值增加表示软件执行特权增加。
-
EL0处的执行称为无特权执行,不能处理异常。
-
EL2提供对虚拟化的支持。
-
EL3提供了在两个安全状态(安全状态和非安全状态)之间切换的支持。
一个实现可以不包括所有的异常级别,但都必须包括EL0和EL1。EL2和EL3是可选的。
如下是典型的异常级别使用模型:
2. 同步异常和异步异常
如果满足以下所有条件,则将异常描述为同步的:
-
由于直接执行某个指令而产生异常。
-
异常处理程序的返回地址可以表明导致该异常的指令。
-
异常是精确的。
如果满足以下任一条件,则将异常描述为异步的:
-
不是因为直接执行某条指令而产生异常。
-
异常处理程序的返回地址不可以表明导致该异常的指令。
-
异常是不精确的。
3. 主要寄存器
(1)通用寄存器R0-R30
在基本指令集处理指令时,将使用通用寄存器组。它包括31个通用寄存器R0-R30。这些寄存器可以作为31个64位寄存器X0-X30或31个32位寄存器W0-W30进行访问。
(2)堆栈指针寄存器SP
在AArch64状态下,除了通用寄存器外,还为以下每个异常级别实现了专用的堆栈指针寄存器,
堆栈指针寄存器为:
-
SP_EL0和SP_EL1。
-
如果实现包括EL2,则为SP_EL2。
-
如果实现包括EL3,则为SP_EL3。
堆栈指针寄存器选择:
在EL0上执行时,处理器使用EL0堆栈指针SP_EL0。在其他任何异常级别执行时,可以将处理器配置为使用SP_EL0或配置为对应该异常级别的堆栈指针SP_ELx。默认情况下,采用目标异常级别的堆栈指针SP_ELx。例如,EL1的异常选择SP_EL1,软件可以在目标异常级别执行的时候通过更新PSTATE.SP来指向SP_EL0的堆栈指针。
可以通过异常级别的堆栈指针后缀表明所选的堆栈指针:
t表明使用SP_EL0堆栈指针。
h表明使用SP_ELx堆栈指针。
t和h后缀基于线程(thread)和处理程序(handler)的首字母。
(3)保存的程序状态寄存器SPSR
保存的程序状态寄存器SPSR(Saved Program Status Registers)用于在发生异常时保存处理器状态。在AArch64状态下,每个异常级别都有一个SPSR:
-
SPSR_EL1,发生在EL1的异常。
-
如果实现了EL2,则为SPSR_EL2,发生在EL2的异常。
-
如果实现了EL3,则为SPSR_EL3,发生在EL3的异常。
注:EL0不能处理异常。
当处理器发生异常时,会将处理器状态从SPSTATE中的PSTATE(Process state)保存到对应异常级别的SPSR。例如,如果异常发生在EL1,则将处理器状态保存在SPSR_EL1中。
保存处理器状态意味着异常处理程序可以:
-
从异常返回时,将处理器状态恢复到SPSR中存储的异常级别的状态