0. 前言
大家好,本篇文章我给大家彻底讲通透U-Boot完整启动流程 。网上绝大多数Uboot文章存在:ARMv7/ARMv8混杂、代码残缺、逻辑跳跃、不讲内存布局等问题。
本文基于 U-Boot v2021.01、纯ARMv8-AArch64架构,以i.MX8M、ZynqMP、RK3568等主流芯片为平台,从零拆解:
- ✅ BootROM固化层
- ✅ SPL精简引导层(SRAM运行)
- ✅ U-Boot Proper完整版(DDR运行)
- ✅ Linux Kernel内核启动
全文无废话、纯工程向、通俗易懂、保留源码+内存图+调试排错 ,适合BSP入门、芯片Bring-up、面试复习、工作存档。
建议收藏,这是目前最通顺、最完整的ARMv8 Uboot解析文章。
1. 嵌入式四级启动架构总览
1.1 完整启动链路(必背)
现代ARMv8 SoC标准启动顺序,层级严格不可颠倒 :
上电复位 → BootROM(片内ROM) → SPL(片内SRAM) → U-Boot Proper(DDR) → Linux Kernel(DDR)
1.2 四大启动阶段详细对比表
| 启动阶段 | 运行内存 | 代码归属 | 核心工作职责 | 容量限制 | 权限等级 |
|---|---|---|---|---|---|
| BootROM | 片内ROM | 芯片厂商固化 | 硬件自检、识别启动介质、加载SPL至SRAM | ≤64KB | EL3(最高权限) |
| SPL | 片内SRAM | U-Boot精简编译 | 初始化时钟、DDR、串口;搬运U-Boot到DDR | ≤256KB | EL3/EL2 |
| U-Boot Proper | 外部DDR | U-Boot完整编译 | 外设初始化、内存重定位、命令行、加载内核 | 无严格限制 | EL2 |
| Linux Kernel | 外部DDR | 开源内核 | 系统调度、驱动加载、用户空间启动 | 无限制 | EL1 |
1.3 可视化启动流程图
加电复位
│
▼
┌─────────────┐
│ BootROM │ 芯片固化,不可修改
└─────────────┘
│
检测启动介质(SD/eMMC/NAND/NOR)
│
▼
┌─────────────┐
│ 加载SPL到SRAM │
└─────────────┘
│
▼
┌────────────────────────────┐
│ SPL运行阶段 │
│ 1.关闭中断、配置异常向量 │
│ 2.初始化时钟、关闭看门狗 │
│ 3.初始化DDR内存控制器 │
│ 4.加载完整版U-Boot到DDR │
│ 5.跳转移交执行权 │
└────────────────────────────┘
│
▼
┌────────────────────────────┐
│ U-Boot Proper阶段 │
│ 1.内存重定位、外设全初始化 │
│ 2.加载环境变量、存储驱动 │
│ 3.网络、串口、文件系统初始化│
│ 4.倒计时:命令行/自动启动 │
│ 5.加载Linux内核+设备树 │
└────────────────────────────┘
│
▼
启动Linux内核,系统引导完成
2. 为什么必须要有SPL?(核心面试题)
2.1 硬件痛点
- 片内SRAM极小:32KB~256KB
- 完整版U-Boot:500KB~2MB,根本塞不进去
- 上电瞬间没有DDR,必须有人初始化DDR
2.2 分级启动设计
- SPL(二级加载器):极致裁剪、只保留DDR+串口+存储驱动,跑在SRAM;
- U-Boot Proper:功能完整,DDR就绪后搬运到大内存运行。
一句话总结:SPL就是为了初始化DDR,把大Uboot搬运进DDR的工具人。
3. SPL阶段深度底层解析(重点)
SPL是BSP最难调试的阶段:无DDR、无虚拟内存、无BSS清零、栈极小、硬件耦合度极高。
3.1 SPL链接脚本内存布局
路径:spl/u-boot-spl.lds
3.1.1 核心源码
ld
MEMORY {
.sram : ORIGIN = 0x40300000, LENGTH = 256KB
}
ENTRY(_start)
SECTIONS
{
.text : {
__start = .;
*(.vectors) // 向量表必须置顶
arch/arm/cpu/armv8/start.o (.text*)
*(.text*)
} >.sram
.rodata : { *(.rodata*) } >.sram
.data : { *(.data*) } >.sram
.bss : { *(.bss*) } >.sram
}
3.1.2 三大硬性规则
- 向量表置顶:ARMv8架构强制要求;
- 全部段放入SRAM:DDR此时不存在;
- _start为唯一入口:CPU上电第一行代码。
3.2 汇编入口 _start 启动流程
路径:arch/arm/cpu/armv8/start.S
3.2.1 反汇编代码
asm
000000002049a000 <_start>:
2049a000: 1400000a b 2049a028 <reset>
2049a004: d503201f nop
000000002049a028 <reset>:
2049a028: 140005b6 b 2049b700 <save_boot_params>
3.2.2 执行逻辑
- 上电跳转 reset;
- 优先保存BootROM寄存器参数,防止丢失。
3.3 save_boot_params:保存ROM硬件参数
BootROM会通过 X0~X30 传递关键硬件信息:
- 启动介质类型
- 设备树物理地址
- 硬件版本、时钟参数
如果不保存,后续初始化覆盖寄存器,硬件信息永久丢失。
3.4 异常等级检测(ARMv8关键)
3.4.1 异常等级权限排序
EL3 > EL2 > EL1 > EL0
- EL3:安全监控(SPL默认运行在此)
- EL2:虚拟化(Uboot Proper)
- EL1:内核态(Linux)
- EL0:用户态
3.4.2 核心作用
动态识别当前CPU等级,绑定对应向量表,防止上电崩溃。
3.5 lowlevel_init:底层极简初始化
SPL第一个C函数,严格限制:无BSS、无DDR、无动态内存 。
只做三件事:
- 关闭看门狗(防止自动复位)
- 初始化极简串口
- 开辟SRAM临时栈,为C语言铺路
3.6 _main:汇编转C语言(最重要过渡函数)
路径:arch/arm/lib/crt0.S
3.6.1 执行流程
- 栈16字节对齐(ARMv8强制规范)
- 分配gd全局数据结构体
- 预留早期malloc内存
- 调用board_init_f
- 栈重定位
- 跳转board_init_r
3.6.2 gd结构体(必考)
global_data,Uboot贯穿全程的全局结构体,常驻内存。
- relocaddr:重定位地址
- ram_top:DDR最高地址
- baudrate:串口波特率
- env_addr:环境变量地址
3.7 board_init_f:SPL核心业务
c
void board_init_f(ulong dummy)
{
arch_cpu_init(); // CPU架构初始化
preloader_console_init();// 串口打印
dram_init(); // 最难:DDR初始化
spl_load_image(); // 读取U-Boot完整镜像
jump_to_uboot(); // 跳转DDR
}
DDR初始化是BSP开发最容易卡死、黑屏、死机的地方。
需要配置:频率、时序、阻抗、刷新周期、片选。
3.8 多核CPU启动策略
ARMv8多核芯片统一策略:主核运行、从核休眠
- 主CPU:正常初始化、搬运镜像
- 从CPU:wfe休眠,由Linux内核唤醒
- 目的:规避多核资源竞争,简化SPL代码
4. U-Boot Proper完整版阶段解析
SPL跳转完成后,Uboot运行在GB级DDR,硬件环境全部就绪。
4.1 SPL与Uboot交接对比
| 环境参数 | SPL阶段 | U-Boot Proper阶段 |
|---|---|---|
| 运行内存 | 片内SRAM(≤256KB) | 外部DDR(GB级) |
| 权限等级 | EL3/EL2 | EL2 |
| 外设驱动 | 极少驱动 | 全部外设驱动 |
| 功能 | 极简搬运 | 命令行、内核启动 |
4.2 内存重定位原理(高频面试)
Uboot一开始跑在低端内存,容易被内核、设备树覆盖。
重定位:把Uboot整体搬运到DDR高端空闲内存,永久不被覆盖。
4.2.1 内存布局简图
DDR最高地址 ──> ┌─────────────────────┐
│ 重定位后U-Boot代码区 │
├─────────────────────┤
│ 通用malloc内存池 │
├─────────────────────┤
│ global_data结构体 │
├─────────────────────┤
│ 程序栈(向下增长) │
├─────────────────────┤
│ 重定位前原始代码区 │
DDR最低地址 ──> └─────────────────────┘
4.3 board_init_r:重定位后完整初始化
重定位完成后,按顺序初始化全部外设:
- malloc内存池
- 串口控制台
- 环境变量
- MMC/SD存储
- 以太网、USB、I2C、SPI
4.4 main_loop:Uboot最终稳态
4.4.1 自动启动模式
倒计时无按键,执行 bootcmd
shell
bootcmd=mmc dev 0; mmc load 0 0x80000000 Image; booti 0x80000000 - 0x83000000
4.4.2 交互式命令行
按下按键进入调试模式,常用指令:
- md:内存查看
- mw:内存写入
- tftp:网络下载
- fatload:读取镜像
5. SPL 与 U-Boot 编译差异
| 编译特征 | SPL编译模式 | U-Boot Proper编译模式 |
|---|---|---|
| 宏定义 | CONFIG_SPL_BUILD 开启 | CONFIG_SPL_BUILD 关闭 |
| 编译优化 | -Os 极致体积优化 | -O2 速度优化 |
| 链接脚本 | u-boot-spl.lds | u-boot.lds |
| 输出镜像 | u-boot-spl.bin | u-boot.bin |
| 驱动裁剪 | 极少驱动 | 全部外设驱动 |
| 命令行 | 无 | 完整命令行 |
5.1 高阶拓展:TPL三级启动
高端芯片(RK3588、ZynqMP)采用:
BootROM→SPL→TPL→U-Boot
用于高频DDR、复杂电源管理芯片初始化。
6. 工程调试 & 故障排查(工作实用)
6.1 如何区分当前处于哪个启动阶段?
6.1.1 串口打印判断
shell
# SPL阶段打印
U-Boot SPL 2021.01 (Jan 15 2024 - 10:00:00 +0800)
Trying to boot from MMC1
# U-Boot Proper阶段打印
U-Boot 2021.01 (Jan 15 2024 - 10:00:00 +0800)
DRAM: 2 GiB
MMC: sdhci@0: 0
Hit any key to stop autoboot: 0
=>
6.2 三大高频故障
6.2.1 卡死在SPL
- 原因:存储偏移错误、DDR时序不匹配、电源纹波大
- 解决:核对分区、恢复原厂DDR参数
6.2.2 DDR初始化黑屏
- 原因:DDR颗粒不匹配、布线阻抗异常
- 解决:mtest内存压力测试
6.2.3 跳转崩溃
- 原因:链接脚本地址冲突、重定位偏移错误
- 解决:反汇编核对入口地址
6.3 工业调试命令
shell
# 反汇编SPL
aarch64-linux-gnu-objdump -d spl/u-boot-spl | less
# 查看函数符号
aarch64-linux-gnu-nm spl/u-boot-spl | grep board_init
7. 启动方案选型总结
- 无SPL方案:低端MCU、NOR Flash、SRAM > 512KB;
- 二级SPL方案:主流工业ARMv8(RK3568、i.MX8),最通用;
- 三级TPL方案:高端旗舰SoC(RK3588、ZynqMP)。
8. 全文总结
- SPL:SRAM运行、极致精简、只为初始化DDR+搬运Uboot;
- U-Boot Proper:DDR运行、功能齐全、重定位+加载内核;
- 分级启动解决了现代SoC内存硬件限制,是所有Linux嵌入式开发的底层基石。
附录:极简U-Boot启动执行链路思维导图(CSDN直接复制)
U-Boot完整启动流程
├─ 上电复位
├─ BootROM【厂商固化】
│ ├─ 硬件自检
│ ├─ 识别启动介质
│ └─ 加载SPL到SRAM
├─ SPL阶段【SRAM运行】
│ ├─ 汇编初始化
│ │ ├─ _start入口
│ │ ├─ 保存ROM参数
│ │ ├─ 异常向量配置
│ │ └─ 栈初始化
│ ├─ 底层硬件初始化
│ │ ├─ 关闭看门狗
│ │ ├─ 早期串口
│ │ └─ CPU架构初始化
│ ├─ 核心业务board_init_f
│ │ ├─ DDR初始化【最难】
│ │ ├─ MMC/NAND初始化
│ │ └─ 读取U-Boot镜像
│ ├─ 多核处理
│ │ ├─ 主核正常运行
│ │ └─ 从核自旋休眠
│ └─ 跳转DDR移交执行权
├─ U-Boot Proper阶段【DDR运行】
│ ├─ 重定位前board_init_f
│ │ ├─ 内存规划
│ │ └─ 计算重定位地址
│ ├─ 内存重定位
│ │ └─ 搬运至高端内存
│ ├─ 重定位后board_init_r
│ │ ├─ malloc初始化
│ │ ├─ 环境变量加载
│ │ ├─ 存储/网络/外设初始化
│ │ └─ 控制台初始化
│ ├─ main_loop主循环
│ │ ├─ 自动启动:执行bootcmd加载内核
│ │ └─ 手动模式:交互式命令行
│ └─ booti启动Linux内核
└─ Linux Kernel
├─ 内核解压
├─ 设备树解析
├─ 驱动加载
└─ 根文件系统启动
参考文献
1\] U-Boot Official Documentation. Board Initialisation Flow\[Z\]. 2021. \[2\] ARM Limited. ARMv8-A Architecture Reference Manual\[Z\]. 2020. \[3\] NXP Semiconductors. i.MX8M Plus Boot Flow Manual\[Z\]. 2022. \[4\] 嵌入式Linux底层开发指南:U-Boot深度剖析\[M\]. 机械工业出版社, 2023.