3. Bootloader裁剪与优化(上):U-Boot启动流程分析、去除不必要的驱动与命令、配置CONFIG选项以减小体积
Bootloader 是系统上电后跑的第一段代码。它决定了你能不能用上 Linux,也决定了启动快不快。
很多人觉得 U-Boot 就是个引导工具,随便编一个就能用。其实不然。我见过不少项目,U-Boot 体积动辄 2MB 以上,启动要花好几秒。对于嵌入式设备来说,这太奢侈了。
这一节,我们专门聊聊 U-Boot 的裁剪。我会从启动流程讲起,然后告诉你哪些驱动和命令可以砍掉,最后教你如何通过配置选项把体积降下来。
3.1 U-Boot 启动流程:你得知道它在干什么
裁剪之前,先搞清楚 U-Boot 到底干了哪些事。你想想看,如果连流程都不清楚,你怎么知道哪些能砍?
U-Boot 的启动大致分两个阶段:SPL(Secondary Program Loader)和 U-Boot 本体。SPL 是精简版,负责初始化硬件、加载 U-Boot 本体到内存。U-Boot 本体则负责更复杂的初始化、环境变量加载、命令解析,最后引导内核。
核心流程速览:
- SPL 阶段: 初始化时钟、DDR、串口,从存储介质(NAND、SD卡、SPI Flash)读取 U-Boot 本体。
- U-Boot 本体阶段: 初始化更多外设(网卡、USB、文件系统),解析环境变量,进入命令行或自动启动内核。
我个人习惯把启动流程画成一张图,这样裁剪的时候心里有数。下面这张图展示了关键节点:
嗯,这里要注意:SPL 阶段能做的事情非常有限。它不需要网卡驱动,也不需要 USB 支持。很多开发板默认把 SPL 编得很大,其实没必要。
3.2 去除不必要的驱动:别让 SPL 背太多包袱
U-Boot 的驱动模型很灵活,但默认配置往往把能开的都开了。我见过一个项目,SPL 里居然编译了 5 种网卡驱动,而实际只用了一颗 PHY 芯片。
我的建议: 打开 include/configs/your_board.h,把 CONFIG_SPL_* 相关的驱动逐个审查。只保留你板子上真正用到的。
举个例子,如果你的板子从 SD 卡启动,SPL 只需要 MMC 驱动和文件系统驱动(FAT/EXT4)。其他像 SPI、NAND、USB 都可以关掉。
// 裁剪前的 SPL 配置(典型默认值)
#define CONFIG_SPL_MMC_SUPPORT
#define CONFIG_SPL_SPI_SUPPORT // 不需要
#define CONFIG_SPL_NAND_SUPPORT // 不需要
#define CONFIG_SPL_USB_SUPPORT // 不需要
#define CONFIG_SPL_NET_SUPPORT // 不需要
#define CONFIG_SPL_WATCHDOG_SUPPORT // 不需要
// 裁剪后的 SPL 配置
#define CONFIG_SPL_MMC_SUPPORT
#define CONFIG_SPL_FAT_SUPPORT
// 其他全部注释掉
我曾经在一个项目里,把 SPL 从 128KB 砍到了 48KB。怎么做到的?就是把这些没用的驱动全关了。启动时间也快了 0.3 秒。
3.3 砍掉不必要的命令:U-Boot 不是瑞士军刀
U-Boot 的命令行功能很强大,但嵌入式产品最终用户根本不会进命令行。你想想看,产品出厂后,谁会去敲 md、mw、crc32 这些命令?
我建议你把命令分成三类:
| 命令类别 | 典型命令 | 裁剪建议 |
|---|---|---|
| 必须保留 | bootm, bootz, go, reset | 引导内核和重启必须 |
| 按需保留 | mmc, sf, fatload, ext4load | 根据存储介质选择 |
| 可以砍掉 | bmp, crc32, i2c, md, mw, loop, sleep | 调试用,产品中不需要 |
注意: 砍命令之前,先确认你的 bootcmd 脚本里有没有用到这些命令。我曾经砍掉了 sf 命令,结果启动脚本里正好用 sf read 加载内核,直接变砖。
配置命令裁剪,通常在 configs/your_board_defconfig 里操作:
# 关闭不需要的命令
# CONFIG_CMD_BMP is not set
# CONFIG_CMD_CRC32 is not set
# CONFIG_CMD_I2C is not set
# CONFIG_CMD_MD is not set
# CONFIG_CMD_MW is not set
# CONFIG_CMD_LOOP is not set
# CONFIG_CMD_SLEEP is not set
# 保留必要的命令
CONFIG_CMD_BOOTM=y
CONFIG_CMD_MMC=y
CONFIG_CMD_FAT=y
这样一搞,U-Boot 本体体积能减少 30% 以上。我有个客户,把命令从 80 多个砍到 15 个,体积从 1.2MB 降到了 700KB。
3.4 配置 CONFIG 选项:精细控制每一字节
除了驱动和命令,U-Boot 还有很多 CONFIG 选项可以控制体积。说白了,就是让你决定哪些功能要,哪些不要。
我个人习惯用 make menuconfig 来配置,图形界面比较直观。但如果你在服务器上编译,直接改 defconfig 文件也行。
下面是一些关键的体积优化选项:
| CONFIG 选项 | 作用 | 体积影响 |
|---|---|---|
| CONFIG_BOOTDELAY=0 | 取消启动延时 | 减少约 2KB |
| CONFIG_SYS_MALLOC_F_LEN=0x400 | 减小 SPL 堆大小 | 减少约 4KB |
| CONFIG_CMDLINE_EDITING=n | 关闭命令行编辑 | 减少约 8KB |
| CONFIG_AUTO_COMPLETE=n | 关闭命令自动补全 | 减少约 6KB |
| CONFIG_LTO=y | 启用链接时优化 | 减少 10-20% |
| CONFIG_DEBUG=n | 关闭调试输出 | 减少约 20KB |
重点: CONFIG_LTO 是我最近特别喜欢用的一个选项。它能在链接阶段去掉未使用的函数,效果非常明显。我测试过一个项目,开启 LTO 后 U-Boot 体积从 1.1MB 降到了 880KB。
配置示例:
# 在 defconfig 中添加
CONFIG_LTO=y
CONFIG_BOOTDELAY=0
CONFIG_CMDLINE_EDITING=n
CONFIG_AUTO_COMPLETE=n
CONFIG_DEBUG=n
# 减小环境变量大小
CONFIG_ENV_SIZE=0x2000
CONFIG_ENV_SECT_SIZE=0x2000
嗯,这里还要提醒一句:CONFIG_ENV_SIZE 别设太小。我见过有人设成 0x1000,结果环境变量存不下,启动时一直报错。一般来说 8KB 到 16KB 就够用了。
3.5 实战:一个裁剪案例
最后,我分享一个实际案例。之前做一款工业网关,用的是 i.MX6ULL 平台,NAND Flash 只有 256MB。U-Boot 默认编译出来 1.8MB,太占空间了。
我做了以下裁剪:
- SPL 中只保留 MMC 和 NAND 驱动,砍掉 USB、网卡、SPI。
- U-Boot 本体中砍掉 40 多个命令,只保留 bootm、nand、mmc、fatload。
- 关闭 DEBUG、命令行编辑、自动补全。
- 开启 LTO。
- 环境变量从 64KB 减到 8KB。
最终 U-Boot 体积降到了 680KB,启动时间从 3.2 秒缩短到 1.8 秒。效果非常明显。
小技巧: 每次裁剪后,用 size 命令查看各段体积:arm-linux-gnueabihf-size u-boot。这样你能清楚看到 text、data、bss 的变化。
裁剪 U-Boot 其实不难,关键是要知道哪些能砍、哪些不能砍。记住一句话:只保留你需要的,其他都是负担。