3. Bootloader原理与U-Boot

3.1 Bootloader在嵌入式系统中的核心作用

在ARM嵌入式Linux系统中,Bootloader是系统上电后最先执行的代码。它负责完成硬件初始化、内存映射建立、系统时钟配置,并将操作系统内核加载到内存中执行。Bootloader是连接硬件与操作系统内核的桥梁,其执行效率与功能完整性直接影响系统启动速度与稳定性。

Bootloader三大核心职责:
  1. 硬件初始化:设置CPU模式、关闭看门狗、初始化时钟、配置SDRAM/DDR控制器、初始化串口等外设。
  2. 引导加载:从Flash、SD卡、网络等存储介质读取内核映像与设备树,并校验完整性。
  3. 传递启动参数:将硬件信息、内存布局、命令行参数通过ATAG或设备树方式传递给内核。

嵌入式Bootloader与PC上的BIOS/UEFI有本质区别:嵌入式Bootloader通常直接操作寄存器,无固件抽象层,且需要针对具体板卡进行移植与配置。

特性 PC BIOS/UEFI 嵌入式Bootloader
硬件适配 通用固件,支持多种主板 针对单板或系列板卡定制
存储位置 主板Flash芯片 NAND/NOR Flash、SD卡、eMMC
交互方式 图形界面/命令行 串口命令行(CLI)为主
代码体积 数MB至数十MB 数十KB至数百KB
启动阶段 多阶段固件链 通常分SPL+U-Boot两阶段

3.2 U-Boot简介

U-Boot(Das U-Boot)是目前ARM嵌入式Linux领域最广泛使用的开源Bootloader,由DENX Software Engineering维护。它支持数百种CPU架构与数千款开发板,具备完整的网络协议栈、文件系统支持、设备树解析能力以及灵活的脚本环境。

U-Boot关键特性:
  • 支持ARM、MIPS、RISC-V、x86等多种架构
  • 内置TFTP、NFS、HTTP网络引导协议
  • 支持FAT、EXT4、UBIFS、JFFS2等文件系统
  • 提供HUSH风格的Shell环境,支持变量、循环、条件判断
  • 支持FIT(Flattened Image Tree)统一映像格式
  • 内置设备树(DTB)处理与修改功能

U-Boot采用两阶段启动模型:第一阶段(SPL,Secondary Program Loader)在片上SRAM中运行,完成最小硬件初始化并加载第二阶段(U-Boot proper)到DDR中执行。这种设计解决了片上SRAM容量有限而U-Boot本体体积较大的矛盾。

U-Boot两阶段启动流程 ROM Code 片上ROM固件 SPL 片上SRAM运行 U-Boot proper DDR中运行 Linux内核 DDR中执行 SPL:最小硬件初始化 DDR初始化、加载U-Boot U-Boot proper:完整功能 网络、文件系统、脚本 内核接管系统控制权 挂载根文件系统

3.3 U-Boot源码结构

U-Boot源码采用模块化组织,顶层目录结构清晰,便于移植与裁剪。理解源码结构是进行板级移植与功能定制的基础。

目录/文件 功能说明
arch/ CPU架构相关代码,如arch/arm/、arch/riscv/,包含启动汇编、异常处理、MMU设置
board/ 板级支持包,每个板卡一个子目录,包含板级初始化、GPIO配置、时序参数
configs/ 默认配置文件,如stm32mp15_defconfig,通过make xxx_defconfig使用
drivers/ 设备驱动,包括串口、网卡、MMC、NAND、USB、SPI等
include/ 全局头文件,configs/下存放板级配置头文件
common/ 通用命令实现,如cmd_bootm.c、cmd_nand.c
net/ 网络协议栈,支持TFTP、NFS、DHCP、DNS
fs/ 文件系统支持,FAT、EXT4、UBIFS等
tools/ 主机端工具,如mkimage映像打包工具
dts/ 设备树源文件(.dts)与绑定文档
移植注意事项:板级移植通常只需修改board/、include/configs/和dts/三个目录下的文件。不要直接修改arch/下的通用代码,除非需要适配新的CPU核。
U-Boot源码模块依赖关系 U-Boot顶层Makefile arch/ CPU架构初始化 board/ 板级配置与驱动 drivers/ 外设驱动实现 common/ 命令与核心逻辑 configs/ 默认配置模板 include/configs/ 板级配置头文件 dts/ 设备树源文件 tools/ 主机工具链 移植工作主要集中在board/、include/configs/、dts/三个目录 configs/提供默认配置入口,通过make menuconfig进行可视化裁剪 tools/mkimage用于生成U-Boot可识别的映像格式

