4. BootROM启动流程详解

聊启动性能,第一个问题就是:你到底要优化什么?

我见过不少团队,一上来就埋头改代码,改了一个月发现启动时间没怎么变。为什么?因为连衡量标准都没定清楚。你想想看,没有尺子,你怎么知道改了多少?

这一节,我们就先把尺子造好。

4.1 启动时间到底怎么定义?

很多人觉得启动时间很简单——从按下电源键到看到桌面。但做嵌入式的人都知道,事情没那么简单。

我个人习惯把启动时间拆成三个层次:

  • 硬件级启动:从 POR(Power-On Reset)到 CPU 开始执行第一条指令。这包括电源稳定、时钟起振、PLL 锁定、复位释放。通常几十微秒到几毫秒。
  • BootROM 级启动:从 CPU 取指执行 BootROM 第一条指令,到跳转到用户代码(或第二级 Bootloader)。这是我们的主战场。
  • 系统级启动:从 BootROM 跳转到 OS 或应用,到用户交互可用。这个范围更广,但 BootROM 阶段往往是瓶颈。

我在项目中遇到过一种情况:客户说“启动太慢”,结果一测,发现 BootROM 只用了 30ms,但后面 OS 初始化花了 2 秒。所以,定义启动时间时,一定要明确边界。

核心定义:在本课程中,我们说的“BootROM 启动时间”是指从 CPU 开始执行 BootROM 第一条指令,到 BootROM 执行完最后一条指令(跳转到用户代码)之间的时间间隔。

这个定义的好处是:边界清晰,可测量,可复现。你不需要关心外部设备的状态,只需要在 BootROM 入口和出口打上时间戳。

4.2 关键性能指标(KPI)

定义清楚了,接下来就是选哪些指标来衡量。我一般会关注以下 5 个 KPI:

KPI 定义 典型值(参考) 说明
冷启动时间 从 POR 到 BootROM 退出,不含休眠唤醒 50ms - 500ms 最核心指标,用户感知最强
热启动时间 从 WDT 复位或软件复位到 BootROM 退出 10ms - 100ms 比冷启动快,因为部分硬件已初始化
唤醒时间 从低功耗模式唤醒到 BootROM 退出 1ms - 10ms 对 IoT 设备至关重要
代码执行时间 BootROM 中纯 CPU 执行指令的时间 10ms - 200ms 反映代码效率,不含外设等待
外设初始化时间 BootROM 中初始化外设(DDR、Flash、时钟等)的时间 20ms - 300ms 往往是最大瓶颈

我的经验:不要只看冷启动时间。有一次我优化了一个项目,冷启动从 200ms 降到了 80ms,但客户还是不满意。后来发现,他们的设备每天要唤醒几百次,唤醒时间才是痛点。所以,KPI 要跟使用场景挂钩。

除了这些,我还会额外关注两个衍生指标:

  • 时间分布方差:同一设备多次启动的时间波动。波动大说明有不确定性因素,比如等待某个外设就绪。
  • 代码路径覆盖率:BootROM 中实际执行的代码占全部代码的比例。有些分支可能从未执行,但占用了 I-Cache 空间。

4.3 性能基准测试方法

指标有了,怎么测?我见过有人用秒表按,有人用示波器看 GPIO 电平。这些方法不是不行,但精度和可重复性都差。

我建议用以下三种方法,按场景选择:

4.3.1 方法一:硬件时间戳(最推荐)

利用 SoC 内部的定时器,在 BootROM 入口和出口分别读取计数值。这是最精确的方法,误差通常在纳秒级。

// 伪代码示例:使用 SoC 内部定时器测量 BootROM 执行时间
uint32_t start_tick, end_tick;
uint32_t freq = get_timer_frequency();  // 获取定时器频率,单位 Hz

start_tick = read_timer();   // BootROM 入口处

// ... BootROM 主逻辑 ...

end_tick = read_timer();     // BootROM 出口处

uint32_t elapsed_us = (end_tick - start_tick) * 1000000 / freq;
// 输出 elapsed_us,单位微秒

注意:定时器本身必须在 BootROM 执行期间保持运行。有些 SoC 在复位后定时器默认关闭,需要先初始化。我曾经踩过这个坑——测出来全是 0,查了半天才发现定时器没开。

4.3.2 方法二:GPIO 示波器法(调试阶段常用)

在 BootROM 入口拉高一个 GPIO,出口拉低。用示波器测量高电平持续时间。这个方法的好处是直观,不依赖串口或日志。

// 伪代码示例:GPIO 示波器法
void bootrom_entry(void) {
    gpio_set_pin(GPIO_PIN_0, 1);  // 拉高,开始计时
    // ... BootROM 主逻辑 ...
    gpio_set_pin(GPIO_PIN_0, 0);  // 拉低,结束计时
}

精度取决于示波器的采样率。一般 100MHz 的示波器可以测到 10ns 级别的变化,足够了。

4.3.3 方法三:JTAG/SWD 跟踪(最全面)

使用调试器(如 J-Link、ST-Link)的跟踪功能,记录 PC 指针的变化。可以精确到每条指令的执行时间,还能看到函数调用关系。

这个方法我一般在做深度分析时用。比如发现某个函数耗时异常,就用 JTAG 跟踪看它内部到底在干什么。

我的建议:日常开发用方法一(硬件时间戳),定位问题时用方法三(JTAG 跟踪)。方法二(GPIO 示波器)适合给老板演示——拿示波器一看,波形清清楚楚,比数据更有说服力。

4.4 知识体系:启动性能指标全景图

下面这张图把启动性能指标的核心逻辑串起来了。从定义到 KPI,再到测试方法,一条线走通。

启动性能指标全景图 启动时间定义 硬件级启动 BootROM级启动 系统级启动 关键性能指标(KPI) 冷启动时间 热启动时间 唤醒时间 代码执行时间 外设初始化时间 性能基准测试方法 硬件时间戳法 GPIO示波器法 JTAG/SWD跟踪法

这张图你看懂了吗?从上到下,先定义范围,再选指标,最后定测试方法。每一步都依赖前一步。我见过有人跳过定义直接测,结果测出来的数据根本没法用——因为连测的是哪一段都不知道。

4.5 避坑指南

最后,分享几个我踩过的坑:

  • 不要用软件延时来测量:我曾经用 for 循环做粗略计时,结果编译器优化把循环优化掉了,测出来全是 0。嗯,尴尬。
  • 注意测量本身的开销:读取定时器、拉 GPIO 这些操作本身也耗时。如果 BootROM 总共只有 1ms,测量开销占 0.1ms 就不能忽略。需要做校准。
  • 多次测量取平均值:单次测量可能有噪声(比如 Cache miss、总线仲裁)。我习惯测 10 次,去掉最大最小值,再取平均。
  • 记录环境条件:温度、电压、时钟频率都会影响启动时间。测试报告里一定要注明这些参数,否则数据没有可比性。

重要提醒:性能基准测试不是一次性工作。每次修改代码后都要重新跑一遍基准测试,确保没有引入性能退化。我建议把基准测试脚本集成到 CI/CD 流程中,自动触发,自动生成报告。