3. Bootloader优化:U-Boot启动流程分析、裁剪不必要的驱动与功能、配置启动参数优化
好,咱们进入第三章。Bootloader的优化,说白了就是给系统启动“抢时间”。你想想看,从按下电源键到看到Logo,中间那几秒甚至十几秒,U-Boot到底在干嘛?能不能让它少干点活?
我个人习惯,拿到一个新平台,第一件事就是拿串口抓U-Boot的完整log。别急着改代码,先看清楚它每一步花了多少时间。我见过太多团队,上来就改内核,结果发现瓶颈其实在U-Boot里。
3.1 U-Boot启动流程:到底慢在哪?
U-Boot的启动,大致分三个阶段:
- SPL(Secondary Program Loader):这是最精简的加载器,负责初始化DDR、时钟,然后加载完整的U-Boot。
- U-Boot proper:完整的Bootloader,负责驱动初始化、环境变量加载、启动内核。
- Bootm命令执行:解压内核、设备树,跳转到内核入口。
我在项目中遇到过一个问题:某款车机从按下Power键到显示Logo,足足花了8秒。一抓log,发现U-Boot proper阶段就占了5秒。为什么?因为它把所有的USB、网络、存储驱动都初始化了一遍,哪怕这些设备根本用不上。
核心思路:U-Boot的优化,就是“能不做的,坚决不做”。
3.2 裁剪不必要的驱动与功能
这一步,我建议你从两个维度入手:
- 删除不用的驱动:比如你的产品只用eMMC启动,那SATA、NAND、SPI Flash的驱动统统可以删掉。
- 关闭调试功能:U-Boot默认开了很多调试打印,比如`DEBUG`、`CONFIG_BOOTSTAGE`。这些打印每输出一行,就要花几毫秒。量产版本里,这些都应该关掉。
具体怎么做?我拿一个实际案例来说:
// 在 include/configs/your_board.h 中
// 关闭不必要的命令
#undef CONFIG_CMD_USB
#undef CONFIG_CMD_NET
#undef CONFIG_CMD_PING
#undef CONFIG_CMD_DHCP
// 关闭调试输出
#undef CONFIG_BOOTSTAGE
#undef DEBUG
// 只保留最小命令集
#define CONFIG_CMD_BOOTM
#define CONFIG_CMD_EXT4
#define CONFIG_CMD_FAT
我的经验:裁剪驱动时,别一次性删太多。我习惯先注释掉,编译烧录测试一次。没问题再彻底删除。否则,你可能会遇到“启动到一半卡死”的尴尬局面。
另外,U-Boot的设备树也要裁剪。很多开发板默认的设备树包含了所有外设节点,比如HDMI、MIPI DSI、多个I2C控制器。如果你的产品只用LVDS屏幕,那其他显示接口的节点都可以删掉。设备树解析也是要花时间的。
3.3 配置启动参数优化
启动参数,就是传给内核的`bootargs`。这里有个常见的误区:很多人喜欢在U-Boot里写一大串参数,比如`console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait`。其实,很多参数可以写死在内核设备树里,没必要在U-Boot里重复传递。
我建议你这样做:
- 固定参数写死到设备树:比如`console`、`root`、`rootwait`这些,直接在设备树的`chosen`节点里定义。
- 动态参数用环境变量:比如`mtdparts`分区表,如果分区固定,也写死到设备树。
- 减少环境变量加载次数:U-Boot默认会从存储设备加载环境变量,如果环境变量很大(比如几百KB),加载时间会很长。我建议把环境变量大小控制在4KB以内。
// 设备树中的 chosen 节点示例
/ {
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw";
stdout-path = &uart0;
};
};
注意:如果你把`bootargs`写死到设备树,那U-Boot的环境变量里就不能再设置`bootargs`了,否则U-Boot的环境变量会覆盖设备树里的值。我曾经因为这个坑,排查了整整两天。
3.4 实战:一个典型的优化案例
我记得有一次,帮一家Tier1优化车机启动速度。他们的U-Boot启动时间从4.2秒优化到了1.8秒。怎么做到的?
| 优化项 | 优化前 | 优化后 | 节省时间 |
|---|---|---|---|
| 关闭USB初始化 | 800ms | 0ms | 800ms |
| 关闭网络初始化 | 600ms | 0ms | 600ms |
| 裁剪设备树节点 | 300ms | 100ms | 200ms |
| 关闭调试打印 | 500ms | 50ms | 450ms |
| 优化环境变量加载 | 200ms | 50ms | 150ms |
| 总计 | 2400ms | 200ms | 2200ms |
你看,光是关闭USB和网络,就省了1.4秒。这些功能在量产车上根本用不到——你总不会在车机上插U盘升级系统吧?
3.5 避坑指南
最后,分享几个我踩过的坑:
- 别盲目裁剪:有些驱动虽然启动时不用,但后续的OTA升级可能会用到。比如,如果你的车机支持USB升级,那USB驱动就不能删。我建议你列一个“必须保留”的清单。
- 注意时序依赖:有些外设的初始化顺序是固定的。比如,先初始化时钟,再初始化DDR,最后初始化存储。如果你乱删,可能会造成系统不稳定。
- 测试要覆盖全:优化完后,至少做三轮测试:冷启动、热启动、异常掉电重启。我曾经遇到过,裁剪后冷启动正常,但热启动会卡在某个驱动上。
嗯,Bootloader的优化,其实就是一个“做减法”的过程。你删掉的每一行代码,都是在为系统启动争取时间。下一章,我们会聊内核启动的优化,那又是另一番天地了。