3.4 U-Boot编译与烧写

3.4.1 编译环境准备

编译U-Boot需要交叉编译工具链,推荐使用Linaro GCC或ARM官方GNU工具链。以ARM Cortex-A7为例,工具链前缀为arm-linux-gnueabihf-。

编译步骤(以STM32MP157开发板为例):
# 1. 设置交叉编译环境变量
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

# 2. 清理旧配置
make distclean

# 3. 加载板级默认配置
make stm32mp15_defconfig

# 4. 进入菜单配置界面(可选裁剪)
make menuconfig

# 5. 编译U-Boot
make -j$(nproc)

# 6. 编译完成后生成关键文件:
#    u-boot.bin        —— 完整U-Boot映像
#    u-boot-spl.bin    —— SPL映像
#    u-boot.img        —— 带FIT头部的U-Boot映像
#    u-boot.dtb        —— 编译生成的设备树

3.4.2 烧写方式对比

烧写方式 存储介质 烧写工具 适用场景
SD卡烧写 SD卡 dd、balenaEtcher 开发调试阶段,快速迭代
NAND/NOR Flash烧写 板载Flash JTAG、flashcp、nandwrite 量产阶段,固定存储
eMMC烧写 板载eMMC U-Boot自身mmc write命令 批量生产,通过SD卡引导后写入eMMC
网络烧写 RAM + Flash TFTP + U-Boot命令 无存储卡槽的板卡,远程更新

3.4.3 SD卡烧写示例

将U-Boot写入SD卡的特定偏移位置(通常为1KB偏移,即扇区2),注意不要覆盖分区表。

# 假设SD卡设备为 /dev/sdb,请确认设备名称
# 写入SPL(偏移1KB)
sudo dd if=u-boot-spl.bin of=/dev/sdb bs=1k seek=1 conv=fsync

# 写入U-Boot proper(偏移129KB,即SPL之后)
sudo dd if=u-boot.img of=/dev/sdb bs=1k seek=129 conv=fsync

# 验证写入
sudo dd if=/dev/sdb bs=1k skip=1 count=2 | hexdump -C | head
烧写风险提示:
  • 务必确认/dev/sdX设备名称,误写会导致主机硬盘数据丢失。
  • 偏移量(seek值)由板卡ROM Code决定,不同SoC要求不同,需查阅芯片手册。
  • 烧写后可通过串口日志验证:上电后若打印U-Boot版本信息则烧写成功。

3.4.4 网络烧写(TFTP)流程

网络烧写适用于无存储卡槽或需要远程更新的场景,需先搭建TFTP服务器。

# U-Boot命令行操作
# 1. 设置网络参数
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
setenv netmask 255.255.255.0

# 2. 通过TFTP下载U-Boot映像到内存
tftp 0xc0000000 u-boot.img

# 3. 擦除Flash分区(以NAND为例)
nand erase 0x0 0x200000

# 4. 写入Flash
nand write 0xc0000000 0x0 0x200000

# 5. 保存环境变量
saveenv

# 6. 重启验证
reset
编译优化建议:
  • 使用make -j$(nproc)并行编译,大幅缩短编译时间。
  • 首次编译建议不加-j参数,便于定位错误。
  • 若修改了板级配置头文件,需执行make clean后再编译,否则配置可能不生效。
  • 使用make V=1可输出详细编译日志,便于调试。