4. U-Boot启动流程详解

U-Boot 作为 ARM 嵌入式 Linux 系统的引导加载程序,其启动流程分为两个清晰的阶段:第一阶段(汇编阶段)和第二阶段(C语言阶段)。第一阶段负责最底层的硬件初始化与运行环境搭建,第二阶段则完成板级设备初始化、内存规划与操作系统加载。本章将深入分析 start.S、board_init_f 和 board_init_r 三个核心模块的执行逻辑。

4.1 第一阶段(汇编阶段):start.S 核心分析

第一阶段代码位于 arch/arm/lib/start.S(或具体 SoC 目录下的 start.S),是 U-Boot 的入口点。该阶段完全由汇编语言编写,主要完成以下关键任务:

步骤 操作 说明
1 设置 CPU 为 SVC32 模式 关闭 IRQ/FIQ,确保 CPU 处于特权模式
2 关闭 MMU 与 Cache 使用物理地址访问,避免虚拟地址映射干扰
3 设置中断向量表 将异常向量表拷贝到 0x00000000 或链接地址
4 初始化关键外设 如看门狗、时钟、SDRAM 控制器
5 设置堆栈指针 (SP) 为 C 语言运行提供栈空间
6 清零 BSS 段 清除未初始化全局变量区域
7 跳转到 C 语言入口 调用 board_init_f
关键代码片段:start.S 中的异常向量与模式设置
@ arch/arm/lib/start.S (简化)
_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 快速中断

reset:
    mrs r0, cpsr                 @ 读取当前程序状态寄存器
    bic r0, r0, #0x1f            @ 清除模式位
    orr r0, r0, #0xd3            @ 设置为 SVC32 模式,关闭 IRQ/FIQ
    msr cpsr, r0                 @ 写入 CPSR
    /* 后续关闭 MMU、初始化时钟、SDRAM 等 */
实战要点:在移植 U-Boot 到新板时,start.S 中的时钟初始化与 SDRAM 控制器配置是最容易出错的环节。建议先通过 JTAG 调试器确认 DDR 初始化时序是否正确,再继续后续流程。

4.2 第一阶段到第二阶段的过渡:内存布局与重定位

汇编阶段完成后,U-Boot 需要将自身从 Flash(或 ROM)重定位到 SDRAM 中运行,以获得更快的执行速度。这一过程涉及两个关键地址:

  • 链接地址 (Link Address):编译时指定的运行地址,通常位于 SDRAM 高端(如 0x87800000)。
  • 加载地址 (Load Address):U-Boot 实际被烧录的位置,如 SPI Flash 的 0x00000000 或 NAND Flash 的起始块。

重定位流程在汇编阶段末尾执行,通过 relocate_code 函数完成:

@ start.S 中的重定位代码
relocate_code:
    ldr r1, =__image_copy_start   @ 源地址(Flash 中的 U-Boot 起始)
    ldr r2, =__image_copy_end     @ 结束地址
    ldr r0, =_start               @ 目标地址(SDRAM 中的链接地址)
    /* 循环拷贝 U-Boot 自身到 SDRAM */
copy_loop:
    ldmia r1!, {r3-r10}           @ 从 Flash 加载 8 个字
    stmia r0!, {r3-r10}           @ 存储到 SDRAM
    cmp r1, r2
    blo copy_loop
    /* 跳转到 SDRAM 中的 board_init_f */
U-Boot 启动第一阶段与第二阶段过渡流程 start.S 入口 设置 SVC32 / 关 MMU 初始化时钟 / SDRAM 设置 SP / 清零 BSS 重定位到 SDRAM board_init_f 板级初始化 board_init_r 加载内核 / 启动 Flash → SDRAM 更新全局偏移表 (GOT)

4.3 第二阶段(C语言阶段):board_init_f 深度解析

board_init_f 是 U-Boot 进入 C 语言环境后的第一个函数,位于 common/board_f.c。它执行一系列板级初始化操作,但此时 U-Boot 仍在 Flash 或 ROM 中运行(尚未完成最终重定位)。该函数的核心任务包括:

  • 初始化串口:使能调试输出,便于打印启动信息。
  • 初始化定时器:提供时间基准。
  • 初始化 GPIO、I2C、SPI 等外设:为后续设备驱动做准备。
  • DRAM 初始化:配置内存控制器,检测可用内存大小。
  • 计算并预留内存空间:为 U-Boot 自身、设备树、堆栈、malloc 区域等规划内存布局。
