在了解AArch64异常等级模型之前,有必要先理解特权的概念。现代软件被开发成不同的模块,每个模块对系统和处理器资源有不同的访问级别。比如操作系统内核和用户应用程序的划分。操作系统内核需要高级别的权限来访问系统资源,然而应用程序应当被限制其配置系统的能力。即特权规定了软件实体可以看到和控制哪些处理器资源。
AArch64架构通过实现不同级别的特权来实现这种拆分。当前特权级别只能在处理器发生异常或从异常返回时进行更改。因此,这些特权级别在Arm体系结构中被称为异常级别。
AArch64使用 EL 来命名异常等级,EL3权限最高,EL0权限最低。如下图所示,为一种通用的异常等级模型:
有两种与AArch64异常模型相关的特权类型:
ARM架构的A处理器系列实现了一个虚拟内存系统:内存管理单元,Memory Management Unit (MMU),MMU允许软件对不同的内存区域分配不同的内存属性。这些属性包括读/写权限,可以配置为允许对特权访问和非特权访问的单独访问权限。
当处理器在EL0中执行时启动的内存访问将根据无特权的访问权限进行检查。来自EL1、EL2和EL3的内存访问将按照特权访问权限进行检查。
由于这种内存配置是由软件使用MMU的translation tables来控制的,所以我们应该考虑编辑这些table时的特权等级。因为MMU的配置是存储在处理器系统寄存器里的,而控制访问这些寄存器的权限也来自于当前的异常等级。
在ARM架构中,处理器的寄存器主要分成两大类:
- 提供系统控制(system control)和状态显示(status reporting)的寄存器
- 用于指令处理的寄存器,比如结果累计以及异常处理。
AArch64处理器的配置被保存在一系列的系统寄存器当中,系统控制寄存器中的各种配置的组合决定了当前处理器的上下文(context)。并且,访问系统寄存器的权限也由当前处理器的异常等级决定。
ARM架构里有许多概念上有相似功能的寄存器,它们仅仅是异常等级后缀名不同,比如AArch 64中的VBAR(Vector Base Address Register)寄存器存在VBAR_EL1,VBAR_EL2以及VBAR_EL3,这些寄存器是相互独立的存在,在指令集中有不同的解码,以及在硬件中独立的实现。这些寄存器名字相同是因为他们的功能相似,但是它们是完全独立的寄存器,具有它们自己的访问语义。系统寄存器名称的后缀表示可以从中访问该寄存器的最低异常级别。
比如AArch64处理器中的系统控制寄存器,System Control Register (SCTLR),为每个异常等级都实现了一个SCTLR。每个寄存器控制着当前异常等级的一些架构级别的特性,比如MMU,cache以及对齐访问检查功能等:
Register | Name | Description |
---|---|---|
Exception Link Register | ELR_ELx | 异常链接寄存器,保存着导致异常发生的那条指令的地址 |
Exception Syndrome Register | ESR_ELx | 异常综合寄存器,保存着异常发生的原因相关的信息 |
Fault Address Register | FAR_ELx | 错误地址寄存器,保存着异常发生时的异常错误地址 |
Secure Configuration Register | SCR_ELx | 安全配置寄存器, 控制着安全状态,以及进入EL3的异常 |
Hypervisor Configuration Register | HCR_ELx | 控制着虚拟化配置,以及进入EL2的相关异常 |
System Control Register | SCTLR_ELx | 系统控制寄存器,控制着标准内存,系统配置,以及为已实现的相关功能提供状态信息。 |
Saved Program Status Register | SPSR_ELx | 当异常发生,并切换到ELx时,保存着处理器的相关状态 |
Vector Base Address Register | VBAR_ELx | 当异常发生时,处于ELx,保存着ELx的异常向量表的基址 |
上文提到过,AArch64 架构支持4种异常等级,除了异常等级,它还支持两种执行状态(AArch32和AArch64),以及两种安全状态(secure和non-secure)。 所以对于Armv8-A 或者Armv9-A的处理器,它们的当前状态,取决于当前的执行状态以及异常等级。
当前的执行状态,不仅决定了处理器的通用寄存器(general-purpose register)的位宽:AArch32状态下是32 bit,AArch64状态下是64 bit的宽度。还决定了当前处理器所使用的指令集(instruction sets):AArch32状态下使用A32指令集,AArch64状态下使用A64指令集。
从Armv8-A 开始,支持以下两种执行状态:
如果想改变处理器的执行状态,只有以下两种方式:
当处理器进行异常等级切换的时候,有可能改变当前的执行状态,但是无论怎么在AArch32和AArch64直接切换,都必须要遵守如下规则:
把这两个规则放在一起,意味着64-bit 可以承载 32-bit,但是反之不可。比如,64-bit的操作系统内核支持64-bit应用和32-bit应用,但是32bit的操作系统却只能跑32-bit应用。如下图所示:
在这个例子中,我们使用了操作系统和应用程序作为示例,其实这个规则对所有异常等级都适用。比如,当EL2是一个32-bit的hypervisor时,EL1只能是一个32-bit的操作系统。同理,当最高异常等级(EL3)为AArch32 状态时,当前处理器一定不支持通过异常处理切换,进入到AArch64,因为当前已经是最高异常等级(EL3),无法通过切到更高的异常等级进入AArch64。
在Armv8-A架构中,所有的异常等级都支持AArch32和AArch64,这是根据具体的架构实现定义的功能,对于每个异常等级,可以选择都支持,或者只支持其中一个。此外ARMv8 处理器的默认执行状态(即reset之后的执行状态)是具体的实现定义的,比如Cortex-A32在reset之后都是AArch32 状态。
在Armv9-A架构中,AArch64是强制要求所有异常等级实现的状态,并且只有EL0才支持AArch32(实现定义),其他异常等级都是处于AArch64下。当发生reset时,处理器的执行状态将始终是AArch64。
大部分Cortex-A系列处理器都支持两种安全状态:
比如在Android 系统,一般跑在Normal world(non-secure)下,当时当支付时,或者运行DRM (数字版权管理,Digital Right Management,DRM)系统时,将会切到Secure状态下。我们需要对安全世界系统有更高程度的信任,并需要将它们分开,以保护诸如支付细节和密钥等信息,Secure和non-secure两种安全状态可以提供这种分离。下图时异常等级和Secure状态之间的组合:
如果处理器实现了TrustZone,那么便可以通过 SCR_EL3的NS bit来选择 Secure 状态还是Non-Secure状态。EL3状态拥有最高特权的异常等级,并且EL3的的安全状态是固定的,当处于EL3时,它能访问所有的bank 寄存器。Armv8-A的 EL3 总是处于安全状态(Secure state),在Armv9-A中,EL3是安全状态的一部分,除非实现了RME。如果实现了RME,则Root state是EL3与安全状态的其他部分的分离。
无论何时,要从一种安全状态切换到另一种安全状态,都必须通过EL3。EL3上的软件负责管理对不同可用安全状态的访问,控制着EL2,EL1以及EL0的安全状态。如何改变安全状态呢:
是否将所有的异常等级的都实现,这是有具体的架构实现定义的,并且可以定义每个异常等级的所支持的执行状态。
其中EL1和EL0是必须要实现的,而EL3和EL2是可选的。
如果EL2或EL3没有实现,那必须考虑以下:
在ARMv8.0 A中,EL2只支持Non-secure状态,这是因为Secure 状态下不支持虚拟化扩展。从ARMv8.4-A开始,增加了 S.EL2作为一个可选的属性,并附带一个使能 bit : SCR_EL3.EEL2,以提供向后的兼容性。在Armv9-A中,如果实现了EL2,那么必须要支持两种安全状态(secure和non-secure),并且也提供了一个disable bit。
Armv8-A系列的处理器可以为每个异常等级选择哪些执行状态有效,并且这也是具体的实现定义的。如果AArch32被运行执行在某个异常等级,那么也必须可以执行在比当前更低的异常等级。比如EL3可以跑AArch32,那么EL2,EL1以及EL0肯定都可以跑。
很多处理器的具体实现会把所有的安全状态以及所有的异常等级都打开,但是仍存在着一些限制,比如:
AArch32提供对老的32-bit 架构(比如ARMv7)的向后兼容性,但是从ARMv9开始,AArch32被限制在只能跑在EL0,并且还只是可选的实现,同时全方便支持AArch64(所有异常等级都支持)。这意味着ARMv9虽然支持32-bit,但是也只限于一些应用程序,像操作系统内核、hypervisor,虚拟化程序以及firmware固件都采用64-bit。