2. U-Boot源码结构:顶层目录结构、关键子目录功能、Makefile与Kconfig构建系统

好,咱们正式开始啃U-Boot的源码。很多新手拿到U-Boot第一反应就是——这目录也太多了吧?别慌,我刚开始看的时候也晕。其实它的结构非常清晰,说白了就是一套「硬件抽象 + 板级适配 + 通用框架」的组合。今天我就带你把这套结构拆开来看。

2.1 顶层目录结构:一眼看穿U-Boot的家底

你解压U-Boot源码后,第一眼看到的顶层目录大概有20多个。但真正核心的,其实就那么几个。我习惯把它们分成三类:

类别 目录名 一句话说明
核心框架 common/ 命令解析、环境变量、控制台等通用逻辑
核心框架 include/ 全局头文件、配置宏、数据结构定义
核心框架 lib/ 字符串、CRC、加密等基础库函数
硬件适配 board/ 各家开发板的板级初始化代码
硬件适配 cpu/ 不同CPU架构的启动与异常处理
硬件适配 drivers/ 串口、网卡、Flash、I2C等外设驱动
构建系统 configs/ 各板子的默认配置文件(defconfig)
构建系统 arch/ 架构相关的汇编启动代码和链接脚本
构建系统 scripts/ Kconfig解析、dtc编译等构建脚本
我的习惯: 拿到一个新板子,我第一件事就是去 board/ 和 configs/ 目录下找同名文件夹。这俩目录基本决定了这块板子能不能跑起来。

2.2 关键子目录功能详解

2.2.1 board/ —— 板级灵魂

这个目录下每个子文件夹对应一款开发板或一个系列。比如 board/ti/am335x/ 就是TI的AM335x系列。里面通常有:

  • board.c:板级初始化函数 board_init_f() 和 board_init_r(),负责时钟、DDR、GPIO的早期配置。
  • mux.c:引脚复用配置,说白了就是告诉芯片哪个脚当串口、哪个脚当网口。
  • Kconfig:板级特有的配置选项,比如DDR型号、网卡PHY地址。

我记得有一次移植一块国产板子,死活起不来串口。查了两天,最后发现是 board.c 里漏配了一个PLL分频系数。嗯,这种坑踩过一次就记住了。

2.2.2 cpu/ —— 架构核心

这里按CPU架构分目录,比如 cpu/armv7/、cpu/armv8/、cpu/mips/。里面最关键的是:

  • start.S:汇编入口,设置异常向量表、初始化C运行环境、跳转到 board_init_f()。
  • cpu.c:CPU核心初始化,比如使能ICache/DCache、设置SMP启动。
  • interrupts.c:中断控制器初始化。
注意: 如果你要调试启动阶段的死机问题,cpu/ 下的汇编代码是第一个要盯的地方。我曾经在 start.S 里加过GPIO点灯来定位卡死位置,这招虽然土,但真管用。

2.2.3 drivers/ —— 外设驱动仓库

这个目录下按设备类型分:serial/、net/、spi/、mmc/、usb/ 等。每个驱动通常包含:

  • xxx.c:驱动实现,遵循U-Boot的驱动模型(DM)。
  • Kconfig:驱动相关的配置选项。
  • Makefile:编译规则。

U-Boot现在的驱动模型(Driver Model)挺成熟的。你写一个新驱动,只需要实现几个回调函数,然后注册到udevice框架里就行。我建议你调试网卡驱动时,先在 drivers/net/ 里找一个现成的、同芯片型号的驱动对照着看,效率高很多。

2.2.4 common/ —— 命令与框架

这个目录是U-Boot的「大脑」。里面包含了:

  • command.c:命令注册与解析框架。你敲的每个命令,最终都会走到这里来匹配。
  • console.c:控制台输入输出管理。
  • env_common.c:环境变量读写接口。
  • main.c:U-Boot主循环,也就是那个等待你敲命令的shell。

