ARM架构学习(1)——Exception level

本学习基于arm官网文档:learn_the_architecture_-_aarch64_exception_model

exception level和privilege

ELx等级划分

在这里插入图片描述
ARM架构不会规定哪个EL等级运行什么软件,但是一般用法都是按照上面的用法来用的。
EL等级只会在下面几种情况改变:

  1. Taking an exception(只会升高EL等级)
  2. Returning from an exception(只会降低EL等级)
  3. 处理器重启
  4. 进入调试模式时
  5. 退出调试模式

特权模式

内存特权模式(Memory privilege)

A核会提供MMU来做这种特权访问,EL0等级会做非特权检查,EL1、EL2、EL3会做非特权检查。
MMU的配置也受EL等级控制

寄存器访问特权模式(Register access)

AArch64 处理器的配置设置保存在一系列称为系统寄存器的寄存器中。
访问这些寄存器也是需要EL等级来控制的。比如VBAR_EL1这种后缀的寄存器,需要在EL1等级以上的上下文中访问才行。
不同后缀的寄存器是独立的、单独的寄存器(虽说功能相同,但是权限不同),在指令集中有自己的编码,并将在硬件中单独实现。
在这里插入图片描述

Execution and Security states

ARM-A核有两种Execution states和最高四种Security states

Execution states

当前的执行状态决定了寄存器的位宽和可用的指令集。还会影响内存模型的各个方面以及异常的管理方式。
Armv8-A 和 Armv9-A支持以下两种运行时:
AArch32:
AArch64:

只有在重启的时候,或者EL等级改变的时候,运行时状态才会被改变,并且需要遵循下面的条件:

  1. 当从低等级跳到高等级时,运行时状态只能保持当前模式或者改变到AArch64模式。
  2. 当从高等级跳到低等级时,运行时状态只能保持当前模式或者改变到AArch32模式。
    运行时状态改变条件
    以上条件也适用于所有的EL等级,例如:EL2等级上运行一个32-bit的虚拟机,那么它只能运行32-bit的OS

Security states

AArch64 允许实现多种安全状态。 这允许进一步划分软件以隔离和划分可信软件。
大多数A核处理器支持两种安全模式:
**安全模式:**这种模式可以访问安全地址和非安全地址(物理地址),和被隔离的寄存器
**非安全模式:**这种模式只能访问非安全物理地址,和允许非安全访问的寄存器。

在这里插入图片描述
每当您想要从一种安全状态切换到另一种安全状态时,您必须通过 EL3切换。
每个EL等级,PE都可能会处于安全模式或者非安全模式。时通过寄存器来确定的。

RME模式

Armv9-A引入了RME模式,在RME模式下,它额外支持两种安全模式:
Realm state:在此模式下,PE可以访问非安全地址和Realm域地址空间
Root state:在此模式下,PE可以访问所有的物理地址,此模式只能在EL3下使用

RME把EL3视作特殊的安全模式,与其他EL等级不一样,也称做Root。EL3模式运行平台代码和初始化启动代码,默认这些代码是绝对安全的。

RME模式图表:
在这里插入图片描述

实现EL等级的影响

EL0和EL1等级是是必须要实现的,EL2和EL3等级是可选的。
如果没有用到EL2和EL3等级,那么需要考虑下面几件事情:

  1. EL2包含了很多虚拟化功能,EL0和EL1无法使用这些特性
  2. EL3是唯一个一个可以改变安全模式的EL等级,如果没有用EL3,那么PE将只能用一种安全模式,并且这种模式是一开始定义的并一直持续到生命周期结束。

在Armv8.0-A,EL2只能运行在非安全模式下,因为虚拟化实现不支持安全模式,但是Armv8.4-A添加了S.EL2寄存器,所以可以运行在两种模式下;在Armv9-A,你如果在EL2下必须支持两种安全模式,即使disable位还存在。

AArch32兼容了老的32位架构,并且从Armv9-A开始,就限制了它只能在EL0下使用,AArch64可以在所有的EL等级下使用。

Exception types

异常会中断当前正在运行的程序,并转去执行发生异常的处理代码。每个异常都有自己的回调函数。
在这里插入图片描述

