4、U-Boot深度优化:裁剪配置、启用Falcon模式、快速启动
说到U-Boot优化,我脑子里第一个蹦出来的词就是「瘦身」。你想想看,一个嵌入式设备从按下电源到进入系统,U-Boot占了多大比重?我见过不少项目,U-Boot启动花了3秒多,结果整个系统启动时间被拖到了5秒开外。这其实挺冤的——U-Boot本身不需要那么重。
4.1 裁剪配置:给U-Boot做减法
U-Boot默认配置里塞了很多你用不上的东西。比如你用的是ARM Cortex-A7,它却把x86、MIPS、RISC-V的驱动全编译进去了。我刚开始做Yocto时也犯过这错,直接bitbake u-boot一把梭,结果生成的u-boot.bin有800多KB。后来一查,光是不需要的网卡驱动就占了200KB。
裁剪的核心思路就一条:只保留你硬件平台真正需要的。具体怎么做?
- 禁用不需要的板级支持:在
defconfig里把CONFIG_TARGET_xxx以外的全关掉 - 砍掉多余的文件系统支持:如果你只用ext4,就别开btrfs、f2fs、ubifs
- 精简命令集:
CONFIG_CMD_xxx里只保留bootm、load、mmc等基础命令 - 去掉调试信息:
CONFIG_BOOTDELAY设成0,CONFIG_SILENT_CONSOLE打开
我个人的裁剪清单(以i.MX6ULL为例):
# 只保留MMC启动
CONFIG_MMC=y
CONFIG_CMD_MMC=y
CONFIG_CMD_FAT=y
CONFIG_CMD_EXT4=y
# 关闭所有网络相关
# CONFIG_CMD_NET is not set
# CONFIG_CMD_PING is not set
# CONFIG_CMD_DHCP is not set
# 关闭多余命令
# CONFIG_CMD_EDIT is not set
# CONFIG_CMD_BDI is not set
# CONFIG_CMD_LOADS is not set
# 加速启动
CONFIG_BOOTDELAY=0
CONFIG_SILENT_CONSOLE=y
裁剪完之后,u-boot.bin从800KB降到了280KB。启动时间从1.8秒降到了0.6秒。嗯,这还只是第一步。
4.2 启用Falcon模式:跳过U-Boot第二阶段
Falcon模式是什么?说白了就是让U-Boot只做一件事:初始化硬件,然后直接跳转到内核。跳过U-Boot第二阶段(那个加载环境变量、解析脚本、显示Logo的过程)。
我记得第一次在项目里用Falcon模式时,团队里有人质疑:「跳过第二阶段,万一启动失败怎么办?」我的回答是:「你想想看,产品量产之后,谁会去按空格进U-Boot命令行?」
启用Falcon模式的步骤:
- 配置U-Boot支持Falcon模式:在
defconfig里加上CONFIG_SPL_OS_BOOT=y - 准备内核和设备树:把
zImage和.dtb放到FAT分区或RAW分区 - 设置SPL加载地址:在
include/configs/yourboard.h里定义CONFIG_SYS_SPL_ARGS_ADDR - 配置SPL直接启动内核:设置
CONFIG_SPL_OS_BOOT_KEY为某个魔数
避坑指南:我曾经在i.MX6Q上踩过一个坑——Falcon模式下,SPL加载内核后直接跳转,但DDR初始化只做了一半。后来发现是CONFIG_SPL_BUILD里的DDR配置和完整U-Boot不一致。解决办法:把DDR初始化代码单独抽出来,SPL和U-Boot共用同一份。
启用Falcon模式后,启动流程变成:
ROM Code → SPL → 内核
(原来:ROM Code → SPL → U-Boot → 内核)
这一步优化,通常能省下0.5到1.5秒。具体取决于你的U-Boot第二阶段有多重。
4.3 快速启动:从硬件到软件的全面提速
裁剪配置和Falcon模式搞定了,但还不够。快速启动是个系统工程,我习惯从三个维度去抠时间:
4.3.1 硬件初始化加速
- DDR训练优化:如果硬件设计固定,可以把DDR训练参数写死,跳过每次启动时的自动训练。我在一个项目里把DDR训练从300ms降到了10ms。
- 时钟配置精简:关闭不需要的外设时钟。比如你只用UART1,就别初始化UART2~UART8的时钟。
- GPIO初始化后置:把非关键的GPIO初始化放到内核里去,U-Boot只初始化启动必需的引脚。
4.3.2 存储设备读取优化
| 优化项 | 优化前 | 优化后 | 节省时间 |
|---|---|---|---|
| MMC读取块大小 | 512字节 | 4096字节 | 约200ms |
| 启用DMA传输 | PIO模式 | DMA模式 | 约150ms |
| 内核和设备树合并 | 分两次读取 | 一次读取 | 约50ms |
我建议把内核和设备树打包成一个fitImage。这样U-Boot只需要一次读取操作,就能拿到所有启动数据。配合DMA传输,读取时间能压缩到原来的三分之一。
4.3.3 环境变量优化
U-Boot的环境变量默认存在MMC的某个扇区里。每次启动都要读取、解析、校验。如果你把环境变量设成只读的,或者直接编译进U-Boot二进制里,能省下不少时间。
注意:把环境变量编译进U-Boot意味着你不能再通过saveenv修改启动参数。对于量产产品来说这反而是好事——防止现场人员误操作。我一般会在开发阶段保留可写环境变量,量产前再切回只读模式。
4.4 实战:一个完整的快速启动配置示例
下面是我在一个基于i.MX6ULL的项目里用的配置。这个项目要求启动时间小于2秒(从上电到内核解压完成)。
# defconfig 关键配置
CONFIG_SPL_OS_BOOT=y
CONFIG_BOOTDELAY=0
CONFIG_SILENT_CONSOLE=y
CONFIG_SYS_MMC_MAX_DEVICE=1
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=n
# board.h 关键配置
#define CONFIG_SYS_SPL_ARGS_ADDR 0x12000000
#define CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR 0x80
#define CONFIG_SYS_MMCSD_RAW_MODE_KERNEL_SECTOR 0x1000
#define CONFIG_SYS_MMCSD_RAW_MODE_DTB_SECTOR 0x2000
# 启动脚本(编译进U-Boot)
#define CONFIG_EXTRA_ENV_SETTINGS \
"loadkernel=mmc read 0x12000000 0x1000 0x4000\0" \
"loaddtb=mmc read 0x13000000 0x2000 0x100\0" \
"bootargs=console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait\0" \
"bootcmd=run loadkernel; run loaddtb; bootm 0x12000000 - 0x13000000\0"
这套配置跑下来,从ROM Code到内核开始执行,总共花了1.2秒。其中SPL初始化花了0.3秒,读取内核和设备树花了0.5秒,跳转花了0.4秒。嗯,离2秒的目标还有余量。
4.5 总结:优化效果对比
| 优化阶段 | 优化前 | 优化后 | 节省时间 |
|---|---|---|---|
| 裁剪配置 | 1.8秒 | 0.6秒 | 1.2秒 |
| Falcon模式 | 0.6秒 | 0.3秒 | 0.3秒 |
| 快速启动优化 | 0.3秒 | 0.15秒 | 0.15秒 |
| 总计 | 2.7秒 | 1.05秒 | 1.65秒 |
你看,从2.7秒压到1秒出头,U-Boot这块的优化空间其实挺大的。我见过有些团队一上来就盯着内核启动优化,结果U-Boot占了启动时间的大头,怎么优化内核都突破不了3秒。说白了,先搞定U-Boot,再谈内核优化,这个顺序不能乱。
下一章我们会聊内核启动优化,到时候你会看到,U-Boot省下来的每一毫秒,到了内核那边都会被放大。嗯,先打好基础吧。