2、ARMv7异常等级模型:异常等级(EL0-EL3)介绍,安全世界与非安全世界,MMU与异常等级的关系

好,咱们正式开始聊ARMv7的异常等级模型。说实话,我刚接触ARMv7的时候,也被EL0到EL3这几个东西搞得有点晕。你想想看,一个CPU为什么要分这么多等级?直接像单片机那样跑裸机不香吗?

嗯,等你真正做过几个项目,踩过几次坑,就会明白——这套等级模型,其实是ARM为了兼顾安全和性能搞出来的精妙设计。今天我就把这块掰开揉碎了讲给你听。

2.1 异常等级(EL0-EL3)概述

ARMv7架构定义了四个异常等级,从EL0到EL3。数字越大,特权越高。我习惯把它们比作公司里的四个层级:

异常等级 特权级别 典型用途 我给的比喻
EL0 最低(无特权) 用户应用程序 普通员工,只能用自己的工位
EL1 中等 操作系统内核 部门经理,能管整个部门的资源
EL2 高 Hypervisor(虚拟机监控器) 总监,能管多个部门
EL3 最高 安全监控器(Secure Monitor) CEO,啥都能管

每个等级能访问的寄存器、能执行的指令都不一样。低等级想干高等级的事?门都没有,得通过异常(Exception)来“请求”高等级帮你做。

关键点:异常等级不是“运行模式”,而是“权限层级”。每个等级都有自己的栈指针(SP)、自己的页表基地址(TTBR)、自己的控制寄存器。切换等级时,硬件自动保存和恢复上下文。

2.2 安全世界与非安全世界

ARMv7还有一个更狠的设计——TrustZone。它把整个系统切成了两个世界:安全世界(Secure World)和非安全世界(Non-Secure World)。

我刚开始做TEE(可信执行环境)的时候,就被这个“两个世界”的概念绕进去过。说白了,你可以把CPU想象成有两套独立的硬件资源:

  • 非安全世界:跑你的Android、Linux、RTOS。这部分代码永远不能访问安全世界的资源。
  • 安全世界:跑指纹识别、支付、DRM等敏感应用。这部分可以访问非安全世界的内存(但通常不这么做)。

两个世界通过监控模式(Monitor Mode)切换,这个模式运行在EL3。切换指令是SMC(Secure Monitor Call)。

我曾经踩过的坑:在非安全世界的EL1里直接写安全世界的物理地址,以为MMU关了就能随便访问。结果CPU直接挂死。后来才明白,TrustZone的隔离是在总线层面做的,跟MMU开不开没关系。非安全世界的访问请求,在总线上就被过滤掉了。

2.3 异常等级与安全世界的组合矩阵

把异常等级和安全世界组合起来,就形成了一个4×2的矩阵。每个组合都有自己的页表、自己的异常向量表:

异常等级 非安全世界 安全世界
EL0 普通App 安全App(Trusted App)
EL1 Linux/RTOS内核 Trusted OS(如OP-TEE)
EL2 Hypervisor 安全Hypervisor(很少用)
EL3 安全监控器(唯一共享的等级)

你注意看,EL3是唯一一个“不分安全/非安全”的等级。为什么?因为EL3本身就是安全世界的最高权限,它负责在两个世界之间切换。

2.4 MMU与异常等级的关系

好,现在聊重点——MMU跟这些等级到底什么关系?

每个异常等级都有自己的页表基地址寄存器(TTBR0/TTBR1)。也就是说,EL0、EL1、EL2、EL3各自维护一套独立的页表。切换等级时,MMU自动切换到对应的页表。

我个人习惯把MMU的配置分成三层来看:

  1. 页表本身:每个等级各自维护,互不干扰。
  2. 地址翻译规则:由系统控制寄存器(SCTLR)的M位控制。每个等级可以独立开关MMU。
  3. 内存属性:包括Cache策略、共享属性、执行权限等。这些属性在页表条目里定义,不同等级可以不同。

一个小技巧:我在调试MMU问题时,经常先检查当前在哪个异常等级。用MRS x0, CurrentEL读一下CurrentEL寄存器,就能知道当前特权级别。如果发现页表没生效,八成是配错了等级的页表。

2.5 异常等级切换时的MMU行为

异常等级切换时,MMU的行为很关键。我画了一张图帮你理解:

异常等级切换与MMU行为 EL3 (安全监控器) MMU: 独立页表, 可开关 EL2 (Hypervisor) MMU: 独立页表, 可开关 EL1 (OS内核) MMU: 独立页表, 可开关 EL0 (用户App) MMU: 继承EL1页表, 只读 SMC/IRQ HVC/IRQ SVC/IRQ ⚠ 切换时的MMU行为: 1. 硬件自动保存当前等级的页表基地址(TTBR)到系统寄存器 2. 加载目标等级的页表基地址 → 3. TLB可能被刷掉(取决于异常类型)

切换等级时,硬件会做这几件事:

  • 保存当前等级的TTBR0/TTBR1到系统寄存器
  • 加载目标等级的页表基地址
  • 根据异常类型决定是否刷TLB(Translation Lookaside Buffer)

注意:从EL1切到EL0时,MMU不会刷TLB。因为EL0的页表是EL1页表的一部分(EL0使用TTBR0,EL1使用TTBR1)。但如果你改了EL0的页表,记得手动刷TLB,否则旧映射还在。

2.6 实战中的配置建议

说了这么多理论,来点实际的。我在做嵌入式系统时,通常这样配置MMU和异常等级:

场景 EL配置 MMU配置 我的建议
裸机系统 EL1或EL3 简单页表,1:1映射 用EL1就够了,EL3留给安全监控器
Linux + TEE EL1(非安全) + EL3(安全) Linux用2级页表,TEE用1级页表 注意安全世界和非安全世界的页表不要重叠
虚拟化场景 EL2 + EL1 + EL0 EL2管理物理地址,EL1管理虚拟地址 EL2的页表要小心配置,错了整个系统崩

我的个人习惯:调试阶段,我会把所有等级的MMU都打开,但把页表设成1:1映射(虚拟地址=物理地址)。这样既能享受MMU的权限保护,又不用处理复杂的地址翻译问题。等系统跑稳了,再优化页表。