board_init_f 中的初始化函数调用表(部分)
// common/board_f.c 中的 init_sequence_f 数组
static init_fnc_t init_sequence_f[] = {
    setup_cpu,           // CPU 微架构初始化
    setup_board,         // 板级特定设置
    env_init,            // 环境变量初始化
    init_baud_rate,      // 波特率设置
    serial_init,         // 串口初始化
    console_init_f,      // 控制台早期初始化
    display_options,     // 打印 U-Boot 版本信息
    display_text_info,   // 显示文本信息
    print_cpuinfo,       // 打印 CPU 信息
    dram_init,           // DRAM 初始化
    setup_dest_addr,     // 计算目标地址
    reserve_round_4k,    // 4KB 对齐预留
    reserve_mmu,         // 预留 MMU 页表空间
    reserve_trace,       // 预留 trace 缓冲区
    reserve_uboot,       // 预留 U-Boot 自身空间
    reserve_malloc,      // 预留 malloc 堆空间
    reserve_board,       // 预留板级信息结构体
    setup_machine,       // 设置机器类型
    reserve_global_data, // 预留全局数据区
    reserve_fdt,         // 预留设备树空间
    reserve_stacks,      // 预留堆栈空间
    setup_dram_config,   // 设置 DRAM 配置
    show_dram_config,    // 显示 DRAM 配置
    NULL,                // 结束标记
};
内存规划技巧:board_init_f 中的 reserve_* 系列函数从 DRAM 顶端向下分配内存,确保 U-Boot 自身、设备树、堆栈等区域互不重叠。调试时可通过 bdinfo 命令查看最终的内存布局。

4.4 第二阶段核心:board_init_r 与最终启动

board_init_r 位于 common/board_r.c,在 U-Boot 完成重定位到 SDRAM 后执行。此时 U-Boot 已在高速内存中运行,可以执行更复杂的初始化操作:

初始化阶段 关键函数 作用
控制台完善 console_init_r 初始化完整控制台,支持输入输出重定向
中断使能 interrupt_init 初始化中断控制器,使能全局中断
网络初始化 eth_initialize 初始化以太网控制器,支持网络启动
存储设备 mmc_initialize / spi_flash_init 初始化 MMC/SD 卡、SPI Flash 等存储介质
文件系统 fs_init 初始化文件系统支持(FAT、ext4、UBIFS 等)
环境变量 env_relocate 从存储设备加载环境变量到内存
设备树 fdt_init 解析设备树,更新硬件信息
启动内核 main_loop 进入命令行或自动启动流程
常见问题:如果 board_init_r 执行过程中出现“No working FDT”或“MMC init failed”错误,通常是因为设备树地址错误或存储驱动未正确适配。检查 CONFIG_OF_CONTROL 配置以及 fdt_addr 环境变量是否指向正确的设备树位置。

4.5 第一阶段与第二阶段对比总结

对比维度 第一阶段(汇编) 第二阶段(C语言)
编程语言 ARM 汇编 C 语言
运行位置 Flash / ROM SDRAM(重定位后)
核心文件 start.S board_f.c / board_r.c
主要任务 CPU 模式、时钟、SDRAM、堆栈 外设、文件系统、网络、启动内核
调试手段 JTAG、LED 指示灯 串口打印、U-Boot 命令
执行速度 极快(无复杂逻辑) 较慢(驱动初始化耗时)
board_init_f 与 board_init_r 职责分工 board_init_f(早期初始化) 串口 / 时钟 DRAM 初始化 内存规划预留 设备树预留 仍在 Flash 中运行,不可使用全局变量 board_init_r(后期初始化) 网络 / 存储 文件系统 环境变量加载 启动内核 在 SDRAM 中运行,完整 C 环境可用 重定位

4.6 启动流程中的关键数据结构

U-Boot 在启动过程中维护了多个关键数据结构,用于在不同阶段传递信息:

  • gd (global_data):全局数据指针,存储 DRAM 大小、波特率、启动标志等。在汇编阶段通过 gd_t 结构体初始化,C 语言阶段通过 gd 宏访问。
  • bd (board_info):板级信息结构体,包含内存起始地址、大小、MAC 地址等。由 board_init_f 填充,board_init_r 使用。
  • 设备树 (FDT):Flattened Device Tree,描述硬件拓扑。在 board_init_f 中预留空间,board_init_r 中解析并应用。
gd 结构体关键字段示例
// include/asm-generic/global_data.h
typedef struct global_data {
    bd_t        *bd;             // 板级信息指针
    unsigned long   flags;       // 启动标志
    unsigned long   baudrate;    // 串口波特率
    unsigned long   cpu_clk;     // CPU 时钟频率
    unsigned long   bus_clk;     // 总线时钟频率
    unsigned long   ram_size;    // 检测到的 DRAM 大小
    unsigned long   relocaddr;   // 重定位后的 U-Boot 地址
    unsigned long   reloc_off;   // 重定位偏移量
    unsigned long   start_addr_sp; // 堆栈指针
    void        *jt;             // 跳转表(用于 API 接口)
    char        env_buf[32];     // 环境变量缓冲区
} gd_t;
调试建议:在 board_init_f 和 board_init_r 中插入 printf 打印 gd->ram_size 和 gd->relocaddr,可以快速验证内存检测与重定位是否正常。如果打印乱码,说明串口初始化或时钟配置有问题。

通过以上分析可以看出,U-Boot 的两阶段启动设计充分考虑了嵌入式系统的资源限制:汇编阶段以最小代码量完成硬件核心初始化,C 语言阶段则利用 SDRAM 的高速特性执行复杂的板级配置与内核加载。理解 start.S、board_init_f 和 board_init_r 的协作关系,是进行 U-Boot 移植与启动优化的基础。