4、Bootloader裁剪与优化(下):U-Boot Falcon模式加速启动、SPL深度优化、Flash分区规划
上一讲我们把U-Boot的启动流程和基础裁剪聊透了。这一讲,咱们要动真格的了——把启动时间从秒级压到毫秒级。
你可能会问:启动快几秒,真的那么重要吗?我做过一个工业相机项目,客户要求上电后200ms内必须输出第一帧图像。普通U-Boot启动要1.2秒,根本没法用。最后就是靠Falcon模式和SPL优化,硬生生把时间压到了180ms。
好,咱们直接进入正题。
4.1 Falcon模式:跳过U-Boot完整启动
Falcon模式的核心思想很简单:SPL加载完硬件初始化后,直接跳转到内核,不再回到U-Boot完整版。
说白了,就是把U-Boot的“二次加载”阶段整个砍掉。你想想看,U-Boot完整版要解析环境变量、加载驱动、检查文件系统……这些在量产产品里其实都是多余的。
Falcon模式启动流程对比
- 传统模式:ROM → SPL → U-Boot完整版 → 内核
- Falcon模式:ROM → SPL → 内核(跳过U-Boot完整版)
我习惯把Falcon模式叫做“三级跳变两级跳”。省掉的那一级,通常能省下300-800ms。
4.1.1 启用Falcon模式
在U-Boot配置中打开:
# 在include/configs/your_board.h中
#define CONFIG_SPL_OS_BOOT
#define CONFIG_SYS_SPL_ARGS_ADDR 0x82000000
#define CONFIG_CMD_SPL
然后在SPL中指定内核和设备树的加载地址:
// board/your_board/spl.c
void spl_board_prepare_for_linux(void) {
// 关闭不必要的硬件外设
gpio_direction_output(CONFIG_LED_GPIO, 0);
// 确保DDR已经稳定
mdelay(1);
}
经验之谈:Falcon模式下,SPL需要自己完成所有硬件初始化。我遇到过DDR时序没配好,内核直接挂掉的情况。建议先在完整U-Boot下确认DDR稳定性,再切Falcon模式。
4.1.2 生成Falcon镜像
把内核、设备树、根文件系统打包成一个SPL能直接加载的镜像:
# 生成Falcon镜像
mkimage -A arm -O linux -T kernel -C none -a 0x80008000 -e 0x80008000 \
-n "Falcon Kernel" -d zImage falcon_kernel.img
# 烧录到Flash指定位置
# 注意:这个位置必须和SPL中CONFIG_SYS_MMCSD_RAW_MODE_KERNEL_SECTOR一致
4.2 SPL深度优化:把每一微秒都榨出来
SPL是启动的第一段用户代码,它的效率直接决定了整个系统的启动速度。我见过很多团队,SPL代码写得跟完整U-Boot一样臃肿,白白浪费了几百毫秒。
4.2.1 裁剪SPL功能
SPL不需要的功能,一个都不要留:
# 在defconfig中关闭不必要的SPL功能
# CONFIG_SPL_NET_SUPPORT is not set
# CONFIG_SPL_USB_HOST_SUPPORT is not set
# CONFIG_SPL_FAT_WRITE is not set
# CONFIG_SPL_EXT_SUPPORT is not set
# CONFIG_SPL_YMODEM_SUPPORT is not set
# 只保留最核心的
CONFIG_SPL_MMC_SUPPORT=y
CONFIG_SPL_SERIAL_SUPPORT=y
CONFIG_SPL_DRIVERS_MISC_SUPPORT=y
为什么要这么狠?因为SPL的每一行代码,都是拿启动时间换的。我做过测试,一个没用的printk就能多花5ms。
4.2.2 优化DDR初始化
DDR初始化是SPL中最耗时的部分。很多开发板直接用U-Boot的完整DDR训练流程,那是在浪费时间。
我的做法是:
- 固定时序参数:量产时DDR颗粒型号固定,不需要每次都重新训练
- 使用预置配置:把训练好的参数直接硬编码到SPL中
- 减少校准次数:DDR写电平校准从3次减到1次
// 硬编码DDR时序,跳过训练
static struct dram_timing fixed_timing = {
.tRCD = 10,
.tRP = 10,
.tRAS = 24,
.tWR = 10,
.tWTR = 4,
.tRFC = 80,
};
void sdram_init(void) {
// 直接应用固定时序,不训练
apply_dram_timing(&fixed_timing);
// 只做一次写电平校准
if (dwc_ddrphy_apb_wrlevel(0, 1) != 0) {
// 校准失败才回退到完整训练
full_dram_training();
}
}
注意:固定时序只适用于同一批次、同一型号的DDR颗粒。换批次或换供应商后,必须重新验证。我曾经因为没注意DDR颗粒批次变更,导致产品在高温下随机死机,排查了整整两周。
4.2.3 优化代码执行位置
SPL通常在SRAM中执行,但SRAM速度比DDR慢得多。我的优化策略是:
- 关键路径代码放在SRAM开头:减少指令预取缺失
- 大块数据操作放在DDR初始化之后:利用DDR的高带宽
- 使用内联函数:减少函数调用开销
// 把最常用的函数内联
static inline void early_uart_putc(char c) {
while (*(volatile uint32_t *)UART_LSR & UART_LSR_TEMT);
*(volatile uint32_t *)UART_THR = c;
}
4.3 Flash分区规划:为快速启动铺路
Flash分区规划看似简单,但很多人一开始没想清楚,后面改起来痛不欲生。我见过一个产品,因为分区规划不合理,OTA升级时不得不重写整个Flash布局,差点把产品搞成砖。
4.3.1 典型分区布局
| 分区名称 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| SPL | 0x00000000 | 64KB | 一级引导程序 |
| U-Boot环境变量 | 0x00010000 | 64KB | env存储区 |
| U-Boot完整版 | 0x00020000 | 512KB | 完整U-Boot(Falcon模式可省略) |
| Falcon内核 | 0x000A0000 | 4MB | Falcon模式专用内核镜像 |
| 设备树 | 0x004A0000 | 128KB | 设备树blob |
| 根文件系统 | 0x004C0000 | 剩余空间 | 只读根文件系统 |
| 数据分区 | 末尾 | 动态 | 用户数据、日志等 |
这个布局有几个关键点:
- SPL放在最前面:ROM默认从0地址加载
- 环境变量单独分区:避免擦写时影响其他区域
- Falcon内核紧跟在U-Boot后面:SPL可以直接按偏移加载
4.3.2 分区对齐与擦除块
NAND Flash和eMMC都有擦除块的概念。分区起始地址必须对齐到擦除块边界,否则会出现“写一个字节擦整个块”的悲剧。
// 以NAND Flash为例,擦除块大小128KB
#define FLASH_ERASE_SIZE (128 * 1024)
// 分区起始地址必须对齐
#define PART_SPL_START 0x00000000
#define PART_ENV_START ALIGN(0x00010000, FLASH_ERASE_SIZE)
#define PART_UBOOT_START ALIGN(0x00020000, FLASH_ERASE_SIZE)
#define PART_KERNEL_START ALIGN(0x000A0000, FLASH_ERASE_SIZE)
我的习惯:在分区表里预留一个“备用分区”。万一后续需要增加功能(比如双备份系统),不用重新规划整个布局。我一般预留4MB,够用了。
4.3.3 Falcon模式下的分区优化
如果用了Falcon模式,U-Boot完整版分区可以缩小甚至去掉。但我不建议完全去掉——保留一个最小化的U-Boot用于紧急恢复。
优化后的布局:
SPL: 64KB
环境变量: 64KB
U-Boot恢复: 256KB (最小化版本,仅用于刷机)
Falcon内核: 4MB
设备树: 128KB
根文件系统: 剩余空间
这样,SPL加载Falcon内核时,直接从偏移0x00060000读取,不需要任何文件系统解析,速度最快。
4.4 实战:把启动时间从1.2秒压到180ms
回到我开头说的那个工业相机项目。我们来看看具体怎么做的:
- 启用Falcon模式:跳过U-Boot完整版,省掉400ms
- SPL裁剪:去掉网络、USB、文件系统支持,省掉150ms
- DDR固定时序:跳过训练流程,省掉300ms
- Flash分区优化:把内核放在固定偏移,省掉文件系统解析的80ms
- 代码优化:内联关键函数、减少printk,省掉90ms
最终启动时间:180ms。客户很满意。
关键数据:优化前后对比
| 阶段 | 优化前 | 优化后 | 节省 |
|---|---|---|---|
| ROM → SPL | 50ms | 50ms | 0ms |
| SPL初始化 | 450ms | 60ms | 390ms |
| U-Boot完整版 | 400ms | 0ms | 400ms |
| 加载内核 | 300ms | 70ms | 230ms |
| 总计 | 1200ms | 180ms | 1020ms |
嗯,这就是Bootloader裁剪与优化的全部内容。从Falcon模式到SPL深度优化,再到Flash分区规划,每一步都能帮你省下几百毫秒。别小看这几百毫秒,在工业控制、汽车电子这些领域,它就是产品能不能用的分水岭。