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 这样的宏定义。
2.3.2 Makefile:分层编译
U-Boot的Makefile分三层:
- 顶层Makefile:定义目标、链接脚本、全局变量
- 子目录Makefile:每个目录下的编译规则
- 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源码的核心关系画出来了。你看一眼,心里就有谱了。
2.5 避坑指南
- 别直接改顶层Makefile!板级参数应该写在
board/xxx/config.mk里。 - Kconfig 的
depends on和select容易搞混。前者是依赖,后者是强制选中,用错了会导致配置冲突。 - 设备树文件(.dts)和板级代码(board.c)的分工要清楚:硬件描述放dts,初始化逻辑放board.c。