4. MMU 控制寄存器:SCTLR_EL1/EL2/EL3 的关键位及其对启动的影响

系统控制寄存器(System Control Register, SCTLR)是 ARMv8-A 架构中控制 MMU、缓存、对齐检查等核心功能的寄存器。在启动流程中,正确配置 SCTLR 的各个位域,是 MMU 安全启用、页表正确遍历的前提。不同异常级别(EL1/EL2/EL3)拥有独立的 SCTLR 实例,但其位域定义基本一致。

4.1 SCTLR 寄存器概览

SCTLR 是一个 64 位寄存器,但启动阶段最常关注的位集中在低 32 位。下表列出了对 MMU 初始化和启动流程影响最大的关键位:

位域 名称 默认值(Reset) 功能描述 启动影响
[0] M 0 (EL1/EL2/EL3) MMU 使能位。0=禁用,1=启用。 MMU 开关,必须在页表就绪后置 1。
[2] C 0 数据缓存(Data Cache)使能位。0=禁用,1=启用。 启动早期必须关闭,防止脏数据回写。
[12] I 0 (EL1/EL2/EL3) 指令缓存(Instruction Cache)使能位。0=禁用,1=启用。 可在页表建立前开启以加速取指。
[19] WXN 0 写权限不可执行(Write permission implies eXecute Never)。0=允许写且可执行,1=写权限页面自动标记为不可执行。 增强安全性,防止代码被篡改后执行。
[25] EE 由硬件复位决定 异常处理时的端序(Endianness)。0=小端,1=大端。 影响异常向量表和数据访问的字节序。
[1] A 0 对齐检查使能。0=禁用,1=启用。 启动阶段通常关闭,避免非对齐访问触发异常。
[3] SA 0 栈指针对齐检查。0=禁用,1=启用。 SP 必须 16 字节对齐,否则触发异常。
启动经验: 在 MMU 启用前,务必确保 C 位和 M 位为 0。如果从 Boot ROM 跳转到裸机代码时,I 位可能已被 Boot ROM 置 1,此时无需额外关闭指令缓存。

4.2 关键位深度解析

4.2.1 M 位:MMU 总开关

M 位是 MMU 的全局使能位。当 M=0 时,处理器直接使用物理地址访问内存,所有页表遍历逻辑被旁路。当 M=1 时,MMU 开始根据页表基地址寄存器(TTBR0_EL1/TTBR1_EL1)进行地址翻译。

  • 启动顺序: 必须先建立页表、配置 MAIR 和 TCR,最后才置位 M 位。
  • 同步要求: 修改 M 位后,必须执行 ISB(指令同步屏障)指令,确保后续指令在 MMU 开启后的新地址空间下执行。
  • EL 差异: 在 EL3 下修改 SCTLR_EL3.M 会影响整个系统;在 EL2 下修改仅影响非安全世界;在 EL1 下修改影响当前 OS。

4.2.2 C 位与 I 位:缓存控制

缓存一致性是启动阶段最容易出问题的环节。

  • C 位(数据缓存): 启动时必须为 0。因为页表通常存放在内存中,如果数据缓存使能,页表修改可能只停留在缓存中,MMU 遍历时读到的是过时的内存数据。此外,外设寄存器访问必须绕过缓存。
  • I 位(指令缓存): 可以在页表建立前开启。开启 I 位能显著加速从 Flash 或 ROM 中取指。但需要注意,如果代码是自修改的(如动态加载),修改后必须执行 IC IALLU 使指令缓存失效。
常见陷阱: 在关闭 MMU 的情况下,如果 C 位被意外置 1,CPU 可能会从缓存中读取数据,导致外设寄存器或内存映射 IO 访问异常。务必在 MMU 初始化前执行 DC CISW 或 DC ZVA 清理数据缓存。

4.2.3 WXN 位:写权限与执行权限互斥

WXN 位提供了一种硬件级别的安全保护。当 WXN=1 时,任何具有写权限的内存区域(页表描述符中的 AP[2:1] 允许写入)都会被 MMU 自动标记为不可执行(XN=1)。这意味着即使页表描述符中 XN 位为 0,只要该区域可写,CPU 就无法从中取指。

  • 启动阶段: 通常建议在 MMU 启用后立即设置 WXN=1,防止栈溢出或堆破坏后执行恶意代码。
  • 与 PXN 的区别: PXN(Privileged Execute Never)限制特权级执行,而 WXN 限制可写区域的执行,两者互补。

4.2.4 EE 位:端序控制

EE 位控制异常处理时的数据访问端序。ARMv8 架构支持在 EL 级别混合使用大端和小端,但启动阶段通常保持与复位默认值一致。

  • 影响范围: 异常向量表、页表遍历时的描述符读取、系统寄存器访问等。
  • 启动建议: 除非有特殊的大端需求,否则保持 EE=0(小端)。修改 EE 位后,必须重新建立页表,因为页表描述符的字节序会改变。

