1. U-Boot 概述:历史、作用与启动流程

做嵌入式开发的朋友,对 U-Boot 应该都不陌生。它全称是 Das U-Boot,德语里是“潜艇”的意思。嗯,这名字挺有意思,但它的作用可一点也不“潜”——它是嵌入式系统的启动加载器,说白了,就是负责把操作系统内核从存储介质里捞出来,然后启动它。

我最早接触 U-Boot 是在 2010 年左右,当时做一款基于 ARM9 的工控板。那会儿调试启动问题,经常一搞就是大半天。后来慢慢摸透了它的脾气,才发现 U-Boot 的设计其实非常精巧。今天咱们就来聊聊它的历史、作用,以及整个启动流程。

1.1 U-Boot 的历史与作用

U-Boot 最初是由德国工程师 Wolfgang Denk 在 1999 年发起的,基于 PPCBoot 和 ARMBoot 合并而来。它支持几乎所有的嵌入式处理器架构:ARM、MIPS、RISC-V、x86、PowerPC……你想想看,一个 bootloader 能覆盖这么多平台,这在开源世界里是非常难得的。

它的核心作用,我总结为三点:

  • 硬件初始化:上电后,CPU、内存、串口、Flash 等基本外设需要先跑起来。U-Boot 就是干这个的。
  • 加载操作系统:从 NAND、eMMC、SD 卡、网络(TFTP)等介质中读取内核镜像,然后跳转到内核入口。
  • 提供交互式调试环境:通过串口命令行,你可以手动修改环境变量、烧写 Flash、测试内存、甚至直接执行裸机程序。

我个人习惯:在项目初期,先把 U-Boot 的串口输出调通。只要能看到 U-Boot 的启动信息,后面的事情就好办多了。如果连串口都没输出,那八成是硬件连接或时钟配置的问题。

1.2 U-Boot 在嵌入式系统中的启动流程

嵌入式系统上电后,U-Boot 的启动流程大致可以分为两个阶段:SPL(Secondary Program Loader) 和 U-Boot 本体。为什么分两段?因为早期的嵌入式芯片内部 SRAM 很小,放不下完整的 U-Boot,所以先跑一个精简版 SPL,初始化 DDR 内存,再把完整的 U-Boot 从 Flash 搬到内存里运行。

下面这张图是我画的启动流程,你可以对照着看:

U-Boot 启动流程框架图 U-Boot 启动流程框架图 上电复位 / ROM Code SPL 阶段(初始化 SRAM) 初始化 DDR 内存 加载 U-Boot 本体到 DDR U-Boot 第二阶段(完整初始化) 加载并启动 Linux 内核 说明 SPL 通常只有 4KB~32KB 运行在芯片内部 SRAM 中 负责初始化 DDR 和加载 U-Boot

这张图里,我特意把 SPL 阶段标红了。为什么?因为这是最容易出问题的地方。我曾经遇到过一个项目,SPL 在初始化 DDR 时时序参数配错了,导致 U-Boot 本体加载到内存后,一运行就死机。查了整整两天,最后用示波器量了 DDR 时钟才发现问题。

1.3 U-Boot 源码结构概览

U-Boot 的源码目录结构,初看会觉得有点乱。但如果你理解了它的设计思路,就会发现其实很有条理。下面这个表格列出了最关键的几个目录:

目录/文件 作用 我的经验
arch/ 架构相关代码,如 ARM、RISC-V、x86 移植时主要改这里,尤其是 arch/arm/mach-xxx
board/ 板级初始化代码,每个板子一个目录 我习惯先在这里找到最接近的参考板,再改
drivers/ 外设驱动,如串口、网卡、MMC、NAND 调试新硬件时,先确认驱动是否已支持
include/configs/ 板级配置头文件,定义宏开关 配置错了,启动就卡住。我吃过这个亏
common/ 通用代码,如命令行、环境变量、启动逻辑 调试启动流程时,常在这里加打印信息
dts/ 设备树源文件(.dts) 新板子必须自己写 dts,后面章节会细讲

你看,U-Boot 的源码结构其实很清晰:arch 管 CPU 架构,board 管板级硬件,drivers 管外设,include/configs 管配置。移植一个新板子,基本上就是在这几个目录里做文章。

一个小技巧:如果你拿到一块新板子,想快速上手 U-Boot 移植,可以先在 board/ 目录下找一个芯片型号相近的参考板,把它的配置文件和板级代码复制过来,然后逐步修改。这样比从零开始写要快得多。

1.4 启动流程中的关键点

启动流程里,有几个关键点我想特别提一下。这些地方是故障的高发区:

  1. 时钟配置:CPU 主频、DDR 频率、外设时钟,任何一个配错了,系统都可能起不来。我建议先确认晶振频率,再对照芯片手册配 PLL。
  2. DDR 初始化:这是 SPL 阶段最核心的工作。DDR 的时序参数(如 CAS 延迟、刷新周期)必须和硬件设计一致。我曾经因为 DDR 型号换了一个批次,忘了改参数,结果板子间歇性死机。
  3. 存储介质识别:U-Boot 要从 Flash 或 eMMC 里读内核,如果存储控制器初始化失败,或者分区表不对,就会卡在“Loading ... ”那里。
  4. 设备树传递:U-Boot 启动内核时,会把设备树(DTB)的地址传给内核。如果 DTB 地址不对,内核会 panic。嗯,这个后面我们会专门用一章来聊。

避坑指南:我曾经在调试一块 i.MX6 板子时,SPL 能正常跑,但 U-Boot 本体加载后串口就没输出了。查了半天,发现是 SPL 里初始化了 UART,但 U-Boot 第二阶段又重新初始化了一次,把波特率改错了。解决方案是在 U-Boot 的板级代码里跳过 UART 的重复初始化。

1.5 小结

这一章我们聊了 U-Boot 的历史、作用、启动流程和源码结构。说白了,U-Boot 就是嵌入式系统的“看门人”——它负责把硬件初始化好,然后把内核请进来。理解它的启动流程,是后续调试和故障定位的基础。