news 2026/1/31 6:54:50

aarch64启动流程深度剖析:从上电到内核入口的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
aarch64启动流程深度剖析:从上电到内核入口的完整指南

从零开始:aarch64启动流程全解析——如何让一颗ARM CPU“活”起来

你有没有想过,当你按下电源键的那一刻,一块冰冷的硅片是如何一步步“苏醒”,最终跑起Linux系统的?尤其是在如今无处不在的aarch64架构设备上——从树莓派到服务器、从边缘网关到超算节点——理解这个过程,不只是好奇心的满足,更是系统级开发者的生存技能

今天我们就来一次“逆向手术”,深入aarch64芯片内部,追踪从上电复位内核入口_start的完整执行轨迹。这不是简单的流程图罗列,而是一场结合硬件行为、寄存器操作和固件逻辑的真实旅程。


上电第一跳:CPU从哪里开始执行?

一切始于一个物理地址。当电源稳定后,CPU核心会从预定义的复位向量(Reset Vector)取指执行。在绝大多数aarch64 SoC中,这个地址是:

0x0000_0000 或 0xFFFF_0000

具体选择哪一端取决于芯片设计中的VBAR(Vector Base Address Register)初始映射策略,但无论哪种,它都指向一片只读区域 ——Boot ROM

这块Boot ROM由SoC厂商固化在芯片内部,不可修改。你可以把它看作是CPU的“出厂引导程序”。它的任务非常明确:

  • 初始化最基本的时钟源;
  • 配置电源管理单元(PMIC);
  • 探测可用的启动介质(eMMC、SD卡、SPI Flash、USB等);
  • 从中加载下一阶段代码(通常是SPL或BL2);
  • 校验其完整性与合法性(用于安全启动);
  • 最终将控制权交给这段加载进内存的代码。

比如,在NXP i.MX8M系列中,Boot ROM会尝试从多种外设读取SPL,并将其复制到片上SRAM(OCRAM),然后跳转执行。

此时,CPU运行在EL3—— aarch64权限最高的异常级别。这是冷启动唯一能执行代码的地方。所有后续的安全世界切换、虚拟化支持、可信执行环境,都要从这里出发。


权限之塔:EL0~EL3到底意味着什么?

aarch64引入了四个异常级别(Exception Levels, EL),形成了一座自上而下的权限金字塔:

EL名称典型角色
EL3安全监控器安全世界与非安全世界的调度中枢
EL2虚拟机监控器KVM、Xen等Hypervisor
EL1内核态Linux kernel、RTOS
EL0用户态应用程序

数字越大,权限越高。你可以把它想象成一栋四层楼的大厦:

  • 地下室(EL3)住着保安队长,掌管大门钥匙;
  • 一楼(EL2)是物业经理,负责分配房间给不同租户;
  • 二楼(EL1)是房东,管理自家房子;
  • 三楼(EL0)才是普通住户。

关键点在于:只能向下移交控制权,不能反向“爬楼”。除非通过特殊机制,比如触发异常或调用SMC(Secure Monitor Call)指令。

所以整个启动流程的本质,就是一场精心策划的“逐级放权”:

上电 → EL3 (Boot ROM / BL1) → EL3 (ATF BL2/BL31) → [可选] EL2 (Hypervisor) → EL1 (Linux Kernel) → EL0 (init 进程)

每一步都需要正确配置上下文、栈指针、异常向量表和状态寄存器,否则轻则挂起,重则死机无声。


ATF登场:可信固件如何完成“权力交接”

说到aarch64启动,绕不开的就是Arm Trusted Firmware(ATF)。它是ARM官方维护的一套开源参考实现,专门用来处理从EL3到EL1的安全过渡。

ATF的核心组件分工

ATF采用分阶段加载模型,各阶段职责清晰:

