2. ARMv8基础概念:AArch64与AArch32执行状态、异常级别(EL0-EL3)、寄存器模型(通用寄存器、特殊寄存器)以及基本数据类型和内存模型
ARMv8架构是ARM处理器发展史上的重要里程碑,它首次在经典ARM处理器中引入了64位执行能力。理解ARMv8的基础概念,是进行系统级开发和底层驱动设计的基石。本章将深入剖析AArch64与AArch32两种执行状态的区别、异常级别(Exception Level)的权限分层、完整的寄存器模型,以及基本数据类型与内存模型。这些概念直接决定了操作系统如何实现进程隔离、中断处理、虚拟内存管理以及硬件资源访问控制。对于嵌入式系统工程师、BSP开发者和操作系统移植人员而言,掌握这些底层机制是编写可靠、高效系统代码的前提。
上图展示了ARMv8架构中几个核心概念之间的逻辑关系。执行状态决定了指令集和寄存器宽度,异常级别定义了软件运行的权限层级,而寄存器模型则是实现状态切换和权限控制的具体硬件载体。下面我们将逐一深入探讨这些主题。
2.1 AArch64与AArch32执行状态
核心原理
ARMv8架构定义了两种执行状态:AArch64和AArch32。AArch64是64位执行状态,使用64位通用寄存器和全新的A64指令集,支持64位虚拟地址空间。AArch32是32位执行状态,兼容ARMv7架构的指令集(A32和T32),使用32位通用寄存器。处理器在复位后可以进入其中任意一种状态,并且可以在运行时通过异常级别切换来改变执行状态。
这两种状态并非简单的寄存器宽度差异,而是涉及指令集编码、异常模型、内存管理单元(MMU)配置等多个层面的根本性变化。AArch64取消了ARMv7中用于条件执行的S suffix(几乎全部指令),引入了新的加载/存储对(LDP/STP)指令,并统一了异常向量表布局。
判断方法或操作流程
在实际开发中,判断当前处理器处于哪种执行状态,可以通过读取CurrentEL系统寄存器来获取当前异常级别,但无法直接读取执行状态。不过,我们可以通过以下方式间接判断:
- 检查
SCTLR_EL1寄存器的EE位(端序配置)和WXN位等,这些位的含义在AArch64和AArch32下不同。 - 尝试执行A64特有的指令(如
LDP),如果触发未定义指令异常,则说明当前处于AArch32状态。 - 在系统启动代码中,通常通过读取
ID_AA64PFR0_EL1寄存器来确认处理器是否支持AArch64。
从AArch64切换到AArch32的唯一途径是发生异常并将异常返回目标设置为AArch32。这通常发生在安全监控器(Secure Monitor)或Hypervisor层,用于运行32位客户操作系统或可信固件。
案例拆解或实战场景
假设我们正在为一块基于Cortex-A72的SoC编写启动固件。该SoC支持AArch64和AArch32两种状态。在复位后,处理器默认进入AArch64 EL3。我们的固件需要:
- 在EL3初始化安全世界环境,配置GIC(通用中断控制器)和TrustZone地址空间控制器(TZASC)。
- 通过
ERET指令切换到EL2或EL1,并选择保持AArch64状态。 - 如果后续需要引导一个32位的RTOS(如FreeRTOS),则需要在EL3中配置
SCR_EL3寄存器的RW位,将下一个异常级别的执行状态设置为AArch32。
关键代码片段如下:
// 设置SCR_EL3.RW = 0,使EL2进入AArch32状态
MRS X0, SCR_EL3
BIC X0, X0, #(1 << 10) // 清除RW位
MSR SCR_EL3, X0
ISB
// 配置EL2的入口地址和SPSR后执行ERET
这种混合状态支持在同一个SoC上运行64位主操作系统和32位安全固件,是移动设备中常见的TrustZone实现方式。
常见误区或总结复盘
总结:AArch64与AArch32是互斥的执行状态,它们之间的切换是受控的、有代价的。系统设计时应明确各异常级别使用的执行状态,避免频繁切换带来的性能开销。
2.2 异常级别(EL0-EL3)
核心原理
ARMv8引入了4个异常级别(Exception Level),编号从EL0到EL3,数字越大权限越高。这种设计取代了ARMv7中基于处理器模式(User, Supervisor, IRQ等)的权限模型,提供了更清晰、更安全的层次化隔离。
- EL0:用户应用程序运行级别,权限最低,无法直接访问硬件资源。
- EL1:操作系统内核级别,支持虚拟内存管理,运行Linux、Windows等操作系统。
- EL2:Hypervisor级别,支持虚拟化扩展,可管理多个EL1客户操作系统。
- EL3:Secure Monitor级别,最高权限,负责安全世界与普通世界的切换,是TrustZone技术的核心。
异常级别决定了软件可访问的系统寄存器集合、MMU配置能力以及中断路由方式。例如,只有EL2或EL3才能配置虚拟化相关的寄存器,只有EL3才能访问安全配置寄存器。
判断方法或操作流程
软件可以通过读取CurrentEL寄存器来获取当前异常级别。该寄存器是只读的,其值编码如下:
| CurrentEL[3:2] | 异常级别 |
|---|---|
| 00 | EL0 |
| 01 | EL1 |
| 10 | EL2 |
| 11 | EL3 |
异常级别的提升只能通过异常发生(如中断、系统调用、未定义指令)来实现,而降低则通过ERET指令完成。每个异常级别都有独立的堆栈指针(SP_ELx)和保存状态寄存器(SPSR_ELx)。
案例拆解或实战场景
以Linux内核启动过程为例:
- 引导加载程序(如U-Boot)在EL3启动,初始化DDR和时钟。
- U-Boot通过
ERET降级到EL2,加载Hypervisor(如KVM)或直接降级到EL1。 - Linux内核在EL1运行,管理进程调度和内存。
- 用户进程在EL0运行,通过系统调用(SVC指令)陷入EL1。
这种分层确保了即使EL0的应用程序被攻破,攻击者也无法直接访问EL1的内核数据,更无法触及EL3的安全固件。
常见误区或总结复盘
总结:异常级别是ARMv8安全模型的骨架。理解每个级别的职责和权限边界,是设计安全系统的基础。
2.3 寄存器模型
核心原理
ARMv8的寄存器模型分为三类:通用寄存器、特殊寄存器和系统寄存器。
通用寄存器:在AArch64状态下,有31个64位通用寄存器(X0-X30),其中X30用作链接寄存器(LR)。此外,还有零寄存器XZR和堆栈指针SP。在AArch32状态下,有16个32位寄存器(R0-R15),其中R13是SP,R14是LR,R15是PC。
特殊寄存器:包括程序计数器(PC)、保存的程序状态寄存器(SPSR_ELx)、异常链接寄存器(ELR_ELx)等。SPSR用于保存发生异常前的PSTATE(处理器状态),ELR保存异常返回地址。
系统寄存器:通过MSR/MRS指令访问,用于配置MMU、中断、时钟等。例如TTBR0_EL1存放页表基地址,MAIR_EL1定义内存属性。
判断方法或操作流程
在AArch64中,通用寄存器的使用遵循AAPCS64(ARM 64位过程调用标准):
| 寄存器 | 角色 | 调用者保存? |
|---|---|---|
| X0-X7 | 参数寄存器 | 是 |
| X8 | 间接结果寄存器 | 是 |
| X9-X15 | 临时寄存器 | 是 |
| X19-X29 | 被调用者保存 | 否 |
| X30 | 链接寄存器 | 否 |
特殊寄存器中,DAIF位域控制中断屏蔽,NZCV位域保存条件标志。系统寄存器的访问必须使用MRS/MSR指令,且只能在允许的异常级别下访问。
案例拆解或实战场景
编写一个简单的异常处理程序,需要保存现场并恢复:
// 保存通用寄存器到栈
SUB SP, SP, #256
STP X0, X1, [SP, #0]
STP X2, X3, [SP, #16]
// ... 保存更多寄存器
// 读取异常返回地址
MRS X0, ELR_EL1
// 处理异常...
// 恢复寄存器
LDP X2, X3, [SP, #16]
LDP X0, X1, [SP, #0]
ADD SP, SP, #256
ERET
这段代码展示了在EL1处理中断时的标准流程:先保存现场,再读取异常信息,处理完毕后恢复现场并返回。
常见误区或总结复盘
SP和XZR在编码上是同一个寄存器号(31),但语义完全不同。使用SP时表示堆栈指针,使用XZR时表示零寄存器。混淆二者会导致严重的内存错误。
总结:寄存器模型是汇编编程的基础。开发者必须清楚哪些寄存器是调用者保存的,哪些是被调用者保存的,以及特殊寄存器的访问约束。
2.4 基本数据类型与内存模型
核心原理
ARMv8支持的基本数据类型包括:字节(8位)、半字(16位)、字(32位)、双字(64位)和四字(128位,用于SIMD)。内存模型遵循以下原则:
- 字节序:支持小端(Little-Endian)和大端(Big-Endian),通过系统寄存器配置。
- 对齐:AArch64要求自然对齐访问,未对齐访问可能导致异常或性能下降。
- 内存属性:通过MMU页表项中的属性位定义内存类型(Normal, Device, Strongly-ordered)和缓存策略(Write-Back, Write-Through, Non-Cacheable)。
内存模型还定义了多核之间的缓存一致性协议(如MESI),以及内存屏障指令(DMB, DSB, ISB)来保证内存访问顺序。
判断方法或操作流程
配置内存属性的流程如下:
- 在页表中设置
MAIR_EL1寄存器,定义内存属性索引(如0表示Device-nGnRnE,1表示Normal Write-Back)。 - 在页表描述符中引用这些索引。
- 配置
TCR_EL1设置页表粒度(4KB/16KB/64KB)和地址宽度。 - 执行
TLBI指令使TLB失效,确保新配置生效。
对于外设寄存器,通常映射为Device内存,禁止缓存和推测访问;对于DDR,映射为Normal内存,启用缓存以提升性能。
案例拆解或实战场景
假设我们需要在Linux内核中映射一个DMA缓冲区,要求CPU和DMA控制器看到一致的数据。正确做法是:
- 将缓冲区映射为Normal Non-Cacheable或使用DMA API进行缓存一致性维护。
- 在设备树中配置
dma-coherent属性。 - 如果使用Cacheable映射,则需要在DMA操作前后调用
dma_map_single和dma_unmap_single。
一个典型的错误是直接将内核线性映射的地址(Cacheable)传给DMA,导致数据不一致。正确代码应使用dma_alloc_coherent分配一致性内存。
常见误区或总结复盘
总结:内存模型决定了系统性能和稳定性。开发者需要根据硬件特性选择合适的内存属性,并在多核场景下正确使用内存屏障。
本章详细介绍了ARMv8的基础概念,包括执行状态、异常级别、寄存器模型和内存模型。这些知识是后续学习MMU配置、中断处理、多核启动和TrustZone技术的必要前提。在实际开发中,建议通过阅读ARM Architecture Reference Manual(ARM DDI 0487)加深理解,并结合具体SoC的TRM进行实践。