2. 嵌入式启动流程基础

在嵌入式 Linux 系统中,从上电到进入用户空间并不是一步完成的,而是一条由多个阶段接力执行的启动链。理解这条启动链,是进行 U-Boot 网络 OTA 部署的前提:只有明确每个阶段运行在哪里、能做什么、不能做什么,才能判断 OTA 升级应该在哪一级写入、由谁校验、失败后如何回退。典型启动链为:BootROM → SPL → U-Boot → Linux 内核 → 根文件系统。

核心结论:BootROM 负责“把第一段代码搬进片内 RAM”,SPL 负责“把 U-Boot 搬进 DDR”,U-Boot 负责“把内核和根文件系统搬进 DDR 并启动”,内核负责“初始化硬件并挂载根文件系统”,根文件系统负责“提供用户空间与应用程序”。

一、启动链的整体结构与职责划分

下面这张图展示了各阶段的执行位置、加载目标与职责边界。可以看到,越靠前的阶段,运行环境越受限,代码体积越小,但权限越底层;越靠后的阶段,功能越丰富,但依赖的存储与内存资源也越多。

嵌入式 Linux 启动链:加载顺序与职责 BootROM 片内固化代码 加载 SPL 到 SRAM SPL 运行于 SRAM 初始化 DDR,加载 U-Boot U-Boot 运行于 DDR 加载内核与设备树 Linux 内核 初始化硬件驱动 挂载根文件系统 根文件系统 提供 /sbin/init 启动用户空间应用 存储介质 eMMC / NAND / SD 存放 SPL、U-Boot、内核 内存层次 SRAM:小容量、免初始化 DDR:大容量、需初始化 OTA 关注点 U-Boot 环境变量 内核与根文件系统分区 上电复位 DDR 就绪 内核加载 驱动初始化 用户空间 关键参数与地址示例(以典型 ARM SoC 为例) • BootROM 加载地址:0x0000_0000(SRAM) • SPL 大小限制:通常 ≤ 64KB • U-Boot 加载地址:0x8000_0000(DDR) • 内核加载地址:0x8000_8000 • 设备树地址:0x8000_0000 附近 • 根文件系统:由 bootargs 中 root= 指定 • OTA 升级时需确保 U-Boot 环境变量中的 bootcmd、bootargs 指向正确的分区与地址

二、各阶段详细职责与运行环境

阶段 运行位置 核心职责 典型大小 OTA 相关性
BootROM SoC 片内 ROM 上电复位、时钟初始化、从启动介质读取 SPL 到 SRAM 固化不可改 不可升级,决定启动介质顺序
SPL 片内 SRAM 初始化 DDR、时钟、串口,加载 U-Boot 到 DDR 约 20~64KB 通常不参与 OTA,但需保证兼容
U-Boot DDR 提供命令行、网络、存储驱动,加载内核与设备树 约 200~500KB OTA 核心:环境变量、bootcmd、网络传输
Linux 内核 DDR 初始化硬件驱动、挂载根文件系统、启动 init 约 3~8MB OTA 升级对象之一,需校验与回退
根文件系统 DDR + 存储 提供用户空间、库、应用程序与配置 约 50~500MB OTA 升级对象之一,常采用 A/B 分区

三、典型启动流程与日志分析

以一块 ARM 开发板为例,上电后串口会依次输出各阶段日志。通过日志可以判断启动卡在哪一级,这对 OTA 失败排障非常关键。

# BootROM 阶段(通常无输出或仅输出芯片信息)
ROM: BootROM V1.2
Boot media: eMMC

# SPL 阶段
U-Boot SPL 2023.07 (Jan 10 2024 - 10:00:00)
Trying to boot from MMC1
DDR init done, loading U-Boot...

# U-Boot 阶段
U-Boot 2023.07 (Jan 10 2024 - 10:00:00 +0800)
CPU: ARM Cortex-A53
DRAM: 512 MiB
MMC: sdhci@fe330000: 0
In: serial
Out: serial
Err: serial
Net: eth0: ethernet@fe300000
Hit any key to stop autoboot: 0
=> boot

# 内核阶段
Starting kernel ...
[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 5.10.160
[    2.345678] mmc0: SDHCI controller on fe330000
[    3.456789] EXT4-fs (mmcblk0p3): mounted filesystem

# 根文件系统阶段
[    5.678901] systemd[1]: Started Journal Service.
Welcome to Buildroot
buildroot login:
排障技巧:如果串口只输出到 “Trying to boot from MMC1” 就停止,说明 SPL 无法读取 U-Boot,通常是分区表或存储介质问题;如果输出到 “Starting kernel ...” 后无反应,说明内核加载地址或设备树不匹配;如果内核启动后卡在挂载根文件系统,说明 bootargs 中 root= 参数或分区内容有误。

四、OTA 部署中的关键关注点与常见误区

在网络 OTA 部署实践中,启动链的每一级都可能成为升级的瓶颈或故障点。以下是最容易出错的几个环节:

  • 误区一:直接升级 SPL。SPL 运行在 SRAM 中,且由 BootROM 直接加载,一旦写坏,设备将无法启动,通常只能通过 JTAG 或 USB 烧录恢复。OTA 方案应尽量避开 SPL 和 BootROM 区域。
  • 误区二:忽略 U-Boot 环境变量的持久化。OTA 升级后需要更新 bootcmd、bootargs 或分区偏移,如果环境变量未正确保存到存储介质,重启后会回退到旧配置。
  • 误区三:内核与根文件系统版本不匹配。内核中的驱动与根文件系统中的模块、库版本必须一致,否则会出现驱动加载失败或应用无法运行。
  • 误区四:没有 A/B 分区与回退机制。单分区升级一旦失败,设备变砖。推荐采用 A/B 双分区,由 U-Boot 根据启动标志选择可启动分区。
在 OTA 方案设计时,必须明确:哪些分区允许网络升级、升级后由谁校验、校验失败后如何回退。通常建议只升级内核分区与根文件系统分区,U-Boot 本身如需升级,应保留双备份并配合硬件看门狗。

五、小结:启动链与 OTA 的对应关系

BootROM 和 SPL 是启动的“地基”,通常不参与网络 OTA;U-Boot 是 OTA 的“调度中心”,负责网络传输、分区写入和启动选择;内核与根文件系统是 OTA 的“主要升级对象”,需要配合校验、双分区和回退策略。只有把这条启动链的每一级职责和边界弄清楚,后续设计 U-Boot 网络 OTA 方案时,才能准确决定升级目标、写入位置和失败恢复路径。