2. U-Boot 源码结构解析:关键目录、配置系统与编译流程

好,咱们直接进入正题。U-Boot 这玩意儿,说白了就是一个“超级引导小程序”。你把它烧到 Flash 里,上电后它先跑起来,初始化硬件,然后加载内核。要玩转它,你得先知道它的“骨架”长什么样。

我刚开始接触 U-Boot 的时候,第一反应是:这目录也太多了吧?别慌,其实核心的就那么几个。咱们今天就把它的源码结构、配置系统和编译流程彻底捋一遍。

2.1 关键目录:别被文件夹淹没了

U-Boot 的源码根目录下,大概有几十个文件夹。但你记住,真正干活的主要就这几个:

目录名 作用 我的经验
arch/ 架构相关代码。ARM、x86、RISC-V 都在这里。 调试启动卡死,90% 的问题出在这里的 start.S。
board/ 板级初始化代码。你的板子特有的东西。 比如 DDR 参数、GPIO 配置,都在这里。
common/ 通用命令和框架。比如 cmd/ 里的命令实现。 你想加个自定义命令?改这里。
drivers/ 驱动。串口、网卡、Flash、MMC 等等。 网卡驱动不通?先查这里。
include/ 头文件。特别是 configs/ 下的板级配置。 配置宏定义都在 include/configs/ 里。
lib/ 库函数。比如字符串处理、CRC 校验。 一般不用动,但调试时偶尔会进去看。
tools/ 主机端工具。比如 mkimage。 制作内核镜像时必用。
我的习惯:拿到一个新板子,我第一件事就是看 board/ 和 include/configs/。这两个目录决定了你的板子能不能跑起来。

2.2 配置系统:Kconfig 与板级头文件

U-Boot 的配置系统经历过一次“进化”。老版本用的是“板级头文件”方式,新版本引入了 Kconfig。现在两者并存,你得都懂。

2.2.1 老方式:include/configs/ 头文件

这种方式很直接。每个板子有一个头文件,比如 include/configs/rpi.h。里面全是宏定义:

/* 一个典型的板级配置头文件片段 */
#define CONFIG_SYS_TEXT_BASE    0x00200000
#define CONFIG_SYS_MALLOC_LEN   (32 << 20)  /* 32 MB */
#define CONFIG_BOOTCOMMAND      "fatload mmc 0:1 0x1000000 zImage; bootz"

嗯,这里要注意:这种方式的优点是简单粗暴,缺点是“配置爆炸”。你想想看,几百个宏堆在一个文件里,维护起来很痛苦。

2.2.2 新方式:Kconfig

Kconfig 是 Linux 内核那套配置系统。U-Boot 从 2014 年开始引入。它把配置项组织成树形结构,支持依赖和选择。

配置流程是这样的:

  1. defconfig:板子的默认配置。比如 configs/rpi_3_defconfig。
  2. Kconfig 文件:分布在各个目录下,定义配置项。
  3. .config:最终生成的配置文件。
  4. autoconf.mk:从 .config 自动生成的 Makefile 片段。
关键点:Kconfig 和头文件是“叠加”的。Kconfig 控制“是否编译某个模块”,头文件控制“模块的参数和行为”。两者缺一不可。

2.3 编译流程:从源码到二进制

编译 U-Boot 其实就三步。但每一步背后都有讲究。我带你走一遍。

2.3.1 第一步:配置

# 以树莓派 3 为例
make rpi_3_defconfig

这条命令会做两件事:

  • 从 configs/ 目录下找到 rpi_3_defconfig。
  • 生成 .config 文件。

你可以用 make menuconfig 来图形化修改配置。我个人习惯是先跑 defconfig,再用 menuconfig 微调。

2.3.2 第二步:编译

# 编译
make -j4

编译过程大致是这样的:

  1. 预处理:处理 #include 和宏定义。
  2. 编译:生成 .o 文件。
  3. 链接:把所有 .o 链接成 u-boot 文件。
  4. 格式转换:生成 u-boot.bin(纯二进制)和 u-boot.img(带 U-Boot 头)。
我曾经踩过的坑:编译时加了 -j8,结果链接报错。原因是某些 .o 文件还没生成就开始链接了。后来我改成 -j4 就没事了。多核编译虽好,但别贪心。

2.3.3 第三步:链接脚本

链接脚本 u-boot.lds 决定了 U-Boot 的内存布局。它通常在 arch/arm/cpu/ 或 board/ 目录下。

一个典型的链接脚本片段:

SECTIONS
{
    . = 0x00200000;  /* 起始地址 */
    .text : {
        *(.text*)
    }
    .rodata : {
        *(.rodata*)
    }
    .data : {
        *(.data*)
    }
    .bss : {
        *(.bss*)
    }
}

这里要注意:CONFIG_SYS_TEXT_BASE 必须和链接脚本的起始地址一致。否则,U-Boot 跑起来就会“飞掉”。

2.4 整体流程:一张图说清楚

下面这张图展示了从源码到二进制、再到烧录的完整流程。我建议你把它存下来,以后遇到编译问题,对着图查。

U-Boot 编译与配置流程 U-Boot 编译与配置流程 源码目录 板级配置头文件 Kconfig 文件 make xxx_defconfig make menuconfig .config make -j4 u-boot.bin u-boot.img u-boot.srec 烧录到 Flash / SD 卡 说明 .config 决定编译哪些模块 链接脚本决定内存布局

2.5 避坑指南:我踩过的几个坑

坑一:配置不一致
我曾经在 include/configs/ 里定义了一个宏,但 Kconfig 里也有同名选项。结果编译出来,宏生效了,Kconfig 的没生效。排查了半天。后来我统一用 Kconfig,头文件只放参数。
坑二:链接地址不对
有一次我改了 CONFIG_SYS_TEXT_BASE,但忘了改链接脚本。结果 U-Boot 启动后,直接跑飞。用 JTAG 一看,PC 指针指向了错误地址。嗯,从那以后,我每次改地址都会检查链接脚本。
我的建议:如果你用的是新板子,先找一个参考板(比如同款 SoC 的开发板),复制它的配置,然后逐步修改。别从头写,太容易出错了。

2.6 小结

U-Boot 的源码结构其实不复杂。你只要记住:arch/ 管 CPU,board/ 管板子,include/configs/ 管配置,drivers/ 管外设。配置系统是 Kconfig + 头文件双轨制。编译流程就是配置、编译、链接三步。