3. U-Boot 源码结构解析:顶层目录、板级目录、CPU 架构目录的功能划分
拿到 U-Boot 源码包,第一眼看到那一堆文件夹,很多人会懵。我刚开始接触时也一样,感觉像进了迷宫。其实它的结构很有章法,说白了就是三个层次:顶层管全局、板级管具体硬件、架构管 CPU 核心。搞懂这三层,移植工作就成功了一半。
3.1 顶层目录:U-Boot 的“大脑”
顶层目录是 U-Boot 的调度中心。你解压源码后,第一眼看到的那些文件和文件夹,就是它了。我习惯先看 Makefile 和 Kconfig,因为整个编译流程都靠它们串联。
| 目录/文件 | 作用 | 我的经验 |
|---|---|---|
Makefile |
顶层构建脚本,定义编译规则 | 移植时经常要改这里添加新板子 |
Kconfig |
配置系统入口,管理菜单选项 | 用 make menuconfig 就是读它 |
arch/ |
CPU 架构相关代码 | ARM、RISC-V 都在这里 |
board/ |
板级初始化代码 | 移植的核心战场 |
common/ |
通用函数库 | 命令解析、环境变量都在这里 |
drivers/ |
设备驱动 | 串口、网卡、Flash 驱动 |
include/ |
头文件 | 配置宏定义、数据结构 |
lib/ |
库函数 | 字符串操作、CRC 计算等 |
tools/ |
主机端工具 | mkimage、fdt 处理工具 |
board/ 和 include/configs/ 下的文件。这两个地方藏着板子的“身份证”。
3.2 板级目录:你的板子在这里“安家”
板级目录 board/ 是移植工作的主战场。每个板子在这里都有一个专属文件夹,里面放着板子特有的初始化代码。说白了,就是告诉 U-Boot:“我的板子长什么样,怎么启动。”
举个例子,我移植过一块基于 TI AM335x 的板子,它的路径是 board/ti/am335x/。里面通常有:
board.c—— 板级初始化函数,比如board_init()mux.c—— 引脚复用配置Kconfig—— 板级配置选项Makefile—— 编译规则
我记得有一次,客户板子的 DDR 参数和参考设计不一样。我就是在 board.c 里找到 dram_init() 函数,把时序参数改掉才跑起来的。嗯,这里要注意:板级目录只放“这个板子特有”的代码,通用的东西别往里塞。
arch/,板子特有的放 board/。
3.3 CPU 架构目录:芯片的“内核”
arch/ 目录下按 CPU 架构分家。ARM、x86、RISC-V、MIPS……各占一个子目录。以 ARM 为例,路径是 arch/arm/,里面又细分:
| 子目录 | 内容 | 说明 |
|---|---|---|
cpu/ |
CPU 核心初始化 | 启动汇编、异常向量表 |
include/ |
架构相关头文件 | 寄存器定义、内存映射 |
lib/ |
架构通用函数 | cache 操作、MMU 设置 |
mach-*/ |
SoC 厂商代码 | 比如 mach-sunxi、mach-imx |
你想想看,为什么要把架构代码单独拎出来?因为不同 CPU 的启动流程天差地别。ARM 要关中断、设栈指针、开 MMU;RISC-V 要处理 M 模式和 S 模式的切换。这些代码如果混在板级目录里,移植到另一个板子时根本没法复用。
核心原则:
arch/—— 管“CPU 怎么跑”board/—— 管“板子长什么样”- 顶层 —— 管“怎么编译、怎么配”
3.4 三者的协作关系
光说理论不够,我画张图帮你理清关系。这张图展示了 U-Boot 启动时,三个目录如何配合工作:
从这张图能看明白:启动是从架构层开始的。CPU 上电后先跑 arch/arm/cpu/start.S,做完最低限度的初始化,再跳到板级代码。板级代码把 DDR、时钟、串口搞定后,才进入通用的 common/ 流程。
arch/ 或 board/ 的早期初始化。如果打印了但卡在某个命令,那就是 common/ 或 drivers/ 的问题。
3.5 其他重要目录
除了上面三个核心目录,还有几个地方你也会经常打交道:
drivers/—— 设备驱动的大本营。串口、网卡、Flash、USB……按子系统分文件夹。移植时如果板子换了网卡芯片,主要改这里。include/configs/—— 板级配置头文件。比如am335x_evm.h,里面定义了内存大小、环境变量分区、默认启动命令。我每次移植第一件事就是复制一个参考板子的 .h 文件,然后改参数。common/—— 通用代码。命令处理(cmd/)、环境变量(env/)、启动流程(board_f.c、board_r.c)都在这里。这部分一般不用动,除非你要加自定义命令。tools/—— 主机工具。比如mkimage用来给内核镜像加头部,fdtgrep用来裁剪设备树。交叉编译时这些工具要在 PC 上跑。
include/configs/ 里写死硬件参数。我见过有人把 DDR 时序直接写在 .h 文件里,结果换了个 DDR 型号,改起来痛苦得要命。正确的做法是用设备树(DTB)来描述硬件,U-Boot 从设备树里读参数。
3.6 总结一下
U-Boot 的源码结构,说白了就是“分而治之”:
- 顶层管编译和配置,是总调度
- 架构层管 CPU 核心,是“骨架”
- 板级层管具体硬件,是“血肉”
- 驱动层和通用层提供“器官”和“功能”
搞懂这个分层逻辑,移植时就知道代码该往哪放。不会把 CPU 初始化和板子初始化混在一起,也不会在通用代码里塞板子特有的 hack。嗯,这就是我多年移植经验的总结——结构清晰了,问题就少了一半。