4.3 SCTLR 配置流程与 EL 差异

不同异常级别下,SCTLR 的复位值和可访问性不同。下表总结了 EL1/EL2/EL3 在典型启动场景下的配置差异:

异常级别 典型启动场景 SCTLR 复位值(关键位) 启动时需修改的位 注意事项
EL3 Secure Monitor / ATF M=0, C=0, I=0, EE=硬件决定 设置 I=1(可选),配置 EE,最后置 M=1 EL3 页表通常使用 4KB 粒度,TCR_EL3 需单独配置
EL2 Hypervisor (KVM / Xen) M=0, C=0, I=0, EE=0 设置 I=1,配置 WXN=1,置 M=1 EL2 页表用于 stage-2 翻译,需与 EL1 页表协同
EL1 Linux Kernel / RTOS M=0, C=0, I=0, EE=0 设置 I=1,C=1(MMU 开启后),WXN=1,置 M=1 EL1 使用 TTBR0_EL1 和 TTBR1_EL1 双页表基址

4.4 SCTLR 配置代码示例(ARM64 汇编)

以下代码展示了在 EL1 下安全配置 SCTLR_EL1 的典型步骤:

/* 假设页表已建立,TTBR0_EL1 和 MAIR_EL1 已配置 */
/* 步骤 1: 读取当前 SCTLR_EL1 */
mrs     x0, sctlr_el1

/* 步骤 2: 清除 M、C、A 位,确保 MMU 和缓存关闭 */
bic     x0, x0, #(1 << 0)   /* 清除 M 位 */
bic     x0, x0, #(1 << 2)   /* 清除 C 位 */
bic     x0, x0, #(1 << 1)   /* 清除 A 位 */

/* 步骤 3: 设置 I 位和 WXN 位 */
orr     x0, x0, #(1 << 12)  /* 设置 I 位:开启指令缓存 */
orr     x0, x0, #(1 << 19)  /* 设置 WXN 位:写权限不可执行 */

/* 步骤 4: 写入 SCTLR_EL1 */
msr     sctlr_el1, x0
isb                                 /* 指令同步屏障,确保配置生效 */

/* 步骤 5: 启用 MMU(置位 M 位) */
orr     x0, x0, #(1 << 0)   /* 设置 M 位 */
msr     sctlr_el1, x0
isb                                 /* 关键:ISB 保证后续指令在 MMU 开启后执行 */

/* 此时 MMU 已启用,PC 开始使用虚拟地址 */
关键点: 上述代码中,M 位和 C 位是分两步设置的。这是因为在 MMU 关闭时,数据缓存必须禁用;而在 MMU 开启后,可以立即启用数据缓存(C 位)。实际启动中,通常会在 MMU 开启后、进入 C 代码前,再单独设置 C 位。

4.5 SCTLR 配置流程图

下图展示了从复位到 MMU 启用过程中,SCTLR 关键位的状态变化流程:

SCTLR 关键位状态变化流程(复位 → MMU 启用) 复位状态 M=0, C=0, I=0 关闭数据缓存与对齐检查 确保 C=0, A=0 配置页表基址与属性 TTBR0/MAIR/TCR 设置 I=1, WXN=1 开启指令缓存与写保护 置位 M=1 执行 ISB 同步 启动顺序说明 1. 复位后所有缓存和 MMU 关闭 2. 页表建立前保持 C=0 3. 最后一步才置位 M 位 4. 每次修改后执行 ISB

4.6 启动阶段 SCTLR 配置检查清单

在编写启动代码时,建议按照以下清单逐一确认 SCTLR 的状态:

  1. 复位后立即检查: 确认 M=0, C=0, I=0(或由 Boot ROM 设置),A=0。
  2. 页表建立前: 执行数据缓存清理(DC CISW),确保缓存中无脏数据。
  3. 页表建立后: 配置 TTBR、MAIR、TCR,但此时仍保持 M=0。
  4. MMU 启用前: 设置 I=1(可选),WXN=1(推荐),EE 根据平台需求设置。
  5. MMU 启用时: 先写 SCTLR 清除 M 位(确保状态),再写 SCTLR 置位 M 位,中间插入 ISB。
  6. MMU 启用后: 立即设置 C=1(数据缓存使能),并再次执行 ISB。
  7. 安全检查: 读取 SCTLR 回读验证 M 位和 C 位是否已置位。
调试提示: 如果 MMU 启用后发生异常(如同步外部中止),首先检查 SCTLR 的 M 位是否被意外清除,或者页表所在内存区域的缓存策略是否配置正确。使用 JTAG 读取 SCTLR 和 FAR_EL1(错误地址寄存器)是定位问题的第一步。