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里只保留bootmloadmmc等基础命令
  • 去掉调试信息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模式的步骤:

  1. 配置U-Boot支持Falcon模式:在defconfig里加上CONFIG_SPL_OS_BOOT=y
  2. 准备内核和设备树:把zImage.dtb放到FAT分区或RAW分区
  3. 设置SPL加载地址:在include/configs/yourboard.h里定义CONFIG_SYS_SPL_ARGS_ADDR
  4. 配置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省下来的每一毫秒,到了内核那边都会被放大。嗯,先打好基础吧。