2. 源码获取与目录结构:从官方Git仓库获取U-Boot源码、顶层目录结构分析

好,咱们正式开始动手了。这一章我带你做两件事:第一,把U-Boot源码从官方仓库拉下来;第二,把顶层目录结构掰开揉碎讲清楚。这两步走完,你就能对U-Boot有个整体印象,知道什么东西该去哪找。

2.1 从官方Git仓库获取源码

U-Boot的官方仓库托管在denx.de的Git服务器上。我个人习惯用denx的master分支,因为它是主线,最干净。当然你也可以用u-boot的官方GitHub镜像,但要注意——镜像可能比主线慢一两个commit。

拉取命令很简单:

git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
git checkout master

嗯,这里要注意:如果你在公司内网,可能连不上source.denx.de。我遇到过这种情况,当时用git://git.denx.de/u-boot.git也不行。后来发现是防火墙把9418端口封了。解决办法是用GitHub镜像:

git clone https://github.com/u-boot/u-boot.git

拉下来之后,看看版本:

git describe --tags

你会看到类似v2024.01-rc3-123-gabcdef这样的输出。这告诉你当前是基于哪个tag,后面有多少个commit。我建议你至少用v2023.10之后的版本,因为早期的目录结构跟现在差别挺大。

小技巧:如果你只想看某个特定架构的代码,可以用git clone --depth=1 --branch v2024.01只拉一个tag,省带宽。但做BSP开发,我建议全量拉取,因为后面你可能需要看历史commit来理解某个改动的原因。

2.2 顶层目录结构概览

拉完代码,ls一下,你会看到一堆目录。别慌,我帮你理一理。U-Boot的顶层目录大概分这么几类:

  • arch/ — 架构相关代码。这是BSP开发的核心区域之一。
  • board/ — 板级代码。每个板子一个子目录。
  • common/ — 通用功能模块。比如命令行、环境变量、启动流程。
  • drivers/ — 驱动代码。串口、网卡、存储、显示等。
  • include/ — 头文件。全局的配置、数据结构定义。
  • lib/ — 库函数。比如字符串处理、数学运算、压缩算法。
  • cmd/ — 命令实现。每个U-Boot>下的命令对应一个文件。
  • net/ — 网络协议栈。TFTP、NFS、DHCP等。
  • fs/ — 文件系统支持。ext4、FAT、UBIFS等。
  • scripts/ — 构建脚本、配置工具。
  • tools/ — 主机工具。比如mkimage、fdtget。
  • doc/ — 文档。说实话,有些文档写得比代码还难懂。

我画了一张结构图,帮你理解这些目录之间的关系:

U-Boot 顶层目录结构 核心层 common/ — 启动流程、命令行、环境变量 架构层 arch/ — CPU初始化、MMU、中断、异常处理 板级层 board/ — 板级初始化、设备树、外设配置 驱动层 drivers/ — 串口、网卡、存储、显示、GPIO、I2C、SPI 功能模块 cmd/ — 命令实现 | net/ — 网络协议 | fs/ — 文件系统 lib/ — 库函数 | include/ — 头文件 | scripts/ — 构建脚本 依赖方向 ↓

你看,这个结构是分层的。核心层在最上面,它不依赖任何板子。架构层在下面,它依赖CPU架构。板级层再往下,它依赖具体的板子。驱动层在最下面,它被上面所有层调用。功能模块是横向的,它们可以被任何层使用。

2.3 关键目录深度解析

arch/ — 架构相关

这个目录下是按CPU架构分的子目录:

arch/
├── arm/       # ARM架构
├── x86/       # x86架构
├── riscv/     # RISC-V架构
├── mips/      # MIPS架构
├── powerpc/   # PowerPC架构
├── sandbox/   # 模拟器架构(用于测试)
└── ...

每个架构目录下又有cpu/、include/、lib/等子目录。以ARM为例:

arch/arm/
├── cpu/          # CPU初始化、异常向量表
│   ├── armv7/    # Cortex-A7/A8/A9等
│   ├── armv8/    # Cortex-A53/A72等
│   └── ...
├── include/      # 架构相关的头文件
├── lib/          # 架构相关的库函数
├── mach-*/       # 厂商相关的代码(mach-sunxi、mach-rockchip等)
└── dts/          # 设备树源文件

我在做i.MX6ULL的BSP时,经常要改arch/arm/cpu/armv7/下的代码。比如调整CPU频率、配置MMU页表。你想想看,如果连CPU都跑不起来,后面所有工作都是白搭。

board/ — 板级代码

这个目录是按厂商和板子名组织的:

