4. SPL 裁剪与加速:最小化SPL代码量
SPL 这玩意儿,说白了就是 U-Boot 的「迷你版」。它负责把完整的 U-Boot 从 Flash 里搬到 DDR 里。但问题来了——SPL 本身跑在 SRAM 里,空间就那么一丁点。我见过不少板子,SPL 空间只有 64KB 甚至 32KB。你想想看,这点空间要完成初始化、加载、跳转,代码稍微一膨胀就爆了。
所以,SPL 裁剪的核心目标就一个:在满足功能的前提下,把代码体积压到最小。体积小了,加载就快,启动自然就加速了。
4.1 为什么 SPL 需要裁剪?
先看一组我实际项目中的数据。某款 ARM 平台,默认 SPL 编译出来 128KB,但 SRAM 只有 96KB。结果呢?启动直接挂掉。后来我花了三天时间裁剪,最终压到 52KB,启动时间从 1.2 秒降到了 0.4 秒。
| 优化项 | 优化前体积 | 优化后体积 | 节省空间 |
|---|---|---|---|
| 默认 SPL | 128 KB | 52 KB | 59% |
| 驱动裁剪 | 45 KB | 18 KB | 60% |
| 功能裁剪 | 30 KB | 10 KB | 67% |
| 链接优化 | 53 KB | 24 KB | 55% |
你看,每个环节都能砍掉一大半。关键是,你得知道从哪下手。
4.2 裁剪的三大方向
我个人习惯把 SPL 裁剪分成三个维度:功能裁剪、驱动裁剪、链接裁剪。一个一个说。
4.2.1 功能裁剪:关掉不需要的模块
U-Boot 的 Kconfig 里有一堆 SPL 相关的配置项。很多默认是打开的,但你的板子根本用不上。比如:
- SPL_FAT_SUPPORT:如果你从 RAW NAND 启动,FAT 文件系统支持就是多余的。
- SPL_PARTITIONS:分区表解析,只有从 MMC 或 SD 卡启动时才需要。
- SPL_NET_SUPPORT:网络启动?SPL 阶段基本用不到。
- SPL_USB_SUPPORT:USB 下载模式,调试时有用,量产时可以关掉。
我在项目中遇到过一件事:某款产品 SPL 死活放不下,最后发现是 SPL_CMD_BOOT 这个命令集没关。它包含了 bootm、bootz 等一堆命令,SPL 根本不需要。关掉后,体积直接少了 12KB。
核心原则:SPL 只做两件事——初始化硬件、加载 U-Boot。其他功能一律砍掉。
4.2.2 驱动裁剪:只保留必要的驱动
SPL 里的驱动,能少就少。比如:
- 串口驱动:调试用,量产时可以关掉
CONFIG_SPL_SERIAL。 - 时钟驱动:只保留 PLL 和关键外设时钟,其他外设时钟等 U-Boot 阶段再初始化。
- 存储驱动:只保留你用的那一种。比如用 NAND 启动,就把 MMC、SPI Flash 驱动全关掉。
嗯,这里要注意:驱动裁剪不能盲目。我曾经把 SPL_I2C 关掉了,结果 DDR 初始化失败——因为 DDR 的 SPD 信息需要通过 I2C 读取。所以,裁剪前一定要搞清楚硬件依赖链。
我的经验:先编译一次 SPL,用 size 命令看各段体积。然后用 nm 或 objdump 找出最大的函数,优先优化它们。
4.2.3 链接裁剪:优化代码布局
链接阶段的优化,很多人会忽略。其实这里能省不少空间。
- 使用 LTO(Link Time Optimization):GCC 的
-flto选项可以跨文件优化,去掉未使用的函数。我实测能省 5%~10%。 - 调整链接脚本:把 .text、.data、.bss 段紧凑排列,减少填充字节。
- 去掉调试符号:
CONFIG_SPL_SYS_THUMB_BUILD如果平台支持,用 Thumb 指令集能省 30% 空间。
// 链接脚本示例:紧凑排列各段
SECTIONS
{
. = ALIGN(4);
.text : {
*(.text*)
}
. = ALIGN(4);
.rodata : {
*(.rodata*)
}
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
.bss : {
*(.bss*)
}
}
4.3 裁剪后的验证
裁剪完了,别急着高兴。一定要验证功能是否正常。我吃过这个亏:有一次把 SPL_DM(驱动模型)关掉了,结果 U-Boot 阶段加载时找不到设备树,直接 panic。
验证步骤很简单:
- 编译 SPL,检查体积是否在 SRAM 限制内。
- 烧录到板子,看能否正常打印 SPL 日志。
- 检查能否正确加载 U-Boot 并跳转。
- 用示波器或逻辑分析仪测量 SPL 阶段耗时。
警告:不要一次性关掉太多配置。每关一个,编译一次,测试一次。否则出了问题,你根本不知道是哪个配置导致的。
4.4 实战:一个裁剪案例
拿我最近做的一个 i.MX6ULL 项目举例。原始 SPL 配置如下:
CONFIG_SPL_FAT_SUPPORT=y
CONFIG_SPL_PARTITIONS=y
CONFIG_SPL_NET_SUPPORT=y
CONFIG_SPL_USB_SUPPORT=y
CONFIG_SPL_CMD_BOOT=y
CONFIG_SPL_SERIAL=y
CONFIG_SPL_I2C=y
CONFIG_SPL_MMC=y
CONFIG_SPL_NAND=y
裁剪后:
CONFIG_SPL_FAT_SUPPORT=n
CONFIG_SPL_PARTITIONS=n
CONFIG_SPL_NET_SUPPORT=n
CONFIG_SPL_USB_SUPPORT=n
CONFIG_SPL_CMD_BOOT=n
CONFIG_SPL_SERIAL=y // 保留,调试用
CONFIG_SPL_I2C=y // 保留,DDR 需要
CONFIG_SPL_MMC=y // 保留,从 MMC 启动
CONFIG_SPL_NAND=n
体积从 98KB 降到了 41KB。启动时间从 0.8 秒降到了 0.3 秒。
4.5 SPL 裁剪知识体系
下面这张图,是我总结的 SPL 裁剪知识体系。你可以对照着检查自己的项目。
4.6 总结
SPL 裁剪,说白了就是「做减法」。你想想看,SPL 的生命周期就那么几百毫秒,跑完就没了。没必要把 U-Boot 的完整功能塞进去。
我个人习惯是:先关掉所有非必要功能,然后逐个打开必须的。每开一个,编译一次,测试一次。这样既能保证体积最小,又能确保功能完整。
最后提醒一句:裁剪后的 SPL,一定要做压力测试。我曾经遇到过裁剪后 SPL 启动正常,但 U-Boot 加载时因为缺少某个初始化步骤而崩溃。嗯,这种问题最难排查。