U-Boot源码结构:顶层目录、board、arch、common、drivers等关键目录解析

拿到U-Boot源码包,第一件事是什么?我个人的习惯是——先看目录结构。别急着编译,别急着改代码。你得先搞清楚,这个庞大的工程里,每个文件夹是干什么的。

U-Boot的源码结构,说白了就是一个嵌入式操作系统的微缩版。它有自己的硬件抽象层、驱动框架、命令行解析器,甚至还有文件系统支持。你想想看,一个能在裸机上跑起来的bootloader,要管内存初始化、设备枚举、网络协议栈……这复杂度可不低。

核心观点:U-Boot源码的目录划分,遵循的是"架构相关 vs 架构无关"、"板级相关 vs 板级无关"两条主线。搞懂这个,你移植的时候就知道改哪里、不改哪里。

2.1 顶层目录:U-Boot的"门面"

解压U-Boot源码后,你看到的顶层目录大概有20-30个文件夹。我挑几个最重要的说。

目录名 作用 我眼中的重要性
board/ 板级初始化代码,每个板子一个子目录 ★★★★★ 移植必改
arch/ CPU架构相关代码(ARM、RISC-V、x86等) ★★★★★ 选对架构就行
common/ 通用功能模块(命令行、环境变量、启动流程) ★★★★☆ 理解启动流程必看
drivers/ 设备驱动(串口、网卡、MMC、NAND等) ★★★★☆ 调试硬件时频繁进出
include/ 头文件、配置宏、数据结构定义 ★★★★★ 配置系统核心
lib/ 通用库函数(字符串、数学、压缩算法) ★★★☆☆ 一般不动
net/ 网络协议栈(TFTP、NFS、DHCP) ★★★☆☆ 网络启动时关注
fs/ 文件系统支持(FAT、EXT4、UBIFS) ★★★☆☆ 需要读文件系统时看
tools/ 主机端工具(mkimage、fdt工具等) ★★☆☆☆ 打包镜像时用
doc/ 文档说明 ★★☆☆☆ 遇到问题翻一翻
我的小技巧:刚接触一个新板子时,我习惯先看 board/ 和 include/configs/ 这两个目录。前者告诉你板子怎么初始化,后者告诉你功能怎么配。这两个搞定了,移植就完成了一半。

2.2 board/ 目录:板级代码的"根据地"

board/ 目录下,每个板子厂商都有自己的子目录。比如 board/ti/am335x/、board/freescale/imx6/。这里面放的是什么呢?

  • 板级初始化函数:board_init_f()、board_init_r()、dram_init() 等
  • 板级特定配置:GPIO复用、时钟树设置、DDR参数
  • 低电平驱动:板载外设的初始化代码

我记得有一次移植一个新板子,DDR死活初始化不过。折腾了两天,最后发现是 board/ 目录下的 dram_init() 函数里,时序参数写错了。嗯,这种坑,踩过一次就记住了。

避坑指南:我曾经见过有人把板级代码直接塞进 arch/ 目录里。千万别这么干!板级代码就应该放在 board/ 下,这是U-Boot的约定。乱放会导致后续维护困难,别人接手你的代码时想骂人。

2.3 arch/ 目录:CPU架构的"大本营"

arch/ 目录按CPU架构划分。ARM、RISC-V、x86、MIPS……每个架构一个子目录。以ARM为例:

arch/arm/
├── cpu/          # CPU核心代码(异常处理、MMU、cache)
│   ├── armv7/    # Cortex-A7/A8/A9
│   ├── armv8/    # Cortex-A53/A72
│   └── ...
├── include/      # 架构相关头文件
├── lib/          # 架构通用函数(如 cache 操作)
├── mach-*/       # SoC厂商代码(mach-sunxi、mach-imx等)
└── dts/          # 设备树文件

这里有个关键点:你移植时基本不用改 arch/ 下的代码。除非你用的是全新的CPU核,或者要修复某个架构的bug。大多数情况下,你只需要选对架构,然后通过配置宏来适配。

