U-Boot零基础入门第二篇(如何看懂uboot目录?)
引言
当我们从香橙派官网下载uboot之后我们进行一直
第一部分:文件(构建系统和项目管理)
Makefile 、Kconfig 、.config、config.mk 之间的关系
为什么需要这四个文件?
你有 U-Boot 的源代码,上千个 .c 文件散落在几十个目录里。现在问题来了:
问题1:怎么知道编译哪些文件? 你的开发板可能需要 USB 驱动,但不需要网卡驱动。另一个板子正好相反。不可能把所有源文件都编译进去,镜像会太大,而且有些代码会冲突。→ 需要一个配置系统,让你选择"要 USB、不要网卡",这个系统生成一个配置清单→ 这个配置清单就是 .config 文件 → 生成这个清单的工具叫 Kconfig
问题2:用什么编译器编译? 你在 x86 电脑上开发,但目标板子是 ARM 架构。不能用本机的 gcc,必须用 ARM 交叉编译器 arm-linux-gnueabi-gcc。编译选项也要设置好,比如优化等级 -O2、警告开关 -Wall。→ 需要一个地方统一定义编译工具和参数 → 这个文件叫 config.mk → 它里面写着:CC = arm-linux-gnueabi-gcc、CFLAGS = -O2 -Wall
问题3:怎么自动化整个编译过程? 手动编译是这样的:gcc -c file1.c -o file1.ogcc -c file2.c -o file2.o ...重复几百次gcc file1.o file2.o ... -o u-boot太蠢了,而且文件改了还要记住重新编译哪些。→ 需要一个自动化工具,告诉它最终目标是 u-boot.bin,它自动找出要编译哪些文件、按什么顺序→ 这个工具是 GNU Make,它读取的规则文件叫 Makefile
问题4:子目录的编译怎么处理? drivers/usb/ 目录下有一堆 USB 相关的 .c 文件,需要统一管理。每个子目录都要有编译规则,但不能每个目录都写一遍重复的逻辑。→ 需要一套通用的构建规则模板,所有子目录都遵循这套规则
→ 这套规则叫 Kbuild 系统 → 顶层 Makefile 引入 Kbuild 后,可以用统一的方式处理所有子目录
它们怎么协作?
第一步:配置阶段(决定编译什么)
你运行 make menuconfig,看到一个菜单:
[ ] Enable USB support
[ ] Enable Network support
[*] Enable MMC support
→ 这个菜单是 Kconfig 文件生成的。Kconfig 里写着:
config USB_SUPPORTbool "Enable USB support"depends on DM_USB
→ 你勾选了 USB support,Kconfig 检查依赖:USB 需要先启用 DM_USB,于是自动帮你勾上 DM_USB
→ 退出菜单保存后,生成 .config 文件:
CONFIG_USB_SUPPORT=y
CONFIG_DM_USB=y
CONFIG_NETWORK=n
CONFIG_MMC=y
第二步:编译阶段(实际执行编译)
你运行 make。
第一件事:Makefile 准备编译环境
Makefile 开头执行:
include config.mk
→ 读取 config.mk,获得:
CROSS_COMPILE = arm-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
CFLAGS = -O2 -Wall -fno-common
→ 现在 Makefile 知道了用什么编译器、用什么参数
第二件事:Makefile 读取配置
Makefile 继续执行:
include .config
→ 读取 .config,看到 CONFIG_USB_SUPPORT=y、CONFIG_MMC=y
→ 这些配置会影响后面的编译决策
第三件事:Makefile 引入 Kbuild 规则
Makefile 执行:
include scripts/Kbuild.include
→ 引入 Kbuild 的通用规则,比如怎么递归编译子目录、怎么处理依赖关系
→ 现在 Makefile 有了处理复杂项目的能力
第四件事:开始递归编译
Makefile 看到要编译的目录列表(lib/、drivers/、common/ 等),开始递归处理。
进入 drivers/usb/ 目录,这个目录有个 Kbuild 文件(或 Makefile):
obj-$(CONFIG_USB_SUPPORT) += usb-common.o
obj-$(CONFIG_USB_STORAGE) += usb-storage.o
→ Kbuild 读取 .config,看到 CONFIG_USB_SUPPORT=y
→ obj-$(CONFIG_USB_SUPPORT) += usb-common.o 展开成 obj-y += usb-common.o
→ obj-y 表示要编译的文件,于是 usb-common.c 被加入编译列表
→ 执行编译命令(使用 config.mk 定义的编译器和参数):
arm-linux-gnueabi-gcc -O2 -Wall -DCONFIG_USB_SUPPORT -c usb-common.c -o usb-common.o
→ 注意 -DCONFIG_USB_SUPPORT,这是把 .config 中的配置传给编译器的宏定义
所有子目录都这样处理完后,最后链接:
arm-linux-gnueabi-ld usb-common.o mmc.o ... -o u-boot
生成最终的 u-boot.bin
四个文件的角色总结
Kconfig:决策者
- 作用时机:配置阶段(
make menuconfig) - 干什么:生成 .config 文件,记录"要编译什么"
- 不参与实际编译
config.mk:工具箱
- 作用时机:编译阶段开始前
- 干什么:告诉 Makefile"用什么工具编译"(编译器路径、编译参数)
- 被 Makefile include,提供编译环境
Kbuild:执行规则
- 作用时机:编译阶段
- 干什么:提供通用的编译规则模板,处理复杂的递归编译
- 被 Makefile include,提供自动化能力
Makefile:总指挥
- 作用时机:编译阶段(
make) - 干什么:协调其他三者,执行整个编译流程
- 先 include config.mk 获取工具
- 再 include .config 获取配置
- 再 include Kbuild 获取规则
- 最后递归编译所有目录,生成最终文件
完整流程示例
你要给 ARM 板子编译支持 USB 的 U-Boot:
-
配置:
make menuconfig→ Kconfig 生成菜单 → 你选中 USB → 保存生成 .config 文件(CONFIG_USB_SUPPORT=y) -
编译:
make→ Makefile 开始工作:
- include config.mk → 获得
CC=arm-linux-gnueabi-gcc - include .config → 知道
CONFIG_USB_SUPPORT=y - include Kbuild.include → 获得递归编译能力
- 递归进入 drivers/usb/ → 读取该目录的 Kbuild → 看到
obj-$(CONFIG_USB_SUPPORT)+=usb-common.o→ 因为 CONFIG_USB_SUPPORT=y,所以编译 usb-common.c → 用 arm-linux-gnueabi-gcc 编译 - 所有目录处理完 → 链接生成 u-boot.bin
配置阶段(make menuconfig)
═══════════════════════════════════════════════════════════════════┌─────────────┐│ Kconfig │ 定义所有配置选项(USB、网卡、MMC...)│ (规则定义) │ 和它们之间的依赖关系└──────┬──────┘││ 用户通过menuconfig选择配置│ Kconfig检查依赖、自动启用必需项↓┌─────────────┐│ .config │ CONFIG_USB_SUPPORT=y│ (配置清单) │ CONFIG_MMC=y└─────────────┘ CONFIG_NETWORK=n│ (决定编译哪些代码)││ 配置完成,进入编译阶段│↓编译阶段(make)
═══════════════════════════════════════════════════════════════════┌────────────────────────┐│ Makefile ││ (总指挥/协调者) │└───────────┬────────────┘│┌───────────┼───────────┐│ │ │第1步 │ 第2步 │ 第3步 │↓ ↓ ↓┌──────────────┐ ┌─────────┐ ┌──────────────┐│ config.mk │ │ .config │ │Kbuild.include││ (工具箱) │ │(配置单) │ │ (执行规则) │└──────────────┘ └─────────┘ └──────────────┘│ │ ││ 提供: │ 提供: │ 提供:│ CC=arm-gcc │ CONFIG_ │ build函数│ CFLAGS=-O2 │ USB=y │ 递归编译规则│ LD=arm-ld │ CONFIG_ │ 依赖处理│ │ MMC=y │└─────────┬───────┴──────┬──────┴──────┬─────────│ │ │└──────────────┼─────────────┘││ Makefile获得完整能力:│ ① 知道用什么编译│ ② 知道编译什么│ ③ 知道怎么编译│↓┌─────────────────────────┐│ 递归处理各个子目录 │└────────────┬────────────┘│┌───────────────────────┼───────────────────────┐│ │ │↓ ↓ ↓┌─────────┐ ┌─────────┐ ┌─────────┐│drivers/ │ │ lib/ │ │common/ ││ usb/ │ │ │ │ │└────┬────┘ └────┬────┘ └────┬────┘│ │ ││ 读取该目录的Kbuild │ │↓ │ │┌──────────────────────┐ │ ││drivers/usb/Kbuild │ │ ││ │ │ ││obj-$(CONFIG_USB) │ │ ││ += usb-common.o │ │ │└──────┬───────────────┘ │ ││ │ ││ CONFIG_USB=y │ ││ 所以展开为: │ ││ obj-y += usb- │ ││ common.o │ │↓ ↓ ↓┌──────────────────────────────────────────────────────┐│ 执行实际的编译命令 ││ ││ arm-linux-gnueabi-gcc \ ││ -O2 -Wall \ ← 来自config.mk ││ -DCONFIG_USB_SUPPORT \ ← 来自.config ││ -c usb-common.c \ ← 来自Kbuild规则 ││ -o usb-common.o │└───────────────────────┬──────────────────────────────┘││ 所有.c编译成.o│↓┌───────────────┐│ usb-common.o ││ mmc.o ││ serial.o ││ ... │└───────┬───────┘││ 链接所有.o文件↓┌───────────────┐│ arm-ld \ ││ *.o \ ││ -o u-boot │└───────┬───────┘│↓┌───────────────┐│ u-boot.bin │ ← 最终产物└───────────────┘关系图(各文件职责)
═══════════════════════════════════════════════════════════════════Kconfig config.mk Kbuild Makefile(配置者) (工具箱) (规则库) (总指挥)│ │ │ ││生成 │定义 │定义 │协调全局↓ ↓ ↓ ↓.config 编译工具 构建规则 编译流程(要编什么) (用什么编) (怎么编) (按序执行)│ │ │ │└─────────────────┴────────────────┴────────────────┘│↓编译成功u-boot.bin数据流向
═══════════════════════════════════════════════════════════════════用户选择配置↓[Kconfig] ─生成→ [.config]││ 读取↓[config.mk] ←─包含─ [Makefile] ←─包含─ [Kbuild.include]│ │ ││提供工具 │协调 │提供规则│ ↓ │└──────→ 编译命令 ←────────────────────┘↓[源码.c] → [目标.o] → [u-boot.bin]
这就是四个文件如何配合完成从配置到编译的整个过程。
README - 项目说明文档。U-Boot 的简介、编译方法、基本使用。拿到源码首先要看这个文件。
MAINTAINERS - 代码维护者列表。记录每个模块的负责人和联系方式。提交补丁时需要抄送对应维护者。
COPYING - 版权声明文件。U-Boot 使用 GPL 开源协议,这里是协议原文。说明了代码的使用和分发规则。
CREDITS - 贡献者名单。记录了对 U-Boot 有贡献的开发者,这是致谢和荣誉名单。
.gitignore - Git 版本控制的忽略规则。定义哪些文件不纳入版本管理(编译生成的 .o 文件、u-boot.bin 等临时文件)。这是 Git 工具使用的,与 U-Boot 功能无关。
第二部分:文件夹(功能模块)
第一层:CPU 和开发板适配
arch/ - 处理器架构实现代码。你的 RK3588S 是 ARM64 架构,上电后第一条指令在 arch/arm 下。包含:
- CPU 启动汇编代码(start.S)
- 异常向量表
- MMU 和 Cache 控制
- 架构相关的底层操作
board/ - 开发板配置代码。这个目录下有多少个文件夹,就表示当前 U-Boot 支持多少个开发板。每个开发板的硬件连接不同:电源管理芯片、DDR 型号、外设连接方式。
重要机制说明:为了管理越来越多的开发板,board 下采用了两级目录结构:
- 第一级是厂商目录(vendor,如 rockchip、samsung)
- 第二级是具体的开发板目录
- 你的板子路径应该是:board/rockchip/orangepi-5-pro 或类似结构
- 配置阶段(Kconfig)需要正确指定这个路径,否则编译时找不到文件
configs/ - 板级配置文件集合。每个开发板有一个 _defconfig 文件,定义了编译选项:启用哪些驱动、支持哪些功能、内存地址设置。你的板子是 orangepi-5-pro_defconfig。执行 make orangepi-5-pro_defconfig 应用这个配置,这一步就是在确定使用 board 目录下的哪个文件夹。
dts/ - 设备树编译的工作目录。实际的 .dts 源文件在 arch/arm/dts 下,编译时在这个目录生成 .dtb 二进制文件。这是设备树的中转站。
第二层:硬件驱动
drivers/ - 硬件驱动代码。这些驱动大部分是从 Linux 内核移植过来的,但 U-Boot 是裸机程序,所以驱动是 Linux 驱动的简化版本。包含:
- GPIO、I2C、SPI 等总线驱动
- MMC/SD 卡驱动
- USB 驱动
- 网卡驱动
- NAND Flash 驱动
开发板必须用到的驱动都在这里。
include/ - 头文件集中目录。U-Boot 和 Linux 内核采用同样的管理方式:所有头文件集中放在 include 目录,而不是跟着对应的 .c 文件。包含:
- 函数声明
- 结构体定义
- 寄存器地址宏定义
- CONFIG_ 开头的配置选项
第三层:存储和文件系统
disk/ - 磁盘分区管理。解析 SD 卡和 eMMC 上的分区表(GPT 或 MBR),确定各分区的位置和大小。
fs/ - 文件系统支持。从 Linux 移植过来的文件系统驱动:
- FAT32(SD 卡常用)
- EXT4(Linux 分区)
- UBIFS(NAND Flash)
- BTRFS、SquashFS 等
有了文件系统驱动才能按文件名读取内核镜像。
第四层:启动和命令系统
boot/ - 启动流程主控。协调各模块完成启动:定位内核、加载到内存、加载设备树、设置参数、跳转到内核入口。
cmd/ - 命令行实现。U-Boot 提供的所有命令都在这里实现:
- bootm:启动内核
- mmc:操作 SD 卡
- setenv:设置环境变量
- md/mw:读写内存
每个命令对应一个 cmd_xxx.c 文件。
common/ - 公共功能代码。不属于特定硬件的通用逻辑:
- cmd_ 开头的文件:命令系统的框架实现
- env_ 开头的文件:环境变量的管理实现
- 控制台输入输出
- CRC 校验
这是连接底层硬件和上层功能的桥梁。
env/ - 环境变量管理的底层实现。环境变量(bootcmd、bootargs、ipaddr 等)需要持久化到 Flash,这里实现了不同存储介质的环境变量读写。
第五层:网络功能
net/ - 网络协议栈。实现了:
- TCP/IP、UDP 协议
- DHCP、TFTP、NFS
- ping、tftp 等命令
支持通过网络加载内核、网络启动。
第六层:库和通用支撑
lib/ - 基础库函数。所有架构通用的库代码:
- 字符串操作
- 数据结构
- 加密算法(AES、RSA、SHA)
- 压缩解压(gzip、lzma)
这是最底层的运算能力提供者。
api/ - 应用程序接口。定义了外部程序调用 U-Boot 功能的接口,允许在 U-Boot 环境下运行扩展应用。
第七层:开发工具
scripts/ - 构建辅助脚本。Makefile 调用的各种脚本:
- 设备树编译器(dtc)
- Kconfig 配置工具
- 依赖关系分析
- 头文件生成
这些脚本在编译时自动运行。
tools/ - 开发工具集。在主机上使用的工具:
- mkimage:制作 U-Boot 格式的镜像文件(重要)
- menuconfig:图形配置界面
- 镜像处理工具
这些工具不在开发板运行,是交叉编译时使用的。
第八层:测试和示例
test/ - 测试代码。单元测试、集成测试、Python 测试脚本,验证功能正确性。
examples/ - 示例程序。演示如何使用 U-Boot 的 API 编写独立应用。
post/ - 上电自检(Power-On Self Test)。系统启动时可选的硬件检测:内存测试、CPU 测试、外设自检。
第九层:文档
doc/ - 文档目录。使用手册、移植指南、设计文档、各模块 README。虽然是英文且较杂乱,但是理解源码的重要参考。
Licenses/ - 开源许可证。GPL 协议原文和各组件的授权声明。
关键理解点
配置阶段的重要性
U-Boot 的可移植性依靠配置系统维护。配置阶段解决的核心问题是:
- 确定使用哪个开发板:从 board 目录的众多文件夹中选择一个
- 确定文件路径:board 下有的是一级目录(开发板名),有的是二级目录(厂商/开发板),配置时必须指定正确的路径深度
- 确定编译选项:启用哪些驱动、哪些功能
不配置就无法编译,因为编译器不知道要编译哪些文件、去哪里找这些文件。
目录重要性总结
必须深入理解的:
- board/ - 开发板适配的关键
- arch/arm/mach-rockchip/rk3588/ - RK3588 芯片初始化
- configs/orangepi-5-pro_defconfig - 你的板子配置
- arch/arm/dts/rk3588s-orangepi-5-pro.dts - 设备树
需要了解的:
- drivers/ - 驱动在哪里
- common/ - 命令和环境变量实现
- include/ - 头文件位置
- lib/ - 库函数
移植时几乎不动的:
- arch/arm/cpu/ - SoC 代码,同芯片的板子通用
- net/、fs/ - 协议和文件系统,通用代码
- lib/ - 库函数,通用代码