对于AArch64来说,中断也是一种由外部触发的特殊的异常。
异常会被应用到如下几种场景中:

  1. 模拟虚拟设备
  2. 虚拟内存管理
  3. 处理软件错误
  4. 处理硬件错误
  5. 调试
  6. 执行对不同特权或安全状态的调用
  7. 处理中断(比如定时器中断,设备中断等)
  8. 不同的Execution states之前的处理

同步异常

同步异常由当前被执行的指令引起,或者与当前被执行的指令相关。比如,当试图写一块被MMU定义为只读的内存区域时,就会发生同步异常。

如果以下全部适用,则异常是同步的:

  1. 异常是由于直接执行或尝试执行指令而生成的。
  2. The address to return to once the exception has been handled (return address) has an architecturally-defined relationship with the instruction that caused the exception。
  3. 异常是精确的,这意味着在进入异常处理程序时的寄存器状态,与出错指令前的寄存器状态是一致的,但在这条指令之后就不一致了

Arm存在很多同步异常,甚至一条指令会引起多条异常,它为不同的同步异常提供了不同的优先级顺序。

无效指令和陷阱异常

这个异常引起的原因可能有:

  1. 执行未定义的指令
  2. 执行当前EL等级不支持的指令
  3. 执行被禁用的指令
  4. 陷阱异常。比如在OS或者hypervisor,为低等级的EL设置一个陷阱(比如读写某个寄存器),当执行到此时会中断当前操作,触发异常。

陷阱异常举例:
kernel运行在EL1层,它会禁用EL0层的浮点运算单元用来节省进程切换的时间,当一个FP或者SIMD指令运行的时候,它会引起陷阱异常并跳转到EL1层处理,在这里打开SIMD/FP单元并且标记已经使能了这个单元。

陷阱异常在虚拟化应用中非常重要,可以参考官网文档:AArch64 virtualization guide

内存访问异常

内存访问导致的同步异常可能是MMU执行检查的结果,也可能是由于内存系统返回的错误。比如:访问了没有权限的内存地址或者写了只读区域的地址。
内存访问也可能会引起异步异常(SError)。

指令产生的异常

通常是低权限的软件用来请求高权限软件的服务,也就是系统调用。
ARM提供了三条指令来产生指令异常:

  1. SVC(The Supervisor Call):用来在EL0请求EL1的服务
  2. HVC(The Hypervisor Call): 当使能了虚拟化之后,用来给OS请求EL2服务
  3. SMC(The Secure Monitor Call ):当使能了安全扩展之后,用来请求EL3的服务。

EL0级的服务不能直接调用HVC和SMC,只有通过SVC进入kernel之后,才能调用SVC和SMC调用相关服务。
各种指令的路由关系:
在这里插入图片描述

调试异常

调试异常会跳转到debugger所在的异常等级,包括下面几种异常:

  1. 断点指令异常
  2. 断点异常
  3. 观察点异常
  4. 向量捕获异常
  5. 软件步进异常

异步异常

异步异常也叫物理中断,外界通过中断告诉PE发生了外部事件,然后通过指定的回调来处理,主要有下面几种:

  1. SError
  2. IRQ或者FIQ
  3. 虚拟中断

SError(system error)

通常是内存系统用来响应非预期的事务,包括:

  1. 访问内存时,通过了MMU的检查,但是在内存总线上出错了
  2. 奇偶校验或者ECC出错
  3. 从cache line往外部存储器回写脏数据时

SErrors are treated as a separate class of asynchronous exception because you would normally have separate handlers for these cases. SError generation is IMPLEMENTATION DEFINED。

IRQ或者FIQ

硬件中断,区分于SError,这个中断用来标记外部硬件的中断,比如timer或者外围硬件,他们通过GIC来管理。

虚拟中断

当支持虚拟化时,在hypervisor中或者VM中处理的中断就是虚拟中断,包括外部设备产生的中断和软件中断,AArch64支持虚拟中断:

  1. vSError
  2. vIRQ
  3. vFIQ

虚拟中断可以在hypervisor中产生,也可以通过中断控制器产生。hypervisor通过配置HCR.EL2寄存器来产生异常,产生的异常将物理异常路由到EL2,并将虚拟异常 signaling到EL1。

处理异常

以下是AArch64处理中断时的术语:

  1. PE在响应异常时,叫做taken
  2. PE在响应异常前的时刻叫做taken from
  3. PE在响应异常后的时刻叫做taken to
  4. PE执行异常返回指令的时候叫做return fram
  5. PE之后异常返回指令之后的时刻叫做return to

