2、Bootloader优化:uboot阶段裁剪与快速启动配置

好,咱们直接进入正题。机顶盒启动,uboot是第一个跑起来的软件。它干的事不少——初始化硬件、加载内核、传递参数。但说实话,很多uboot默认配置,对量产机顶盒来说,太啰嗦了。

我刚开始做机顶盒优化时,就踩过这个坑。uboot启动花了3秒多,老板问我能不能压到1秒以内。我当时心想,这怎么可能?后来才发现,uboot里有一堆调试用的功能,量产根本用不上。裁剪掉,速度就上来了。

2.1 uboot启动流程回顾

先简单过一下uboot的启动流程。你心里有个谱,才知道从哪下手。

  1. SPL(Secondary Program Loader):这是uboot的第一阶段。代码量极小,负责初始化DDR、时钟,然后加载完整的uboot到内存。
  2. uboot主程序:第二阶段。初始化外设(网卡、存储、显示),解析环境变量,最后加载内核。

嗯,这里要注意:SPL阶段是硬实时,代码越精简越好。uboot主程序阶段,才是我们裁剪的重点。

2.2 裁剪uboot的三大方向

我个人习惯,把裁剪分成三个方向。你照着做,基本不会漏掉什么。

2.2.1 去掉不必要的驱动和命令

uboot默认编译,会带上大量驱动。比如你用的是eMMC启动,那NAND Flash的驱动就可以删掉。还有网络协议栈,如果量产机顶盒不需要网络升级uboot,那网卡驱动和TCP/IP协议栈都可以去掉。

我在项目中遇到过,一个客户的产品只从SD卡启动。uboot里却编译了USB、SATA、NAND、SPI Flash的驱动。去掉这些后,uboot体积从800KB降到了400KB,加载时间直接减半。

具体操作,修改uboot的配置文件(比如include/configs/你的板子.h):

/* 去掉不需要的驱动 */
#undef CONFIG_USB_EHCI
#undef CONFIG_MMC
#undef CONFIG_CMD_NET
#undef CONFIG_CMD_PING

/* 保留必要的命令 */
#define CONFIG_CMD_BOOTM
#define CONFIG_CMD_LOADB
#define CONFIG_CMD_SAVEENV

警告: 别删得太狠。比如你保留了网络升级功能,却把网卡驱动删了,那uboot就变砖了。我建议先做增量裁剪,每删一个模块,就编译测试一次。

2.2.2 缩短延时和等待时间

uboot里有很多硬编码的延时。比如等待DDR稳定、等待网络PHY自协商。这些延时,很多是给开发调试用的,量产时可以缩短甚至去掉。

举个例子,DDR初始化后的等待延时:

/* 原代码:等待200ms */
mdelay(200);

/* 优化后:根据实际芯片手册,缩短到50ms */
mdelay(50);

还有网络PHY的自协商。默认等待3秒,太长了。如果你的机顶盒固定连接某个交换机,可以直接固定网口速率和双工模式,跳过自协商。

小技巧: 我曾经在某个项目里,把uboot里所有mdelayudelay都列出来,逐个分析能不能删。最后发现,有3处延时加起来1.2秒,其实根本不需要。去掉后,启动时间直接少了1秒。

2.2.3 精简环境变量和启动脚本

uboot的环境变量,默认会加载很多。比如bootargsbootcmdloadaddr等等。如果环境变量存储在eMMC或NAND里,读取它们也需要时间。

我建议:

  • 把环境变量的大小限制在4KB以内。别存一堆没用的变量。
  • 启动脚本(bootcmd)尽量用最简形式。比如直接从固定地址加载内核,不要做复杂的文件系统检测。

举个例子,优化前的启动脚本:

bootcmd=mmc dev 0; mmc read 0x82000000 0x800 0x4000; bootm 0x82000000

优化后,去掉不必要的设备检测:

bootcmd=mmc read 0x82000000 0x800 0x4000; bootm 0x82000000

别小看这一行。少一次mmc dev命令,就少了几十毫秒。

2.3 快速启动配置

裁剪完,咱们再聊聊怎么让uboot跑得更快。说白了,就是让它在最短时间内把控制权交给内核。

2.3.1 启用快速启动模式

很多芯片厂商(比如海思、晶晨)的uboot,都支持快速启动模式。这个模式会跳过一些不必要的初始化步骤。

比如,在配置文件中添加:

#define CONFIG_BOOTDELAY 0    /* 取消用户按键等待 */
#define CONFIG_SILENT_CONSOLE /* 关闭控制台输出,减少打印时间 */

你想想看,CONFIG_BOOTDELAY默认是3秒。改成0,用户按不按键都不等了。这一下就省了3秒。

核心思路: uboot的每一行打印,每一次延时,每一个驱动初始化,都是时间成本。我们要做的,就是砍掉所有「量产不需要」的部分。

2.3.2 使用FIT Image或压缩内核

uboot加载内核时,如果内核是未压缩的,那加载时间会很长。我建议使用FIT Image(Flattened Image Tree),它支持内核和设备树打包在一起,并且可以压缩。

举个例子,生成FIT Image的命令:

mkimage -f fit.its fit.itb

然后在uboot里,直接用bootm命令加载:

bootm 0x82000000

uboot会自动解压内核。虽然解压需要一点时间,但相比从存储设备读取大文件,还是快得多。

2.3.3 优化存储设备读取速度

uboot从eMMC或NAND读取内核时,读取速度很关键。我建议:

  • 使用DMA模式读取,而不是PIO模式。DMA模式能大幅提高读取速度。
  • 调整eMMC的时钟频率。很多uboot默认用低速模式,你可以根据芯片手册,适当提高频率。

我曾经在一个项目里,把eMMC的读取时钟从26MHz提高到52MHz。内核加载时间从800ms降到了400ms。嗯,这个优化很实在。

2.4 避坑指南

最后,分享几个我踩过的坑。你遇到了,能少走弯路。

我曾经... 把uboot里的所有打印信息都关了,结果板子启动到一半卡住了。没有打印,根本不知道哪里出了问题。所以,我建议在开发阶段保留关键打印,量产时再关闭。

还有一次... 我把DDR初始化延时从200ms改到10ms,结果板子随机死机。后来查手册才发现,DDR的稳定时间必须大于50ms。所以,改延时一定要看芯片手册,别凭感觉。

我的习惯: 每次裁剪后,都做一次完整的启动测试。至少跑100次冷启动,确保没有随机失败。机顶盒是消费电子产品,用户可不想看开机黑屏。

2.5 小结

uboot优化,说白了就是做减法。去掉不需要的驱动、缩短不必要的延时、精简启动脚本。每一步省几十毫秒,加起来就是一两秒。

下一章,咱们聊聊内核阶段的优化。内核启动比uboot复杂得多,但优化空间也更大。到时候,我会分享一些内核裁剪和启动参数调优的实战经验。

好,今天就到这儿。你去试试,把uboot启动时间压到1秒以内,不是梦。