2. 硬件初始化基础:ARM64处理器模式(EL0-EL3)、异常级别与启动权限、复位向量表(Reset Vector)解析
ARMv8-A 架构引入的异常级别(Exception Levels)是理解 Linux 内核启动流程的基石。与传统的 ARM32 处理器模式不同,ARM64 通过四个特权等级(EL0-EL3)实现了更严格的隔离和更清晰的启动权限划分。本节将深入剖析这些级别的定义、转换规则以及复位后的硬件行为。
2.1 ARM64 异常级别(EL0-EL3)概述
ARM64 定义了四个异常级别,数字越大,特权越高。每个级别拥有独立的虚拟内存配置(TTBR0_ELx/TTBR1_ELx)、堆栈指针(SP_ELx)以及异常向量表(VBAR_ELx)。
| 异常级别 | 名称 | 典型用途 | 执行权限 |
|---|---|---|---|
| EL0 | 用户态(User) | 应用程序(App) | 最低,无法访问系统寄存器 |
| EL1 | 内核态(Kernel) | 操作系统内核(Linux Kernel) | 可配置 MMU、中断、系统调用 |
| EL2 | 虚拟机监视器(Hypervisor) | KVM、Xen、Type-1 Hypervisor | 虚拟化扩展,控制 VM 切换 |
| EL3 | 安全监视器(Secure Monitor) | ARM Trusted Firmware (ATF)、Secure Boot | 最高,控制安全世界切换 |
2.2 异常级别与启动权限
复位(Reset)后,ARM64 处理器默认进入最高的异常级别——EL3。这是启动流程的起点,也是硬件初始化权限最高的阶段。不同异常级别下,可访问的寄存器集合和系统控制能力存在显著差异。
- EL3 权限: 可以配置 SCR_EL3(安全配置寄存器),决定是否启用 EL2、EL1 的 AArch32 支持,以及设置安全世界状态。这是 TrustZone 技术的核心。
- EL2 权限: 可以配置 HCR_EL2(虚拟化配置寄存器),控制虚拟内存系统架构(VMSA)的虚拟化行为,并决定是否将某些异常路由到 EL2。
- EL1 权限: 可以配置 SCTLR_EL1(系统控制寄存器),启用 MMU、缓存、对齐检查等。Linux 内核主要工作在此级别。
- EL0 权限: 无法访问任何系统控制寄存器,只能通过 SVC(Supervisor Call)指令陷入 EL1 请求内核服务。
EL3 (ATF/BL1) → EL2 (Hypervisor/BL2) → EL1 (Linux Kernel) → EL0 (User App)每次降级通过
ERET 指令实现,该指令会从当前异常级别的保存状态寄存器(SPSR_ELx)中恢复目标异常级别。
2.3 复位向量表(Reset Vector)解析
ARM64 的异常向量表(Vector Table)是处理所有异常(包括复位、中断、同步异常)的入口点。每个异常级别都有自己独立的向量表,基地址由 VBAR_ELx 寄存器指定。
复位向量是向量表中的第一个条目,它定义了处理器上电或复位后执行的第一条指令的地址。在 ARM64 中,复位向量的地址由硬件实现定义,通常位于 ROM 或固定的物理地址(如 0x00000000 或 0xFFFF0000)。
/* ARM64 异常向量表结构(每个条目 128 字节) */
.align 11 /* 2^11 = 2048 字节对齐 */
.global vectors
vectors:
/* 当前异常级别,使用 SP_EL0 */
.align 7 /* 2^7 = 128 字节对齐 */
.ventry el1_sync_invalid // 同步异常 (Synchronous)
.ventry el1_irq_invalid // IRQ 中断
.ventry el1_fiq_invalid // FIQ 中断
.ventry el1_error_invalid // SError 错误
/* 当前异常级别,使用 SP_ELx */
.align 7
.ventry el1_sync // 同步异常 (如系统调用)
.ventry el1_irq // IRQ 中断
.ventry el1_fiq // FIQ 中断
.ventry el1_error // SError 错误
/* 低异常级别(AArch64),使用 SP_EL0 */
.align 7
.ventry el0_sync_64 // 来自 EL0 的同步异常
.ventry el0_irq_64 // 来自 EL0 的 IRQ
.ventry el0_fiq_64 // 来自 EL0 的 FIQ
.ventry el0_error_64 // 来自 EL0 的 SError
/* 低异常级别(AArch32),使用 SP_EL0 */
.align 7
.ventry el0_sync_32 // 来自 AArch32 EL0 的同步异常
.ventry el0_irq_32 // 来自 AArch32 EL0 的 IRQ
.ventry el0_fiq_32 // 来自 AArch32 EL0 的 FIQ
.ventry el0_error_32 // 来自 AArch32 EL0 的 SError
.align 11),且每个条目必须 128 字节对齐(.align 7)。如果对齐错误,处理器在发生异常时会跳转到错误地址,导致不可预测的行为。在启动汇编代码中,务必在定义 vectors 标签前使用 .align 11。
2.4 复位后的硬件状态与启动流程
当 ARM64 处理器从复位中唤醒时,硬件处于一个确定但极简的状态。理解这个初始状态对于编写启动代码至关重要。
- 异常级别: EL3(最高特权)。
- MMU: 关闭(指令和数据缓存可能处于未知状态,通常需要 invalidate)。
- 堆栈指针: SP_EL3 的值由硬件复位值决定,通常指向一个安全 RAM 区域。
- 向量表: VBAR_EL3 指向复位向量地址。
- 通用寄存器: 大部分寄存器(X0-X30)的值是未知的,除了 X0 可能包含一些平台特定的信息(如 DTB 地址)。
下图展示了从复位到 Linux 内核入口的典型权限切换流程:
2.5 启动权限的硬件约束
ARM64 架构对异常级别的切换施加了严格的硬件约束,这些约束直接影响了启动代码的设计:
| 操作 | 硬件约束 | 影响 |
|---|---|---|
| 权限提升 | 只能通过异常(Exception)实现 | 用户态无法直接切换到内核态,必须通过 SVC 指令触发同步异常 |
| 权限降级 | 只能通过 ERET 指令实现 |
内核返回用户态时,必须从 SPSR_EL1 恢复目标级别 |
| EL3 不可绕过 | 复位后强制进入 EL3 | 所有启动流程必须经过 EL3 固件(如 ATF)的初始化 |
| 向量表独立 | 每个 EL 拥有独立的 VBAR_ELx | 不同异常级别的异常处理程序完全隔离 |
1. 配置当前级别的系统寄存器(如 SCTLR_EL3、SCR_EL3)。
2. 设置目标异常级别的堆栈指针(SP_ELx)。
3. 将目标异常级别的入口地址写入 ELR_ELx(异常链接寄存器)。
4. 将目标异常级别和状态写入 SPSR_ELx。
5. 执行
ERET 指令完成切换。
2.6 复位向量表与启动加载器交互
在实际的嵌入式系统中,复位向量表通常位于 SoC 内部 ROM 中。上电后,ROM 代码会执行基本的硬件初始化(如时钟、DDR),然后跳转到下一个启动介质(如 eMMC、NAND Flash)中的启动加载器。
以典型的 ARM64 开发板(如 Raspberry Pi 4 或 QEMU virt 平台)为例,复位后的执行流如下:
- ROM 阶段: 处理器从复位向量地址(如
0x00000000)开始执行。ROM 代码初始化关键外设,并将完整的启动加载器(如 U-Boot SPL)加载到 SRAM 中。 - SPL 阶段: 运行在 EL3 的 SPL 代码初始化 DDR 控制器,然后将主启动加载器(U-Boot proper)从存储介质复制到 DDR 中。
- U-Boot 阶段: U-Boot 通常运行在 EL2 或 EL1。它加载设备树(DTB)和 Linux 内核镜像到内存,然后通过
booti命令跳转到内核入口点。 - 内核阶段: Linux 内核在 EL1 启动,完成架构初始化、内存管理、调度器初始化等,最终启动 init 进程。
/* 典型的 U-Boot 跳转到内核的汇编片段 (arch/arm/lib/board.c) */
void boot_jump_linux(bootm_headers_t *images, int flag)
{
unsigned long machid = images->os.arch;
unsigned long fdt_addr = images->ft_addr;
unsigned long kernel_entry = images->ep;
/* 清理缓存并关闭 MMU */
cleanup_before_linux();
/* 设置内核启动参数 */
printf("## Starting kernel at 0x%lx ...\n", kernel_entry);
/* 跳转到内核入口点 (EL1) */
kernel_entry(kernel_entry, machid, fdt_addr);
}
flush_dcache_all 和 invalidate_icache_all 来保证一致性。