前言:U-Boot 的核心定位
U-Boot(全称为 Das U-Boot,即 “The Universal Boot Loader”)是嵌入式世界中一款举足轻重、应用极为广泛的开源引导加载程序(Bootloader)。在嵌入式 Linux 生态系统中,它扮演着无可替代的角色,是连接底层硬件固件与上层操作系统内核的关键桥梁,已成为事实上的行业标准。
其核心使命可以高度概括为两大任务:
-
承上启下,初始化硬件:在操作系统内核获得控制权之前,U-Boot 负责完成系统硬件环境的全面初始化。这其中最关键的一步是建立并初始化 DRAM 内存,因为现代操作系统内核体积庞大,必须在足够容量的 DRAM 中才能运行。除此之外,它还包括时钟、电源管理单元(PMU)、总线等基础硬件的配置。
-
加载并启动操作系统:U-Boot 负责定位并读取存储介质(如 eMMC、SD 卡、NAND/SPI Flash、甚至网络)中的操作系统内核镜像及相关数据(如设备树
DTB、initramfs),将它们加载到 DRAM 的指定地址,最后以精确、约定的方式将 CPU 的控制权安全地交接给内核,从而启动整个操作系统。
第一章:U-Boot 的多阶段启动流程
现代 U-Boot 的启动流程并非一步到位,而是遵循一个清晰的多阶段模型。这种设计的根本原因源于芯片硬件的物理限制:SoC(系统级芯片)内部集成的高速静态内存(SRAM)容量非常有限(通常为几十到几百 KB),而外部动态内存(DRAM)容量则大得多(可达数 GB)。因此,必须有一个“先遣队”在有限的 SRAM 中运行,以完成 DRAM 的初始化,为完整功能的 U-Boot 创造运行空间。
结合芯片固化的 BootROM,完整的启动链条可以划分为以下几个核心阶段:
| 阶段 | 别名/术语 | 运行位置 | 主要作用 |
| 阶段 0 | BootROM / BL1 | SoC 内部固化的 ROM | 硬件启动的根源。上电后执行的第一段代码,负责基础时钟和 SRAM 初始化,并从预设的启动介质中加载并验证下一阶段引导程序(SPL)。 |
| 阶段 1 | SPL / BL2 | 芯片内部 SRAM | DRAM 的初始化者。作为 U-Boot 的“先遣队”,核心使命是初始化外部 DRAM 控制器和 DRAM 芯片,从而极大扩展可用内存空间,并加载完整的 U-Boot 主体到 DRAM 中。 |
| 阶段 2 | U-Boot 主体 / BL3 | 外部 DRAM | 功能完备的引导平台。负责全面的外设初始化(网络、存储、显示等),建立环境变量,提供交互式命令行,并最终加载、配置并启动操作系统内核。 |
第二章:启动时间线:从上电到内核的详细执行路径
现在,我们将上述阶段模型与具体的代码执行流程相结合,深入探究从 SoC 上电到 Linux 内核启动的每一步细节。
2.1 阶段 0:BootROM (BL1) - 硬件的苏醒
此阶段由芯片制造商在 SoC 内部的掩模 ROM (Mask ROM) 中硬编码,用户无法修改。当系统通电或复位时,处理器会从一个固定的地址(复位向量)开始执行 BootROM 中的代码。
-
核心任务:
-
最小系统初始化:初始化 CPU 核心本身、基础时钟、看门狗以及内部 SRAM。
-
启动介质扫描:根据熔断丝(fuses)或 GPIO 引脚的配置,按照预设的优先级顺序(例如:先检查 SD 卡,再检查 eMMC,然后是 SPI Flash 等)扫描可引导设备。
-
加载与校验:在启动介质的特定偏移地址(如 0 地址或特定扇区)查找引导程序的头部信息。BootROM 会对头部进行合法性校验(如计算校验和)。
-
交接控制权:校验通过后,将下一阶段的引导程序(通常是 U-Boot 的 SPL 部分)从存储介质加载到芯片内部的 SRAM 中,然后将程序计数器(PC)跳转到 SPL 的入口地址,完成控制权交接。
-
⏩ 跳转目标: U-Boot SPL 的入口点 (_start)。
2.2 阶段 1:SPL (BL2) - 在 SRAM 中搭建舞台
SPL (Secondary Program Loader) 是 U-Boot 的精简版,其设计目标是在资源极其有限的 SRAM 中完成关键任务。
-
代码入口:
arch/$(ARCH)/cpu/$(CPU)/start.S文件中的_start标号。这是由链接脚本(如u-boot-spl.lds)指定的整个 SPL 程序的入口。 -
执行流程:
-
汇编阶段 (
start.S):-
关闭中断:屏蔽所有中断,防止意外干扰。
-
设置 CPU 模式:将处理器设置为管理模式(SVC Mode),以获得足够的权限。
-
设置堆栈:在 SRAM 的高端地址设置一个临时的堆栈指针(SP),为即将到来的 C 函数调用做准备。
-
清除 BSS 段:
_bss_start到_bss_end之间的内存区域(用于存放未初始化的全局变量和静态变量)被清零。 -
跳转到 C 代码: 通过
bl board_init_f指令,跳转到 SPL 的 C 语言入口函数。
-
-
C 语言阶段 (
spl.c,board_c.c等):-
board_init_f()函数会执行一系列基本的初始化,其中最重要的任务是初始化 DRAM 控制器。这是 SPL 的核心使命。 -
一旦 DRAM 可用,SPL 会继续初始化用于加载下一阶段代码的存储设备(如 eMMC 控制器)。
-
最后,SPL 将完整的 U-Boot 主体镜像(
u-boot.bin)从存储介质加载到刚刚初始化完成的 DRAM 的某个临时地址。 -
完成加载后,SPL 会跳转到 DRAM 中 U-Boot 主体的入口点,正式结束自己的历史使命。
-
-
⏩ 跳转目标: U-Boot 主体在 DRAM 中的入口点 (_start)。
2.3 阶段 2:U-Boot 主体 (BL3) - 在 DRAM 中全面展开
完整的 U-Boot 在宽敞的 DRAM 中运行,此时它才能执行所有复杂的功能。其执行流程巧妙地分为两个子阶段:重定位前(Pre-relocation)和重定位后(Post-relocation)。
2.3.1 子阶段 A:重定位前 (board_init_f) - 准备最终运行环境
U-Boot 主体被 SPL 加载到 DRAM 的地址可能不是其最终运行的地址(链接地址)。“重定位”就是将 U-Boot 自身代码从加载地址拷贝到链接地址的过程。board_init_f 阶段的代码就是为此做准备。
-
代码入口: 同样是
arch/$(ARCH)/cpu/$(CPU)/start.S中的_start,但这是 U-Boot 主体镜像的_start。 -
文件: 主要逻辑在
common/board_f.c。 -
主要任务:
-
初始化早期外设:通常会初始化一个串口,以便能够尽早输出调试信息。
-
初始化全局数据结构
gd:分配并初始化一个名为global_data的关键数据结构,它像一个“全局变量总线”,用于在 U-Boot 的不同子系统和阶段之间传递信息。 -
执行
init_sequence_f[]:这是一个函数指针数组,包含了重定位前必须执行的一系列初始化函数,如内存控制器(此时是确认并记录DRAM大小)、定时器等。 -
计算并准备重定位:确定代码的当前加载地址和最终链接地址,为搬移做准备。
-
执行重定位:调用
relocate_code()将 U-Boot 自身的.text,.data等段从加载地址拷贝到链接地址。 -
清空 BSS 段:再次对重定位后的 BSS 段进行清零。
-
跳转到下一阶段:最后,跳转到重定位后的 C 代码入口
board_init_r。
-
⏩ 跳转目标: board_init_r(),位于重定位后地址的 U-Boot C 代码入口。
2.3.2 子阶段 B:重定位后 (board_init_r) - U-Boot 主逻辑阶段
board_init_r (r for relocation) 是 U-Boot 的“main”函数,标志着 U-Boot 所有功能的完全启动。
-
文件: 主要逻辑在
common/board_r.c。 -
主要任务:
-
全面硬件初始化:执行
init_sequence_r[]函数指针数组,对系统中几乎所有的外设进行初始化,包括但不限于:网络接口(Ethernet)、存储控制器(NAND/eMMC/SPI)、USB、I2C/SPI 总线、显示控制器等。 -
环境变量加载:从 Flash 或其他非易失性存储中读取并解析环境变量。这些变量(如
bootcmd,bootargs,serverip)是 U-Boot 行为配置的核心。 -
控制台与命令行建立:初始化完整的控制台,使 U-Boot 的交互式命令行接口完全可用。用户此时可以通过串口与 U-Boot 交互。
-
执行自动引导 (
autoboot):启动一个倒计时。如果在倒计时结束前用户没有按下任何键中断,U-Boot 将自动执行bootcmd环境变量中定义的命令序列,开始加载并引导操作系统内核。
-
-
关键逻辑:
common/autoboot.c中的autoboot_command()负责解析并执行bootcmd。
第三章:U-Boot 加载内核与设备树的终极使命
执行 bootcmd 是 U-Boot 引导流程的最高潮。一个典型的 bootcmd 如下所示:
bootcmd=mmc dev 0; fatload mmc 0:1 ${kernel_addr_r} Image; fatload mmc 0:1 ${fdt_addr_r} xxx.dtb; booti ${kernel_addr_r} - ${fdt_addr_r}
这条命令清晰地展示了加载内核与设备树的步骤:
-
mmc dev 0: 切换到 eMMC/SD 卡设备 0。 -
fatload mmc 0:1 ${kernel_addr_r} Image: 从设备 0 的分区 1(FAT格式)中,加载Image文件(内核镜像)到环境变量kernel_addr_r指定的 DRAM 地址。 -
fatload mmc 0:1 ${fdt_addr_r} xxx.dtb: 同样,加载xxx.dtb文件(设备树)到fdt_addr_r指定的 DRAM 地址。 -
booti ${kernel_addr_r} - ${fdt_addr_r}: 执行引导命令。
3.1 内核与设备树的加载与引导
当用户输入或 bootcmd 自动执行 booti / bootm / bootz 等命令时,U-Boot 内部会执行一系列复杂的函数调用。
-
对应文件:
cmd/booti.c(用于 ARM64Image格式),cmd/bootz.c(用于 ARMzImage格式),common/bootm.c(一个通用的、支持多种格式的旧命令)。 -
函数逻辑:
-
镜像检查与解析:检查指定地址处的镜像头部,识别其类型(如 uImage、zImage、FIT Image、PE/COFF 等)和属性(架构、压缩方式等)。
-
解压缩(如果需要):如果内核是压缩的(如
zImage或gzip压缩的Image),U-Boot 会在内存中对其进行解压。 -
设备树处理:
-
U-Boot 会读取用户加载的 DTB 文件。
-
动态修改 (FDT Manipulation):这是非常关键的一步。U-Boot 会向设备树中添加或修改信息。例如,它会在
/chosen节点下创建bootargs属性,将 U-Boot 的bootargs环境变量的值写入其中,供内核解析。它还会添加内存保留区信息,告知内核哪些内存已被 U-Boot 使用。
-
-
设置引导参数:根据目标架构,准备好传递给内核的参数。对于现代 ARM 系统,这主要是准备好存放 DTB 地址的寄存器。
-
跳转至内核:最后,关闭缓存和 MMU,将 DTB 的地址放入指定的寄存器(如 ARM32 的
r2,ARM64 的x0),然后执行一条跳转指令,将 CPU 控制权交给内核的入口地址。
-
boot_jump_linux(kernel_entry, machid, r2); // 示例跳转调用
至此,U-Boot 的使命宣告完成,波澜壮阔的 Linux 内核启动过程正式开始。
第四章:总结与关键索引
4.1 提炼后的 U-Boot 启动流程图
[SoC 上电]
└──> [BootROM (BL1) in ROM] - 最小化初始化,加载 SPL
↓
[SPL (BL2) in SRAM]
├──> [_start in start.S] - 设置汇编环境 (栈, CPU模式)
│ ↓
└──> [board_init_f in C] - 初始化 DRAM,加载 U-Boot 主体到 DRAM
↓
[U-Boot Main Body in DRAM]
├──> [Pre-Relocation: _start -> board_init_f] - 准备环境,自我搬移
│ ↓ (Relocate & Jump)
├──> [Post-Relocation: board_init_r] - 全面初始化外设,加载环境变量,启动命令行
│ ↓
└──> [autoboot executes 'bootcmd'] - 自动执行引导命令
↓
[Commands: load, booti/bootm/bootz]
├──> 从存储加载 Kernel + DTB 到 DRAM
├──> 解析镜像,处理设备树 (FDT)
└──> 设置引导参数,准备跳转
↓
[Handover to Kernel] -> 内核开始执行
4.2 关键代码文件索引
| 功能 | 主要相关文件 | 备注 |
| 启动入口 (汇编) | arch/$(ARCH)/cpu/$(CPU)/start.S | SPL 和 U-Boot 主体都有各自的 _start 入口。 |
| SPL C 语言逻辑 | common/spl/spl.c | SPL 的 C 语言主流程。 |
| 重定位前初始化 | common/board_f.c | board_init_f 的实现。 |
| 重定位后主逻辑 | common/board_r.c | board_init_r 的实现。 |
| 内核启动命令 | cmd/booti.c, cmd/bootz.c, common/bootm.c | booti, bootz, bootm 命令的实现。 |
| 设备树处理 (FDT) | lib/fdtdec.c, common/fdt_support.c | 设备树的解析、读取和修改功能。 |
| 自动引导逻辑 | common/autoboot.c | bootcmd 的倒计时与执行。 |
4.3 U-Boot 的核心职责精炼
总而言之,U-Boot 的职责远非简单的“加载文件”。它是一个承上启下、高度复杂的系统软件。其核心职责可精炼为三点:
-
承接硬件:从一个仅有 CPU 和片上 ROM 的“裸机”环境开始,逐步唤醒并初始化DRAM、时钟、电源等核心硬件资源,为上层软件的运行搭建起坚实的物理舞台。
-
建立环境:通过初始化各类外设,建立起一套灵活的环境变量机制和功能强大的交互式命令行,为开发者提供一个可调试、可配置、可扩展的引导平台。
-
启动系统:作为其最终目标,精确地加载操作系统内核及其所需的数据(如设备树),并严格遵循体系架构的规范,将系统控制权完美、安全地交接给操作系统。
3899

被折叠的 条评论
为什么被折叠?