board/
├── freescale/    # 飞思卡尔/恩智浦的板子
│   ├── mx6ul_14x14_evk/
│   └── ...
├── rockchip/     # 瑞芯微的板子
│   ├── evb_rk3399/
│   └── ...
├── sunxi/        # 全志的板子
│   ├── sun8i/
│   └── ...
└── ...

每个板子目录下通常有:

  • board.c — 板级初始化函数
  • Kconfig — 板级配置选项
  • MAINTAINERS — 维护者信息
  • Makefile — 构建规则

嗯,这里要注意:有些板子代码可能不在board/下,而是放在arch/arm/mach-*/里。这是历史遗留问题,新板子都建议放board/下。

drivers/ — 驱动代码

这是U-Boot里最大的目录,没有之一。它按设备类型分了子目录:

drivers/
├── serial/       # 串口驱动
├── net/          # 网卡驱动
├── mmc/          # MMC/SD卡驱动
├── spi/          # SPI控制器驱动
├── i2c/          # I2C控制器驱动
├── gpio/         # GPIO控制器驱动
├── video/        # 显示驱动
├── usb/          # USB主机/设备驱动
├── pinctrl/      # 引脚控制驱动
├── clk/          # 时钟驱动
├── reset/        # 复位驱动
├── power/        # 电源管理驱动
└── ...

每个子目录下又有具体的芯片驱动。比如drivers/net/下可能有fec_mxc.c(恩智浦的以太网控制器)、dwmac.c(DesignWare的GMAC)等。

重点:U-Boot的驱动模型(Driver Model)从2016年开始引入,现在已经是主流。所有新驱动都应该基于DM框架。你会在drivers/下看到很多xxx-uclass.c文件,那就是DM的uclass实现。

common/ — 通用功能

这个目录放的是U-Boot的核心逻辑:

common/
├── main.c          # 主循环
├── cli.c           # 命令行接口
├── cli_hush.c      # Hush shell解析器
├── env_common.c    # 环境变量管理
├── bootm.c         # 启动内核的核心函数
├── bootstage.c     # 启动阶段计时
├── image.c         # 镜像解析
└── ...

我刚开始学U-Boot时,最喜欢看common/main.c。它从board_init_r()开始,一路调用到cli_loop(),整个启动流程一目了然。

include/ — 头文件

这个目录下最重要的文件是:

  • configs/ — 板级配置头文件(.h文件)
  • config.h — 自动生成的配置头文件
  • common.h — 全局通用头文件
  • dm/ — 驱动模型相关的头文件
  • fdtdec.h — 设备树解析接口

你可能会问:为什么U-Boot还用.h文件做配置,而不是完全用Kconfig?其实U-Boot现在已经是Kconfig为主了,但有些老板子还在用include/configs/下的头文件。我建议新板子直接用Kconfig,别走回头路。

2.4 目录结构背后的设计思想

U-Boot的目录结构不是随便分的。它背后有几个设计原则:

  1. 分层隔离 — 架构层、板级层、驱动层互不干扰。你换一块板子,只需要改board/和arch/下的代码,common/和drivers/基本不用动。
  2. 模块化 — 每个功能都是一个独立的模块。你想加一个命令,就在cmd/下加一个文件;你想加一个驱动,就在drivers/下加一个文件。
  3. 可移植性 — 通过Kconfig和Makefile,你可以精确控制哪些代码被编译进最终镜像。不需要的驱动一个字节都不会被链接。

说白了,U-Boot的目录结构就是一套「乐高积木」。你只需要选对积木,拼在一起,就能得到一个能跑起来的bootloader。

避坑指南:我曾经在移植一个新板子时,直接把另一个板子的board/目录复制过来改。结果编译出来死活起不来。后来发现是arch/arm/mach-*/下的代码跟板子不匹配。记住:board/和arch/是强关联的,不能随便混用。

2.5 动手实践:浏览你的U-Boot源码

光看不练假把式。我建议你现在就打开终端,进到U-Boot源码目录,执行以下命令:

# 查看顶层目录
ls -F

# 查看arch下的架构
ls arch/

# 查看board下的板子
ls board/

# 查看drivers下的驱动类型
ls drivers/

# 查看common下的核心文件
ls common/*.c | head -20

你可能会发现,有些目录下有很多文件,有些目录下只有几个。这很正常。比如drivers/serial/下可能有几十个文件,因为串口驱动太多了。而drivers/rtc/下可能只有十几个文件。

嗯,看到这里,你应该对U-Boot的源码结构有了整体认识。下一章,我会带你深入arch/arm/,看看CPU是怎么初始化的。不过在那之前,我建议你先自己翻翻代码,找找你的目标板子对应的目录在哪。

可可精品资料库