第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上。

U-Boot 架构支持全景图 U-Boot 核心(通用代码) arch/ 架构适配层(CPU初始化、异常处理、缓存管理) ARM x86 MIPS PowerPC RISC-V 其他 board/ 板级支持包(DDR初始化、GPIO配置、时钟设置) drivers/ 驱动层(串口、网卡、Flash、I2C、SPI、MMC) tools/ 工具层(mkimage、fdt工具、环境变量处理) 说明:U-Boot通过 arch/ → board/ → drivers/ 三层隔离,实现“一次编写,多架构运行”

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.S
common/spl/spl.c
初始化CPU、DDR、加载U-Boot主程序
U-Boot主程序(第二阶段) common/board_f.c
common/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源码就不会觉得头大了。