第2章:U-Boot源码结构——顶层目录解析与构建系统

好,咱们直接进入正题。上一章聊了Bootloader到底是个什么角色,这一章我们把手伸进U-Boot的源码里,看看它到底是怎么组织的。说实话,我第一次打开U-Boot源码树的时候,也有点懵——目录太多了。但别怕,摸清楚规律之后,你会发现它的结构其实非常清晰。

2.1 顶层目录结构总览

U-Boot的顶层目录,说白了就是一套“按功能分家”的布局。我习惯把它分成三大块:平台相关、通用功能、构建系统。下面这个表帮你快速建立全局印象:

目录/文件 一句话说明 我常去翻它的场景
arch/ CPU架构相关代码(ARM、RISC-V、x86等) 移植新板子时,先看arch/arm/
board/ 具体板级初始化、外设配置 调试DDR参数时,必改这里
common/ 核心框架、命令解析、环境变量 想改启动流程时,翻common/
drivers/ 各类外设驱动(串口、网卡、MMC等) 加新硬件时,先找对应子目录
include/ 全局头文件、配置宏、数据结构 查寄存器定义时,翻include/
net/ 网络协议栈(TFTP、NFS、DHCP) 调试网络启动时,改这里
cmd/ 命令行命令的实现 想加自定义命令时,看这里
lib/ 通用库函数(字符串、CRC、加密) 写工具函数时,先看看有没有现成的
fs/ 文件系统支持(ext4、FAT、UBIFS) 需要从存储读文件时,用这里
doc/ 文档和README 遇到新特性时,先翻doc/
tools/ 主机端工具(mkimage、env工具) 制作镜像时,用tools/下的工具
我的小习惯:刚接触一个新板子时,我通常会先看 board/ 和 arch/ 两个目录。因为板级初始化代码和CPU启动代码,是移植工作的起点。其他目录虽然也重要,但可以后面再细看。

2.2 核心目录深度解析

2.2.1 arch/ —— CPU架构的“家”

arch/ 目录下按CPU架构分文件夹。比如 arch/arm/、arch/riscv/、arch/x86/。每个架构下面又细分:

  • cpu/:CPU核的启动代码、异常处理、缓存操作。比如ARM的 cpu/armv7/、cpu/armv8/。
  • include/:架构相关的头文件,比如寄存器地址、中断向量表定义。
  • lib/:架构相关的库函数,比如内存拷贝、cache刷新。
  • dts/:设备树源文件(Device Tree Source),描述硬件拓扑。

我记得有一次移植一个新ARM芯片,发现启动到一半就挂了。查了半天,原来是 arch/arm/cpu/armv8/start.S 里的异常向量表没对齐。嗯,这种底层细节,真的只有翻 arch/ 才能找到。

2.2.2 board/ —— 板级定制的“战场”

board/ 目录按厂商和板子名称组织。比如 board/ti/am335x/、board/rockchip/evb_rk3399/。每个板子目录下通常包含:

  • board.c:板级初始化函数,比如 board_init_f()、board_init_r()。
  • Kconfig:板级配置选项。
  • MAINTAINERS:维护者信息。
  • Makefile:编译规则。
避坑指南:我曾经在 board/ 里改DDR时序参数,改完发现编译不过。后来才意识到,board/ 下的代码是跟 arch/ 里的CPU初始化紧密耦合的。改板级代码时,一定要同步检查 arch/ 里的启动流程。否则,很容易出现“板子初始化到一半就崩了”的情况。

2.2.3 common/ —— U-Boot的“大脑”

common/ 目录是U-Boot的核心逻辑所在。这里包含了:

  • main.c:U-Boot的主循环,命令解析、执行。
  • cli.c:命令行接口实现。
  • env_common.c:环境变量管理。
  • bootm.c:启动内核的核心函数。
  • autoboot.c:自动启动逻辑。

说白了,你敲的每一个U-Boot命令,最终都会落到 common/ 里的某个函数去处理。我调试启动流程时,最喜欢在 common/main.c 里加打印,看看到底卡在哪一步。

2.2.4 drivers/ —— 外设驱动的“仓库”

drivers/ 目录按设备类型分文件夹:

子目录 包含的驱动
serial/ 串口驱动(UART)
net/ 网卡驱动(MAC、PHY)
mmc/ MMC/SD卡驱动
spi/ SPI总线及设备驱动
i2c/ I2C总线及设备驱动
usb/ USB主机/设备驱动
gpio/ GPIO驱动

