1. 课程导入

各位同学,咱们今天正式开讲《ARMv8 内存管理与 MMU 配置调试实战》的第一章。说实话,ARMv8 刚出来那会儿,我也觉得它挺复杂的——尤其是从 ARMv7 跳过来的时候。但后来我发现,只要把几个核心概念理清楚,后面的 MMU 配置其实水到渠成。

这一章咱们就聊聊 ARMv8 的三大基石:执行状态、异常等级和寄存器模型。别小看这些基础,我见过不少工程师在调试时翻车,就是因为没搞懂 EL0 和 EL1 的区别。

1.1 AArch64 与 AArch32:两种执行状态

ARMv8 最让我眼前一亮的设计,就是它同时支持两种执行状态——AArch64 和 AArch32。说白了,就是一颗芯片能跑 64 位和 32 位两种模式。

特性 AArch64 AArch32
寄存器宽度 64 位通用寄存器 32 位通用寄存器
指令集 A64(固定 32 位指令) A32/T32(ARM/Thumb)
异常等级 EL0-EL3,共 4 级 PL0-PL3,共 4 级
虚拟地址宽度 最大 48 位(可选 52 位) 最大 32 位
MMU 页表格式 L0-L3 四级页表 一级/二级页表

我个人习惯在项目初期就确定好执行状态。如果你跑的是 Linux,那基本就是 AArch64 没跑了。但如果你要做兼容老系统的 bootloader,AArch32 可能更合适。

小提示: 同一个核可以在不同异常等级切换执行状态。比如 EL3 跑 AArch64,EL1 跑 AArch32。我在一个车载项目里就这么干过——secure monitor 用 64 位,普通 OS 用 32 位,兼容性拉满。

1.2 异常等级(EL0-EL3):权限的分层设计

ARMv8 把运行权限分成了 4 个等级,从 EL0 到 EL3。数字越大,权限越高。你想想看,这就像一栋楼的楼层——EL0 是地下室,EL3 是顶楼总裁办公室。

核心概念: 异常等级决定了你能访问哪些资源。EL0 不能碰系统寄存器,EL1 可以配 MMU,EL2 管虚拟化,EL3 管安全世界。
  • EL0:用户态,跑应用程序。权限最低,不能直接操作硬件。
  • EL1:操作系统内核态。Linux 内核就跑在这儿,能配 MMU、中断控制器。
  • EL2:虚拟化层。Hypervisor 的领地,管理多个虚拟机。
  • EL3:安全监控层。Secure Monitor 的专属,负责安全世界和普通世界的切换。

我曾经在一个项目里踩过坑——在 EL1 尝试写一个 EL2 才允许访问的寄存器,结果直接触发 Undefined Instruction 异常。嗯,从那以后我写代码前都会先查一下当前异常等级。

1.3 寄存器模型:从 X0 到 X30 再到系统寄存器

AArch64 的寄存器模型比 ARMv7 清爽多了。通用寄存器从 X0 到 X30,一共 31 个 64 位寄存器。X30 是链接寄存器(LR),存返回地址。

还有几个特殊的:

  • SP:栈指针,每个异常等级有自己的 SP_ELx。
  • PC:程序计数器,不能直接写,只能通过分支指令修改。
  • XZR:零寄存器,读出来永远是 0,写进去被忽略。

系统寄存器这块,我建议你重点关注几个:

寄存器 全称 作用
SCTLR_EL1 System Control Register 控制 MMU、Cache、对齐检查等
TCR_EL1 Translation Control Register 配置页表格式、地址宽度、内存属性
TTBR0_EL1 / TTBR1_EL1 Translation Table Base Register 存放页表基地址
MAIR_EL1 Memory Attribute Indirection Register 定义内存属性(Cache 策略、共享属性)
注意: 系统寄存器只能通过 MSR 和 MRS 指令读写。别想着用 LDR/STR 去碰它们——我见过有人这么干,结果就是 Undefined 异常。

1.4 知识结构图:ARMv8 架构核心脉络

下面这张图是我自己整理的,把执行状态、异常等级和寄存器模型串在了一起。你把它记在脑子里,后面学 MMU 会轻松很多。

ARMv8 架构核心脉络 执行状态 AArch64 AArch32 异常等级 EL0 EL1 EL2 EL3 寄存器模型 通用寄存器 X0-X30 系统寄存器 说明:执行状态决定指令集和寄存器宽度,异常等级决定权限,寄存器模型提供操作接口。 三者共同构成 ARMv8 的架构基础,MMU 配置依赖这些概念。

1.5 实战中的一点体会

说了这么多理论,我分享一个实际案例吧。之前做一款车规级芯片的 bootloader,需要在 EL3 初始化安全世界,然后切到 EL1 启动 Linux。当时我犯了个低级错误——在 EL3 配了 MMU,但忘了切页表,结果 Linux 一启动就挂了。

后来怎么解决的?我在 EL3 的退出代码里加了一行 MSR TTBR0_EL1, x0,把正确的页表基地址传给 EL1。嗯,就是这么一行代码,折腾了我两天。

我的建议: 刚开始学 ARMv8,别急着写代码。先把 EL0-EL3 的权限边界画清楚,把 X0-X30 的用途记熟。后面配 MMU 的时候,你会发现这些基础有多重要。