嵌入式 Linux U-Boot 完整启动流程深度解析

一、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_fboard_init_r 两个关键函数完成系统的全面初始化。

5.1 crt0.S 与 _main 入口

从汇编阶段跳转到 C 语言阶段的桥梁是 _main 函数,位于 arch/arm/lib/crt0.S 中。其执行流程可分为几步:

  1. 设置初始 C 运行环境,为调用 board_init_f 准备栈和全局数据结构(gd,即 global_data)
  2. 调用 board_init_f 进行早期初始化
  3. 调用重定位代码,将 U-Boot 从当前位置搬迁到目标地址
  4. 建立最终环境,初始化 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>;
    };
};

设备树的使用流程

  1. 为目标板编写 .dts 文件
  2. 使用 DTC 编译器编译为 .dtb 二进制文件
  3. U-Boot 将 DTB 加载到内存
  4. U-Boot 的驱动模型(Driver Model)根据 DTB 初始化硬件
  5. 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 学习路径建议

  1. 初学者阶段

    • 使用 QEMU 模拟环境熟悉 U-Boot 编译和基本命令
    • 理解 U-Boot 两阶段启动的宏观流程
    • 练习常用的 U-Boot 命令行操作
  2. 进阶阶段

    • 深入学习 board_init_fboard_init_r 的初始化序列
    • 掌握设备树的编写和调试技巧
    • 尝试为常见开发板(如 Raspberry Pi、BeagleBone)编译和定制 U-Boot
  3. 深入阶段

    • 学习 SPL 的初始化和 DDR 训练机制
    • 掌握 DM(Driver Model)驱动的开发和适配
    • 移植 U-Boot 到新的开发板
    • 实现 Falcon Mode 快速启动
  4. 专家阶段

    • 实现安全启动(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 学习资源推荐

十三、总结

U-Boot 作为嵌入式 Linux 系统的"第一声问候",其启动流程虽然复杂,但层次清晰、模块分明。从 SPL 的硬件初探,到汇编阶段的底层初始化,再到 C 语言阶段的系统构建,每一层都有其明确的定位和不可替代的作用。

掌握 U-Boot 的启动流程,不仅有助于开发者在板级移植和调试中迅速定位问题,更能为整个嵌入式系统的性能优化和功能增强打下坚实的基础。无论是快速启动优化、远程固件升级,还是安全启动链构建,U-Boot 都提供了成熟可靠的解决方案。


本指南基于 U-Boot v2024.01 及以上版本编写,部分高级特性(如 ulib、VPL)仍在积极开发中,建议关注 U-Boot 官方文档获取最新信息。

相关推荐
底层开发智库1 天前
无需硬件开发板,从零构建并运行ARM aemfvp-a-rme软件栈
arm开发·arm
黑猫学长呀4 天前
存储宝典第1篇:Nand SCA是什么
arm开发·arm·nand·存储芯片·nandflash·onfi
凉、介4 天前
Armv8-A virtualization 笔记 (二)
笔记·学习·嵌入式·arm·gic
代码讲故事4 天前
mac电脑上docker突然无法运行,不停的出现弹框,“com.docker.vmnetd”将对你的电脑造成伤害。附国内不同芯片高速下载地址,下载直接运行。
macos·docker·容器·arm·mac·intel·下载
你刷碗5 天前
嵌入式UART printf 数据处理方法
c语言·单片机·嵌入式硬件·arm
secondyoung7 天前
Arm架构解析:Cortex-R系列架构概览
arm开发·单片机·嵌入式硬件·mcu·arm
clear sky .12 天前
[arm]HardFault_Handler()来源定位
arm
zz_lzh19 天前
arm版AI牛马:armbian(rk3588)设备部署openclaw
arm开发·人工智能·arm
v_JULY_v21 天前
ARM——用于长时序操作的优势奖励建模:采用三态标注策略(前进/后退/停滞),实现对相对优势的估计(含SARM详解)
arm·优势奖励建模·三态标注策略·相对优势的估计·sarm·阶段感知奖励建模·ra-bc