每个驱动目录下,通常有 Kconfig 和 Makefile,用来控制哪些驱动被编译进U-Boot。你想想看,如果板子上用的是某个冷门网卡芯片,大概率就是在这里加驱动。

2.2.5 include/ —— 全局配置的“枢纽”

include/ 目录里最重要的文件是 configs/ 子目录下的板级配置头文件。比如 include/configs/am335x_evm.h。这些头文件定义了:

  • 内存布局(DDR地址、大小)
  • 外设基地址(UART、MMC、网卡)
  • 环境变量默认值
  • 启动命令(bootcmd)

另外,include/ 下还有 linux/、u-boot/ 等子目录,存放各种数据结构定义和宏。我经常在 include/linux/mtd/ 里查NAND Flash的分区定义。

2.3 构建系统:Makefile与Kconfig

U-Boot的构建系统,说白了就是“Kconfig + Makefile”的组合。Kconfig负责“选什么”,Makefile负责“怎么编”。

2.3.1 Kconfig —— 配置的“菜单”

Kconfig文件分布在各个目录下,比如 arch/arm/Kconfig、drivers/net/Kconfig。它们定义了一系列配置选项,比如:

config CMD_BOOTM
    bool "Enable bootm command"
    default y
    help
      This enables the 'bootm' command which is used to boot
      an operating system from memory.

执行 make menuconfig 时,U-Boot会读取所有Kconfig文件,生成一个配置菜单。你勾选或取消选项,最终会生成 .config 文件。

关键点:Kconfig的层级关系决定了配置的依赖。比如,你选了 CMD_NET,它可能会自动依赖 NET 和 CMD_BOOTP。这种依赖关系,在Kconfig里用 depends on 和 select 来控制。

2.3.2 Makefile —— 编译的“流水线”

U-Boot的Makefile体系是递归的。顶层 Makefile 负责:

  • 读取 .config,确定哪些模块被选中。
  • 递归进入各个子目录,执行子目录的 Makefile。
  • 链接所有目标文件,生成 u-boot 可执行文件。

每个子目录的 Makefile 通常长这样:

obj-y += board.o
obj-$(CONFIG_CMD_BOOTM) += bootm.o
obj-$(CONFIG_CMD_NET) += net.o

这里 obj-y 表示总是编译,obj-$(CONFIG_XXX) 表示条件编译。如果 .config 里定义了 CONFIG_CMD_BOOTM=y,那么 bootm.o 就会被编译进去。

我的经验:调试时,如果想临时加一个文件编译,可以直接在对应目录的 Makefile 里加一行 obj-y += my_test.o。但记得调试完删掉,否则提交代码时会出问题。我曾经就因为这个,被同事吐槽过。

2.4 构建流程与配置关系图

下面这张SVG图,帮你理清U-Boot从配置到编译的完整流程:

U-Boot构建流程与配置关系图 U-Boot构建流程与配置关系 Kconfig 配置 make menuconfig .config 配置选项定义 Makefile 编译规则 生成 控制 编译 make u-boot 最终可执行文件 说明: Kconfig 定义配置选项 → .config 记录选中项 → Makefile 根据 .config 条件编译 → 最终链接生成 u-boot 镜像。 整个流程是“配置驱动编译,编译生成镜像”。

2.5 实战:如何快速定位代码

最后,分享几个我常用的“找代码”技巧:

  1. 找板级初始化:先看 board/ 目录,找到对应板子的 board.c。里面通常有 board_init_f() 和 board_init_r()。
  2. 找CPU启动代码:去 arch/ 目录,找到对应架构的 start.S。这是CPU上电后执行的第一段代码。
  3. 找命令实现:比如想查 bootm 命令,去 cmd/ 目录找 bootm.c。
  4. 找驱动:根据设备类型,去 drivers/ 下找对应子目录。比如网卡驱动在 drivers/net/。
  5. 找配置宏:先在 include/configs/ 下找板级头文件,如果找不到,再去 include/ 下全局搜索。
避坑指南:我曾经为了找一个宏定义,翻了半天 include/ 目录,结果发现它定义在 arch/arm/include/ 里。所以,搜索时别只盯着一个目录,多用 grep -r 全局搜索。U-Boot的代码量不小,但只要你掌握了目录结构,定位问题会快很多。