说白了,arch/ 是U-Boot团队帮你写好的。你只需要告诉它"我用的是ARMv7",它就会把对应的启动代码、异常向量表、MMU配置都准备好。

2.4 common/ 目录:U-Boot的"大脑"

common/ 目录存放的是U-Boot的核心逻辑。这里面的代码,与硬件无关。比如:

  • main.c:U-Boot的主循环,命令行解析入口
  • cli.c:命令行交互实现
  • env_*.c:环境变量存储与加载(env_nand.c、env_mmc.c、env_sf.c)
  • bootm.c:启动内核的核心函数
  • autoboot.c:自动启动逻辑

我建议你花点时间读一读 common/main.c 里的 main_loop() 函数。它只有几十行,但把U-Boot的启动流程串起来了:检查环境变量、等待用户输入、超时自动启动。你想想看,整个bootloader的"大脑"就在这里。

关键理解:common/ 目录的代码,你几乎不需要修改。但你必须理解它。因为你的板级代码最终会被 common/ 里的启动流程调用。搞不懂这个调用关系,移植就是瞎蒙。

2.5 drivers/ 目录:硬件驱动的"仓库"

drivers/ 目录下,每个子系统一个子目录:

drivers/
├── mmc/          # MMC/SD卡驱动
├── net/          # 网卡驱动(设计以太网、RTL8169等)
├── serial/       # 串口驱动
├── spi/          # SPI控制器驱动
├── i2c/          # I2C控制器驱动
├── gpio/         # GPIO驱动
├── mtd/          # NAND Flash、NOR Flash驱动
├── usb/          # USB主机/设备驱动
└── ...

移植时,你主要关注 mmc/、serial/、net/ 这三个。为什么?因为:

  1. 串口:没有串口驱动,你看不到任何打印信息
  2. MMC:你要从EMMC启动,MMC驱动是必须的
  3. 网卡:调试阶段,网络启动比从存储介质启动方便得多

我记得有一次调一个板子,串口死活没输出。查了半天,发现是 drivers/serial/ 里的波特率计算公式和实际晶振频率对不上。这种问题,光看代码是看不出来的,必须拿示波器量波形。

2.6 核心知识结构图

下面这张图,把U-Boot源码的核心目录和它们之间的关系画出来了。你移植时,重点关注蓝色节点。

U-Boot 源码核心目录结构 U-Boot 顶层目录 board/ arch/ common/ drivers/ 板级初始化 DDR参数 GPIO复用 CPU异常处理 MMU/Cache 设备树 命令行解析 环境变量 启动流程 MMC驱动 串口驱动 网卡驱动 移植关注度分级 🔴 红色节点(board/、arch/、drivers/):移植时频繁修改 🟢 绿色节点(common/):理解即可,几乎不改 🟣 紫色节点:具体实现模块,按需修改 🟡 橙色节点:核心逻辑,建议深入阅读

2.7 其他重要目录

除了上面四个核心目录,还有几个你迟早会碰到的:

  • include/configs/:板级配置头文件。每个板子一个 .h 文件,里面用宏定义开启或关闭功能。移植时,这个文件是你的"主战场"。
  • include/dt-bindings/:设备树绑定头文件。如果你用设备树,这里定义了各种中断号、时钟号、GPIO号的宏。
  • scripts/:编译脚本、配置脚本。Kconfig 配置文件也在这里。
  • configs/:默认配置文件。每个板子一个 _defconfig 文件,比如 am335x_evm_defconfig。
我的建议:移植一个新板子时,先找一个和你板子最接近的 include/configs/ 下的配置文件,复制一份,然后逐行修改。别从零开始写,那太容易漏东西了。

2.8 总结一下

U-Boot的源码结构,其实就三句话:

  1. board/ 管板子差异,移植时改这里
  2. arch/ 管CPU差异,选对架构就行
  3. common/ + drivers/ 管通用逻辑和驱动,理解它们怎么被调用