嵌入式LinuxU-Boot引导与硬件适配实战 - 第1节
1.1 U-Boot 的前世今生
U-Boot,全称是 "Universal Boot Loader"。说白了,它就是嵌入式系统上电后跑的第一段正经程序。
我刚开始接触嵌入式时,总觉得 Bootloader 不就是把内核加载进内存嘛,有什么好研究的?直到有一次,我在一块新板子上折腾了整整三天,系统就是起不来。最后发现,是 U-Boot 的 DDR 初始化参数配错了。嗯,从那以后我再也不敢小看它了。
U-Boot 的历史脉络
U-Boot 的源头可以追溯到 1999 年的 PPCBoot,当时主要给 PowerPC 架构用。后来 ARM 架构火了,社区把它扩展成了支持多架构的通用 Bootloader,改名叫 U-Boot。
- 1999 年:PPCBoot 诞生,专为 PowerPC 设计
- 2002 年:更名为 U-Boot,开始支持 ARM、MIPS 等架构
- 2008 年:Denx 软件工程公司接管维护,社区活跃度飙升
- 2015 年至今:引入 Device Tree、FIT Image、SPL 等机制,成为事实标准
核心认知:U-Boot 不是 Linux 内核的一部分,它是独立于内核的开源项目。你完全可以在 U-Boot 上跑别的操作系统,比如 VxWorks 或裸机程序。
1.2 U-Boot 在嵌入式 Linux 启动流程中的位置
嵌入式 Linux 的启动,就像一场接力赛。每一棒都有明确的任务,交接不好就会翻车。
完整的启动链条是这样的:
- ROM Code(芯片内部固件,不可修改)
- SPL / TPL(U-Boot 的一级加载器,初始化最小硬件)
- U-Boot 主程序(完整功能,加载内核)
- Linux 内核(解压、初始化、挂载根文件系统)
- 用户空间(init 进程、应用程序)
你想想看,如果没有 U-Boot,芯片上电后 ROM Code 直接去加载 Linux 内核行不行?理论上可以,但实际不行。因为 ROM Code 太小了,它不认识文件系统,不认识网络协议,更不会做 DDR 训练。U-Boot 就是那个"承上启下"的角色。
我的经验:调试启动问题时,我习惯先确认 U-Boot 阶段是否正常。方法很简单——看串口有没有输出 U-Boot 的版本号。如果连这个都没有,说明硬件或 ROM Code 阶段就挂了,别急着查内核。
1.3 U-Boot 源码结构概览
U-Boot 的源码目录,我第一次看的时候也觉得有点乱。但摸清楚规律后,其实很好找东西。
顶层目录结构
| 目录/文件 | 作用 | 我常用的场景 |
|---|---|---|
arch/ |
架构相关代码(arm、riscv、x86 等) | 改 CPU 初始化、异常向量表 |
board/ |
板级代码(厂商自定义) | 加新板子时,先复制这个目录 |
drivers/ |
外设驱动(串口、网卡、MMC 等) | 移植新网卡芯片时必看 |
include/ |
头文件、配置宏 | 改 include/configs/ 下的板级配置 |
common/ |
通用命令和框架代码 | 加自定义命令时参考 |
cmd/ |
命令行命令实现 | 每个 .c 文件对应一个命令 |
net/ |
网络协议栈(TFTP、NFS 等) | 调试网络启动时排查 |
scripts/ |
编译脚本、配置工具 | Kconfig 配置系统相关 |
Makefile |
顶层构建文件 | 加编译选项、链接脚本 |
避坑提醒:我曾经犯过一个低级错误——直接在 arch/arm/cpu/ 下改代码,结果编译报错找不到符号。后来才发现,U-Boot 的编译依赖 board/ 目录下的板级配置,不是所有 ARM 芯片都走同一套初始化流程。改代码前,先确认你的板子用的是哪个 SoC 系列。
SPL 与主程序的分离
现代 U-Boot 引入了 SPL(Secondary Program Loader)机制。为什么需要它?因为有些芯片的片上 SRAM 只有几十 KB,装不下完整的 U-Boot。SPL 就是那个"瘦身版"的加载器,只做一件事——初始化 DDR,然后把完整的 U-Boot 从 Flash 拷贝到 DDR 里运行。
源码中,SPL 相关的代码分散在:
spl/目录:SPL 框架代码arch/arm/mach-xxx/spl.c:SoC 相关的 SPL 初始化board/xxx/board.c:板级 SPL 配置
编译时,通过 CONFIG_SPL_BUILD 宏来区分是编译 SPL 还是主程序。同一个 .c 文件,用 #ifdef CONFIG_SPL_BUILD 可以写两套逻辑。
我的习惯:调试 SPL 时,我会在 spl/spl.c 的 board_init_f() 函数里加串口打印。因为 SPL 阶段还没有完整的控制台,用 putc() 或直接写寄存器最靠谱。等 SPL 跑通了,再调主程序。
配置系统:从 include/configs 到 Kconfig
老版本的 U-Boot 用 include/configs/ 下的头文件做配置,比如 include/configs/vexpress_common.h。里面全是 #define 宏,改起来直接,但容易冲突。
新版本逐步迁移到 Kconfig 机制(跟 Linux 内核一样)。配置项定义在 Kconfig 文件中,用 make menuconfig 图形化配置。
我个人更推荐用 Kconfig 方式,原因很简单:
- 依赖关系自动处理,不会漏配
- 支持默认值,减少重复劳动
- 配置项可搜索,比翻头文件快多了
但说实话,很多老项目还在用头文件方式。如果你接手的是 legacy 代码,别急着迁移,先保证能编译通过再说。
1.4 本章小结
U-Boot 是嵌入式 Linux 启动的第一道关卡。它不复杂,但细节很多。记住三点:
- 它负责从 ROM Code 手里接过控制权,把内核请进内存
- 源码结构按架构、板级、驱动分层,改哪里找哪里
- SPL 是"瘦身版"U-Boot,专为资源受限的芯片设计