2. head.S入口分析:el2_setup与el1_setup的初始化,异常等级切换对MMU的影响

在ARM64 Linux内核启动的早期阶段,head.S是第一条被执行的汇编代码。当引导加载程序(如UEFI或U-Boot)将控制权交给内核时,CPU可能处于EL2(非安全态、虚拟化扩展启用)或EL1(非安全态、无虚拟化)等异常等级。head.S的首要任务之一就是通过el2_setup和el1_setup两个关键例程,将CPU统一初始化到EL1,并为后续的页表构建和MMU使能铺平道路。

核心要点:异常等级切换不仅仅是改变一个寄存器值,它直接决定了页表基址寄存器(TTBR0_EL1/TTBR1_EL1)的可用性、MMU控制寄存器(SCTLR_EL1)的访问权限,以及内存属性(MAIR_EL1)的配置方式。错误的等级切换会导致MMU使能后立即触发异常。

2.1 入口点与异常等级判定

内核入口点_text处的第一条指令通常是mrs x0, CurrentEL,用于读取当前异常等级。根据CurrentEL的值,代码分支进入不同的初始化路径。

CurrentEL[3:2] 值 当前异常等级 执行路径 典型场景
0b10 (0x8) EL2 el2_setup KVM/虚拟化启动,或UEFI在EL2下启动内核
0b01 (0x4) EL1 el1_setup 引导加载程序已降级到EL1,或裸机启动
0b00 (0x0) EL0 非法状态 内核不可能在EL0启动,触发panic
head.S 入口异常等级判定流程 _text 入口 CurrentEL [3:2] 判定 0b10 (EL2) el2_setup 0b01 (EL1) el1_setup 统一进入 EL1 初始化

2.2 el2_setup 详解:从EL2降级到EL1

当CPU在EL2启动时,el2_setup负责执行以下关键步骤,这些步骤直接影响后续MMU在EL1下的行为:

  1. 配置HCR_EL2(Hypervisor Configuration Register):设置RW位(bit[31])为1,使EL1为AArch64状态;清除VM位(bit[0])以禁用EL2下的阶段2地址翻译,确保MMU在EL1下从零开始构建页表。
  2. 配置SCTLR_EL2(System Control Register):清除M(MMU使能)、A(对齐检查)、C(数据缓存)等位,确保在切换前EL2的MMU和缓存处于关闭状态。
  3. 设置虚拟化计时器:通过CNTHCTL_EL2允许EL1访问物理计时器。
  4. 配置异常向量基址:设置VBAR_EL2指向EL2的异常向量表(通常为空或最小实现)。
  5. 执行ERET降级:通过mov x0, #(SPSR_EL1 | SPSR_EL2)设置SPSR_EL2,然后执行eret指令,将CPU切换到EL1。
常见陷阱:在el2_setup中,如果忘记清除HCR_EL2.VM位,EL2的阶段2翻译会残留。当切换到EL1并使能MMU后,CPU会尝试进行两阶段地址翻译,导致页表遍历失败,触发Translation Fault。这是MMU使能后立即panic的常见原因之一。
el2_setup 关键寄存器配置与降级流程 HCR_EL2.RW = 1 HCR_EL2.VM = 0 SCTLR_EL2.M = 0 SCTLR_EL2.C/A = 0 VBAR_EL2 异常向量 SPSR_EL2 = SPSR_EL1 (M[3:0] = 0b0101) ERET CPU 切换到 EL1 ⚠ 关键:ERET 使用 SPSR_EL2 恢复 PSTATE,将异常等级从 EL2 降为 EL1 同时 PC 跳转到 el1_setup 或 __primary_switch 继续执行

2.3 el1_setup 详解:EL1下的基础初始化

当CPU已经在EL1,或从el2_setup通过eret降级到EL1后,el1_setup负责配置EL1级别的系统寄存器。这些配置是MMU使能的直接前置条件:

