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 |
2.2 el2_setup 详解:从EL2降级到EL1
当CPU在EL2启动时,el2_setup负责执行以下关键步骤,这些步骤直接影响后续MMU在EL1下的行为:
- 配置HCR_EL2(Hypervisor Configuration Register):设置
RW位(bit[31])为1,使EL1为AArch64状态;清除VM位(bit[0])以禁用EL2下的阶段2地址翻译,确保MMU在EL1下从零开始构建页表。 - 配置SCTLR_EL2(System Control Register):清除
M(MMU使能)、A(对齐检查)、C(数据缓存)等位,确保在切换前EL2的MMU和缓存处于关闭状态。 - 设置虚拟化计时器:通过
CNTHCTL_EL2允许EL1访问物理计时器。 - 配置异常向量基址:设置
VBAR_EL2指向EL2的异常向量表(通常为空或最小实现)。 - 执行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的常见原因之一。
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将跳转到随机地址,导致系统完全失控。
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(非零且对齐)。这个检查步骤虽然增加了几十条指令,但能避免数小时的调试痛苦。