1. ARM64 启动流程概述:从 Reset 到 C 语言环境的建立

ARM64 架构的启动过程是一个从硬件复位到高级语言环境建立的精密链条。在系统上电或复位后,CPU 从 EL3(异常级别 3)的复位向量开始执行,经过一系列初始化步骤,最终建立起可供 C 语言运行的栈、BSS 段清零、数据段重定位等环境。本章节聚焦于这一流程的整体脉络,并明确 head.S 在启动链中的核心位置与职责。

1.1 启动流程全景图

下图展示了从 Reset 向量到 C 语言入口 start_kernel 的完整启动阶段。每个阶段都有明确的硬件状态要求和软件职责。

关键概念: ARM64 启动必须经历“异常级别降级”过程——从最高权限的 EL3 逐步下降到目标 OS 运行的 EL1(内核态)或 EL0(用户态)。head.S 通常运行在 EL2(虚拟化扩展)或 EL1,负责最底层的硬件初始化。
ARM64 启动流程全景图 阶段 1: Reset EL3 复位向量 ROM Bootloader 阶段 2: Bootloader ATF / U-Boot SPL DDR 初始化 阶段 3: head.S 异常向量表配置 页表 / MMU 初始化 阶段 4: C 环境 BSS 清零 start_kernel() 异常级别变化:EL3 → EL2(ATF) → EL1(内核) Reset 阶段子步骤 1. 硬件复位 2. 读取复位向量 3. 最小硬件配置 Bootloader 子步骤 1. DDR 初始化 2. 时钟 / PLL 配置 3. 加载内核镜像 head.S 子步骤 1. 异常向量表安装 2. 页表 / MMU 使能 3. 栈指针 (SP) 设置 C 环境子步骤 1. BSS 段清零 2. 数据段重定位 3. 跳转 start_kernel head.S 在启动链中的关键作用 • 异常向量表基地址寄存器 (VBAR_ELx) 配置:head.S 负责将异常向量表 的物理地址写入 VBAR_EL1/EL2,确保内核能正确处理中断和异常。 • 页表初始化:在开启 MMU 前,head.S 建立恒等映射(identity mapping) 和内核虚拟地址映射,保证 CPU 在开启 MMU 后不丢失指令流。 • 栈指针 (SP) 设置:为 C 语言函数调用提供合法的栈空间。 • 异常级别切换:通过 eret 指令从 EL2 降级到 EL1,进入内核主入口。

1.2 启动阶段详细分解

ARM64 启动流程可以拆解为四个主要阶段,每个阶段都有明确的输入、输出和硬件依赖。下表总结了各阶段的关键特征:

阶段 异常级别 主要任务 关键寄存器/硬件 输出状态
Reset EL3 硬件复位、读取复位向量、最小系统配置 RVBAR_EL3、SCR_EL3 CPU 处于最高特权级,Cache 可能关闭
Bootloader EL3 / EL2 DDR 初始化、时钟配置、加载内核镜像到内存 DDR 控制器、GIC、UART 内存可用,内核镜像已加载到物理地址
head.S EL2 / EL1 异常向量表配置、页表建立、MMU 使能、栈设置 VBAR_ELx、TTBR0_ELx、SCTLR_ELx MMU 开启,虚拟地址空间就绪,栈可用
C 环境 EL1 BSS 清零、数据段重定位、调用 start_kernel SP、BSS 起始/结束符号 纯 C 语言环境,可执行复杂逻辑

1.3 head.S 在启动链中的位置与作用

head.S 是 ARM64 Linux 内核启动的第一个汇编文件,通常位于 arch/arm64/kernel/head.S。它在启动链中扮演着“硬件抽象层”的角色——将 Bootloader 传递的原始硬件状态,转换为 C 语言环境所需的标准化状态。

调试视角: 在 head.S 阶段,由于 MMU 可能尚未开启或处于过渡状态,所有地址访问必须使用物理地址。调试器(如 JTAG)在此阶段可以直接观察物理内存和系统寄存器,是排查启动早期崩溃的关键窗口。

1.3.1 head.S 的核心职责清单

  • 异常向量表安装: 将异常向量表的物理地址写入 VBAR_EL2 或 VBAR_EL1,确保中断、同步异常、SError 等能正确路由。
  • 页表初始化: 建立恒等映射(identity mapping)和内核虚拟地址映射,配置 TTBR0_ELx 和 TTBR1_ELx。
  • MMU 使能: 设置 SCTLR_ELx.M 位,开启 MMU,同时配置 Cache 和分支预测。
  • 栈指针设置: 为每个 CPU 核初始化独立的栈空间,写入 SP_ELx。
  • 异常级别切换: 通过 eret 指令从 EL2 降级到 EL1,或从 EL1 进入内核主入口。
  • CPU 特性检测: 读取 MIDR_EL1、ID_AA64MMFR0_EL1 等寄存器,识别 CPU 型号和特性。