处理异常

当异常发生之后,处理器保存当前PE的状态和异常返回地址,转去处理异常。
当处理异常时,会进行下面的操作:

  1. PSTATE上下文会被写入SPSR_ELx
  2. 异常返回地址会被写入ELR_ELx
  3. 对于同步异常和SError,异常信息会被写入ESR_ELx
  4. 对于地址访问相关的异常,导致异常的虚拟地址会被写入FAR_ELx

当异常发生之后,PE会跳转到异常向量表中处理异常,对于AArch64来说,这个向量表不止是地址,还包含有最多32条指令。
在这里插入图片描述
需要注意,提升ELx等级唯一的方法就是产生异常,降低EL等级唯一的办法就是从异常中返回,每种异常都有自己对应的EL等级目标。

因为有可能AArch32的程序可以通过异常跳转到高等级的EL等级,从而执行AArch64的代码,因此AArch64回调处理函数可能会处理AArch32的寄存器,因此两者有一个对应关系:
在这里插入图片描述

处理异常1: 保存当前处理器状态

AArch64有一个处理器状态的概念,称为PSTATE。它保存了当前的EL等级和ALU状态等,包括:

  1. Condition flags
  2. Execution state controls
  3. Exception mask bits
  4. Access control bits
  5. Timing control bits
  6. Speculation control bits
    PSTATE中存在一个DAIF位,可以用来屏蔽对应的异常:
    D:Debug exception mask bit
    A:SError asynchronous exception mask bit, for example, asynchronous external abort
    I:IRQ asynchronous exception mask bit
    F:FIQ asynchronous exception mask bit

当处理一个异常时,寄存器保存的方向如下:
在这里插入图片描述

处理异常2: 异常路由和中断控制器

每个异常类型的EL等级由下面两种控制:

  1. 异常类型中隐含的EL等级
  2. 通过系统寄存器的某些bit控制

举例:
3. 像SVC、HVC、SMC这些指令是自带EL等级的
4. 像是IRQ、FIQ、SError等异常,可以通过寄存器配置他们是在哪个EL等级。

需要注意的是,当SCR寄存器和HCR寄存器发生冲突时,SCR寄存器的配置会覆盖HCR寄存器的配置。

进入和返回到一个未定义的EL等级都会引起错误。

路由到当前异常级别的异常可以被当前级别屏蔽。路由到较低异常级别的异常始终被屏蔽。异常将挂起,直到PE更改为与路由到的异常级别相等或更低的异常级别。这符合一条规则,即您永远不会因为执行异常而失去特权。如下图:
在这里插入图片描述

运行时状态取决于高一级EL等级的寄存器配置,如图:
在这里插入图片描述

处理异常3: AArch64向量表

每个EL等级都有属于自己的中断向量表,基地址存放在寄存器VBAR_ELx中(Vector Base Address Register),但是EL0没有自己的中断向量表。所有的向量表格式都是一样的,每个异常类别都有一个异常向量,位于向量基地址的固定偏移处。

VBAR寄存器地址复位之后会被清空,所以需要在使能中断之前配置这个寄存器。
异常向量表偏移对应的向量入口:
在这里插入图片描述
在这里插入图片描述
从上表可以看出,表格被分为2个组,每个组又被分为两个小组,分别是:
在这里插入图片描述

需要注意的是,运行时状态是取决于发生异常时要跳到的EL等级,而不是当前运行时的EL等级,举例:当从EL0到EL1时,向量表入口取决于EL1的运行时状态;并且,当从EL0到EL2时,向量表也是取决于EL1的运行时状态,这是因为我们一般希望hypervisor运行在EL2,而VM运行在EL1和EL0,hypervisor需要知道kernel运行在EL1时的运行时状态,而不仅仅是应用的运行时状态(因为AArch32的应用可能运行在AArch32的OS上,也可能运行在AArch64的OS上)。

处理器处理异常过程举例:
在这里插入图片描述

栈指针选择和栈指针寄存器

