U-Boot SPL阶段与主阶段深度解析:从ROM到Kernel的完整引导之旅(ARMv8)

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 分级启动设计

  1. SPL(二级加载器):极致裁剪、只保留DDR+串口+存储驱动,跑在SRAM;
  2. 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 三大硬性规则
  1. 向量表置顶:ARMv8架构强制要求;
  2. 全部段放入SRAM:DDR此时不存在;
  3. _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 执行逻辑
  1. 上电跳转 reset;
  2. 优先保存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、无动态内存

只做三件事:

  1. 关闭看门狗(防止自动复位)
  2. 初始化极简串口
  3. 开辟SRAM临时栈,为C语言铺路

3.6 _main:汇编转C语言(最重要过渡函数)

路径:arch/arm/lib/crt0.S

3.6.1 执行流程
  1. 栈16字节对齐(ARMv8强制规范)
  2. 分配gd全局数据结构体
  3. 预留早期malloc内存
  4. 调用board_init_f
  5. 栈重定位
  6. 跳转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最高地址 ──&gt; ┌─────────────────────┐
                │ 重定位后U-Boot代码区 │
                ├─────────────────────┤
                │  通用malloc内存池    │
                ├─────────────────────┤
                │  global_data结构体  │
                ├─────────────────────┤
                │  程序栈(向下增长)    │
                ├─────────────────────┤
                │ 重定位前原始代码区   │
DDR最低地址 ──&gt; └─────────────────────┘

4.3 board_init_r:重定位后完整初始化

重定位完成后,按顺序初始化全部外设:

  1. malloc内存池
  2. 串口控制台
  3. 环境变量
  4. MMC/SD存储
  5. 以太网、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
=&gt; 

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. 启动方案选型总结

  1. 无SPL方案:低端MCU、NOR Flash、SRAM > 512KB;
  2. 二级SPL方案:主流工业ARMv8(RK3568、i.MX8),最通用;
  3. 三级TPL方案:高端旗舰SoC(RK3588、ZynqMP)。

8. 全文总结

  1. SPL:SRAM运行、极致精简、只为初始化DDR+搬运Uboot;
  2. U-Boot Proper:DDR运行、功能齐全、重定位+加载内核;
  3. 分级启动解决了现代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.

相关推荐
三品吉他手会点灯2 小时前
C语言学习笔记 - 33.数据类型 - printf函数的详细用法
c语言·开发语言·笔记·学习·算法
t-think4 小时前
深入理解指针(2)
c语言·开发语言
我不是懒洋洋4 小时前
从零开始实现一个简单的神经网络:C语言版
c语言
百万老师5 小时前
自然语言编程时代,如何零基础学习掌握嵌入式编程
c语言·单片机·嵌入式硬件·学习·ai全流程闭环开发
淞綰6 小时前
c语言的练习-字符串的练习-寻找最长连续字符以及出现次数
c语言·数据结构·学习·算法·c语言的练习
三品吉他手会点灯8 小时前
C语言学习笔记 - 36.数据类型 - 为什么需要输出控制符
c语言·开发语言·笔记·学习
阳光九叶草LXGZXJ8 小时前
自制数据库迁移工具-C版-07-HappySunshineV1.6-(支持PG、达梦、Gbase8a)
linux·c语言·开发语言·数据库·学习·postgresql
星恒随风8 小时前
从0开始的操作系统学习之路(2)
c语言·笔记·学习
孬甭_9 小时前
单链表详解
c语言·数据结构