如果你要添加自定义命令,只需要在 common/ 下新建一个 cmd_xxx.c,然后用 U_BOOT_CMD 宏注册就行。我经常用这个方法来加一些调试用的内存读写命令,方便定位问题。

2.3 Makefile与Kconfig构建系统

U-Boot的构建系统是从Linux内核抄来的,说白了就是 Kconfig + Makefile + 递归编译 这套组合拳。理解它,你才能知道编译时到底选了哪些文件、哪些功能。

2.3.1 Kconfig:配置的「开关面板」

Kconfig文件定义了一堆配置选项,比如 CONFIG_CMD_MEMORY、CONFIG_DM_SERIAL。你执行 make menuconfig 时看到的图形界面,就是由这些Kconfig文件生成的。

配置选项的传递路径是这样的:

  1. Kconfig 定义选项 → 生成 .config 文件
  2. .config 被转换为 include/autoconf.mk(给Makefile用)
  3. 同时生成 include/generated/autoconf.h(给C代码用)

举个例子,你在 configs/xxx_defconfig 里写了 CONFIG_CMD_MEMORY=y,那么编译时 common/cmd_memory.c 就会被编译进去。

避坑指南: 我曾经在调试时发现某个命令编译进去了但运行报错,查了半天发现是Kconfig里有个 depends on 依赖没满足,导致宏定义虽然存在,但实际代码被条件编译跳过了。所以,改配置后一定要检查 include/autoconf.mk 里宏是否真的被定义了。

2.3.2 顶层Makefile:编译的「总指挥」

顶层Makefile负责整个编译流程。它的核心逻辑是:

  • 解析 .config,确定要编译哪些子目录。
  • 递归进入每个子目录,执行子目录的Makefile。
  • 链接所有目标文件,生成 u-boot、u-boot.bin、u-boot.img 等镜像。

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

# drivers/serial/Makefile 示例
obj-$(CONFIG_DM_SERIAL) += serial-uclass.o
obj-$(CONFIG_SYS_NS16550) += ns16550.o
obj-$(CONFIG_PL011_SERIAL) += pl011.o

这里的 obj-$(CONFIG_XXX) 就是条件编译。如果 CONFIG_SYS_NS16550 为 y,就把 ns16550.o 编译进去。

2.3.3 构建流程速览

我画了一张图,帮你把整个构建流程串起来:

U-Boot 构建系统核心流程 configs/xxx_defconfig make menuconfig .config include/autoconf.mk autoconf.h Makefile 递归编译 u-boot.bin 配置入口 defconfig 是板级默认配置 menuconfig 可交互修改 配置生成 .config 是最终配置快照 自动生成 .mk 和 .h 文件 编译执行 Makefile 根据宏决定编译 哪些 .c 文件参与链接

2.4 实战:如何快速定位一个文件属于哪个目录

你拿到一个U-Boot源码,想找一个功能对应的代码文件,怎么找?我一般用这招:

  1. 看命令名: 比如 md 命令,去 common/ 下找 cmd_memory.c。
  2. 看驱动类型: 比如网卡驱动,去 drivers/net/ 下找对应芯片型号的 .c 文件。
  3. 看配置宏: 在源码根目录执行 grep -r "CONFIG_XXX" --include="*.c" --include="*.h",直接定位引用位置。
  4. 看链接脚本: 如果想知道某个函数被放在镜像的哪个段,去 arch/arm/cpu/u-boot.lds 里看链接脚本。
小技巧: 我习惯在 include/configs/ 下找到板子的头文件,比如 am335x_evm.h。这个文件里定义了板级所有的宏开关,相当于板子的「配置清单」。看懂了它,你就知道这块板子开了哪些功能、用了哪些驱动。

好了,U-Boot的源码结构就讲到这里。你只要记住:board/ 管板子、cpu/ 管架构、drivers/ 管外设、common/ 管命令、Kconfig/Makefile 管编译。这套逻辑搞清楚了,后面调试的时候你就能快速找到该改哪个文件、该看哪段代码。