4. U-Boot 优化策略:裁剪驱动、禁用不必要的命令、优化 DDR 初始化、使用 Fastboot 模式
U-Boot 的启动时间,说白了就是系统从按下电源键到看到内核第一行日志之间的那段“黑暗期”。我见过不少项目,U-Boot 阶段就耗掉了 2-3 秒,这在消费类产品里几乎是不可接受的。今天咱们就聊聊怎么把这部分时间压到 500ms 以内。
核心思路:U-Boot 优化的本质是“少做、快做、并行做”。少加载驱动,少执行命令,让 DDR 快速稳定,最后用 Fastboot 跳过不必要的初始化。
4.1 裁剪驱动:只留你需要的
U-Boot 默认配置会启用大量驱动,很多你根本用不上。比如你的板子只有 eMMC 和 UART,但 U-Boot 却加载了 SPI Flash、NAND、USB Host、网络 PHY 等驱动。每个驱动初始化都要时间,积少成多。
我习惯的做法是:先跑一次完整的 U-Boot 启动,用 bootstage 命令看每个阶段的耗时。然后对照硬件原理图,把没用的驱动一个个关掉。
我的经验:在 include/configs/your_board.h 中,把不需要的 CONFIG_* 宏注释掉。比如你的板子没有网络,就关掉 CONFIG_CMD_NET 和 CONFIG_PHYLIB。我曾经在一个项目里只保留了 12 个驱动,U-Boot 启动时间从 1.8s 降到了 0.6s。
具体操作步骤:
- 第一步:执行
make menuconfig,进入 Device Drivers 菜单 - 第二步:逐个检查每个驱动,确认板子上是否真的有对应硬件
- 第三步:没有的硬件,直接取消勾选
- 第四步:保存配置,重新编译
下面这张表是我在一个 ARM Cortex-A7 平台上做的裁剪记录:
| 驱动类别 | 裁剪前 | 裁剪后 | 节省时间 |
|---|---|---|---|
| SPI Flash | 启用 | 禁用 | ~120ms |
| USB Host | 启用 | 禁用 | ~200ms |
| 网络 PHY | 启用 | 禁用 | ~150ms |
| I2C | 启用 | 禁用 | ~80ms |
| GPIO 扩展器 | 启用 | 禁用 | ~50ms |
注意:裁剪驱动时要小心依赖关系。有些驱动虽然你的板子没有,但其他驱动可能依赖它。我曾经因为关掉了 CONFIG_DM 导致整个 U-Boot 无法启动。建议每次只改 2-3 个选项,编译测试通过后再继续。
4.2 禁用不必要的命令
U-Boot 的命令行功能很强大,但启动时用不到。你想想看,产品量产之后,谁会去敲 md、mw、tftp 这些命令?
我一般只保留这几条命令:
bootm/bootz— 启动内核load— 从存储加载镜像setenv/saveenv— 环境变量操作reset— 复位
其他命令全部关掉。具体在 make menuconfig 的 Command line interface 菜单里操作。
效果数据:禁用不必要的命令可以减少 U-Boot 镜像大小约 30%-50%,同时减少命令解析器的初始化时间。我实测过,从 200+ 条命令减到 15 条,启动时间节省了约 80ms。
常用的可禁用命令列表:
CONFIG_CMD_MEMORY— 内存读写命令(md/mw/mm)CONFIG_CMD_NET— 网络命令(tftp/ping)CONFIG_CMD_USB— USB 相关命令CONFIG_CMD_GPIO— GPIO 操作命令CONFIG_CMD_I2C— I2C 操作命令CONFIG_CMD_MMC— MMC 操作命令(如果不需要手动操作)
4.3 优化 DDR 初始化
DDR 初始化是 U-Boot 启动中最耗时的环节之一。说白了,DDR 控制器需要训练时序、校准电压、等待 PLL 锁定。这个过程少则 200ms,多则 500ms+。
我遇到过最夸张的一次,某款 DDR3 芯片的初始化花了 800ms。后来发现是训练算法太保守,反复校准了 3 次。
优化 DDR 初始化的几个方向:
- 使用预置参数:不要每次都做完整训练。如果你的板子硬件固定,可以把训练好的参数写死在代码里。
- 减少训练次数:DDR 控制器的训练算法通常有多个阶段(如写平衡、读门限、DQ 训练)。在量产板上,可以跳过部分阶段。
- 优化 PLL 配置:PLL 锁定时间取决于分频系数和参考时钟。适当提高参考时钟频率可以加速锁定。
- 并行初始化:如果板子上有多片 DDR,可以尝试并行初始化,而不是串行。
我的做法:在 board_init_f() 函数中,DDR 初始化之前插入 udelay(100) 测试,确认最小稳定时间。然后逐步减少训练次数,直到系统不稳定为止。取稳定边界值再加 20% 余量。这样既保证稳定性,又最大化性能。
下面是一个典型的 DDR 初始化参数优化对比:
| 参数项 | 默认值 | 优化值 | 说明 |
|---|---|---|---|
| 写平衡训练次数 | 3 | 1 | 量产板硬件一致,1次足够 |
| 读门限训练次数 | 5 | 2 | 减少冗余校准 |
| PLL 锁定等待时间 | 500μs | 200μs | 实测 150μs 即可锁定 |
| DQ 训练步长 | 1ps | 5ps | 粗调后微调,减少迭代 |
警告:DDR 参数优化一定要在温度、电压的边界条件下测试。我吃过亏,在常温下调好的参数,到了 -20°C 低温环境就频繁出错。建议做高低温循环测试,确保参数在全温度范围内稳定。
4.4 使用 Fastboot 模式
Fastboot 模式是 U-Boot 提供的一种“快速启动”机制。它的核心思想是:跳过大部分初始化,直接加载内核。
具体来说,Fastboot 模式做了这几件事:
- 跳过串口初始化(如果不需要调试)
- 跳过环境变量加载(使用默认值)
- 跳过命令行等待(不进入交互模式)
- 跳过不必要的硬件检测
- 直接执行
bootcmd
启用 Fastboot 模式的方法:
// 在 include/configs/your_board.h 中添加
#define CONFIG_BOOTDELAY 0 // 不等待用户输入
#define CONFIG_SILENT_CONSOLE // 关闭控制台输出
#define CONFIG_SKIP_LOWLEVEL_INIT // 跳过低级初始化(慎用)
// 或者在环境变量中设置
setenv bootdelay 0
setenv silent 1
saveenv
实测数据:在一个 i.MX6UL 平台上,启用 Fastboot 模式后,U-Boot 启动时间从 1.2s 降到了 0.35s。节省的时间主要来自:跳过串口初始化(~150ms)、跳过命令行等待(~200ms)、跳过环境变量校验(~100ms)、跳过部分硬件检测(~400ms)。
不过要注意,Fastboot 模式也有代价:
- 无法在启动时进入命令行调试
- 环境变量异常时无法自动修复
- 某些硬件初始化可能被跳过,导致内核无法识别
我建议在开发阶段保留完整的 U-Boot,量产时再切换到 Fastboot 模式。可以在 make menuconfig 中准备两套配置,通过 make xxx_defconfig 快速切换。
一个小技巧:在 Fastboot 模式下,如果系统启动失败,可以通过 GPIO 电平或按键检测来“回退”到完整模式。我在一个项目中用了一个 GPIO 引脚:上拉为 Fastboot 模式,下拉为调试模式。这样量产板出问题时,维修人员只需要短接一下就能进入调试模式。
最后总结一下这四种策略的优先级:
| 策略 | 优化效果 | 实施难度 | 风险等级 |
|---|---|---|---|
| 裁剪驱动 | 中(200-400ms) | 低 | 低 |
| 禁用命令 | 低(50-100ms) | 低 | 低 |
| 优化 DDR | 高(200-500ms) | 高 | 中 |
| Fastboot 模式 | 高(300-800ms) | 中 | 中 |
我个人建议的顺序是:先做裁剪驱动和禁用命令,这两步风险低、见效快。然后尝试 Fastboot 模式,如果系统稳定再优化 DDR。DDR 优化放在最后,因为一旦出问题,排查起来最麻烦。