3. U-Boot源码结构分析:目录结构、关键文件功能解析
做嵌入式底层开发,U-Boot 源码是你绕不开的一座山。我第一次接触 U-Boot 时,面对那一堆文件夹,说实话有点懵。但摸爬滚打几年后,我发现它的目录结构其实很有章法。今天我就带你把它拆开看看。
3.1 顶层目录结构:一眼看穿骨架
U-Boot 的顶层目录,说白了就是按功能模块划分的。我习惯把它分成三大块:平台相关、通用驱动、工具脚本。
| 目录名 | 核心作用 | 我眼中的关键点 |
|---|---|---|
arch/ |
CPU架构相关代码 | ARM、RISC-V、x86 等,启动入口就在这里 |
board/ |
具体板级初始化 | 每个开发板一个文件夹,硬件差异全在这 |
drivers/ |
外设驱动 | 串口、网卡、MMC、NAND 等,移植大头 |
common/ |
通用功能模块 | 命令行、环境变量、HUSH shell 等 |
include/ |
头文件与配置 | configs/ 下是板级配置,经常改 |
lib/ |
库函数 | 字符串、压缩、加密等基础库 |
net/ |
网络协议栈 | TFTP、NFS、DHCP 等,调试必备 |
tools/ |
主机端工具 | mkimage、fdt 处理等,打包镜像用 |
board/ 和 include/configs/ 这两个目录。硬件差异基本都体现在这里,其他地方很少动。
3.2 关键文件之一:start.S —— 一切从这里开始
U-Boot 启动的第一条指令,就在 arch/arm/cpu/armv7/start.S(ARM 为例)。这个文件是纯汇编写的,负责把 CPU 从复位状态带到 C 语言环境。
它干了这几件大事:
- 设置异常向量表 —— 中断、复位、未定义指令等入口
- 切换到 SVC 模式 —— 关中断,确保安全
- 初始化 CPU 关键寄存器 —— CP15、MMU、Cache 等
- 设置栈指针 —— 为调用 C 函数做准备
- 清零 BSS 段 —— 全局变量初始化为 0
- 跳转到 board_init_f —— 进入 C 语言世界
我曾经在一个项目中,板子上电后 U-Boot 死活跑不起来。折腾了两天,最后发现是 start.S 里异常向量表地址没对齐。嗯,ARM 要求向量表必须 32 字节对齐,少一个字节都不行。
_start)必须和链接脚本(u-boot.lds)一致。我曾经见过有人改了链接脚本忘了改 start.S,结果跳转地址全错,板子直接变砖。
3.3 关键文件之二:board.c —— 板级初始化的心脏
board.c 位于 board/你的板子/ 目录下。它负责板级硬件初始化,比如:
- 时钟配置(PLL、分频)
- DDR 初始化(时序、参数)
- GPIO 复用设置
- 串口引脚配置
- 板级特定外设上电
这个文件里最重要的函数是 board_init() 和 dram_init()。前者做板级通用初始化,后者告诉 U-Boot 你的 DDR 有多大。
// 一个典型的 board_init 示例
int board_init(void)
{
/* 使能外设时钟 */
enable_peripheral_clocks();
/* 配置调试串口引脚 */
setup_uart_pins();
/* 初始化板级 LED,用于调试指示 */
gpio_direction_output(LED_PIN, 1);
return 0;
}
int dram_init(void)
{
/* 告诉 U-Boot DDR 大小,单位是字节 */
gd->ram_size = PHYS_SDRAM_1_SIZE;
return 0;
}
3.4 关键文件之三:main.c —— 命令行与交互的入口
common/main.c 是 U-Boot 进入命令行模式前的最后一道关卡。它负责:
- 调用
board_init_r()做运行时初始化 - 初始化各种外设驱动(网卡、存储等)
- 加载并执行启动脚本(bootcmd)
- 进入主循环,等待用户输入命令
main.c 里有个函数叫 main_loop(),它是个死循环。说白了,U-Boot 启动后就在这个循环里一直转,等着你敲命令或者自动执行 bootcmd。
我记得有一次调试,板子启动后自动进入了 Linux,但我想停在 U-Boot 命令行。查了半天,发现是 bootdelay 环境变量设成了 0。改成 3 秒后,终于能在启动时按任意键打断了。
3.5 启动流程全景图
下面这张图,把 U-Boot 从复位到命令行的完整流程串起来了。我建议你把它存下来,调试时对照着看。
3.6 三个关键文件的协作关系
这三个文件不是孤立的,它们通过函数调用链串在一起。我画了个简化的调用关系:
复位入口 (start.S)
↓
bl board_init_f (board.c 中实现)
↓
重定位 U-Boot 自身
↓
bl board_init_r (board.c 中实现)
↓
main_loop() (main.c 中实现)
↓
run_command() (解析并执行命令)
3.7 总结:记住这三句话
- start.S —— 把 CPU 从「裸金属」带到「C 语言」
- board.c —— 把板子从「死硬件」带到「活系统」
- main.c —— 把 U-Boot 从「初始化」带到「交互」
搞懂了这三个文件,U-Boot 源码对你来说就不再是黑盒了。下次移植新板子,你就知道该从哪里下手。嗯,先改 start.S 的链接地址,再调 board.c 的 DDR 参数,最后在 main.c 里配好启动命令。这条路,我走过很多遍了。