异常发生时,栈指针选择遵循下面的规则:

  1. 当运行在EL0时,使用EL0的栈指针
  2. 当运行在EL1/EL2/EL3时,栈指针取决于PSTATE的SP bit位
    1. 当PSTATE.SP = 0时,使用EL0的栈指针
    2. 当PSTATE.SP = 1时,使用当前EL等级的栈指针

从异常返回

当从异常返回时,会执行下面两个步骤:

  1. Restoring all previously stacked corruptible regist
  2. Initiating an exception return instruction (ERET)

ERET指令会从SPSR寄存器恢复处理器状态,并从ELR寄存器中拿到程序返回地址并执行。SPSR_ELx寄存器记录了程序返回时的EL状态及运行时,并且运行时状态必须和SCR_ELx或者HCR_ELx中的RW bit位相匹配,不然会报错。

执行ERET指令时,会将SPSR_ELx寄存器恢复到PSTATE,ELR_ELx寄存器的值恢复到程序计数器中,并且这个过程时自动的、原子的,防止PE进入一个未定义的状态。

异常处理举例

同步异常处理

可以通过下面几个寄存器来查看异常信息:

  1. ESR_ELx(The Exception Syndrome Register)寄存器可以查看同步异常的原因和类型。
  2. FAR_ELx(The Fault Address Register)寄存器可以查看地址相关的同步异常发生时的虚拟地址。
  3. ELR_ELx(The Exception Link Register)寄存器可以查看引起异常的指令地址,提供异常返回时的地址。

举例说明:
当一个AArch32的软件运行在EL0,需要通过SVC指令进行系统调用时,此时OS以AArch64状态运行在EL1,会进行以下操作:
4. 将当前PSTATE写入寄存器SPSR_EL1中
5. 将异常返回地址写入寄存器ELR_EL1中
6. 通过HCR_EL2.RW bit判断应该运行在哪个运行时(AArch64)
7. 更新当前PSTATE,同时EL等级切换到EL1,运行时切换到AArch64
8. 处理器转去VBAR_EL1 + 0x600地址去执行,因为根据前面所述规则,从低等级到高等级,并且低等级运行时都是AArch32
9. Defined registers are stacked to maintain register context
10. The type of asynchronous exception is identified from ESR_EL1, in this case SVC
11. 执行指定的SVC回调代码
12. Once the SVC-specific handler code completes, control returns to the high-level handler
13. 恢复之前压栈的寄存器,并执行ERET指令
14. 恢复PSTATE寄存器,以及返回到EL0,运行时切换到AArch32,ELR_ELx寄存器装载到程序计数器继续运行

一个更复杂的例子是,EL0下的非安全的APP请求一个EL1安全OS的服务,这时候,需要从EL0到EL3,再从EL3到安全的EL1,因为EL0不能直接请求EL3的服务,所以这时候需要EL0下通过svc指令进入非安全的EL1,非安全的EL1再通过smc指令进入EL3然后再请求安全的EL1下OS的服务,具体步骤可以参考:TrustZone for AArch64

异步异常处理

当程序运行在EL0时,一个中断打断执行,并进入EL1(HCR_EL2和SCR_EL3配置中断路由到EL1),会执行下面的步骤:

  1. PSTATE写入寄存器SPSR_EL1
  2. 当前运行地址写入ELR_EL1
  3. 通过HCR_EL2.RW bit判断应该运行在哪个运行时(AArch64)
  4. 更新当前PSTATE,同时EL等级切换到EL1,运行时切换到AArch64
  5. 处理器转去VBAR_EL1 + 0x680地址去执行,因为根据前面所述规则,从低等级到高等级,并且低等级运行时都是AArch32
  6. Defined registers are stacked to maintain register context
  7. 执行指定的IRQ回调代码
  8. Once the IRQ-specific handler code completes, control returns to the high-level handler
  9. 恢复之前压栈的寄存器,并执行ERET指令
    10.恢复PSTATE寄存器,以及返回到EL0,运行时切换到AArch32,ELR_ELx寄存器装载到程序计数器继续运行

需要注意的是,处理器不知道当前发生了哪个中断,它时通过读取GIC的IAR(Interrupt Acknowledge Registers)寄存器来判断当前中断ID的。读取IAR寄存器会返回当前的中断ID并标记这个中断为active,然后在处理完这个中断之后,需要回写GIC的EOIR(End of Interrupt Register)寄存器来标记完成了这个中断。

异常 屏蔽和NMI

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值