3. U-Boot源码结构分析:目录结构、关键文件功能解析

做嵌入式底层开发,U-Boot 源码是你绕不开的一座山。我第一次接触 U-Boot 时,面对那一堆文件夹,说实话有点懵。但摸爬滚打几年后,我发现它的目录结构其实很有章法。今天我就带你把它拆开看看。

3.1 顶层目录结构:一眼看穿骨架

U-Boot 的顶层目录,说白了就是按功能模块划分的。我习惯把它分成三大块:平台相关、通用驱动、工具脚本。

目录名 核心作用 我眼中的关键点
arch/ CPU架构相关代码 ARM、RISC-V、x86 等,启动入口就在这里
board/ 具体板级初始化 每个开发板一个文件夹,硬件差异全在这
drivers/ 外设驱动 串口、网卡、MMC、NAND 等,移植大头
common/ 通用功能模块 命令行、环境变量、HUSH shell 等
include/ 头文件与配置 configs/ 下是板级配置,经常改
lib/ 库函数 字符串、压缩、加密等基础库
net/ 网络协议栈 TFTP、NFS、DHCP 等,调试必备
tools/ 主机端工具 mkimage、fdt 处理等,打包镜像用
我的习惯:拿到一个新板子,先看 board/ 和 include/configs/ 这两个目录。硬件差异基本都体现在这里,其他地方很少动。

3.2 关键文件之一:start.S —— 一切从这里开始

U-Boot 启动的第一条指令,就在 arch/arm/cpu/armv7/start.S(ARM 为例)。这个文件是纯汇编写的,负责把 CPU 从复位状态带到 C 语言环境。

它干了这几件大事:

  1. 设置异常向量表 —— 中断、复位、未定义指令等入口
  2. 切换到 SVC 模式 —— 关中断,确保安全
  3. 初始化 CPU 关键寄存器 —— CP15、MMU、Cache 等
  4. 设置栈指针 —— 为调用 C 函数做准备
  5. 清零 BSS 段 —— 全局变量初始化为 0
  6. 跳转到 board_init_f —— 进入 C 语言世界

我曾经在一个项目中,板子上电后 U-Boot 死活跑不起来。折腾了两天,最后发现是 start.S 里异常向量表地址没对齐。嗯,ARM 要求向量表必须 32 字节对齐,少一个字节都不行。

避坑指南:start.S 里的链接地址(_start)必须和链接脚本(u-boot.lds)一致。我曾经见过有人改了链接脚本忘了改 start.S,结果跳转地址全错,板子直接变砖。

3.3 关键文件之二:board.c —— 板级初始化的心脏

board.c 位于 board/你的板子/ 目录下。它负责板级硬件初始化,比如:

  • 时钟配置(PLL、分频)
  • DDR 初始化(时序、参数)
  • GPIO 复用设置
  • 串口引脚配置
  • 板级特定外设上电

这个文件里最重要的函数是 board_init() 和 dram_init()。前者做板级通用初始化,后者告诉 U-Boot 你的 DDR 有多大。

// 一个典型的 board_init 示例
int board_init(void)
{
    /* 使能外设时钟 */
    enable_peripheral_clocks();

    /* 配置调试串口引脚 */
    setup_uart_pins();

    /* 初始化板级 LED,用于调试指示 */
    gpio_direction_output(LED_PIN, 1);

    return 0;
}

int dram_init(void)
{
    /* 告诉 U-Boot DDR 大小,单位是字节 */
    gd->ram_size = PHYS_SDRAM_1_SIZE;
    return 0;
}
关键点:DDR 初始化参数如果配错了,U-Boot 根本跑不起来。我建议先用厂商提供的 DDR 训练工具跑一遍,把参数抄过来,别自己瞎猜。

3.4 关键文件之三:main.c —— 命令行与交互的入口

common/main.c 是 U-Boot 进入命令行模式前的最后一道关卡。它负责:

  • 调用 board_init_r() 做运行时初始化
  • 初始化各种外设驱动(网卡、存储等)
  • 加载并执行启动脚本(bootcmd)
  • 进入主循环,等待用户输入命令

main.c 里有个函数叫 main_loop(),它是个死循环。说白了,U-Boot 启动后就在这个循环里一直转,等着你敲命令或者自动执行 bootcmd。

我记得有一次调试,板子启动后自动进入了 Linux,但我想停在 U-Boot 命令行。查了半天,发现是 bootdelay 环境变量设成了 0。改成 3 秒后,终于能在启动时按任意键打断了。

3.5 启动流程全景图

下面这张图,把 U-Boot 从复位到命令行的完整流程串起来了。我建议你把它存下来,调试时对照着看。

U-Boot 启动流程全景图 start.S (汇编入口) board_init_f (早期初始化) 重定位 (Relocate) board_init_r (运行时初始化) main_loop (命令行循环) 自动启动 Linux 等待用户输入命令 关键文件:start.S → board.c → main.c,分别对应汇编、板级、命令行三个阶段

3.6 三个关键文件的协作关系

这三个文件不是孤立的,它们通过函数调用链串在一起。我画了个简化的调用关系:

复位入口 (start.S)
    ↓
bl board_init_f      (board.c 中实现)
    ↓
重定位 U-Boot 自身
    ↓
bl board_init_r      (board.c 中实现)
    ↓
main_loop()          (main.c 中实现)
    ↓
run_command()        (解析并执行命令)
调试技巧:如果 U-Boot 在某个阶段卡住了,加个 GPIO 翻转或者串口打印。我习惯在 board_init_f 开头点亮一个 LED,board_init_r 开头再点亮另一个。这样一眼就能看出卡在哪一步。

3.7 总结:记住这三句话

  • start.S —— 把 CPU 从「裸金属」带到「C 语言」
  • board.c —— 把板子从「死硬件」带到「活系统」
  • main.c —— 把 U-Boot 从「初始化」带到「交互」

搞懂了这三个文件,U-Boot 源码对你来说就不再是黑盒了。下次移植新板子,你就知道该从哪里下手。嗯,先改 start.S 的链接地址,再调 board.c 的 DDR 参数,最后在 main.c 里配好启动命令。这条路,我走过很多遍了。