4. U-Boot源码结构:目录、关键文件与编译系统

U-Boot 的源码,说实话,我第一次打开的时候也懵了一下。目录多,文件杂,感觉像个大杂烩。但干 BSP 这么多年,我慢慢摸清了它的套路。说白了,它的结构很有章法,你只要抓住几个关键点,就能快速定位到你想要的东西。

这一节,我就带你捋一捋 U-Boot 的目录结构、三个核心文件,以及它的编译系统是怎么工作的。这些东西搞明白了,后面做分区整合你心里就有底了。

4.1 目录结构:U-Boot 的“骨架”

U-Boot 的顶层目录,我习惯把它分成几大类:平台相关、通用功能、驱动、工具脚本。你想想看,一个 Bootloader 要支持那么多板子,代码肯定得分层。

下面这个表,是我觉得最核心的几个目录,你重点记住它们:

目录名 作用 我的备注
arch/ 存放 CPU 架构相关代码,比如 ARM、RISC-V、x86 我们做 ARM 的,主要看 arch/arm/
board/ 存放具体板级代码,每个板子一个子目录 你新加一个板子,主要就是在这里动手
common/ U-Boot 的通用核心逻辑,比如命令行、环境变量 main.c 就在这里,后面会细讲
drivers/ 各种外设驱动,串口、网卡、Flash、MMC 等等 驱动模型化之后,这里结构清晰了很多
include/ 头文件,特别是 configs/ 下的板级配置 改配置经常要动这里
lib/ 通用库函数,比如字符串操作、CRC 校验 嗯,这里的东西比较底层
net/ 网络协议栈,TFTP、NFS 都靠它 调试阶段,网络功能是救命稻草
cmd/ 命令行命令的实现,比如 md、mw、bootm 你想加个自定义命令,就在这里加
scripts/ 编译脚本、Kconfig 语法文件 Kconfig/Makefile 的辅助文件
我的小习惯: 拿到一个新板子的 U-Boot 源码,我第一件事就是看 board/ 和 include/configs/ 这两个目录。一个管硬件初始化,一个管功能配置。这两个搞清楚了,板子就跑起来了。

4.2 关键文件:U-Boot 的“灵魂”

目录是骨架,那文件就是灵魂。U-Boot 启动过程中,有三个文件你无论如何都绕不开。我按启动顺序给你讲讲。

4.2.1 start.S:一切从这里开始

这个文件在 arch/arm/cpu/armv7/ 或者类似的路径下。它是 U-Boot 的第一行代码,用汇编写的。

它干了什么事?说白了就三件:

  • 设置 CPU 模式:从启动的 SVC 模式切到我们需要的模式,关中断。
  • 初始化关键硬件:比如关 MMU 和 Cache、设置栈指针 SP。这时候 C 环境还没准备好,只能用汇编。
  • 跳转到 C 代码:最后调用 _main,进入 C 语言的世界。

我记得有一次,板子上电就挂,死活跑不起来。最后用 JTAG 单步跟踪,发现是 start.S 里栈指针设错了,导致后面调用 C 函数时栈溢出。嗯,从那以后,我每次移植都先检查 SP 的值。

// 一个简化的 start.S 片段,感受一下
reset:
    /* 设置 CPU 为 SVC32 模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0xd3
    msr cpsr, r0

    /* 关中断,设置栈指针 */
    ldr sp, =CONFIG_SYS_INIT_SP_ADDR
    bic sp, sp, #7 /* 8 字节对齐 */

    /* 跳转到 C 语言的 board_init_f */
    bl  board_init_f

4.2.2 board.c:板级初始化的“总指挥”

这个文件在 board/ 目录下,每个板子都有自己的 board.c。它是板级初始化的核心。

它主要干了这些事:

  • board_init_f():前期初始化,比如初始化串口、定时器、DRAM 控制器。这时候 DRAM 还没好,代码在 SRAM 里跑。
  • board_init_r():后期初始化,DRAM 已经可用了,把 U-Boot 自身从 Flash 搬到 DRAM 里,然后初始化各种外设。
  • misc_init_r():一些杂项初始化,比如设置板子 ID、读取 EEPROM。
注意: board_init_f 和 board_init_r 的执行环境完全不同。前者在 SRAM 里,栈空间有限;后者在 DRAM 里,空间充裕。你在写代码时,千万别把大数组定义在 board_init_f 里,否则栈会爆。我曾经就犯过这个错,查了整整一天。

4.2.3 common/main.c:命令行交互的“心脏”

