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

好,咱们直接进入正题。上一章聊了U-Boot是什么,这一章咱们把它的源码扒开看看。说白了,搞移植的第一步,就是得知道代码放在哪、怎么组织的。我刚开始接触U-Boot时,面对那一堆目录也懵过,后来慢慢摸清了门道。

2.1 顶层目录结构:一眼看穿骨架

你拿到U-Boot源码后,第一件事就是ls一下顶层。我建议你记住几个核心目录,其他的可以边用边查。

目录/文件 作用 我的备注
arch/ 架构相关代码(ARM、RISC-V、x86等) 移植时最常改的地方
board/ 具体板级初始化代码 每个板子一个文件夹
common/ 通用功能模块(命令、环境变量等) 一般不动,但得知道
drivers/ 各种外设驱动(串口、网卡、MMC等) 调驱动时天天泡这里
include/ 头文件、配置宏定义 configs/子目录放板级配置
lib/ 库函数(字符串、压缩、加密等) 很少需要改
net/ 网络协议栈 TFTP、NFS都在这
fs/ 文件系统支持(ext4、FAT、UBIFS) 启动内核时用
tools/ 主机端工具(mkimage等) 打包镜像用的
Makefile 顶层构建入口 别手动改,通过配置生成
Kconfig 顶层配置菜单 图形化配置的入口
我的习惯:拿到一个新板子,我一般先看 board/ 和 arch/arm/mach-xxx/ 这两个目录。它们基本决定了板子能不能跑起来。

2.2 关键子目录深度解析

2.2.1 arch/:架构的灵魂

这个目录按CPU架构分。以ARM为例:

arch/arm/
├── cpu/          # CPU核心初始化(如ARMv7、ARMv8)
├── mach-xxx/     # 厂商/SoC相关(mach-sunxi、mach-rockchip)
├── dts/          # 设备树源文件(.dts、.dtsi)
└── lib/          # 架构通用函数(如cache操作)

嗯,这里要注意:mach-xxx 是SoC厂商的专属目录。比如你用的是全志H3,那就去 mach-sunxi 里找。我在移植时经常需要修改这里的 lowlevel_init.S,那是板子上电后最早执行的汇编代码。

2.2.2 board/:板级的个性

每个板子一个文件夹。比如:

board/samsung/smdk2440/
├── Makefile
├── smdk2440.c       # 板级初始化
├── lowlevel_init.S  # 低层硬件初始化
└── config.mk        # 板级编译参数

你想想看,为什么要把板级代码单独放?因为不同板子的GPIO、时钟、DDR参数都不一样。我遇到过最坑的一次,就是DDR时序参数配错了,板子死活起不来,最后拿示波器一根根量信号才找到问题。

2.2.3 common/:共享的零件

这里放的是所有板子都能用的功能:

  • main.c:U-Boot主循环
  • command.c:命令注册与解析
  • env_common.c:环境变量管理
  • console.c:控制台输入输出

这部分代码很稳定,一般不需要动。但如果你想加个自定义命令,就得来 common/ 看看它是怎么注册的。

2.2.4 drivers/:外设的桥梁

驱动目录按设备类型分:

drivers/
├── serial/    # 串口驱动
├── net/       # 网卡驱动
├── mmc/       # SD/MMC卡驱动
├── spi/       # SPI总线驱动
├── i2c/       # I2C总线驱动
├── usb/       # USB主机/设备驱动
└── gpio/      # GPIO驱动

移植时最常打交道的就是 serial/ 和 mmc/。串口不通,你连log都看不到。我曾经在一个新板子上折腾了两天,最后发现是串口时钟分频算错了——一个很蠢的错误,但排查起来真要命。

2.3 构建系统:Makefile与Kconfig

U-Boot的构建系统借鉴了Linux内核,用Kconfig做配置,用Makefile做编译。这套组合拳打好了,移植效率能翻倍。

2.3.1 Kconfig:菜单式配置

Kconfig文件定义了配置选项。顶层 Kconfig 会 source 各个子目录的Kconfig:

# 顶层Kconfig片段
source "arch/Kconfig"
source "board/Kconfig"
source "drivers/Kconfig"

配置时运行 make menuconfig,就会生成 .config 文件。这个文件里全是 CONFIG_XXX=y 这样的宏定义。

关键点:Kconfig 决定了“编译什么”,Makefile 决定了“怎么编译”。两者配合,才能生成最终的 u-boot.bin。

2.3.2 Makefile:分层编译

U-Boot的Makefile分三层:

  1. 顶层Makefile:定义目标、链接脚本、全局变量
  2. 子目录Makefile:每个目录下的编译规则
  3. config.mk:板级编译参数(如交叉编译器前缀)

举个例子,你想给板子加个新驱动,流程是这样的:

# 1. 在 drivers/Kconfig 里加选项
config MY_DRIVER
    bool "My custom driver"
    help
      This is a custom driver for my board.

# 2. 在 drivers/Makefile 里加编译规则
obj-$(CONFIG_MY_DRIVER) += my_driver.o

# 3. 在板级 defconfig 里使能
CONFIG_MY_DRIVER=y

嗯,这套机制的好处是:你只需要改几个文件,剩下的编译系统自动搞定。我刚开始移植时,总想着手动改Makefile,后来发现用Kconfig才是正道。

2.4 知识结构总览

下面这张图把U-Boot源码的核心关系画出来了。你看一眼,心里就有谱了。

U-Boot源码结构总览 U-Boot源码结构总览 U-Boot 源码根目录 硬件相关代码 通用功能模块 构建配置系统 arch/ (CPU架构) board/ (板级代码) drivers/ (外设驱动) common/ (命令/环境) lib/ (库函数) net/ fs/ (协议/文件系统) Kconfig (配置菜单) Makefile (编译规则) .config / defconfig (配置结果) 输出:u-boot.bin / u-boot.img 移植时重点关注:arch/(CPU初始化)、board/(板级配置)、drivers/(外设驱动)、Kconfig/Makefile(构建配置)

2.5 避坑指南

我曾经踩过的坑:
  • 别直接改顶层Makefile!板级参数应该写在 board/xxx/config.mk 里。
  • Kconfig 的 depends on 和 select 容易搞混。前者是依赖,后者是强制选中,用错了会导致配置冲突。
  • 设备树文件(.dts)和板级代码(board.c)的分工要清楚:硬件描述放dts,初始化逻辑放board.c。