3. 启动流程与性能基线测量
R-Car 平台的启动性能优化,第一步不是改代码,而是把“时间花在哪里”看清楚。R-Car 的启动链路比通用 ARM SoC 更长:上电后先由片内 BootROM 建立最基础的信任与加载能力,再交给 TF-A(BL2/BL31)完成 DDR 初始化、安全环境搭建与 U-Boot 装载,随后 U-Boot 加载并跳转 Linux 内核,最后由内核完成驱动探测、根文件系统挂载并进入用户态。任何一段的耗时波动,都会直接反映到“上电到应用可用”的总时间上。
启动链路的阶段划分与可观测点
从测量角度看,R-Car 启动可以拆成五个可独立观测的阶段:
- BootROM 阶段:上电复位到 BootROM 从启动介质(QSPI/eMMC)读取并校验第一阶段引导镜像。该阶段由硬件固化代码控制,软件几乎不可改,但启动模式引脚、启动介质类型、镜像校验方式会显著影响耗时。
- TF-A BL2 阶段:完成 DDR 训练与初始化、加载 BL31/BL33,是整条链路中耗时最容易被低估的一段,DDR 训练在冷启动时通常需要数十毫秒。
- U-Boot 阶段:包括 SPL/正常 U-Boot 的板级初始化、环境变量加载、设备探测、内核与 DTB 的读取和校验。
- Linux 内核阶段:从内核解压、早期初始化到
init进程启动,包含驱动 probe、时钟/电源域初始化、根文件系统挂载。 - 用户态阶段:从
init到目标应用(如仪表 HMI、ADAS 服务)就绪,通常由 systemd 或自研 init 脚本决定。
这五个阶段的分界点,恰好对应一组可打印的时间戳:BootROM 无法直接打点,但可以通过 TF-A 的第一条日志反推;TF-A、U-Boot、内核各自都有可用的时间基准。关键是把它们统一到同一个时间轴上,否则各阶段耗时无法相加。
用 grabserial 建立统一时间轴
最实用的起点是 grabserial。它监听串口,为每一行日志打上相对于“首字符到达”的时间戳,从而把 TF-A、U-Boot、内核的日志串到同一条时间轴上。典型用法如下:
grabserial -d /dev/ttyUSB0 -b 115200 \
-t -m "BL2: Booting" \
-e 120 -o rcar_boot.log
# -t 打印相对时间戳
# -m 匹配到该字符串时把时间归零,便于以 TF-A 为基准
# -e 120 表示 120 秒后自动退出
运行后得到的日志形如:
[0.000000] BL2: Booting
[0.012340] BL2: DDR init start
[0.087650] BL2: DDR init done
[0.095210] BL31: Entry
[0.210880] U-Boot 2021.10
[0.452300] Loading Kernel Image
[0.610450] Starting kernel ...
[1.230100] Linux version 5.10
[2.845600] Run /sbin/init as init process
这些数值是示例格式,实际值取决于板卡与镜像。关键判读方法是:相邻两条关键日志的时间差,就是该子阶段的耗时。例如 DDR 初始化从 0.012s 到 0.088s,约 75ms;U-Boot 从 0.211s 到 0.610s,约 400ms。把这些差值填入基线表,就得到了可比较的起点。
用 bootgraph 定位内核长耗时 initcall
内核阶段内部差异很大,仅靠总时间无法定位。Linux 提供了 initcall_debug 与 bootgraph 组合:在启动参数中加入 initcall_debug,内核会为每个 initcall 打印开始与结束时间;再用 bootgraph.pl 把 dmesg 转换成调用时序图。
# 内核启动参数
console=ttySC0,115200 initcall_debug ignore_loglevel
# 采集 dmesg 后生成图
dmesg > dmesg.txt
scripts/bootgraph.pl dmesg.txt > bootgraph.svg
生成的图中,横轴是时间,每个 initcall 是一条水平条,条越长说明该驱动 probe 越慢。在 R-Car 上常见的“长条”包括:显示子系统(DU/VSP)、摄像头输入(CSI-2/VIN)、PCIe 控制器、以及网络 PHY 的复位等待。定位到具体函数后,优化方向就明确了:把非关键驱动改为模块化延迟加载,或调整 probe 顺序,让显示与网络并行初始化。
建立启动性能基线表
基线不是一组数字,而是一张可复现、可对比的表。建议至少记录以下字段:阶段名称、起止时间戳、耗时、测量工具、镜像版本、板卡编号、环境温度。下面给出一个示例结构(数值为示例,非实测):
| 阶段 | 起始时间(s) | 结束时间(s) | 耗时(ms) | 主要观测点 |
|---|---|---|---|---|
| BootROM | — | 0.000 | — | TF-A 首条日志 |
| TF-A BL2 | 0.000 | 0.095 | 95 | DDR init done |
| TF-A BL31 | 0.095 | 0.211 | 116 | U-Boot 首行 |
| U-Boot | 0.211 | 0.610 | 399 | Starting kernel |
| Kernel | 0.610 | 2.846 | 2236 | init 进程启动 |
| 用户态 | 2.846 | 4.500 | 1654 | 应用就绪标志 |
有了这张表,后续任何优化都能回答两个问题:改的是哪一段?改完省了多少毫秒?如果某次改动让内核阶段从 2236ms 降到 1800ms,而用户态不变,那么总启动时间就减少了约 436ms,收益可量化、可回归验证。
完成基线测量后,优化才有靶心。下一阶段的工作将围绕“哪一段占比最大、哪一段最可改”展开:通常内核与用户态合计占 60% 以上,是优先下手的地方;而 BootROM 与 TF-A 的 DDR 训练虽然难改,却可以通过启动介质选择和镜像裁剪获得稳定收益。