启动阶段划分与 Trace 点设计

从按下电源键到多媒体服务跑起来,系统要经历五个关键阶段。每个阶段都是潜在的瓶颈,也可能藏着异常。我这些年做多媒体启动调试,踩了不少坑,后来总结出一套“分阶段埋点”的方法。说白了,就是给每个阶段装一个监控摄像头,出了问题能快速定位。

阶段一:Boot ROM

Boot ROM 固化在芯片内部,不可修改。它负责最基本的外设初始化和加载下一级 Bootloader。嗯,这里要注意,因为代码没法改,所以 Trace 点要依靠芯片厂商预留的接口,比如硬件时间戳寄存器。

我建议的 Trace 点:

  • 上电时刻戳:CPU 复位到 Boot ROM 执行第一条指令的时间。
  • 片内 SRAM 初始化完成:验证 SRAM 是否稳定,失败直接停住。
  • Boot Mode 选择结果:从 eMMC、NAND 还是 USB 启动,记录选择的引脚电平。
  • Bootloader 加载完成:成功将 Bootloader 数据拷贝到 SRAM 的时刻。
经验分享:我当年遇到一个案子,Bootloader 加载总是超时。幸亏 Boot ROM 里打了加载耗时 trace,一看居然花了 500ms。排查发现是 eMMC 时钟配置错了,降到 25MHz 导致。如果没有这个 trace,根本定位不到硬件层。

阶段二:Bootloader

Bootloader(U-Boot / Little Kernel / ABL)是第一个可修改的软件层。它要初始化 DDR、建立内存映射、加载 Kernel 镜像。这部分 Trace 点直接关系到启动速度的上限。

核心 Trace 点:

  • DDR 初始化耗时:DDR training 时间,尤其是 LPDDR4/5 的 calibration。
  • 存储设备识别:eMMC/UFS 识别是否成功,失败了就进 fastboot。
  • Kernel 镜像加载耗时:从存储设备读到 DDR 的总字节数和用时。
  • 设备树加载:解析设备树并传递给 Kernel 的时刻。

你想想看,光 DDR 初始化就能吃掉几百毫秒。我见过一个项目,DDR training 跑 800ms,客户要求总启动时间 2 秒,光这一项就占了 40%。优化方法?把降频调试模式关掉,并改用预训练的配置文件。

阶段三:Kernel

Kernel 启动是重头戏。解压、子系统初始化、驱动加载、rootfs 挂载,每一步都有可能崩。Trace 点不能太多,否则反而影响启动。

我常用的 Trace 点:

  • Kernel 解压完成时刻:从 lz4/gzip 压缩镜像到完全展开。
  • 关键驱动初始化:比如 GPU、显示控制器、USB 控制器、多媒体相关 DMA。
  • 文件系统挂载:rootfs 挂载成功 / 失败,记录耗时。
  • 用户态 init 进程启动:最后一步,标志着内核阶段结束。
注意:不要所有驱动都加 trace!我曾经试过在每个 probe 函数里加 printk,结果启动慢了 30%。只埋那些“容易出问题”的驱动,比如显示驱动,如果它挂了,多媒体服务就不可能正常启动。

阶段四:Init

Init 进程(systemd / android init)负责解析启动脚本,依次启动各种服务。这个阶段最容易出现依赖问题——服务等了服务,最后超时。

必须打的 Trace 点:

  • init.rc 解析时间:脚本解析和 tokens 构建耗时。
  • 关键服务启动顺序:比如 SurfaceFlinger、MediaServer、audioserver 的启动时刻。
  • 属性设置:系统属性初始化完成时间。
  • SELinux 策略加载:安全上下文加载完成时刻。

我踩过一个坑:多媒体服务启动慢,查 trace 发现 SurfaceFlinger 在等一个 socket 连接超时。深入排查,原来是 SELinux 策略还没加载完,socket 被 denied 了。加上 Init 阶段的 trace 后,这类依赖问题一眼就能看出来。

阶段五:多媒体服务

最后是用户直接感知的多媒体服务——音频、视频、Camera。这部分 Trace 点既要细致,又不能影响用户体验(比如不能卡住主线程)。

我建议的 Trace 点:

  • 音频策略引擎初始化:音频路由和策略文件加载耗时。
  • 视频解码器预热:H.264 / H.265 codec 加载和创建时间。
  • Camera HAL 打开:硬件连接和 ISP 固件加载。
  • 第一帧渲染时间:从服务启动到用户看到画面/听到声音。

我个人习惯把“第一帧渲染时间”作为多媒体启动成功的标志。曾经有一个 Camera 项目,第一帧出现在 3 秒后,查 trace 发现是 ISP 固件加载太慢,原因是固件放在一个低速分区。加上 trace 后,问题一目了然。

启动阶段 Trace 点总览 Boot ROM • 上电时刻戳 • SRAM init 完成 • Bootloader 加载完成 Bootloader • DDR 初始化耗时 • 存储设备识别 • Kernel 镜像加载完成 Kernel • 解压完成时刻 • 关键驱动 init • rootfs 挂载成功 Init • init.rc 解析时间 • 关键服务启动顺序 • SELinux 策略加载 多媒体服务 • 音频策略引擎 init • 视频解码器预热 • 第一帧渲染时间 时间轴 →

上面这张图把五个阶段和对应的关键 Trace 点串起来了。你仔细看,每个阶段左侧的矩形代表一个不可跳过的步骤,右侧说明框里是我认为最值得埋的点。

阶段 关键 Trace 点 我踩过的坑
Boot ROM 上电时刻戳、SRAM init、Bootloader 加载耗时 eMMC 时钟配置错误导致加载超时
Bootloader DDR training 时间、存储识别、Kernel 加载耗时 DDR training 开降频模式浪费 500ms
Kernel 解压完成、关键驱动 init、rootfs 挂载 所有驱动加 printk 导致启动变慢 30%
Init init.rc 解析、服务启动顺序、SELinux 策略 SurfaceFlinger 等 socket 超时,因 SELinux 未加载
多媒体服务 音频策略、视频 codec、Camera HAL、第一帧渲染 ISP 固件放低速分区导致第一帧 3 秒
核心原则:分阶段埋点不是越多越好,而是“关键路径全覆盖,非关键路径少打扰”。每个阶段只埋 3-4 个 Trace 点,足以覆盖 90% 的启动问题。
个人技巧:我会给每个 Trace 点分配一个唯一的 ID(比如 TRC_ROM_001),方便后期用脚本自动解析。trace 内容最好包含时间戳(微妙级)、文件名/行号、错误码。这样出问题时,看一眼 log 就知道哪个阶段挂了。
注意:如果你的系统用了 printk 或 logd,记得开启 logbuffer 提前分配大缓冲区,避免启动早期的日志被覆盖。我曾经因为缓冲区太小,刚进 Kernel 就把 Boot ROM 的 trace 冲掉了,白忙一场。