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.h
  • common.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                     # 编译
注意: 修改配置后,U-Boot会生成一个.config文件。这个文件是自动生成的,不要手动编辑它。要改配置,请用make menuconfig或者直接改defconfig文件。

知识体系结构图

下面这张图展示了U-Boot源码的核心逻辑关系。我画这张图时,特意把配置系统和各目录的依赖关系标了出来:

U-Boot源码结构核心逻辑 配置系统 Kconfig / defconfig board/ 板级初始化 cpu/ 架构相关代码 drivers/ 设备驱动 lowlevel_init.S DDR/时钟初始化 start.S 异常向量/栈设置 serial/net驱动 串口/网卡初始化 U-Boot 可执行镜像 u-boot.bin / u-boot.img 配置系统决定哪些目录和功能被编译进最终镜像 board/、cpu/、drivers/ 是移植时最常修改的三个目录

从这张图你能看出来,配置系统是U-Boot的「大脑」——它决定了哪些代码被编译进去。而board/、cpu/、drivers/这三个目录,是移植时最常打交道的地方。

核心要点:

  • U-Boot源码结构清晰,按功能模块划分目录
  • board/ 负责板级定制,cpu/ 负责架构适配,drivers/ 负责外设驱动
  • 配置系统从Kconfig/defconfig过渡,取代了传统的头文件配置方式
  • 移植U-Boot时,优先修改board/目录下的文件

好了,这一章的内容就到这里。U-Boot的源码结构其实没那么复杂,你只要记住「配置驱动一切,板级决定差异」这个原则,后面学起来就顺了。

可可精品资料库