4. Bootloader原理与移植

Bootloader是嵌入式Linux系统上电后运行的第一段软件,它负责初始化关键硬件、建立运行环境,并最终把Linux内核加载到内存中启动。U-Boot(Universal Boot Loader)是嵌入式领域使用最广泛的开源Bootloader,支持ARM、MIPS、RISC-V、x86等多种架构,具备网络、存储、USB、文件系统等丰富驱动,是产品级方案的首选。

一、U-Boot的两阶段启动流程

U-Boot采用典型的SPL+U-Boot两阶段设计。第一阶段通常称为SPL(Secondary Program Loader)或MLO,运行在片内SRAM中,代码量小,主要完成时钟、DDR、串口等最基础初始化,然后把完整的U-Boot镜像搬运到DDR并跳转执行。第二阶段是完整的U-Boot,具备命令行、驱动模型、环境变量、网络协议栈等能力,最终引导内核。

从代码执行角度看,启动路径大致为:_start → lowlevel_init → _main → board_init_f → relocate_code → board_init_r → main_loop。其中board_init_f在重定位前完成内存布局规划,board_init_r在重定位后完成外设、环境变量、控制台等初始化,最后进入命令行循环。

U-Boot 两阶段启动流程与内存布局 阶段一:SPL / MLO(片内 SRAM) _start → lowlevel_init(时钟/引脚) DDR 初始化 + 串口初始化 加载完整 U-Boot 到 DDR 并跳转 阶段二:完整 U-Boot(DDR) board_init_f(内存布局规划) relocate_code → board_init_r main_loop(命令行 / 引导内核) 跳转 main_loop 引导内核的典型路径 读取环境变量 bootcmd / bootargs 加载内核镜像 zImage / Image 加载设备树 .dtb 跳转内核入口 bootz / bootm 内核启动后接管:解压、初始化子系统、挂载根文件系统、启动 init 进程 U-Boot 使命完成,控制权移交 Linux 内核

二、常用命令与环境变量

U-Boot命令行是调试阶段最直接的工具。常用命令包括:printenv查看环境变量,setenv设置变量,saveenv保存到存储介质,mmc操作SD/eMMC,fatload/ext4load加载文件,tftp通过网络下载,bootz/bootm启动内核,md/mw读写内存,ping测试网络。

# 查看与设置环境变量
printenv bootcmd
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw'
saveenv

# 从 eMMC 加载内核与设备树
fatload mmc 0:1 0x80800000 zImage
fatload mmc 0:1 0x83000000 imx6ull.dtb
bootz 0x80800000 - 0x83000000

# 网络下载并启动
tftp 0x80800000 zImage
tftp 0x83000000 imx6ull.dtb
bootz 0x80800000 - 0x83000000

环境变量中最重要的两个是bootcmd和bootargs。bootcmd定义自动启动时执行的命令序列,bootargs是传递给内核的启动参数,决定控制台、根文件系统、内存大小等关键配置。生产环境通常把bootcmd设置为从eMMC加载,调试阶段则常改为TFTP网络启动,提高迭代效率。

三、板级移植的关键步骤

U-Boot移植的核心是“以相近参考板为起点,逐步适配自己的硬件”。以NXP i.MX6ULL为例,通常选择mx6ull_14x14_evk作为参考板,复制其板级目录与配置文件,然后修改DDR参数、引脚复用、时钟树、存储设备、网络PHY等。

移植项关键文件/配置常见问题
板级目录board/freescale/mx6ull_xxx/Kconfig、Makefile未同步
配置文件configs/mx6ull_xxx_defconfigDDR型号、容量不匹配
设备树arch/arm/dts/mx6ull_xxx.dts引脚复用、PHY地址错误
DDR初始化spl.c / ddr.c时序参数错误导致SPL挂死
环境变量存储CONFIG_ENV_IS_IN_MMC偏移地址与分区冲突
移植时建议先保证串口能打印SPL日志,再逐步验证DDR、eMMC、网络。串口是U-Boot阶段唯一的“眼睛”,没有串口输出时优先检查时钟、引脚复用和DDR参数。

四、编译与烧写方法

U-Boot编译通常分两步:先配置,再编译。以ARM平台为例:

# 清理并配置
make distclean
make mx6ull_xxx_defconfig

# 编译(指定交叉编译器)
make CROSS_COMPILE=arm-linux-gnueabihf- -j8

# 生成物
# u-boot.bin      完整镜像
# SPL             第一阶段镜像
# u-boot.imx      带IVT头的可烧写镜像

烧写方式取决于存储介质。SD卡可用dd命令写入:

sudo dd if=u-boot.imx of=/dev/sdb bs=1k seek=1 conv=fsync

eMMC量产时通常通过USB下载模式(如i.MX的MFG Tool)或已有U-Boot中的mmc write命令更新。更新时务必确认偏移地址,避免覆盖分区表或内核分区。

烧写前必须确认目标设备节点,dd写错盘会直接破坏主机数据。建议先用lsblk确认设备名,并在量产脚本中加入设备序列号校验。

五、常见误区与复盘

  • 只改设备树不改配置:U-Boot的设备树与内核设备树是两套,引脚、时钟、DDR参数需在U-Boot侧单独确认。
  • 环境变量保存位置冲突:环境变量偏移与分区表重叠,会导致保存后无法启动,需核对CONFIG_ENV_OFFSET。
  • 忽略SPL日志:SPL阶段无输出往往意味着DDR或时钟初始化失败,应优先用示波器确认晶振与电源。
  • bootargs与内核配置不匹配:控制台设备名、根文件系统类型写错,内核会panic,需与内核config逐项对照。

掌握U-Boot的启动流程、命令体系、环境变量机制和板级移植方法,是嵌入式Linux系统构建的基础能力。实际项目中,建议把可用的环境变量、烧写脚本和调试命令整理成板级文档,形成可复用的工程资产。