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

拿到一份U-Boot源码,第一眼看到几十个目录,别慌。我刚开始接触U-Boot时也懵过——这玩意儿比Linux内核小多了,但目录结构设计得相当精巧。说白了,U-Boot的源码组织遵循一个核心原则:平台无关代码与平台相关代码分离。搞懂这个,你就抓住了它的命脉。

2.1 顶层目录结构一览

先看顶层目录。我习惯用 tree -L 1 扫一眼,大概长这样:

u-boot/
├── arch/          # 架构相关代码(ARM、RISC-V、x86等)
├── board/         # 板级相关代码(你的板子在这里)
├── common/        # 通用功能模块(命令、环境变量等)
├── configs/       # 默认配置文件(defconfig)
├── doc/           # 文档
├── drivers/       # 设备驱动(串口、网卡、存储等)
├── dts/           # 设备树源文件
├── fs/            # 文件系统支持
├── include/       # 头文件
├── lib/           # 通用库函数
├── net/           # 网络协议栈
├── scripts/       # 构建脚本
├── tools/         # 主机端工具
├── Makefile       # 顶层Makefile
└── Kconfig        # 顶层Kconfig配置入口

嗯,这里要注意:arch/ 和 board/ 是移植工作的主战场。其他目录虽然也重要,但大部分时候你不需要动它们。

2.2 关键子目录功能详解

arch/ —— 架构核心

这个目录按CPU架构分,比如 arch/arm/、arch/riscv/、arch/x86/。每个架构下又细分:

  • cpu/ —— CPU初始化、异常处理、缓存操作
  • lib/ —— 架构相关的库函数(如 memcpy 优化版)
  • include/ —— 架构相关的头文件
  • mach-xxx/ —— 具体SoC厂商的代码(如 mach-sunxi、mach-imx)

我在移植时,90%的汇编级调试都耗在 arch/arm/cpu/ 里。尤其是 lowlevel_init.S 和 start.S,那是U-Boot的第一口呼吸。

board/ —— 你的板子

每个板子一个目录,比如 board/ti/am335x/。里面通常包含:

  • board.c —— 板级初始化函数(board_init、dram_init等)
  • Kconfig —— 板级配置选项
  • MAINTAINERS —— 维护者信息
  • Makefile —— 编译规则
我的习惯: 拿到一块新板子,先找 board/ 下最接近的参考板,复制一份改。别从头写,那是给自己找罪受。

drivers/ —— 驱动大本营

U-Boot的驱动模型(Driver Model, DM)把所有驱动按类别放:

子目录 功能 常见文件
serial/ 串口驱动 ns16550.c, pl011.c
net/ 网卡驱动 designware.c, fec_mxc.c
mmc/ SD/MMC/eMMC驱动 sdhci.c, sunxi_mmc.c
spi/ SPI控制器驱动 spi-sunxi.c, spi-imx.c
i2c/ I2C控制器驱动 i2c-mux.c, mvtwsi.c
gpio/ GPIO驱动 gpio-sunxi.c, gpio-imx.c

我曾经在移植某款全志芯片时,串口死活不出数据。折腾了两天,最后发现是 drivers/serial/ 里的时钟配置宏没打开。嗯,从那以后我养成了先查驱动Kconfig的习惯。

common/ —— 通用功能

这里放的是U-Boot的“内脏”:

  • main.c —— 主循环(命令解析、执行)
  • command.c —— 命令注册与查找
  • env_common.c —— 环境变量操作
  • console.c —— 控制台输入输出
  • board_r.c —— 重定位后的板级初始化流程

你想想看,U-Boot启动第二阶段(重定位到RAM后)的初始化顺序,就在 board_r.c 的 init_sequence_r[] 数组里。想加自己的初始化函数?往这个数组里插就行。

其他重要目录

  • configs/ —— 板级默认配置,make xxx_defconfig 就靠它
  • dts/ —— 设备树源文件,现代U-Boot越来越依赖设备树
  • include/configs/ —— 板级头文件配置(老式配置方式,逐渐被Kconfig取代)
  • lib/ —— zlib、crc32、sha256等通用算法
  • net/ —— TFTP、NFS、DHCP等网络协议实现

