1. U-Boot概述:从历史到启动流程

各位同学,咱们今天开始聊U-Boot。说实话,我最早接触U-Boot是在2008年做一款ARM9的工控板。那时候刚入行,看着串口打印出一堆启动信息,觉得这东西挺神秘的。后来踩了不少坑,才慢慢摸清楚它的脾气。

U-Boot,全称是"Universal Boot Loader"。翻译过来就是"通用引导加载程序"。你想想看,嵌入式设备上电后,CPU首先执行的是固化在ROM里的代码,然后才是U-Boot。它的任务说白了就两件:初始化硬件、加载操作系统。

1.1 U-Boot的历史

U-Boot的前身是8xxROM,由德国工程师Wolfgang Denx在1999年左右发起。后来项目改名为PPCBoot,只支持PowerPC架构。到了2002年,因为要支持ARM、MIPS等其他架构,才正式更名为U-Boot。

我记得有一年参加嵌入式Linux会议,Wolfgang本人还上台讲了U-Boot的设计哲学。他说了一句话我印象很深:"U-Boot不是操作系统,它只是操作系统的搬运工。"

时间 事件 说明
1999年 8xxROM诞生 仅支持PowerPC 8xx系列
2000年 更名为PPCBoot 扩展支持更多PowerPC型号
2002年 更名为U-Boot 支持ARM、MIPS、x86等架构
2008年 进入Denx维护期 社区活跃度大幅提升
2015年 引入设备树支持 硬件描述与代码解耦

现在U-Boot已经支持超过30种CPU架构、上千款开发板。你随便拿一块主流嵌入式板子,十有八九都能跑U-Boot。

1.2 U-Boot在嵌入式系统中的作用

嵌入式系统启动,一般分三个阶段:

  1. ROM代码:芯片出厂固化的,负责最基础的硬件初始化
  2. U-Boot:二次引导,加载内核和设备树
  3. 操作系统:Linux、RTOS等

U-Boot夹在中间,承上启下。它的核心作用我总结为四点:

  • 硬件初始化:DDR、串口、网卡、Flash等外设
  • 环境变量管理:启动参数、IP地址、文件系统路径等
  • 启动加载:从Flash、网络、SD卡等介质加载内核
  • 调试与恢复:提供命令行交互,方便开发调试

我个人的经验:U-Boot最值钱的地方就是环境变量。很多系统启动失败,最后查出来都是环境变量配错了。后面我会专门花几章讲这个。

你可能会问:为什么不用GRUB?嗯,GRUB是为x86 PC设计的,依赖BIOS/UEFI。嵌入式设备没有这些,U-Boot直接操作寄存器,更轻量、更底层。

1.3 U-Boot的启动流程概览

U-Boot启动流程,我习惯分成两个阶段:SPL(Secondary Program Loader)和U-Boot proper。早期U-Boot是单阶段启动,后来芯片内部SRAM太小,才拆成两段。

下面这张图是我自己画的启动流程,你仔细看看:

U-Boot 启动流程概览 上电复位 ROM代码(固化) SPL(第一阶段) U-Boot proper(第二阶段) 环境变量加载 启动内核 说明 SPL 初始化 DDR 和基本外设 U-Boot proper 加载环境变量 环境变量决定启动参数 常见分支 • 串口中断进入命令行 • 自动启动(bootcmd) • 网络启动(tftp/nfs)

流程看起来简单,但每个环节都有坑。我举个例子:SPL阶段如果DDR时序配错了,板子直接死在那,串口连个字符都打不出来。我曾经在一款i.MX6的板子上调DDR参数调了整整两天,最后发现是芯片手册上一个时序值写错了。

小技巧:调试U-Boot启动问题时,优先看串口输出。如果SPL阶段有打印,说明DDR基本正常。如果连SPL的打印都没有,先查电源和时钟。

1.4 环境变量在启动流程中的位置

环境变量加载发生在U-Boot proper阶段。具体来说,是在board_init_r()函数里调用的env_relocate()。这个函数会从存储介质(SPI Flash、EMMC、NAND等)读取环境变量到内存中。

环境变量决定了后续所有行为:

  • bootcmd:自动启动时执行的命令
  • bootargs:传给内核的启动参数
  • ipaddr/serverip:网络启动的IP配置
  • loadaddr:内核加载地址

注意:环境变量存储区域如果损坏,U-Boot会使用默认值。但默认值往往和你的板子不匹配,导致启动失败。我曾经遇到过SPI Flash坏块导致环境变量丢失,板子反复重启。解决办法是在Flash里预留两个备份区域。

1.5 小结

这一章我们聊了U-Boot的来龙去脉、它在嵌入式系统里的角色,以及启动流程的大致脉络。环境变量是U-Boot的灵魂,后面的章节我们会深入每一个变量、每一种存储方式、每一种调试手段。