1.3.2 head.S 与前后阶段的接口

head.S 与 Bootloader 和 C 语言环境之间通过约定的寄存器传递参数。下表列出了标准的参数传递约定:

寄存器 方向 含义 典型值
x0 Bootloader → head.S 设备树 (DTB) 的物理地址 0x4000_0000
x1 Bootloader → head.S 内核镜像的物理地址 0x8008_0000
x2 Bootloader → head.S CPU 编号(主核为 0) 0x0
x3 Bootloader → head.S FDT 或 ACPI 表地址 0x4000_0000
x21 head.S → C 环境 CPU 编号(经过验证) 0x0
x22 head.S → C 环境 设备树地址(经过验证) 0x4000_0000

1.4 典型启动代码片段分析

以下是从 head.S 中提取的关键代码片段,展示了异常向量表配置和栈初始化的典型实现:

/*
 * 异常向量表基地址配置 (VBAR_EL2)
 * 将异常向量表的物理地址写入 VBAR_EL2
 */
__setup_vector:
    adrp    x0, vectors              // 获取 vectors 标签的页基地址
    add     x0, x0, #:lo12:vectors   // 加上页内偏移,得到完整物理地址
    msr     vbar_el2, x0             // 写入 VBAR_EL2
    isb                              // 指令同步屏障,确保生效

/*
 * 栈指针初始化 (SP_EL2)
 * 为当前 CPU 分配独立的栈空间
 */
__setup_stack:
    mrs     x0, mpidr_el1            // 读取多核处理器 ID
    and     x0, x0, #0xff            // 取低 8 位作为 CPU 编号
    ldr     x1, =_stack_end          // 栈结束地址(高地址)
    mov     x2, #0x1000              // 每个 CPU 栈大小 4KB
    mul     x3, x0, x2               // CPU 编号 × 栈大小
    sub     x4, x1, x3               // 栈顶 = 栈结束 - 偏移
    mov     sp, x4                   // 设置 SP_EL2
    msr     sp_el1, x4               // 同时设置 SP_EL1(后续降级使用)
常见陷阱: 在 head.S 阶段,如果异常向量表尚未配置就发生了中断或同步异常(如未对齐访问),CPU 将陷入 “无向量异常” 状态,表现为 CPU 挂起或跳转到未知地址。调试时,应优先检查 VBAR_ELx 是否已正确写入,以及向量表首条指令是否为有效的跳转或处理逻辑。

1.5 启动流程中的关键检查点

在调试启动早期问题时,以下检查点可以帮助快速定位故障阶段:

  1. Reset 后第一条指令: 检查 CPU 是否从预期的物理地址开始执行(通常由硬件复位向量决定)。
  2. 异常向量表基地址: 在 head.S 执行 msr vbar_elx, x0 后,通过 JTAG 读取 VBAR_ELx 确认地址正确。
  3. MMU 使能前后: 在 SCTLR_ELx.M 置位前后,观察指令地址是否从物理地址切换到虚拟地址。
  4. 栈指针有效性: 在跳转到 C 函数前,确认 SP 指向可读写的内存区域,且对齐到 16 字节。
  5. 异常级别切换: 执行 eret 后,检查 CurrentEL 寄存器确认是否已降级到目标异常级别。
head.S 内部执行流程图 head.S 入口 1. 保存 Bootloader 参数 2. 配置异常向量表 3. 建立页表 / 开启 MMU 4. 设置栈指针 (SP) 5. 跳转 start_kernel 关键寄存器变化 x0-x3: 保存 DTB/镜像地址 x21-x22: 传递到 C 环境 VBAR_EL2: 写入向量表基址 SCTLR_EL2: 使能 MMU/Cache TTBR0_EL2: 页表基地址 MAIR_EL2: 内存属性配置 SP_EL2: 栈指针初始化 SP_EL1: 预置降级后栈 CurrentEL: 从 EL2 → EL1 ELR_EL2: 保存返回地址 SPSR_EL2: 保存目标状态 PC: 跳转到 start_kernel SP: 指向有效栈空间

1.6 总结:head.S 的不可替代性

在 ARM64 启动链中,head.S 是唯一一个同时操作系统寄存器、页表、异常向量和栈的组件。它承担了从“硬件裸机状态”到“C 语言运行时”的桥梁作用。任何启动早期的崩溃、异常或挂起,都应首先检查 head.S 的执行路径是否正确,尤其是异常向量表配置和 MMU 使能这两个高风险步骤。

调试黄金法则: 在 head.S 阶段,优先使用物理地址调试。如果 MMU 已开启但配置错误,CPU 可能立即陷入缺页异常,而此时异常向量表可能尚未正确安装,导致“双重故障”死锁。建议在 MMU 使能前设置硬件断点,单步验证页表条目是否正确。