寄存器 关键配置位 对MMU的影响
SCTLR_EL1 M=0, C=0, I=0, SA=0 确保MMU、数据缓存、指令缓存、栈对齐检查在初始化期间全部关闭
CPACR_EL1 FPEN[21:20]=0b11 启用浮点和SIMD访问,避免后续内核代码使用浮点指令时触发UND异常
MAIR_EL1 设置内存属性索引(如0x44EE等) 定义页表中使用的内存类型(Normal、Device等),页表项中的AttrIndx指向此处
TCR_EL1 TG0/TG1, SH0/SH1, ORGN0/IRGN0, T0SZ/T1SZ 配置翻译粒度(4K/16K/64K)、地址空间大小、缓存策略和共享属性
VBAR_EL1 指向异常向量表基址 MMU使能后,任何异常(包括页故障)都需要通过VBAR_EL1找到处理程序
调试技巧:在el1_setup之后、MMU使能之前,建议插入一段校验代码,读取TCR_EL1和MAIR_EL1的值并与预期值比对。如果这些寄存器配置错误,MMU使能后第一次页表遍历就会失败,且故障症状(如立即跳转到异常向量)很难定位根因。

2.4 异常等级切换对MMU的直接影响

异常等级切换不是一次性的配置,它从根本上改变了MMU的行为模型。以下是三个最关键的直接影响:

  • 页表基址寄存器的切换:在EL2下,页表基址由TTBR0_EL2和TTBR1_EL2控制;切换到EL1后,必须使用TTBR0_EL1和TTBR1_EL1。如果el2_setup没有正确清理EL2的页表配置,切换到EL1后,TTBR0_EL1可能指向无效地址。
  • 翻译机制的变化:EL2支持阶段2翻译(虚拟化扩展),而EL1只进行阶段1翻译。如果HCR_EL2.VM位在降级前未被清除,EL1的MMU使能后,CPU会错误地尝试进行两阶段翻译,导致Stage-2 Translation Fault。
  • 异常向量与故障处理:MMU使能后,任何页表遍历失败都会触发Translation Fault或Access Flag Fault。这些异常在EL1下通过VBAR_EL1路由到异常向量表。如果VBAR_EL1在MMU使能前未正确设置,CPU将跳转到随机地址,导致系统完全失控。
异常等级切换对MMU行为的影响对比 EL2 阶段(切换前) • TTBR0_EL2 / TTBR1_EL2 • 支持 Stage-2 翻译 • SCTLR_EL2 控制 MMU • VBAR_EL2 处理异常 ERET EL1 阶段(切换后) • TTBR0_EL1 / TTBR1_EL1 • 仅 Stage-1 翻译 • SCTLR_EL1 控制 MMU • VBAR_EL1 处理异常 切换后对MMU使能的三大直接影响 1. 页表基址失效风险:TTBR0_EL1 必须重新指向有效的页表物理地址,否则 MMU 使能即 fault。 2. 翻译阶段错配:若 HCR_EL2.VM 残留,EL1 的 MMU 使能后会尝试两阶段翻译,触发 Stage-2 fault。 3. 异常向量黑洞:VBAR_EL1 未配置时,MMU 使能后的第一个页故障会导致 CPU 跳转到未知地址。 4. 缓存与MMU状态不一致:SCTLR_EL1 的 M/C/I 位必须在使能 MMU 前全部清零,避免脏数据影响页表遍历。

2.5 常见故障模式与排查清单

在el2_setup和el1_setup阶段,以下故障模式最为常见,且直接影响后续MMU使能的成败:

故障现象 可能原因 排查方法
MMU使能后立即进入异常 TTBR0_EL1指向无效物理地址;或页表未对齐(需页大小对齐) 在MMU使能前打印TTBR0_EL1的值,确认地址在有效DRAM范围内且低12位为0
Stage-2 Translation Fault HCR_EL2.VM位在降级前未被清除 在el2_setup末尾读取HCR_EL2,确认VM位为0
异常向量未命中 VBAR_EL1未设置或指向未映射的内存 确认VBAR_EL1指向已建立恒等映射的地址,且页表属性可读
MMU使能后系统挂死 TCR_EL1的T0SZ配置错误,导致地址空间范围与页表不匹配 核对TCR_EL1.T0SZ与页表覆盖的虚拟地址范围是否一致
数据访问导致同步异常 MAIR_EL1中内存类型配置为Device-nGnRE,但代码访问了非对齐地址 检查MAIR_EL1的Attr0~Attr7配置,确保Normal内存使用正确的缓存策略
最佳实践:在el1_setup的末尾、MMU使能之前,建议执行一次完整的寄存器快照检查。至少验证以下寄存器:SCTLR_EL1(M位=0)、TCR_EL1(T0SZ与物理地址空间匹配)、MAIR_EL1(属性值合理)、VBAR_EL1(非零且对齐)。这个检查步骤虽然增加了几十条指令,但能避免数小时的调试痛苦。