第2章:U-Boot项目简介

U-Boot,全称 Universal Boot Loader。说白了,它就是嵌入式系统的「第一段程序」。CPU上电后,最先跑的就是它。我刚开始接触嵌入式时,总觉得Bootloader就是个跳板,没啥技术含量。后来被坑过几次才明白——U-Boot玩不转,后面Linux内核调试根本无从下手。

这一章,我们先把U-Boot的底细摸清楚。历史、版本、源码结构、启动流程、配置系统,一个一个来。

2.1 U-Boot的历史与版本

U-Boot的前身是8xxROM,由德国工程师Wolfgang Denx在1999年左右发起。后来合并了PPCBoot、ARMBoot等项目,2002年正式更名为U-Boot。这个名字很直白——Universal Boot Loader,通用引导加载程序。

为什么说它「通用」?因为它支持几乎所有的嵌入式CPU架构:ARM、RISC-V、x86、MIPS、PowerPC、RISC-V……我这些年经手的项目,从Cortex-M3到Cortex-A72,从龙芯到飞腾,底层全是U-Boot。

版本方面,U-Boot目前采用基于时间的命名方式:

版本格式 示例 说明
v2023.01 2023年1月发布 年度大版本,功能稳定
v2023.04-rc1 2023年4月候选版 开发中版本,可能有新功能
v2023.07 2023年7月发布 半年一次正式版
我的建议:做产品移植时,别追最新版。选一个经过验证的LTS风格版本,比如v2023.01或v2022.10。我曾经在v2021.04上踩过坑——新版本改了设备树解析逻辑,导致旧板子启动失败,排查了两天才发现是版本兼容性问题。

2.2 U-Boot的源码结构

下载一份U-Boot源码,解压后你会看到这样的目录布局:

u-boot/
├── arch/              # 架构相关代码(核心)
│   ├── arm/           # ARM架构
│   ├── riscv/         # RISC-V架构
│   ├── x86/           # x86架构
│   └── ...
├── board/             # 板级支持包
│   ├── myboard/       # 你的板子
│   └── ...
├── common/            # 通用功能
├── configs/           # 默认配置文件
├── drivers/           # 驱动(串口、网卡、存储等)
├── dts/               # 设备树源文件
├── include/           # 头文件
├── lib/               # 库函数
├── net/               # 网络协议栈
├── cmd/               # 命令行命令
├── tools/             # 编译工具
├── Makefile           # 顶层Makefile
└── Kconfig            # 顶层Kconfig

这个结构,我建议你记住三个关键目录:

  • arch/:CPU架构相关,移植时基本不动
  • board/:板级代码,移植的主战场
  • configs/:配置文件,决定编译哪些功能

嗯,这里要注意。很多新手一上来就改arch/,其实完全没必要。你想想看,CPU架构代码是U-Boot团队维护的,几百号人盯着,一般不会出问题。真正需要你动手的,是board/目录下的板级支持。

2.3 U-Boot的启动流程概述

U-Boot的启动流程,我习惯把它分成三个阶段:

  1. SPL(第一阶段):初始化CPU、时钟、DDR,加载完整U-Boot
  2. U-Boot proper(第二阶段):初始化外设,解析环境变量,进入命令行
  3. 启动内核(第三阶段):加载内核镜像和设备树,跳转到内核入口

下面这张图,是我自己画的一个简化流程:

U-Boot 启动流程概览 SPL 第一阶段 初始化CPU/时钟/DDR 加载U-Boot proper到DDR U-Boot proper 第二阶段 初始化外设(串口/网卡/存储) 解析环境变量 进入命令行(bootdelay) 启动内核 第三阶段 加载内核镜像 加载设备树 跳转到内核入口 💡 说明: SPL不是必须的。小内存芯片(如Cortex-M系列)可以直接运行U-Boot proper。 但大多数ARM Cortex-A芯片,DDR初始化必须在SPL中完成。
关键点:SPL和U-Boot proper是两套独立的代码。SPL编译出来通常只有几十KB,放在ROM或SPI Flash里。U-Boot proper可以到几百KB,放在NAND或eMMC里。我曾经在一个项目里,SPL只有32KB,硬塞下了DDR初始化、串口输出和NAND驱动——那感觉,就像在行李箱里塞进一头大象。

2.4 U-Boot的配置系统(Kconfig/Makefile)

U-Boot的配置系统,沿用了Linux内核的那一套——Kconfig + Makefile。你想想看,U-Boot支持几百种板子,几千个配置选项,没有一套自动化的配置管理,根本玩不转。

配置流程大致是这样的:

  1. make <board>_defconfig:加载板级默认配置
  2. make menuconfig:图形化调整配置(可选)
  3. make:根据配置编译

举个例子,假设你有一块叫「myboard」的板子:

# 第一步:加载默认配置
make myboard_defconfig

# 第二步:如果需要调整,打开图形界面
make menuconfig

# 第三步:编译
make -j4

Kconfig文件定义了配置选项的层次结构。比如你想开启网络功能,需要在Kconfig里找到:

config CMD_NET
    bool "Enable networking commands"
    default y
    help
      This enables basic networking commands like ping, tftp, etc.

Makefile则根据这些配置决定编译哪些文件:

obj-$(CONFIG_CMD_NET) += cmd_net.o
obj-$(CONFIG_CMD_PING) += cmd_ping.o

嗯,这里有个坑。我刚开始移植时,以为改了Kconfig就万事大吉了。结果编译报错,说找不到某个函数。排查了半天才发现——Kconfig只是定义了开关,Makefile里的编译规则也得配上。说白了,Kconfig是「要不要」,Makefile是「怎么要」。

避坑指南:我曾经在一个项目里,为了加一个自定义命令,改了Kconfig和Makefile,但忘了在board目录下实现对应的函数。结果编译通过,运行时直接崩溃。从那以后,我养成了一个习惯——改配置后,先make clean再重新编译,确保没有残留的旧目标文件。

最后,说一个实用技巧。U-Boot的配置系统支持「碎片化配置」——你可以在board目录下放一个名为myboard.config的文件,里面写一些额外的配置项。编译时,这些配置会自动合并到最终配置中。这样,你就不用每次都去改顶层的Kconfig了。

# board/myboard/myboard.config
CONFIG_BOOTDELAY=3
CONFIG_BOOTCOMMAND="tftp 0x82000000 kernel.img; bootm"
CONFIG_BAUDRATE=115200

这个技巧,我在多个项目里都用过。特别是当你的板子有多个变种时,每个变种放一个.config文件,管理起来非常清爽。