第3章 U-Boot概述:历史、特点、支持的架构与源码结构分析
做嵌入式开发这么多年,我接触过的Bootloader不下十种。但要说哪个最值得花时间学,我首推U-Boot。为什么?因为它几乎统治了整个嵌入式Linux世界。今天咱们就来好好聊聊U-Boot——它的来龙去脉、凭什么这么流行、以及源码到底长什么样。
3.1 U-Boot的历史:从PPCBoot到通用引导加载器
U-Boot的故事要从2000年左右说起。那时候PowerPC架构在嵌入式领域很火,德国工程师Wolfgang Denx搞了个叫PPCBoot的项目,专门给PowerPC板子做引导。后来ARM、MIPS这些架构也开始冒头,社区一合计——干脆合并吧!于是2002年,U-Boot(全称Universal Bootloader)正式诞生。
我记得刚入行那会儿,U-Boot还只支持十几种开发板。现在呢?超过2000种板子,从单片机级别的Cortex-M到服务器级的PowerPC,几乎全覆盖。这背后是无数工程师的贡献,也是U-Boot能成为事实标准的原因。
关键里程碑:
- 2000年:PPCBoot项目启动(仅支持PowerPC)
- 2002年:U-Boot 1.0发布,支持ARM、MIPS
- 2008年:U-Boot进入主线Linux内核社区维护体系
- 2012年:引入Device Tree支持,告别硬编码
- 2016年:支持FIT Image(扁平化镜像树)
- 2020年:支持RISC-V架构
3.2 U-Boot的特点:为什么大家都选它?
说白了,U-Boot能活到现在,靠的是几个硬实力。我挑最重要的说。
3.2.1 硬件支持极其广泛
你想想看,一个Bootloader要支持ARM、x86、MIPS、PowerPC、RISC-V、MicroBlaze、Nios II……这本身就是个奇迹。我曾在项目里同时用U-Boot给i.MX6(ARM)和P2020(PowerPC)做引导,代码风格完全一致,只是配置不同。这种跨平台能力,其他Bootloader真比不了。
3.2.2 功能丰富得像个小操作系统
U-Boot不只是引导内核。它自带文件系统驱动(ext4、FAT、UBIFS)、网络协议栈(TFTP、NFS、DHCP)、命令行解释器、内存测试工具、Flash擦写工具……甚至还能跑简单的脚本。有一次我在现场调试,板子连不上网,直接用U-Boot的tftp命令从笔记本拉镜像,省了拆机箱的功夫。
3.2.3 开源且社区活跃
U-Boot采用GPLv2许可证,源码完全开放。社区每两三个月发一个正式版,日常patch不断。遇到问题去邮件列表问,通常当天就有回复。我个人习惯是直接看主线代码,因为厂商提供的BSP往往滞后,主线里已经修了很多坑。
| 特性 | U-Boot | 其他Bootloader(如RedBoot、GRUB) |
|---|---|---|
| 支持的CPU架构 | 10+种 | 通常2-3种 |
| 文件系统支持 | ext4/FAT/UBIFS/JFFS2 | 有限或需额外移植 |
| 网络协议 | TFTP/NFS/DHCP/HTTP | 通常仅TFTP |
| 脚本能力 | Hush shell(类Bash) | 简单宏或硬编码 |
| 调试工具 | MD/MM/MW/CRC/内存测试 | 基本无 |
3.3 支持的架构:一张图看懂
U-Boot支持的架构实在太多,我画了个结构图帮你理清脉络。这张图展示了U-Boot的架构适配层——说白了就是怎么让同一套代码跑在不同CPU上。
3.4 源码结构分析:U-Boot的目录到底放了什么?
下载一份U-Boot源码,解压后你会看到几十个目录。第一次接触的人容易懵。别急,我带你捋一遍核心目录。
3.4.1 顶层目录概览
u-boot/
├── arch/ # 架构相关代码(核心!)
├── board/ # 板级支持包
├── common/ # 通用功能(命令行、环境变量、启动逻辑)
├── configs/ # 默认配置文件(defconfig)
├── doc/ # 文档
├── drivers/ # 设备驱动
├── dts/ # 设备树源文件
├── fs/ # 文件系统支持
├── include/ # 头文件
├── lib/ # 库函数(zlib、crc、sha等)
├── net/ # 网络协议栈
├── scripts/ # 编译脚本、Kconfig
├── test/ # 测试代码
└── tools/ # 主机端工具(mkimage等)
这里面,arch/和board/是移植工作的主战场。我每次移植新板子,80%的时间都花在这两个目录里。
3.4.2 arch/ 目录:CPU的命根子
以ARM为例,arch/arm/下面长这样:
arch/arm/
├── cpu/ # CPU初始化、异常向量、缓存操作
│ ├── armv7/ # Cortex-A7/A8/A9等
│ ├── armv8/ # Cortex-A53/A72等
│ └── ...
├── include/ # 架构相关头文件
├── lib/ # 通用函数(如board_init_f)
├── mach-*/ # 厂商相关(mach-sunxi、mach-imx等)
└── dts/ # 设备树文件
嗯,这里要注意:mach-*目录是按芯片厂商分的。比如全志的H3在mach-sunxi里,NXP的i.MX6在mach-imx里。移植时先找到对应的mach-目录,能省不少事。
3.4.3 board/ 目录:板子的身份证
每个开发板在board/下都有自己的文件夹。里面通常包含:
board.c:板级初始化函数(DDR配置、时钟、GPIO)Kconfig:板级配置选项MAINTAINERS:维护者信息Makefile:编译规则
我曾经接手过一个项目,前任把DDR初始化参数写死在board.c里,换了DDR颗粒后死活起不来。后来我改成从设备树读取时序参数,一劳永逸。所以我的建议是:能用设备树就别硬编码。
3.4.4 configs/ 目录:配置的起点
每个板子都有一个defconfig文件,比如configs/sandbox_defconfig。编译时执行make xxx_defconfig,U-Boot就会生成.config。这个文件决定了哪些功能被编译进去。
小技巧: 如果你要移植新板子,找一个相近的defconfig复制一份,然后修改。比如你的板子用i.MX6ULL,就参考mx6ull_14x14_evk_defconfig。这样比从零开始快得多。
3.4.5 启动流程对应的源码
U-Boot的启动分两个阶段,对应的源码位置如下:
| 阶段 | 主要文件 | 功能 |
|---|---|---|
| SPL(第一阶段) | arch/arm/cpu/armv7/start.Scommon/spl/spl.c |
初始化CPU、DDR、加载U-Boot主程序 |
| U-Boot主程序(第二阶段) | common/board_f.ccommon/board_r.c |
初始化外设、加载内核、进入命令行 |
board_f.c里的init_f函数负责早期初始化,board_r.c里的init_r负责后期初始化。这两个文件我建议你仔细读一遍,能帮你理解U-Boot的启动脉络。
3.5 避坑指南:我踩过的几个坑
我曾经犯过的错:
- 坑1: 直接修改
include/configs/xxx.h里的宏定义,结果下次make distclean后全丢了。正确做法是修改configs/xxx_defconfig,或者用menuconfig。 - 坑2: 以为所有板子都走同样的启动流程。实际上,有些芯片用SPL,有些用TPL(第三阶段引导),还有些直接跳转。移植前先看芯片手册的Boot ROM章节。
- 坑3: 忽略
dts/目录下的设备树文件。U-Boot从2012年就开始用设备树了,如果你还在board.c里写死寄存器地址,那你就out了。
3.6 本章小结
U-Boot之所以能成为嵌入式领域的标配,靠的是广泛的架构支持、丰富的功能、活跃的社区。它的源码结构清晰——arch/管CPU、board/管板子、drivers/管外设、configs/管配置。理解了这些,你再看U-Boot源码就不会觉得头大了。