阶段名称异常级别功能说明
BL1Primary Boot LoaderEL3运行于SRAM,初始化基本环境,加载BL2
BL2Secondary Boot LoaderEL3加载BL31(Monitor)、BL32(Secure OS)、BL33(Non-secure BL)
BL31Secure MonitorEL3→EL1处理SMC调用,实现安全切换
BL32Secure World OSEL3如OP-TEE,提供TEE服务
BL33Non-secure PayloadEL1通常是U-Boot或直接Kernel

这种模块化设计使得系统具备良好的扩展性和安全性。

如何从EL3跳转到EL1?

核心指令只有两个:write_elr_el3()+eret

void __dead2 el3_exit_to_el1( uint64_t entry_point, uint64_t spsr_val, uint64_t context_id ) { // 设置返回后的程序计数器 write_elr_el3(entry_point); // 设置恢复后的处理器状态(PSTATE) write_spsr_el3(spsr_val); // 执行ERET:异常返回,跳转至目标EL eret(); }

这就像交出遥控器前,先设定好频道和音量。

其中:
-ELR_EL3(Exception Link Register)保存目标入口地址;
-SPSR_EL3(Saved Program Status Register)保存目标EL的运行模式、中断屏蔽状态等;

举个典型场景:你想让系统以AArch64模式、使用SP_EL1栈、关闭所有中断进入EL1,那就要这样设置SPSR:

#define MODE_EL1T (0b0101 << 0) // AArch64, EL1 #define MODE_SP_ELX (0b01 << 2) // 使用SP_EL1 #define DISABLE_ALL_EXCEPTIONS (DAIF_BIT) SET_SPSR(spsr, MODE_EL1T, MODE_SP_ELX, DISABLE_ALL_EXCEPTIONS);

然后调用eret(),CPU就会自动降级到EL1并开始执行指定地址的代码。

⚠️ 注意:如果没提前设置好SP_EL1,一旦在EL1发生异常,由于没有有效的栈,系统会立即崩溃。


内存觉醒:页表构建与MMU开启

在启用虚拟内存之前,必须建立页表(Page Tables)并正确配置相关系统寄存器。

aarch64默认使用四级页表结构(4KB粒度),支持高达48位的虚拟地址空间。每一级页表项8字节,包含物理地址、属性标志(如NX、AP、SH)、类型标识等。

启动初期的两种映射策略

  1. 恒等映射(Identity Mapping)
    物理地址 = 虚拟地址,便于早期访问设备寄存器和全局变量。

  2. 内核映射(Kernel Mapping)
    将内核镜像加载到高地址(如PAGE_OFFSET + 0x80000),避免与低区冲突。

常用的系统寄存器包括:

寄存器作用说明
TTBR0_EL1用户空间页表基址
TTBR1_EL1内核空间页表基址
TCR_EL1控制页表粒度、地址范围、共享性等
MAIR_EL1定义内存类型(Normal、Device、Strongly Ordered等)
示例:配置TCR以启用4KB页、48位VA
uint64_t tcr = TCR_T0SZ(16) // 64GB用户空间(48-bit) | TCR_IRGN0_WBWA // Inner: WriteBack + WriteAllocate | TCR_ORGN0_WBWA // Outer: 同上 | TCR_SH0_INNER // Inner Shareable | TCR_TG0_4K // Granule Size: 4KB | TCR_PS_4GB; // 物理地址最大4GB write_tcr_el1(tcr);
构建一级页表项(1GB Block Descriptor)
#define PGD_ADDR_MASK (~((1UL << 30) - 1)) // 保留高34位 #define PTE_BLOCK (1UL << 0) // 块描述符标志 #define PTE_TABLE_AF (1UL << 10) // 访问标志 #define PTE_SHARED (3UL << 8) // Inner Shareable #define PTE_AP_RW (1UL << 6) // 特权读写 uint64_t create_pgd_entry(uint64_t phys_addr) { return (phys_addr & PGD_ADDR_MASK) | PTE_BLOCK | PTE_TABLE_AF | PTE_SHARED | PTE_AP_RW; } // 映射多个1GB内存块 void map_memory_block(uint64_t virt, uint64_t phys, int nblocks) { uint64_t *pgd = (uint64_t *)PGD_BASE; for (int i = 0; i < nblocks; i++) { pgd[pgd_index(virt)] = create_pgd_entry(phys); virt += SZ_1G; phys += SZ_1G; } }

✅ 实战提示:开启MMU前务必插入内存屏障:

dsb sy; isb; // 确保数据同步 tlbi vmalle1is; // 清空TLB dc cvac, x0; // 清数据缓存

同时注意:设备内存应标记为 Device-nGnRE 类型,防止被缓存导致副作用。


异常捕手:向量表是怎么工作的?

aarch64通过异常向量表(Vector Table)来响应中断、缺页、未定义指令等事件。每个异常级别都有自己的向量基址寄存器VBAR_ELx

向量表总长2KB,包含16个入口,每个间隔128字节。布局如下(以VBAR_EL1为例):

Offset异常类型
0x000同步异常,当前EL
0x080IRQ,当前EL
0x100FIQ,当前EL
0x180SError,当前EL
0x200同步异常,较低EL,64位
0x280IRQ,较低EL,64位

这意味着,如果你在EL1运行时触发了一个系统调用(svc #0),CPU会跳转到VBAR_EL1 + 0x000处执行同步异常处理函数。

汇编级异常处理框架示例

.align 11 vector_table_el3: b el3_sync_handler b el3_irq_handler b el3_fiq_handler b el3_error_handler // ...其余12个槽位 el3_sync_handler: stp x0, x1, [sp, #-16]! mrs x0, esr_el3 ubfx x1, x0, #0, #6 // 提取异常类(EC) cmp x1, #0x17 b.eq handle_smc // 若为SMC调用,则进入安全监控 // 其他异常处理... ldp x0, x1, [sp], #16 eret

这里的ESR_EL3是关键诊断寄存器,记录了异常原因、访问大小、指令类型等信息,是调试早期故障的重要依据。

🔍 坑点提醒:多核系统中,每个CPU core需要独立的私有栈和向量表副本,否则会出现栈污染或异常处理错乱。


最终交接:U-Boot如何把接力棒传给Linux内核

到了最后一步,通常是由U-Boot完成对内核的加载与跳转。

典型的启动链条如下:

Boot ROM → SPL → U-Boot → Linux Kernel ↘ OP-TEE(可选)

U-Boot的任务清单

  1. 初始化DRAM控制器;
  2. 加载kernel镜像(Image/zImage/Image.gz)到物理内存(如0x80080000);
  3. 加载设备树(DTB)到安全位置;
  4. 设置启动参数(cmdline);
  5. 准备寄存器状态;
  6. 关闭中断;
  7. 跳转!

跳转前的关键寄存器约定

根据aarch64启动规范(如EFI或传统booti),必须满足以下条件:

寄存器说明
x00必须为0(ATAGS已废弃)
x10保留字段,不得使用
x2DTB物理地址设备树起点

例如:

// 在U-Boot中执行 set_cpu_pc(0x80080000); // kernel入口 register_set(0, 0); // x0 = 0 register_set(1, 0); // x1 = 0 register_set(2, dtb_phys_addr); // x2 = DTB地址 disable_interrupts(); // 关中断 br x0; // 跳!

常见失败场景与排查思路

现象可能原因解决方法
串口无输出MMU未关或跳转地址错误插入LED闪烁定位hang点
“Starting kernel…”后卡住DTB损坏或地址越界fdtdump检查DTB
内核panic无法挂rootfs缺少cmdline或分区错误检查bootargs环境变量
触发undefined instruction内核编译为AArch32确认kernel config启用CONFIG_ARM64

✅ 调试建议:在关键节点加入putc('A')这类简单输出,快速判断执行流走到哪一步。


更进一步:现代启动趋势与工程实践

随着系统复杂度上升,传统的U-Boot单体架构正逐渐被更灵活的方式替代:

  • UEFI on ARM:支持GPT分区、ACPI描述、安全启动链(PK → KEK → db),已在服务器和高端嵌入式平台普及;
  • 多重信任链:ATF + OP-TEE + Trusty 构成完整的TEE生态;
  • 实时需求驱动:对于工业控制、自动驾驶等场景,有人选择裸机+轻量调度器替代Linux;
  • RISC-V挑战者崛起:虽然起步较晚,但其开放性正在吸引大量开发者投入底层启动研究。

尽管如此,aarch64凭借成熟的工具链、标准化的启动框架(如Firmware Abstraction Layer)、广泛的社区支持,依然是高性能嵌入式和云计算领域的主流选择。


掌握这套从上电 → Boot ROM → ATF → U-Boot → Kernel的完整链条,意味着你不再只是“会改配置”的使用者,而是真正理解系统脉搏的掌控者

无论是移植OS到新型SoC、开发定制安全固件,还是解决“为什么板子通电就黑屏”的棘手问题,这些知识都会成为你最坚实的底气。

如果你在实际项目中遇到启动卡顿、异常陷阱或其他疑难杂症,欢迎留言讨论——我们一起拆解每一行汇编,还原真相。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/24 19:20:24

DBeaver数据库管理工具:从入门到精通的完整问题解决方案指南

DBeaver数据库管理工具&#xff1a;从入门到精通的完整问题解决方案指南 【免费下载链接】dbeaver DBeaver 是一个通用的数据库管理工具&#xff0c;支持跨平台使用。* 支持多种数据库类型&#xff0c;如 MySQL、PostgreSQL、MongoDB 等&#xff1b;提供 SQL 编辑、查询、调试等…

作者头像 李华
网站建设 2026/1/30 5:30:40

知识蒸馏流程:Teacher-Student模式实现

知识蒸馏流程&#xff1a;Teacher-Student模式实现 在大模型参数量动辄数十亿、上百亿的今天&#xff0c;部署一个像 Qwen-72B 或 LLaMA3-70B 这样的模型&#xff0c;往往需要多张 A100 显卡和复杂的分布式配置。然而&#xff0c;真实业务场景中更多面对的是边缘设备、移动端或…

作者头像 李华
网站建设 2026/1/30 1:16:27

3分钟解锁三星笔记:Windows电脑的智能伪装终极指南

3分钟解锁三星笔记&#xff1a;Windows电脑的智能伪装终极指南 【免费下载链接】galaxybook_mask This script will allow you to mimic your windows pc as a Galaxy Book laptop, this is usually used to bypass Samsung Notes 项目地址: https://gitcode.com/gh_mirrors/…

作者头像 李华
网站建设 2026/1/27 2:01:11

多卡训练配置指南:device_map简易并行设置

多卡训练配置指南&#xff1a;device_map简易并行设置 在今天的AI工程实践中&#xff0c;一个70亿参数的模型已经不再是什么稀罕事。但当你满怀期待地运行 from_pretrained() 时&#xff0c;显存不足&#xff08;OOM&#xff09;却成了家常便饭——哪怕你手握一张32GB的A100。这…

作者头像 李华
网站建设 2026/1/27 0:49:51

重构认知边界:当PyTorch-YOLOv3遇见文本智能的突破之旅

重构认知边界&#xff1a;当PyTorch-YOLOv3遇见文本智能的突破之旅 【免费下载链接】PyTorch-YOLOv3 eriklindernoren/PyTorch-YOLOv3: 是一个基于PyTorch实现的YOLOv3目标检测模型。适合用于需要实现实时目标检测的应用。特点是可以提供PyTorch框架下的YOLOv3模型实现&#xf…

作者头像 李华
网站建设 2026/1/27 2:25:17

AD20等长走线调整方法:Altium Designer教程完整示例

高速PCB设计实战&#xff1a;在AD20中玩转等长走线&#xff0c;一次搞定DDR信号同步你有没有遇到过这样的场景&#xff1f;FPGA代码跑得飞快&#xff0c;逻辑也没问题&#xff0c;可就是读不出DDR里的数据。示波器一抓——DQS和DQ信号对不上边&#xff01;建立时间不够、保持时…

作者头像 李华