1. U-Boot概述
大家好,我是你们的嵌入式固件讲师。今天咱们来聊聊U-Boot——这个嵌入式世界里几乎绕不开的"看门大爷"。
先问大家一个问题:你按下开发板电源键之后,到Linux内核开始打印启动信息之前,中间发生了什么?
嗯,这中间就是U-Boot的舞台。
1.1 U-Boot是什么?
U-Boot,全称是"Universal Boot Loader",通用引导加载程序。说白了,它就是一块芯片上电后运行的第一个用户可控的程序。
我刚开始做嵌入式那会儿,总觉得Bootloader就是个"跳板",没啥技术含量。后来踩了坑才明白——没有U-Boot,你的Linux内核根本跑不起来。
U-Boot支持什么?我列几个关键点:
- 支持多种架构:ARM、x86、RISC-V、MIPS……几乎你能想到的主流CPU架构它都支持
- 支持多种存储介质:NAND Flash、NOR Flash、SD卡、eMMC、SPI Flash……
- 支持多种文件系统:FAT、ext4、UBIFS、JFFS2……
- 支持网络协议:TFTP、NFS、DHCP……方便你通过网络下载内核
我个人习惯把U-Boot比作"嵌入式系统的BIOS"。但比PC的BIOS灵活得多——你可以修改它、裁剪它、甚至完全重写它的功能。
核心要点:U-Boot是硬件初始化和操作系统加载之间的桥梁。没有它,你的嵌入式设备就是一坨冰冷的硅片。
1.2 U-Boot在嵌入式系统中的角色
U-Boot到底干了哪些活?我把它拆成三个阶段来讲:
第一阶段:硬件初始化
芯片上电后,内部ROM会执行一段固化代码,然后跳转到U-Boot。U-Boot的第一件事就是初始化硬件:
- 设置CPU时钟频率
- 初始化DDR内存控制器
- 配置串口(这样你才能看到打印信息)
- 初始化存储控制器(才能读Flash/SD卡)
我记得有一次项目,板子死活不打印任何信息。查了两天,最后发现是DDR时序参数配错了。嗯,U-Boot的硬件初始化就是这么关键——错一个参数,整个系统就哑巴了。
第二阶段:引导加载
硬件初始化完成后,U-Boot会从存储介质中读取Linux内核镜像(通常是zImage或Image),然后把它加载到内存中。这个过程可以简单到一条命令:
# 从eMMC读取内核到内存地址0x80000000
mmc read 0x80000000 0x800 0x8000
# 启动内核
bootm 0x80000000
当然,实际项目中不会这么手动操作。我们会把启动参数写到环境变量里,让U-Boot自动执行。
第三阶段:传递参数
U-Boot在跳转到内核之前,会做一件重要的事——传递设备树(Device Tree)和内核启动参数。比如:
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw'
bootz 0x80000000 - 0x83000000
这里bootz后面的三个参数分别是:内核地址、initrd地址(没有就填-)、设备树地址。
我的经验:量产项目中,U-Boot的环境变量一定要固化好。我曾经见过一个项目,因为环境变量被意外擦除,导致整批板子无法启动。后来我们都在生产脚本里加了"env save"的校验步骤。
1.3 U-Boot源码结构
U-Boot的源码结构,说白了就是一个大型的C语言工程。我建议你下载一份最新代码(git clone https://source.denx.de/u-boot/u-boot.git),对照着看。
核心目录结构如下:
| 目录/文件 | 作用 |
|---|---|
arch/ |
架构相关代码。比如arch/arm/放ARM相关,arch/x86/放x86相关 |
board/ |
板级支持包。每个开发板或产品在这里有自己的文件夹 |
common/ |
通用功能模块。比如命令行解析、环境变量管理 |
drivers/ |
各种外设驱动。串口、网卡、Flash、I2C、SPI…… |
include/ |
头文件。特别是configs/目录下,放着各板子的配置文件 |
net/ |
网络协议栈。TFTP、NFS、DHCP等 |
fs/ |
文件系统支持。FAT、ext4、UBIFS等 |
cmd/ |
命令行命令的实现。比如mmc、fatload、bootm等 |
tools/ |
主机端工具。比如mkimage(用于制作U-Boot格式的镜像) |
你想想看,这个结构是不是很清晰?架构相关放arch/,板子相关放board/,驱动放drivers/。这种分层设计,让U-Boot能支持上千种不同的板子。
避坑指南:我曾经在移植U-Boot时,直接修改了arch/arm/下的通用代码,结果导致其他ARM板子都编译不过。正确的做法是:板级修改放board/目录,架构修改放arch/目录,别混着来。
下面我用一张SVG图来展示U-Boot的整体架构和启动流程:
这张图展示了U-Boot从芯片上电到启动内核的完整流程。注意SPL那一层——有些芯片内部RAM太小,装不下完整的U-Boot,就需要一个"迷你版"的SPL先初始化DDR,再把完整的U-Boot加载到内存中。
小技巧:查看你的板子是否使用SPL,可以看源码目录下有没有spl/文件夹。如果有,说明你的U-Boot是分两阶段加载的。
最后说说源码的编译方式。U-Boot的编译和Linux内核很像:
# 先配置(以树莓派3为例)
make rpi_3_defconfig
# 然后编译
make -j4
# 编译完成后会生成:
# u-boot.bin —— 主程序
# u-boot.img —— 带U-Boot头部的镜像
# u-boot.srec —— S-Record格式(用于串口下载)
我个人习惯在编译前先执行make menuconfig,图形化界面里可以裁剪不需要的功能。量产项目里,我一般会把网络命令、USB支持、多余的文件系统都去掉,能省下不少空间。