2.3 Makefile与Kconfig构建系统

U-Boot的构建系统是从Linux内核学来的,Kconfig + Makefile 的组合。说白了,就是“配置-编译”两步走。

Kconfig:配置你的U-Boot

Kconfig文件定义了所有可配置选项,顶层 Kconfig 通过 source 语句层层包含:

# 顶层 Kconfig
mainmenu "U-Boot Configuration"

source "arch/Kconfig"
source "board/Kconfig"
source "drivers/Kconfig"
source "fs/Kconfig"
source "lib/Kconfig"
...

每个子目录下的Kconfig定义自己的选项。比如 drivers/serial/Kconfig:

config DM_SERIAL
    bool "Enable Driver Model for UART drivers"
    help
      Enable driver model for serial/UART drivers.
      启用驱动模型下的串口驱动。

config SYS_NS16550
    bool "NS16550 UART support"
    depends on DM_SERIAL
    help
      Support for NS16550-compatible UARTs.

配置时执行 make menuconfig,就会生成 .config 文件。这个文件会被顶层Makefile读取,决定编译哪些文件、定义哪些宏。

避坑: 我曾经在 make menuconfig 里改了串口波特率,但忘了保存。结果烧进去还是115200,排查了半天。记住:menuconfig 改完后一定要选 Save,或者直接 make savedefconfig。

Makefile:编译你的U-Boot

顶层Makefile是整个构建的入口。它的工作流程大致是:

  1. 读取 .config,确定要编译哪些功能
  2. 递归进入各子目录,执行子Makefile
  3. 链接所有目标文件,生成 u-boot 可执行文件
  4. 处理成最终烧录格式(如 u-boot.bin、u-boot.img)

子目录的Makefile写法很统一,比如 drivers/serial/Makefile:

obj-$(CONFIG_SYS_NS16550) += ns16550.o
obj-$(CONFIG_PL011_SERIAL) += pl011.o
obj-$(CONFIG_SUNXI_SERIAL) += sunxi_serial.o

这里的 obj-$(CONFIG_XXX) 是条件编译。如果 .config 里定义了 CONFIG_SYS_NS16550=y,就编译 ns16550.c。没定义?跳过。

我个人觉得,理解了这个机制,移植时加驱动就很简单了:把驱动文件放到对应目录,在Kconfig里加选项,在Makefile里加编译规则,完事。

2.4 知识结构图:U-Boot源码组织与构建流程

下面这张图把U-Boot的目录层级、关键子目录职责、以及从配置到编译的流程串起来了。建议你保存下来,移植时对照着看。

U-Boot 源码结构与构建系统 顶层目录结构 arch/ board/ drivers/ common/ configs/ CPU初始化 异常处理 板级初始化 板级配置 串口/网卡/MMC SPI/I2C/GPIO 命令解析 环境变量 默认配置 defconfig 构建系统流程 make xxx_defconfig make menuconfig make u-boot.bin 关键文件: .config → 配置结果 | u-boot.map → 链接映射 | u-boot.sym → 符号表 | u-boot.cfg → 预处理宏

2.5 移植时最常用的目录速查表

最后,我整理了一张速查表。移植时遇到问题,先看对应目录,能省不少时间。

移植任务 主要目录 关键文件
添加新板子 board/、configs/、include/configs/ board.c、xxx_defconfig、xxx.h
修改串口驱动 drivers/serial/、arch/arm/dts/ ns16550.c、xxx.dts
调整DDR参数 board/xxx/、arch/arm/mach-xxx/ dram.c、dram_init()
添加新命令 common/、cmd/ command.c、cmd/xxx.c
修改启动流程 common/、arch/arm/lib/ board_r.c、board_f.c
配置裁剪 Kconfig(各目录)、configs/ xxx_defconfig、.config