一、U-Boot 概述
U-Boot(Universal Boot Loader)是一个开源的、跨架构的通用引导加载程序,支持 ARM、x86、MIPS、RISC-V 等处理器架构,是嵌入式 Linux 领域事实上的 Bootloader 标准。
U-Boot 的核心职责包括:
- 硬件初始化:上电后完成 DDR、时钟、串口、存储、网络等底层硬件的初始化和配置
- 镜像加载:从 Flash/eMMC/SD/网络加载 Linux 内核、设备树、根文件系统到内存
- 交互调试:提供命令行接口,支持参数配置、固件升级和故障排查
- 环境管理:通过环境变量存储启动参数,适配不同的启动场景
在完整的系统启动链中,U-Boot 的位置如下:
芯片内置 BootROM(固化)→ SPL(片内 SRAM)→ VPL/TPL(可选)→
ATF/OpenSBI(安全固件)→ U-Boot Proper(完整 U-Boot)→
Linux 内核 → 根文件系统
二、U-Boot 启动流程概览
U-Boot 的启动过程一般分为两个核心阶段:汇编阶段和 C 语言阶段。在现代嵌入式平台上,还会引入 SPL(Secondary Program Loader)等前置阶段来应对 BootROM 加载空间的限制。
┌─────────────────────────────────────────────────────────────────────┐
│ U-Boot 完整启动流程图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ BootROM │───▶│ SPL │───▶│ U-Boot │───▶│ Kernel │ │
│ │ (固化) │ │ (SRAM) │ │ Proper │ │ & Rootfs│ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │加载SPL到│ │初始化 │ │初始化 │ │
│ │ SRAM │ │ DDR │ │完整外设 │ │
│ └─────────┘ │加载U-Boot│ │进入CLI │ │
│ │到DDR │ │/加载内核│ │
│ └─────────┘ └─────────┘ │
│ │
│ 阶段0(ROM Boot) 阶段0.5(SPL) 阶段1+2(完整U-Boot) │
└─────────────────────────────────────────────────────────────────────┘
三、SPL(Secondary Program Loader)阶段详解
3.1 SPL 的产生背景与作用
现代 SoC 的 BootROM 空间和功能有限,通常只能从外部存储介质(如 NAND Flash、SD 卡)中读取 4K/8K/16K 等很小一段数据到内部的 SRAM 中运行。这段代码就是 SPL。
SPL 是 U-Boot 第一阶段执行的代码,主要完成以下工作:
- 初始化最基本的硬件(DDR 控制器、时钟、串口等)
- 初始化外部 DRAM 内存
- 将完整的 U-Boot 第二阶段代码从 Flash 搬移到 DRAM 中
- 跳转到 U-Boot Proper 执行
3.2 SPL 的运行约束与执行流程
SPL 运行在芯片内部 SRAM 中,资源极度受限:
- SRAM 容量通常仅几十 KB 到几百 KB
- 无外部 DDR 内存可用
- 仅能初始化极简外设
SPL 的核心业务围绕两大函数展开:dram_init(DDR 内存初始化)和 spl_load_image(镜像加载)。
现代 U-Boot 支持多级 xPL 引导体系:
- TPL(Tertiary Program Loader):最早期初始化,尽可能精简
- VPL(Verifying Program Loader):可选的验证加载器,支持 A/B 验证启动
⚠️ 特别注意:在 PowerPC 架构上,启动顺序是 SPL → TPL → U-Boot,与其他架构有所区别。
四、Stage 1:汇编阶段(第一阶段)
第一阶段使用汇编语言编写,是 U-Boot 的入口代码。它完成最小化的硬件初始化,并为进入 C 语言环境做准备。入口文件通常位于 arch/arm/cpu/armv7/start.S 或对应架构目录下。
4.1 异常向量表建立
U-Boot 入口由链接脚本 u-boot.lds 定义 ENTRY(_start),确保第一条指令是 _start 标号。
assembly
.globl _start
_start:
b reset // 复位异常
ldr pc, _undefined_instruction // 未定义指令
ldr pc, _software_interrupt // 软件中断
ldr pc, _prefetch_abort // 预取指令异常
ldr pc, _data_abort // 数据操作异常
ldr pc, _not_used // 未使用
ldr pc, _irq // IRQ中断
ldr pc, _fiq // FIQ中断
作用 :定义 ARM 异常向量表,各异常向量地址对应不同的处理程序入口,其中复位向量 b reset 会在上电或复位时被 CPU 自动执行。
4.2 CPU 工作模式设置与中断屏蔽
assembly
reset:
/* 设置 CPU 为 SVC32 模式,同时屏蔽 IRQ 和 FIQ */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3 // SVC模式,屏蔽IRQ/FIQ
msr cpsr, r0
作用:CPU 上电后进入 SVC(Supervisor)模式,同时禁用中断,避免在 Bootloader 初始化阶段被中断干扰。
4.3 CPU 关键初始化(cpu_init_crit)
assembly
cpu_init_crit:
/* 关闭 MMU 和缓存 */
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 // 清除 V、IC、DC 位
bic r0, r0, #0x00000087 // 清除 B、C、M 位
mcr p15, 0, r0, c1, c0, 0
/* 初始化 SDRAM */
bl lowlevel_init
作用 :关闭 MMU 和缓存,使能地址对齐检查,清除 TLB,并调用 lowlevel_init 进行芯片级别的初始化,包括系统时钟配置和 DDR 控制器的初始化。
💡 提示 :
lowlevel_init是板级移植的关键函数之一,通常在board/{vendor}/{board}/lowlevel_init.S中实现,包含 DDR 时序参数配置等核心内容。
4.4 重定位(Relocation)
重定位是指将 U-Boot 自身代码从 Flash 拷贝到 DDR 内存中的过程。由于 U-Boot 在 Flash 中运行时,其链接地址与运行地址不一致,需要通过地址无关代码(PIC)实现正确跳转。
assembly
ENTRY(relocate_code)
ldr r0, =__image_copy_start // 源地址(Flash中的起始地址)
ldr r3, =__image_copy_end // 源结束地址
ldr r1, =__image_copy_start // 目标地址(DDR中的地址)
/* 地址比较,判断是否需要重定位 */
cmp r0, r1
beq relocate_done
/* 拷贝循环 */
copy_loop:
ldmia r0!, {r9-r10} // 从源地址加载数据
stmia r1!, {r9-r10} // 保存到目标地址
cmp r0, r3
blo copy_loop
作用:将 U-Boot 代码从 Flash(运行地址)拷贝到 DDR 内存(链接地址),并处理相关的符号重定位。重定位后,U-Boot 可以在 DDR 中获得更快的运行速度和更大的代码执行空间。
五、Stage 2:C 语言阶段(第二阶段)
第二阶段以 C 语言实现,通过 board_init_f 和 board_init_r 两个关键函数完成系统的全面初始化。
5.1 crt0.S 与 _main 入口
从汇编阶段跳转到 C 语言阶段的桥梁是 _main 函数,位于 arch/arm/lib/crt0.S 中。其执行流程可分为几步:
- 设置初始 C 运行环境,为调用
board_init_f准备栈和全局数据结构(gd,即 global_data) - 调用
board_init_f进行早期初始化 - 调用重定位代码,将 U-Boot 从当前位置搬迁到目标地址
- 建立最终环境,初始化 BSS 段,调用
board_init_r
5.2 board_init_f:早期初始化
c
// 位于 common/board_f.c
void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
// 初始化一系列早期硬件
init_sequence_f[] = {
setup_mon_len, // 设置 U-Boot 代码长度
initf_malloc, // 初始化 malloc 池
initf_console_record, // 控制台记录初始化
arch_cpu_init, // CPU 架构相关初始化
initf_dm, // Driver Model 初始化
timer_init, // 定时器初始化
env_init, // 环境变量初始化(早期)
serial_init, // 串口初始化(关键!)
console_init_f, // 控制台初始化
dram_init, // DRAM 初始化
// ... 更多初始化项
};
}
作用:在重定位之前完成必要的硬件初始化,主要包括设置内存映射、初始化串口、准备 RAM 空间,以便将 U-Boot 剩余部分从 Flash 搬运到 RAM 中。
在 board_init_f 中,串口初始化后开发板才能在串口调试助手输出信息,这是判断 U-Boot 是否成功启动到第二阶段的重要标志。
5.3 dram_init:内存映射初始化
c
// 位于 board/{vendor}/{board}/{board}.c
int dram_init(void)
{
gd->ram_size = PHYS_SDRAM_1_SIZE;
// 设置内存起始地址和大小
// 这些参数将在后续向内核传递时使用
return 0;
}
作用:检测系统内存映射,初始化 DRAM,为后续内核加载准备内存空间。
5.4 重定位与 BSS 初始化
c
// crt0.S 伪代码示意
void _main(void)
{
// 1. 设置初始 C 环境
setup_initial_stack();
// 2. 调用 board_init_f(早期初始化)
board_init_f(0);
// 3. 重定位代码到 DDR
relocate_code(CONFIG_SYS_TEXT_BASE);
// 4. 清零 BSS 段
memset(__bss_start, 0, __bss_end - __bss_start);
// 5. 调用 board_init_r(晚期初始化)
board_init_r(gd->new_gd, gd->dest_addr);
}
作用 :relocate_code 将 U-Boot 代码重定位到 DDR 的最终运行地址。BSS 段清零确保未初始化的全局变量不再包含旧数据,这对使用全局变量的 C 代码至关重要。
5.5 board_init_r:主初始化函数
c
// 位于 common/board_r.c
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
// 运行晚期初始化序列
init_sequence_r[] = {
initr_trace, // 跟踪调试初始化
initr_reloc, // 重定位相关设置
initr_caches, // 使能缓存
initr_malloc, // malloc 池初始化
initr_serial, // 完整串口初始化
console_init_r, // 控制台初始化
initr_env, // 环境变量初始化(完整版本)
initr_net, // 网络初始化
initr_pci, // PCI 总线初始化
initr_dm, // Driver Model 完整初始化
run_main_loop, // 进入主循环
};
}
作用 :进行更高级别的硬件初始化,包括网络、存储等外设,完成环境变量加载,最终进入 main_loop() 显示 U-Boot 命令行或执行 bootcmd 自动启动内核。
5.6 main_loop:命令行与内核启动
c
// 位于 common/main.c
void main_loop(void)
{
/* 倒计时启动 */
if (autoboot_cmd != NULL) {
printf("Hit any key to stop autoboot: %2d ", bootdelay);
// 等待按键,超时则执行 bootcmd
}
/* 运行 bootcmd 环境变量中的命令 */
run_command_list(s, -1, 0);
/* 若中断,进入 CLI 交互模式 */
cli_loop(); // U-Boot 命令行
}
作用:提供用户交互,支持 U-Boot 命令的解析和执行。用户可以在此阶段通过串口输入命令,或者触发自动启动加载内核。
六、关键数据结构详解
6.1 global_data (gd) 结构体
gd 是 U-Boot 中最核心的全局数据结构,在阶段切换过程中传递关键信息:
c
typedef struct global_data {
bd_t *bd; // 板级信息结构
unsigned long flags; // 标志位
unsigned long ram_top; // 内存顶地址
unsigned long relocaddr; // 重定位后的地址
unsigned long reloc_off; // 重定位偏移量
unsigned long malloc_limit; // malloc 限制
unsigned long malloc_ptr; // malloc 指针
const void * fdt_blob; // 设备树二进制指针
// ... 更多字段
} gd_t;
作用 :在 board_init_f 阶段创建,存放重定位地址、内存布局等关键信息,这些信息在 board_init_r 阶段继续使用,确保启动过程的连贯性。
6.2 board_info (bd) 结构体
c
typedef struct bd_info {
int bi_baudrate; // 串口波特率
unsigned long bi_ip_addr; // IP 地址
struct environment_s *bi_env;
ulong bi_arch_number; // 机器类型 ID
ulong bi_boot_params; // 启动参数地址
struct // 内存区域信息
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
作用:存储单板信息,包括机器类型 ID(传递给内核的关键参数)、内存映射信息等。
七、开发环境搭建与编译验证
7.1 源码获取与版本选择
bash
# 从官方仓库获取源码
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
# 查看可用的发布版本
git tag -l | grep v20
# 切换到一个稳定的发布版本
git checkout v2024.01
7.2 源码目录结构解析
bash
U-Boot 源码目录结构(以 v2024.01 为例):
├── arch/ # CPU 架构相关代码(ARM/x86/MIPS等)
│ └── arm/ # ARM 架构代码
│ ├── cpu/ # CPU 核心代码(含 start.S)
│ └── mach-* # 各厂商 SoC 代码
├── board/ # 具体开发板的代码,移植物件的主要修改区
│ ├── rockchip/ # 瑞芯微系列
│ ├── samsung/ # 三星系列
│ └── ti/ # TI 系列
├── common/ # 通用功能代码
│ ├── board_f.c # 早期初始化(阶段2前部)
│ ├── board_r.c # 晚期初始化(阶段2后部)
│ └── main.c # 主循环与命令入口
├── configs/ # 板级默认配置文件(*_defconfig)
├── drivers/ # 各类外设驱动程序
├── dts/ # 设备树源文件
├── include/ # 公共头文件
│ └── configs/ # 板级配置头文件
├── lib/ # 通用库函数
├── scripts/ # 编译辅助脚本
├── tools/ # 主机端工具
└── Makefile # 顶层编译文件
7.3 配置与编译
bash
# 第一步:配置目标板(选择对应的 defconfig)
make <board_name>_defconfig
# 第二步:可选:调整配置(不是必须)
make menuconfig # 通过图形界面修改配置选项
# 第三步:交叉编译(以 ARM64 为例)
make CROSS_COMPILE=aarch64-linux-gnu- -j4
# 编译产物:
# - u-boot.bin:可烧写的二进制文件
# - u-boot.srec:SREC 格式文件(适用于调试)
# - u-boot.dtb:设备树二进制文件(如果启用设备树)
# - SPL:次级程序加载器(如果配置了 SPL)
7.4 烧录验证(以 SD 卡为例)
bash
# 将编译好的镜像写入 SD 卡(确保 SD 卡已卸载)
sudo dd if=u-boot.bin of=/dev/sdX bs=512 seek=1
sync
# 连接开发板串口(波特率通常为 115200)
# - Windows:使用 SecureCRT、Putty 或 Xshell
# - Linux:使用 minicom 或 screen
screen /dev/ttyUSB0 115200
八、开源 Demo 验证:QEMU 仿真环境
对于没有真实开发板的开发者,QEMU 提供了一个无痛的 U-Boot 学习和验证环境。
8.1 安装 QEMU
bash
# Ubuntu/Debian
sudo apt-get install qemu-system-arm
# 或者从源码编译
git clone https://gitlab.com/qemu-project/qemu.git
cd qemu
./configure --target-list=arm-softmmu
make -j4
8.2 配置并编译 U-Boot for QEMU ARM
bash
# 为 QEMU ARM Versatile PB 板配置 U-Boot
make versatilepb_defconfig
# 编译
make CROSS_COMPILE=arm-linux-gnueabi- -j4
# 产物:u-boot.bin
8.3 在 QEMU 中启动 U-Boot
bash
# 启动 QEMU 运行 U-Boot
qemu-system-arm -M versatilepb \
-kernel u-boot.bin \
-nographic \
-serial mon:stdio
# 预期输出:U-Boot 启动信息,最后进入命令行 =>
8.4 U-Boot Library (ulib) Demo
U-Boot 提供了 ulib 示例,展示如何将 U-Boot 的能力以库的形式供外部程序调用。
c
// examples/ulib/demo.c 示例代码
int main(void)
{
printf("U-Boot Library Demo\n");
printf("U-Boot version: %s\n", version_string);
// 演示一些 U-Boot 功能
#ifdef CONFIG_SANDBOX
// 沙盒模式下的代码
#else
// 实际硬件上的代码
#endif
return 0;
}
ulib 使得开发者可以:
- 将 U-Boot 编译为共享库
libu-boot.so或静态库libu-boot.a - 用 C 或 Rust 编写程序调用 U-Boot 的功能(驱动、文件系统、网络等)
- 在真实的硬件上编译并运行自定义的 U-Boot 应用
九、U-Boot 命令行(CLI)实战
U-Boot 提供了功能强大的命令行环境,用户可以在系统启动时中断自动启动流程,进入命令提示符 => 进行交互。
9.1 常用命令速查
| 命令 | 说明 | 示例 |
|---|---|---|
printenv |
打印所有环境变量 | => printenv |
setenv |
设置/修改环境变量 | => setenv bootdelay 5 |
saveenv |
保存环境变量到存储 | => saveenv |
bootm |
启动 U-Boot 格式内核镜像 | => bootm 0x80000 |
bootz |
启动 zImage 格式内核 | => bootz 0x8000000 - 0x8300000 |
tftp |
通过网络加载文件 | => tftp 0x8000000 uImage |
load |
从存储设备加载文件 | => load mmc 0:1 0x8000000 uImage |
mmc |
MMC/SD 卡操作 | => mmc list |
nand |
NAND Flash 操作 | => nand read 0x8000000 0x200000 0x500000 |
reset |
重启系统 | => reset |
help |
显示帮助信息 | => help |
9.2 典型网络启动场景
bash
# 在 U-Boot 命令行中的典型操作流程
=> setenv ipaddr 192.168.1.100 # 设置本机 IP
=> setenv serverip 192.168.1.10 # 设置 TFTP 服务器 IP
=> setenv ethaddr aa:bb:cc:dd:ee:ff # 设置 MAC 地址
=> saveenv # 保存配置
# 通过 TFTP 加载内核和设备树
=> tftp 0x80000000 zImage
=> tftp 0x83000000 board.dtb
# 启动内核(传递设备树)
=> bootz 0x80000000 - 0x83000000
9.3 从 eMMC/SD 卡启动示例
bash
# 查看 MMC 设备列表
=> mmc list
# 从 MMC 设备 0 的第一个分区加载内核
=> load mmc 0:1 0x80000000 /boot/uImage
# 加载设备树
=> load mmc 0:1 0x83000000 /boot/board.dtb
# 启动
=> bootm 0x80000000 - 0x83000000
9.4 设置自动启动命令(bootcmd)
bash
# 设置 bootcmd 环境变量,实现开机自动从 MMC 加载内核
=> setenv bootcmd 'load mmc 0:1 0x80000000 /boot/zImage; load mmc 0:1 0x83000000 /boot/board.dtb; bootz 0x80000000 - 0x83000000'
# 设置传递给内核的启动参数
=> setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait'
# 保存配置
=> saveenv
# 复位系统,观察自动启动效果
=> reset
十、进阶知识学习
10.1 设备树(Device Tree)详解
U-Boot 支持设备树(Flattened Device Tree,FDT)来描述硬件信息,实现了硬件描述与驱动代码的彻底分离。
dts
// dts 文件示例(board.dts)
/ {
model = "My ARM Board";
compatible = "myvendor,myboard";
chosen {
bootargs = "console=ttyS0,115200";
stdout-path = "serial0:115200n8";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; // 1GB RAM
};
serial0: serial@10000000 {
compatible = "ns16550a";
reg = <0x10000000 0x100>;
clock-frequency = <24000000>;
current-speed = <115200>;
};
};
设备树的使用流程:
- 为目标板编写
.dts文件 - 使用 DTC 编译器编译为
.dtb二进制文件 - U-Boot 将 DTB 加载到内存
- U-Boot 的驱动模型(Driver Model)根据 DTB 初始化硬件
- U-Boot 将 DTB 地址传递给内核
U-Boot v2026.04 引入了 bootph-* 设备树标记机制,将硬件初始化阶段划分写入设备树:
dts
&uart0 {
bootph-pre-ram; // SPL 阶段初始化
bootph-all; // 所有阶段都可用
status = "okay";
};
10.2 驱动模型(Driver Model)
U-Boot 驱动模型(DM)将硬件驱动与设备树解耦:
c
// 定义 U-Boot 驱动
U_BOOT_DRIVER(my_driver) = {
.name = "my_driver",
.id = UCLASS_MY_DEVICE,
.probe = my_driver_probe,
.remove = my_driver_remove,
.ops = &my_driver_ops,
.flags = DM_FLAG_PRE_RELOC, // 重定位前可用
};
// 定义设备(传统方式)
U_BOOT_DEVICE(my_device) = {
.name = "my_driver",
.platdata = &my_platdata,
};
10.3 调试与移植技巧
10.3.1 串口调试
串口是 U-Boot 调试最主要的工具,通过在关键位置添加打印信息判断启动状态:
c
// 在关键函数中添加调试输出
#define DEBUG
#include <debug_uart.h>
void board_init_f(void)
{
debug_uart_init(); // 早期串口输出,用于汇编阶段的调试
PRINTF("Enter board_init_f\n");
// ...
PRINTF("Exit board_init_f\n");
}
10.3.2 OpenOCD/GDB 硬件调试
对于极早期的启动问题(串口尚未初始化),可以使用 JTAG/SWD 调试器:
bash
# 在 board_init_f 中添加死循环断点
void board_init_f(ulong boot_flags)
{
// 添加调试死循环,供 JTAG 连接
asm volatile("b .\n\t");
// ... 后续代码
}
# GDB 连接调试
arm-none-eabi-gdb u-boot
(gdb) target remote localhost:3333
(gdb) break board_init_r
(gdb) continue
10.3.3 U-Boot 命令链调试
bash
# 使用 U-Boot 命令行内置的诊断工具
=> mmc info # 查看 MMC/SD 卡信息
=> nand info # 查看 NAND Flash 信息
=> md 0x80000000 20 # 显示内存内容
=> mw 0x80000000 0x12345678 # 写内存
=> cmp 0x80000000 0x83000000 100 # 比较内存区间
10.4 启动参数传递详解
U-Boot 通过 标记列表(tag list) 或 设备树(FDT) 向 Linux 内核传递启动参数。
c
// 传统 tag list 方式
struct tag *params = (struct tag *)gd->bd->bi_boot_params;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next(params);
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem);
params->u.mem.start = gd->bd->bi_dram[0].start;
params->u.mem.size = gd->bd->bi_dram[0].size;
// 设置命令行参数
setup_commandline_tag(gd->bd, commandline);
在 Linux 内核中可以通过 cat /proc/cmdline 查看接收到的参数。
10.5 Falcon Mode(快速启动模式)
Falcon Mode 允许 SPL 直接加载和启动 Linux 内核,跳过完整的 U-Boot Proper 执行阶段,将启动时间从数秒缩减到亚秒级别。
bash
# Falcon Mode 的配置和启用
# 1. 在内核配置中启用
CONFIG_SPL_OS_BOOT=y
# 2. 在 U-Boot 命令行中生成启动参数
=> spl export fdt 0x80000000 - 0x83000000
# 3. 将生成的启动参数保存到存储介质
=> saveenv
c
// 代码层面的 Falcon Mode 检测
#ifdef CONFIG_SPL_OS_BOOT
int spl_start_uboot(void)
{
// 检查特定按键或标志,决定启动内核还是进入 U-Boot
if (gpio_get_value(BOOT_TO_UBOOT_GPIO))
return 1; // 进入完整的 U-Boot Proper
return 0; // 直接启动内核
}
#endif
10.6 安全启动(Secure Boot)
U-Boot 支持完整的信任链安全启动,从 BootROM 到 Linux 内核形成不可篡改的信任链路:
bash
# 生成密钥对并签名镜像
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
# 使用 mkimage 创建安全启动镜像
mkimage -f fit-image.its -K public.pem fit-image.itb
配置选项:
CONFIG_SECURE_BOOT=y:启用安全启动支持CONFIG_FIT_SIGNATURE=y:启用 FIT 镜像签名验证CONFIG_SPL_FIT_SIGNATURE=y:SPL 阶段启用签名验证
十一、实际应用场景
11.1 场景一:远程固件升级(TFTP + eMMC)
在工业物联网设备中,远程固件升级是刚需。使用 U-Boot 的 TFTP 功能可以在启动阶段完成固件烧写:
bash
=> setenv serverip 192.168.1.10
=> setenv ipaddr 192.168.1.100
# 通过 TFTP 加载 U-Boot 镜像
=> tftp 0x80000000 u-boot.bin
# 烧写到 eMMC 的 boot 分区
=> mmc dev 0
=> mmc write 0x80000000 0x200 0x1000
# 更新完成后重启
=> reset
11.2 场景二:快速启动优化(Falcon Mode + LVGL)
在 HMI 设备、汽车仪表等需要快速启动的场景中,通过 Falcon Mode 消除 U-Boot Proper 的启动延迟:
bash
# 配置 Falcon Mode
=> setenv boot_os 'if gpio input led_green; then echo "Boot OS directly"; else run bootcmd; fi'
=> setenv falcon_args 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait'
=> spl export fdt 0x80000000 - 0x83000000
结合 SPL 阶段的直接内核加载,Linux 系统启动时间可以从 3-5 秒优化到 1 秒以内,配合 LVGL 等轻量级 GUI,可以实现"上电即见"的用户体验。
11.3 场景三:早期硬件控制(GPIO 初始化)
在 U-Boot 阶段提前使能某些外设(如传感器电源、外设复位),可以确保这些外设在 Linux 内核启动前就已处于预期状态:
c
// 在 board_init_f 中添加 GPIO 控制代码
void board_init_f(ulong boot_flags)
{
// 拉高 GPIO 使能传感器电源
gpio_request(117, "sensor_power");
gpio_direction_output(117, 1);
mdelay(10); // 等待电源稳定
// 释放外设复位信号
gpio_request(118, "device_reset");
gpio_direction_output(118, 0);
mdelay(5);
// ... 继续其他初始化
}
⚠️ 硬件限制提醒:U-Boot 通常在芯片上电约 2 秒后才开始执行,因此无法控制在"上电瞬间"的 GPIO 初始电平。如果硬件需要"上电即拉高",必须选择上电默认高电平的引脚。
11.4 场景四:多启动设备支持(SD/eMMC/NAND/网络)
U-Boot 支持多种启动介质的冗余配置,实现高可靠性:
bash
# 设置多优先级启动顺序
=> setenv bootcmd 'run mmcboot; run nandboot; run netboot'
# mmc 启动(优先级最高)
=> setenv mmcboot 'if mmc dev 0; then load mmc 0:1 0x80000000 /boot/zImage; load mmc 0:1 0x83000000 /boot/board.dtb; bootz 0x80000000 - 0x83000000; fi'
# nand 启动(备用)
=> setenv nandboot 'if nand read 0x80000000 0x200000 0x500000; then setenv bootargs console=ttyS0,115200 root=/dev/mtdblock2; bootm 0x80000000; fi'
# 网络启动(最后备选)
=> setenv netboot 'dhcp; tftp 0x80000000 zImage; tftp 0x83000000 board.dtb; bootz 0x80000000 - 0x83000000'
=> saveenv
11.5 场景五:安全启动链(Secure Boot)
在产品级嵌入式设备中,固件安全至关重要。利用 U-Boot 的安全启动特性,构建从 BootROM 到内核的信任链:
BootROM(不可篡改)→ 验证 SPL 签名 → SPL 验证 U-Boot 签名 →
U-Boot 验证内核签名 → 内核验证文件系统(dm-verity)→ 系统启动
bash
# 生成密钥并制作带签名的 FIT 镜像
openssl genrsa -out dev.key 2048
openssl req -new -x509 -key dev.key -out dev.crt
# 创建 FIT 镜像描述文件 (fit-image.its)
# 包含多个镜像组件及其签名信息
mkimage -f fit-image.its fit-image.itb
# 在板级配置中启用签名验证
CONFIG_FIT_SIGNATURE=y
CONFIG_FIT_VERBOSE=y
CONFIG_SPL_FIT_SIGNATURE=y
11.6 场景六:U-Boot 作为固件库(ulib)
最新的 U-Boot ulib 特性允许将 U-Boot 编译为可重用的库,开发者可以用 C 或 Rust 编写程序调用 U-Boot 的各种功能(驱动、文件系统、网络等),大大加速了固件开发的效率。
十二、总结与学习路径建议
12.1 学习路径建议
-
初学者阶段:
- 使用 QEMU 模拟环境熟悉 U-Boot 编译和基本命令
- 理解 U-Boot 两阶段启动的宏观流程
- 练习常用的 U-Boot 命令行操作
-
进阶阶段:
- 深入学习
board_init_f和board_init_r的初始化序列 - 掌握设备树的编写和调试技巧
- 尝试为常见开发板(如 Raspberry Pi、BeagleBone)编译和定制 U-Boot
- 深入学习
-
深入阶段:
- 学习 SPL 的初始化和 DDR 训练机制
- 掌握 DM(Driver Model)驱动的开发和适配
- 移植 U-Boot 到新的开发板
- 实现 Falcon Mode 快速启动
-
专家阶段:
- 实现安全启动(Secure Boot)完整信任链
- 优化启动时间,定制 U-Boot 功能裁剪
- 贡献代码到 U-Boot 上游社区
12.2 核心关键点总结
| 阶段 | 关键文件 | 核心函数 | 主要任务 |
|---|---|---|---|
| SPL | common/spl/ |
spl_load_image, dram_init |
DDR 初始化,加载 U-Boot Proper |
| Stage 1 汇编 | arch/*/cpu/*/start.S |
reset, cpu_init_crit, lowlevel_init |
异常向量,SVC 模式,关中断,关看门狗,DDR 初始化 |
| Stage 2 C(早) | common/board_f.c |
board_init_f, init_sequence_f[] |
内存映射,串口初始化,准备重定位 |
| 重定位 | arch/*/lib/relocate.S |
relocate_code |
代码搬迁,符号表修正 |
| Stage 2 C(晚) | common/board_r.c |
board_init_r, init_sequence_r[] |
外设完整初始化,环境变量加载,主循环 |
| 命令行 | common/main.c |
main_loop, run_command |
CLI 交互,bootcmd 执行 |
12.3 学习资源推荐
- 官方文档:https://docs.u-boot.org/
- 官方源码仓库:https://source.denx.de/u-boot/u-boot.git
- U-Boot 邮件列表:u-boot@lists.denx.de
十三、总结
U-Boot 作为嵌入式 Linux 系统的"第一声问候",其启动流程虽然复杂,但层次清晰、模块分明。从 SPL 的硬件初探,到汇编阶段的底层初始化,再到 C 语言阶段的系统构建,每一层都有其明确的定位和不可替代的作用。
掌握 U-Boot 的启动流程,不仅有助于开发者在板级移植和调试中迅速定位问题,更能为整个嵌入式系统的性能优化和功能增强打下坚实的基础。无论是快速启动优化、远程固件升级,还是安全启动链构建,U-Boot 都提供了成熟可靠的解决方案。
本指南基于 U-Boot v2024.01 及以上版本编写,部分高级特性(如 ulib、VPL)仍在积极开发中,建议关注 U-Boot 官方文档获取最新信息。