3. Bootloader原理与U-Boot
3.1 Bootloader在嵌入式系统中的核心作用
在ARM嵌入式Linux系统中,Bootloader是系统上电后最先执行的代码。它负责完成硬件初始化、内存映射建立、系统时钟配置,并将操作系统内核加载到内存中执行。Bootloader是连接硬件与操作系统内核的桥梁,其执行效率与功能完整性直接影响系统启动速度与稳定性。
Bootloader三大核心职责:
- 硬件初始化:设置CPU模式、关闭看门狗、初始化时钟、配置SDRAM/DDR控制器、初始化串口等外设。
- 引导加载:从Flash、SD卡、网络等存储介质读取内核映像与设备树,并校验完整性。
- 传递启动参数:将硬件信息、内存布局、命令行参数通过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本体体积较大的矛盾。
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核。
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可输出详细编译日志,便于调试。