1. 特权和异常级别(Privilege and Exception level)
现代软件开发成不同的模块,每个模块对系统和处理器资源有不同级别的访问。操作系统(OS)内核和应用程序就是一个例子。OS需要执行不期望应用程序能够执行的操作。内核需要对系统资源的高级访问,而应用程序需要有限的配置系统的能力。特权(privilege)规定了软件实体可以看到和控制哪些处理器资源。
AArch64架构通过不同级别的特权来实现这种划分。当前权限级别只能在处理器接受异常或从异常返回时更改。这些特权级别在Arm架构中称为异常级别。
1.1 ?异常级别
在AArch64中,特权privilege叫作异常级别(Exception level),缩写为EL。异常级别有编号,缩写为EL<x>,x是0到3之间的数字。特权级别越高,数字就越高。最低级别的特权为EL0。
如下图所示,有四个异常级别:EL0, EL1, EL2和EL3。
常见的使用模型是在EL0上运行应用程序,在EL1上运行rich OS,如Linux等。管理程序(hypervisor)可以使用EL2,固件和安全网关代码可以使用EL3。例如,Linux可以在EL3调用固件函数,使用软件接口标准,从底层细节中抽象出启动或关闭core的意图。这个模型意味着大量的PE处理通常发生在EL0/1。
那什么是rich OS呢?嵌入式系统可以按操作系统分成三类:Rich OS类、RTOS类、Bare-metal类。
Rich OS类(全能操作系统):这类嵌入式系统运行功能非常齐全的操作系统,例如Linux、Android、iOS等。
RTOS类(实时操作系统):这类嵌入式系统运行功能紧凑但具有很强实时性的RTOS,例如FreeRTOS、RT-Threads、uC/OS-II等。这类系统通常使用微控制器,也就是俗称的单片机。
Bare-metal类:不包含任何操作系统,可能会包含事件调度器。这类系统也使用微控制器。这类系统主要是功能比较单一的应用。
异常级别只能在以下情况发生时更改:
接受异常
从异常中返回
处理器复位
在调试状态中
从调试状态退出
当发生异常时,异常级别可以增加或保持不变。永远不能通过接受异常来移动到较低的特权级别。当从异常返回时,异常级别可以降低或保持不变。从异常返回永远不能移动到更高的权限级别。
1.2 ?特权类型(Types of privilege)
与AArch64 异常模型相关的特权有两种类型:
内存系统中的特权
从访问处理器资源的角度来看的特权
这两种类型的特权都受到当前特权异常级别的影响。
Memory privilege:
Arm架构的A-profile实现了虚拟内存系统,其中内存管理单元MMU允许软件为内存区域分配属性。这些属性包括可配置为允许特权访问和非特权访问的单独访问权限的读/写权限。
发生在EL0的内存访问将对照非特权访问权限进行检查。EL1,EL2和EL3的访问将对照特权访问权限进行检查。
由于内存配置是由软件使用MMU转换表编程的,所以在对这些表进行编程时要考虑所需的特权。MMU配置存储在系统寄存器中,并且访问这些寄存器的能力也由当前的异常级别控制。
Register access:
AArch64处理器的配置设置保存在系统寄存器(System register)中。系统寄存器的设置组合定义了当前处理器的上下文。对系统寄存器的访问由当前异常级别控制。
例如,VBAR_EL1是向量基址寄存器(Vector Base Address Register)。它的_EL1后缀表示软件至少需要EL1特权才能访问该寄存器。
该体系结构有许多具有功能相似的寄存器,这些寄存器的名称仅在异常级别后缀上有所不同。它们是独立的寄存器,在指令集中有自己的编码,在硬件中单独实现。
这些寄存器的名称相似,反映了它们执行的任务相似,但它们是完全独立的寄存器,具有自己的访问语义。系统寄存器名字的后缀表示可以访问该寄存器的最低异常级别。
每个实现的异常级别都有一个系统控制寄存器(SCTLR)。每个寄存器控制该EL的架构特性,例如MMU、缓存和对齐检查:
SCTLR_EL1:EL0和EL1的顶级系统控制。
SCTLR_EL2:EL2的顶级系统控制。
SCTLR_EL3:EL3的顶级系统控制。
注意:EL1和EL0共享相同的MMU配置,且只能由运行在EL1上的代码进行控制。因此没有SCTLR_EL0,所有的控制都来自EL1可访问的寄存器。其他控制寄存器通常遵循此模型。
较高的异常级别有权访问控制较低级别的寄存器。例如,EL2在必要时拥有访问SCTLR_EL1的权限。但是不能从EL0访问该寄存器,这样做会产生异常。
在一般的系统操作中,特权异常级别控制自己的配置。然而,特权级别高的级别有时会访问与低异常级别的寄存器。例如,通过这样来实现虚拟化特性和上下文切换。
以下系统寄存器将用于讨论与AArch64异常模型相关的问题。
2. ?执行和安全状态(Execution and Security states)
AArch64架构提供了四种异常级别。还有两个执行状态和最多四个安全状态。Armv8-A或Armv9-A处理器的当前状态由异常级别和当前执行状态决定。
2.1 ?执行状态(Execution states)
当前的执行状态定义了通用寄存器的标准宽度和可用的指令集。执行状态还影响内存模型的各个方面以及异常的管理方式。
Armv8-A和Armv9-A支持两种Execution状态:AArch32和AArch64。
AArch32是32位的执行状态。这种状态下的操作与以前的体系结构向后兼容。它支持T32和A32指令集。标准寄存器宽度是32位。
AArch64是64位的执行状态。它支持A64指令集。标准寄存器宽度是64位。
2.2 ?安全状态(Security states)
AArch64允许实现多个安全状态,可进一步对软件进行分区,以隔离和划分可信软件。大多数处理器支持两种安全状态:
安全状态:Processing?Element?(PE)可以访问安全的和非安全的物理地址空间,以及存储寄存器的安全副本。
非安全状态:通常也被称为正常世界。此时PE只能访问非安全的物理地址空间。PE只能访问允许非安全访问的系统寄存器。
安全状态定义了可以访问哪些异常级别,当前可以访问哪些内存区域,以及这些访问在系统内存总线上是如何表示的。处于非安全状态时,PE只能访问非安全物理地址空间。在安全状态下,PE可以访问安全和非安全的物理地址空间。
举一个例子: 可以让操作系统(如Android)在正常世界中运行,在安全世界中运行支付或DRM系统。我们需要对安全世界系统有更高程度的信任,并将它们分开以保护支付细节和密钥之类的信息。拥有两个安全状态提供了这种分离。
下图显示了异常级别和安全状态,以及不同的执行状态。
改变安全状态
如果实现了TrustZone,那么处理器可以处于安全状态或非安全状态。这是由SCR_EL3.NS bit来选择的。当没有实现RME时,SCR_EL3.NS为0表明EL0和EL1为安全状态,SCR_EL3.NS为1表明EL0、EL1和EL2为非安全状态,SCR_EL3.{EEL2, NS} == {1, 0}表面EL2为安全状态。
在前面的图中,EL3处于安全状态。EL3是特权最高的异常级别,EL3的安全状态是固定的。EL3能够访问banked System register的所有副本。
在Armv8-A中EL3始终处于安全状态。在Armv9-A中,除非实现RME,否则EL3是安全状态的一部分。如果实现了RME,Root状态是EL3与安全状态的其余部分的分离。
当你想从一个安全状态切换到另一个安全状态时,必须通过EL3。EL3的软件负责管理对不同安全状态的访问并充当守门人,控制对EL2、EL1和EL0的安全状态的访问。SCR_EL3.NS位允许从EL3返回时更改安全状态。
注意:在EL0、EL1和EL2时,PE可以处于安全状态或非安全状态。经常会看到这样的情况。NS_EL1:非安全状态,异常级别1;S_EL1:安全状态,异常级别1。
Realm Management Extension(RME)
Armv9-A引入了对Realm Management Extension(RME)的支持。在实现RME时,还支持两种额外的安全状态:
Realm状态:PE可以访问非安全和Realm的物理地址空间。
Root状态:PE可以访问所有的物理地址空间。Root状态仅在EL3中可用。
RME将EL3与所有其他安全状态隔离。使用RME,EL3从安全状态移出,进入自己的叫作Root的安全状态。EL3承载平台和初始boot代码,因此必须在安全、非安全和Realm状态下受软件信任。
2.3 ?异常级别实现的影响
对于一个处理器,是否实现所有异常级别,以及每个异常级别允许哪些执行状态,都是实现选择的。
EL0和EL1是必须实现的,而EL2和EL3是可选的。如果EL3或EL2没有实现,那么需要考虑以下几点:
EL2包含许多虚拟化功能。没有EL2的实现不能访问这些特性。
EL3是唯一可以更改安全状态的级别。如果不实现EL3,PE只能访问一个安全状态。因此,永久处于的状态是实现定义的。
已经开发的一些软件实现要求平台支持这些异常级别。例如,KVM Linux要求为EL2、EL1、EL0。
3. ?异常类型(Exception type)
异常是需要由特权软件采取补救措施或更新系统状态以确保系统平稳运行的条件或系统事件。因此,异常可以是导致当前正在执行的程序暂停的任何事件。接受异常会导致状态发生变化,以执行处理该异常的代码。
其他处理器架构可能将此描述为中断。在AArch64中,中断是一种特定类型的外部生成的异常。
异常有很多不同的原因,包括以下:模拟虚拟设备、虚拟内存管理、处理软件错误、处理硬件错误、调试、执行对不同特权或安全状态的调用、处理中断(定时器,设备交互)、跨不同的执行状态的处理(称为interprocessing)。
当发生异常时,处理器不会移动到当前代码中的下一条指令,而是停止当前的执行并分支到一段代码来处理请求。这段代码称为异常处理程序(exception handler)。一旦事件被处理,执行就可以返回到原始程序。每种异常类型都有自己的异常处理程序。
Arm架构将异常分为两大类:同步异常和异步异常。
3.1 ?同步异常
同步异常是由当前正在执行的指令或者与之相关的操作引起的异常。同步异常与执行流是同步的,因为它们与当前执行的指令直接相关。例如,同步异常将由试图写入MMU定义的只读位置的指令触发。
如果以下所有情况都适用,则异常是同步的:
异常是直接或尝试执行指令的结果。
异常处理后要返回的地址与引起异常的指令有架构定义的关系。
异常是精确的,这意味着在异常处理程序中显示的寄存器状态与违规指令执行之前的每条指令一致,而在违规指令执行之后则不一致。
同步异常有许多不同的类型,一个给定的指令可能会导致多个同步异常。Arm架构为同步异常提供了固定的优先级顺序。
3.2 ?异步异常
某些类型的异常是在外部生成的,因此与当前指令流不同步。异步异常与当前执行的指令没有直接关联,通常来自处理器外部的系统事件。可能是软件需要响应的系统事件,例如计时器的活动或屏幕的触摸。我们不知道什么时候会发生。
根据定义,如果异常不是同步的,那么它就是异步的。异步异常也称为中断。在接受异步异常时,程序流被中断,并传递给专门处理此请求的代码。不可能准确地保证异步异常何时发生,AArch64架构只要求它在有限的时间内发生。
3.2.1 ?物理中断
物理中断是对来自PE外部设备的信号的响应而产生的中断。与core不断轮询外部信号不同,系统通过生成中断来通知core必须发生某些事情。
例如,系统可能使用UART接口与外部世界通信。当UART接收到数据时,它需要一种机制来告诉处理器新数据已经到达并准备好进行处理。UART可以生成中断来向处理器发出信号。
下面是不同类型的物理中断:
系统错误(SError)
系统错误(SError)是内存系统响应意外事件时产生的一种异常类型。我们并不期待这些事件发生,但需要知道是否已经发生。这些事件是异步报告的,因为触发事件的指令可能已经retire了。
SError的一个典型示例是外部异步abort。SError中断的例子包括:
已通过所有MMU检查的内存访问,但随后在内存总线上遇到错误
校验或错误纠正码(ECC)检查某些ram,例如内置缓存中的ram
由脏数据从缓存线回写到外部内存触发的abort
SError被视为一个单独的异步异常类,因为通常会有单独的处理程序来处理这些情况。SError的产生是实现定义的。
IRQ和FIQ
Arm架构有两种异步异常类型,IRQ和FIQ,用于支持外设中断的处理。它们用于发出外部事件的信号,例如计时器停止,而不代表系统错误。它们是与处理器指令流异步的预期事件。
IRQ和FIQ具有独立的路由控制,通常用于实现安全和非安全中断。(注意:在Arm架构的旧版本中,FIQ被用作高优先级的快速中断。这与AArch64不同,在AArch64中FIQ与IRQ具有相同的优先级。)
3.2.2 ?虚拟中断
使用虚拟化的系统对中断处理有更复杂的需求。有些中断可能由hypervisor传递,有些可能在VM中处理。VM看到的中断是虚拟中断。虚拟中断可以由连接到中断控制器的设备在外部生成,也可以由软件生成。因此需要额外的机制来支持这一点,在AArch64中显式地支持虚拟中断。
vSError,虚拟系统错误
vIRQ,虚拟IRQ
vFIQ,虚拟FIQ
虚拟中断根据中断类型进行控制。这些虚拟中断的功能与物理中断相同,但是只能向EL1发送信号。
虚拟中断可以从位于EL2的hypervisor生成,也可以使用中断控制器生成。hypervisor必须在Hypervisor配置寄存器(HCR_EL2)中设置相应的routing bit。例如,要启用vIRQ信号,hypervisor必须设置HCR_EL2.IMO。该设置将物理IRQ异常路由到EL2,并将虚拟异常发送到EL1。
在HCR_EL2中有三个bit控制虚拟中断的产生:
VSE:设置这个位注册一个vSError。
VI:设置该位注册一个vIRQ。
VF:设置该位注册一个vFIQ。
设置其中一个位相当于中断控制器将中断信号断言给一个vCPU。这种方法的一个含义是,hypervisor随后需要模拟VM中中断控制器的操作。当用于频繁操作时,这会导致显著的开销,因此建议使用中断控制器。
3.2.3 ?屏蔽
物理和虚拟的异步异常都可以被临时屏蔽。这意味着异步异常可以一直保持在挂起状态,直到它们被解除屏蔽并接受异常。这对于处理嵌套异常特别有用。
同步异常不能被屏蔽。这是因为同步异常是由指令的执行直接引起的,如果它们被挂起或忽略,就会阻塞执行。
2021扩展的Armv8.8-A和Armv9.3-A增加了不可屏蔽中断(NMI)的支持。当支持并启用该功能时,可以通过该功能将中断作为具有Superpriority的方式呈现给处理器。Superpriority允许在没有Superpriority的中断将被屏蔽时接受中断。
ARM架构内容复杂繁多,学习起来会比较费时费力,推荐以下ARM学习视频,可以快速熟悉和掌握ARM处理器架构,做到事半功倍,有需要的可以尝试一下~
2039
