1. ARMv8架构概述:AArch64与AArch32执行状态、异常等级(EL0-EL3)定义、安全世界与非安全世界
各位同学,咱们今天正式开讲ARMv8。说实话,ARMv8是个分水岭。在这之前,ARM处理器一直是32位的天下;从v8开始,64位计算被正式引入。但ARM没有像x86那样直接砍掉32位兼容,而是搞了一套双执行状态的设计——AArch64和AArch32。我个人觉得,这个设计既保留了生态兼容性,又给未来性能扩展留足了空间。
1.1 AArch64与AArch32:两种执行状态
ARMv8架构最核心的变化,就是引入了AArch64执行状态。你可以把它理解为处理器的“64位模式”。在这个模式下,寄存器是64位的,指令集是全新的A64,地址空间也扩展到了48位(物理地址甚至能到52位)。
而AArch32,说白了就是兼容老ARM代码的模式。它支持现有的ARMv7指令集(A32和T32),寄存器还是32位的。嗯,这里要注意:AArch32并不是简单的“向下兼容”,它其实是ARMv8硬件上重新实现的32位执行环境,性能并不差。
关键区别一览:
| 特性 | AArch64 | AArch32 |
|---|---|---|
| 寄存器宽度 | 64位通用寄存器(X0-X30) | 32位通用寄存器(R0-R14) |
| 指令集 | A64(固定32位指令) | A32(32位)/ T32(16/32位混合) |
| 地址空间 | 最大48位虚拟地址 | 最大32位虚拟地址 |
| 异常等级 | EL0-EL3完整支持 | EL0-EL3(但PL0-PL2映射) |
| 浮点/SIMD | 默认支持(32×128位寄存器) | 可选支持(VFP/NEON) |
我在项目中遇到过一种情况:有些老驱动只编译了A32指令集,移植到新平台时发现跑不起来。后来查了半天,原来是启动时CPU默认进入AArch64,需要先切到AArch32才能执行这些代码。所以,两种状态之间的切换,是底层开发必须掌握的技能。
1.2 异常等级(EL0-EL3):ARMv8的权限分层
ARMv8引入了一个非常清晰的特权模型——异常等级(Exception Level)。你想想看,从EL0到EL3,数字越大,权限越高。这个设计比ARMv7的PL0-PL2要直观得多。
- EL0:用户态。跑应用程序的地方,权限最低。不能直接操作硬件,不能配置MMU,连关中断都不行。
- EL1:操作系统内核态。Linux内核、RTOS内核都跑在这里。可以配置MMU、处理中断、管理进程。
- EL2:虚拟机监视器(Hypervisor)。虚拟化场景下,Host VM跑在这里。可以截获Guest OS的敏感操作。
- EL3:安全监视器(Secure Monitor)。最高权限,负责安全世界与非安全世界的切换。ARM TrustZone的核心就在这里。
我的经验:调试异常等级相关的问题时,我建议你先确认当前CPU在哪个EL下运行。用MRS X0, CurrentEL指令读一下,返回值低两位就是EL编号。我曾经被一个EL2下的虚拟化中断搞了三天,最后发现是EL2的异常向量表配错了。
异常等级之间通过异常(Exception)来切换。比如,用户程序执行了SVC指令,就会触发一个同步异常,CPU从EL0跳到EL1。再比如,外设中断来了,如果当前在EL1,中断会直接由EL1的异常向量表处理。如果当前在EL0,则会先升到EL1再处理。
注意:异常等级只能升高,不能降低。降低等级必须通过异常返回(ERET)指令。而且ERET的目标等级由SPSR_ELx中的M字段决定。我曾经见过有人把SPSR写错了,ERET之后CPU直接跑飞——那个bug查得我头皮发麻。
1.3 安全世界与非安全世界:TrustZone的根基
ARMv8延续了ARMv7时代的TrustZone技术,但做了重要升级。在ARMv8中,安全世界(Secure World)和非安全世界(Non-secure World)是两个独立的执行环境,它们共享物理CPU,但拥有各自的内存、外设和异常向量表。
你想想看,这就像同一台物理机器上跑了两套系统:一套是“普通系统”(比如Android/Linux),另一套是“安全系统”(比如TEE OS)。普通系统永远无法访问安全系统的内存,即使它跑在EL3也不行——因为EL3本身就有安全和非安全两种状态。
具体来说,安全世界和非安全世界的划分是这样的:
- 非安全世界(Normal World):运行普通操作系统和应用程序。所有EL0-EL2都在非安全世界。
- 安全世界(Secure World):运行安全操作系统(如OP-TEE、Trusty)。安全世界有自己的EL0-EL3,但通常只用到EL0和EL1。
- EL3:唯一横跨两个世界的异常等级。安全监视器(Secure Monitor)运行在这里,负责两个世界之间的切换。
世界切换流程(简化版):
- 非安全世界的EL1执行
SMC指令(安全监视器调用)。 - CPU陷入EL3,安全监视器保存非安全世界上下文。
- 安全监视器切换到安全世界,恢复安全世界上下文。
- 安全世界的EL1开始执行安全服务。
- 安全服务完成后,执行
SMC返回EL3。 - 安全监视器恢复非安全世界上下文,ERET回到非安全世界。
我在项目中遇到过一个问题:安全世界的定时器中断没有正确路由到EL3,导致安全服务超时。后来发现是GIC(通用中断控制器)的配置问题——安全中断必须配置为Group 0,并且路由到EL3。这个坑,我建议你们提前记住。
1.4 整体架构关系图
下面这张图,是我自己画的一个ARMv8架构概览。它把执行状态、异常等级、安全世界的关系串在了一起。你仔细看看,应该能对ARMv8的整体结构有个直观印象。
1.5 本章小结
好了,咱们把ARMv8架构的三大核心概念捋了一遍:
- AArch64 vs AArch32:64位和32位两种执行状态,寄存器宽度、指令集、地址空间都不一样。切换时要小心上下文保存。
- EL0-EL3:四个异常等级,从用户态到安全监视器,权限逐级递增。异常只能升不能降,ERET是唯一的降级手段。
- 安全世界 vs 非安全世界:TrustZone的两个世界,通过EL3的SMC指令切换。安全世界的内存对外部完全隔离。
这些概念,是后续所有异常处理、调试接口、故障定位的基础。你如果现在觉得有点绕,没关系——后面每一章都会反复用到这些知识。我个人建议你把这张架构图存下来,调试的时候对照着看,会清晰很多。