2. U-Boot源码结构
大家好,我是你们的嵌入式系统讲师。今天咱们来聊聊U-Boot的源码结构。说实话,我第一次接触U-Boot源码时,也被那一堆文件夹搞得有点懵。但别怕,这东西就像你家的工具箱——看着乱,其实每个抽屉都有它的用处。
U-Boot的源码结构,说白了就是一套为嵌入式系统量身定做的启动加载器框架。它不像Linux内核那么庞大,但麻雀虽小五脏俱全。我个人习惯把U-Boot源码看作一个「乐高积木盒」——你只需要挑选适合你板子的积木,拼起来就能用。
顶层目录结构
我们先从顶层看起。你解压U-Boot源码后,第一眼看到的就是这些目录和文件。我挑几个重点说说:
| 目录/文件 | 作用 | 我的经验 |
|---|---|---|
| board/ | 板级相关代码 | 每个板子一个子目录,定制化程度最高 |
| cpu/ | CPU架构相关代码 | ARM、RISC-V、x86等都在这里 |
| drivers/ | 设备驱动 | 串口、网卡、Flash驱动都在里面 |
| include/ | 头文件 | 配置宏、数据结构定义 |
| common/ | 通用功能 | 命令解析、环境变量处理 |
| lib/ | 库函数 | 字符串处理、CRC校验等 |
| net/ | 网络协议栈 | TFTP、NFS、DHCP等 |
| fs/ | 文件系统支持 | ext4、FAT、UBIFS等 |
| configs/ | 默认配置文件 | defconfig文件都在这 |
| Kconfig | 配置系统入口 | 类似Linux内核的配置方式 |
嗯,这里要注意:board/和cpu/这两个目录,是移植U-Boot时改动最多的地方。我在项目中遇到过好几次,新手一上来就改common/里的代码,结果搞得一团糟。其实大部分定制工作都在board/里完成。
关键子目录功能详解
board/ 目录
这个目录存放的是板级相关的代码。每个板子厂商或开发板都有自己的子目录。比如:
board/
├── samsung/
│ ├── smdk2440/
│ └── exynos/
├── ti/
│ ├── am335x/
│ └── omap/
└── mycompany/
└── myboard/
每个板子目录里通常包含:
board.c:板子初始化代码,比如GPIO配置、时钟设置Kconfig:板级配置选项Makefile:编译规则lowlevel_init.S:底层汇编初始化
我记得有一次帮客户移植U-Boot到一块新板子上,折腾了两天发现是lowlevel_init.S里的DDR时序参数配错了。你想想看,DDR初始化不对,后面所有代码都跑不起来。所以这块一定要仔细核对芯片手册。
cpu/ 目录
这里按CPU架构分类。每个架构下还有具体的SoC型号:
cpu/
├── arm926ejs/
├── armv7/
├── armv8/
├── riscv/
└── x86/
每个架构目录里包含:
start.S:CPU启动的第一段代码,设置异常向量表、初始化栈指针cpu.c:CPU核心功能,比如缓存操作、TLB管理interrupts.c:中断处理
说白了,cpu/目录就是U-Boot的「发动机」。不同架构的启动流程差异很大。比如ARMv7和ARMv8,一个用32位模式,一个用64位模式,启动时MMU和页表的设置完全不同。
drivers/ 目录
设备驱动目录,结构跟Linux内核很像:
drivers/
├── serial/ # 串口驱动
├── net/ # 网卡驱动
├── spi/ # SPI控制器驱动
├── i2c/ # I2C控制器驱动
├── mtd/ # Flash/NAND驱动
└── mmc/ # SD/MMC卡驱动
我个人建议,如果你要添加一个新硬件,先看看drivers/里有没有现成的驱动。U-Boot的驱动模型(Driver Model)从2014年就开始引入了,现在大部分驱动都支持设备树(Device Tree)绑定。
drivers/里找驱动时,可以先用grep搜索芯片型号。比如你的板子用的是DM9000网卡,直接grep -r "DM9000" drivers/net/就能找到对应驱动。
include/ 目录
头文件目录,这里存放了U-Boot所有的宏定义、数据结构声明和配置选项。重点文件有:
configs/:板级配置头文件,比如include/configs/myboard.hcommon.h:通用头文件,几乎所有源文件都会包含它command.h:命令相关的结构体和宏定义dm/:驱动模型相关的头文件
这里有个坑:老版本的U-Boot把所有配置都写在include/configs/下的头文件里。但从2016年左右开始,U-Boot逐步迁移到Kconfig配置系统。如果你拿到一个旧板子的代码,可能会看到两种配置方式混用的情况。我曾经就被这种混合配置坑过——明明在Kconfig里选了某个功能,但头文件里又用#define覆盖了,导致编译出来的结果跟预期不一样。
配置系统:Kconfig与defconfig
U-Boot的配置系统借鉴了Linux内核的Kconfig机制。说白了,就是让你通过菜单式选择来配置U-Boot功能,而不是手动修改头文件。
Kconfig 文件
Kconfig文件定义了配置选项的层次结构。比如:
# drivers/serial/Kconfig
config SERIAL_NS16550
bool "NS16550 serial port driver"
help
Support for NS16550 serial ports.
如果你用的是常见的16550兼容串口,选上这个。
每个子目录下都有Kconfig文件,它们通过顶层Kconfig串联起来。配置时,U-Boot会解析这些Kconfig文件,生成一个配置菜单。
defconfig 文件
defconfig是默认配置文件,存放在configs/目录下。命名规则通常是<板子名>_defconfig。比如:
configs/
├── sandbox_defconfig
├── qemu_arm_defconfig
├── raspberry_pi_3_defconfig
└── myboard_defconfig
defconfig文件的内容很简单,就是一行行配置选项:
CONFIG_SYS_TEXT_BASE=0x80000000
CONFIG_SERIAL_NS16550=y
CONFIG_NET=y
CONFIG_CMD_TFTP=y
使用方式也很直接:
make myboard_defconfig # 加载默认配置
make menuconfig # 进入菜单配置界面
make # 编译
.config文件。这个文件是自动生成的,不要手动编辑它。要改配置,请用make menuconfig或者直接改defconfig文件。
知识体系结构图
下面这张图展示了U-Boot源码的核心逻辑关系。我画这张图时,特意把配置系统和各目录的依赖关系标了出来:
从这张图你能看出来,配置系统是U-Boot的「大脑」——它决定了哪些代码被编译进去。而board/、cpu/、drivers/这三个目录,是移植时最常打交道的地方。
核心要点:
- U-Boot源码结构清晰,按功能模块划分目录
- board/ 负责板级定制,cpu/ 负责架构适配,drivers/ 负责外设驱动
- 配置系统从Kconfig/defconfig过渡,取代了传统的头文件配置方式
- 移植U-Boot时,优先修改board/目录下的文件
好了,这一章的内容就到这里。U-Boot的源码结构其实没那么复杂,你只要记住「配置驱动一切,板级决定差异」这个原则,后面学起来就顺了。
可可精品资料库