这个文件在 common/ 目录下。它实现了 U-Boot 的命令行主循环。

流程很简单:

  1. 初始化完所有硬件后,进入 main_loop()。
  2. 检查有没有预设的启动命令(比如 bootcmd 环境变量)。如果有,直接执行。
  3. 如果没有,或者用户按了任意键,就进入命令行交互模式,等待用户输入命令。

你想想看,我们平时敲的 printenv、setenv、bootm 这些命令,最终都是通过 main.c 里的命令解析器来调度的。

// main_loop 的简化逻辑
void main_loop(void)
{
    char *s;

    // 检查 bootdelay 和 bootcmd
    s = env_get("bootcmd");
    if (s != NULL) {
        // 执行预设的启动命令
        run_command_list(s, -1, 0);
    }

    // 进入命令行循环
    for (;;) {
        // 读取用户输入
        len = cli_readline(CONFIG_SYS_PROMPT);
        // 解析并执行命令
        if (len > 0)
            run_command(lastcommand, 0);
    }
}

4.3 编译系统:Kconfig / Makefile

U-Boot 的编译系统,早期用的是传统的 make xxx_config 方式。后来为了统一管理,引入了 Kconfig 机制。现在你看到的 U-Boot,基本都是 Kconfig + Makefile 的组合。

4.3.1 Kconfig:配置的“菜单”

Kconfig 文件定义了各种配置选项,比如:

  • 选择 CPU 架构(ARM、RISC-V)
  • 选择板子型号(TARGET_MY_BOARD)
  • 开启某个功能(CONFIG_CMD_NET)

你执行 make menuconfig 时,看到的那个图形界面,就是 Kconfig 生成的。它把所有的配置选项组织成树形结构,方便你开关。

// 一个 Kconfig 片段示例
menu "Board selection"
    config TARGET_MY_BOARD
        bool "Support my_board"
        select ARM
        select DM
        help
          This is my custom board.
endmenu

config CMD_NET
    bool "networking commands"
    default y
    help
      Enable networking commands like tftp, ping.

4.3.2 Makefile:编译的“流水线”

Makefile 负责把配置变成实际的二进制文件。U-Boot 的 Makefile 体系很庞大,但核心逻辑就几条:

  • 顶层 Makefile:定义编译目标、链接脚本、生成 u-boot.bin。
  • 子目录 Makefile:每个目录下的 Makefile 或 Kbuild,告诉编译系统这个目录下有哪些文件要编译。
  • 配置生成:make xxx_defconfig 会根据 configs/ 下的默认配置,生成 .config 文件。

我个人建议,你不需要把整个 Makefile 看完。你只需要知道:

  • 怎么加一个新文件:在对应目录的 Makefile 里加一行 obj-y += my_file.o。
  • 怎么加一个新驱动:在 drivers/ 下建目录,写 Kconfig 和 Makefile。
核心流程总结:
  1. make xxx_defconfig → 生成 .config(基于 Kconfig)
  2. make → 读取 .config,编译所有选中的源文件(基于 Makefile)
  3. 输出 u-boot.bin、u-boot.srec 等文件

为了让你更直观地理解 U-Boot 的源码结构和编译流程,我画了一张图:

U-Boot 源码结构与编译流程 源码结构(目录) arch/ — CPU架构代码 board/ — 板级代码 common/ — 通用逻辑 drivers/ — 外设驱动 include/ — 头文件/配置 cmd/ — 命令行命令 关键文件(启动顺序) 1. start.S (汇编入口) ↓ 设置CPU/栈 2. board.c (板级初始化) ↓ 初始化DRAM/外设 3. common/main.c (命令行) ↓ 进入主循环 编译系统 Kconfig (配置菜单) ↓ make menuconfig .config (配置文件) ↓ make Makefile (编译规则) ↓ 链接 编译输出产物 u-boot.bin | u-boot.srec | u-boot.map 核心流程 make xxx_defconfig → 生成 .config → make → 编译链接 → u-boot.bin

这张图把 U-Boot 的源码结构、关键文件和编译流程串在了一起。你从左边看起,理解目录是干什么的;然后看中间,知道启动时哪几个文件在干活;最后看右边,明白怎么把代码变成二进制。

嗯,这一节的内容就到这里。U-Boot 的源码结构,说白了就是“平台相关 + 通用功能 + 编译系统”这三板斧。你只要把 start.S、board.c、main.c 这三个文件搞明白,再学会用 Kconfig 和 Makefile,后面做分区整合就顺手多了。