嵌入式系统启动流程详解:从上电到应用程序运行
本文系统梳理嵌入式系统从上电复位到用户应用程序运行的完整启动链路,涵盖硬件复位、BootROM、Bootloader、内核、根文件系统与 init 进程等关键环节,并对比 ARM 与 x86 的典型差异及常见调试方法。
一、为什么需要理解启动流程
嵌入式设备没有 PC 那样明显的"开机画面"和操作系统安装向导,但每一次上电背后都有一套严格有序的启动链条。理解这套流程,有助于:
- 定位启动失败:卡在哪个阶段、如何抓日志、如何区分硬件与软件问题
- 优化启动时间:裁剪 Bootloader、内核、用户态服务,满足车规、工控等对冷启动时延的要求
- 做安全启动(Secure Boot):在每一级校验下一级镜像的签名与完整性
- 做 OTA 与现场维护:明确哪些分区可写、升级应更新哪些镜像
可以把启动过程想象成多级接力:每一棒只负责把控制权交给下一棒,并准备好下一棒运行所需的最小环境。
二、启动流程总览
典型嵌入式 Linux 系统的启动顺序如下:
上电 / 复位
↓
SoC 硬件初始化(时钟、复位向量)
↓
BootROM(芯片固化代码,不可修改)
↓
第一级 Bootloader(SPL / FSBL / MaskROM 加载的外部代码)
↓
第二级 Bootloader(U-Boot、Barebox、ARM Trusted Firmware 等)
↓
加载设备树(DTB)+ 内核镜像(zImage/Image/uImage)
↓
Linux 内核启动(架构相关初始化 → 驱动子系统 → 挂载根文件系统)
↓
用户空间 init(systemd / busybox init / 自定义脚本)
↓
业务应用程序
裸机系统(无操作系统)通常在上电后由 BootROM 加载一段 Flash 中的程序,直接运行应用或 RTOS,链路更短,但前几级硬件初始化逻辑类似。
三、上电与硬件复位
3.1 上电时发生了什么
设备接通电源后,电源管理芯片(PMIC)按序给各电源域供电。SoC 内部的 Power-On Reset(POR) 电路在电压稳定后产生复位信号,CPU 及主要外设进入已知初始状态。
3.2 复位向量(Reset Vector)
CPU 复位后,程序计数器(PC)被硬件强制设为复位向量地址。该地址由芯片手册规定,例如:
| 架构/平台 | 典型复位向量行为 |
|---|---|
| ARM Cortex-A | 通常从 0xFFFF0000(高向量)或 0x00000000 取指,具体由 VBAR/启动模式引脚决定 |
| ARM Cortex-M | 向量表第 0 项为初始栈指针,第 1 项为 Reset_Handler 地址 |
| RISC-V | mtvec 指向的入口,或由 BootROM 固定映射 |
复位后最先执行的,一定是芯片厂商烧录在片内或外部固定存储器中的代码——即 BootROM 或其加载的第一段代码。
3.3 最小硬件环境
在跳转到可执行代码之前,硬件通常会自动或很快完成:
- 使能基础时钟(有时 BootROM 内再配置 PLL)
- 初始化用于启动的存储接口(SPI NOR、eMMC、SD 等)
- 配置启动引脚(Boot Mode Pins)决定从哪类介质启动
Boot Mode 是现场排障的高频检查点:同一板卡接不同启动介质、拨码不同,会走完全不同的启动路径。
四、BootROM:第一棒,芯片自带
4.1 BootROM 的职责
BootROM 是固化在 SoC 内部的只读程序,主要完成:
- 根据 Boot Mode 选择启动设备
- 从启动设备读取一小段代码到片上 SRAM(内部 RAM,容量通常仅几十 KB~几百 KB)
- 校验该代码(部分芯片支持简单 CRC,安全芯片会做签名验证)
- 跳转到 SRAM 中执行——进入第一级 Bootloader(SPL)
4.2 为什么需要 SPL(Secondary Program Loader)
片上 SRAM 很小,放不下完整的 U-Boot;Flash 中的完整 Bootloader 也可能需要先初始化 DDR 才能运行。因此常见做法是:
- SPL:体积极小,只做 DDR 初始化、时钟树配置、加载下一阶段 Bootloader
- 完整 Bootloader:在 DDR 就绪后运行,功能完整(网络、USB、文件系统、内核加载)
典型命名:
- SPL:U-Boot 体系中的第一级加载器
- FSBL(First Stage Boot Loader):Xilinx Zynq 等 FPGA SoC 术语
- TF-A BL2:ARM Trusted Firmware 安全启动链中的一级
4.3 无外部启动介质时的行为
部分 MCU 支持从 UART/USB 下载(ISP/DFU),BootROM 等待主机下发代码,常用于产线烧录与开发调试。
五、Bootloader:连接硬件与内核的桥梁
第二级 Bootloader 在嵌入式 Linux 中最常见的是 U-Boot,此外还有 Barebox、RedBoot(较老)等。Bootloader 的核心任务可以概括为四句:
初始化外设 → 提供命令行/自动启动脚本 → 加载内核与 DTB → 跳转内核并传递参数
5.1 典型初始化内容
- DDR/SDRAM 控制器(若 SPL 未做完)
- 存储设备:eMMC、NAND、NOR、SPI Flash
- 网络:以太网 PHY、MAC,便于 TFTP 启动与调试
- 串口(UART):打印
U-Boot>提示符,输出启动日志 - 环境变量区:保存在 Flash 专用分区,存
bootcmd、bootargs等
5.2 环境变量与启动脚本
U-Boot 中两个关键变量:
bootargs:传给 Linux 内核的启动参数字符串,例如:console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rwbootcmd:自动启动时执行的命令序列,例如从 eMMC 分区读取内核:fatload mmc 0:1 0x82000000 zImage; fatload mmc 0:1 0x88000000 dtb; bootz 0x82000000 - 0x88000000
开发阶段常用 手动命令 验证各步骤,确认后再写入 bootcmd 固化。
5.3 向内核传递信息的方式
| 方式 | 说明 |
|---|---|
| ATAGs | 较老的 ARM32 方式,通过 r2 寄存器传递链表结构 |
| 设备树(DTB) | 现代 ARM/RISC-V 主流,硬件描述与内核分离 |
| ACPI | 部分 ARM 服务器、x86 使用 |
| UEFI | 部分高端嵌入式/ARM Server 通过 UEFI 固件启动 |
跳转内核时,Bootloader 需按约定设置寄存器(如 ARM32 的 r0=0, r1=机器类型或 0, r2=DTB 地址)并跳转到内核入口。
六、设备树(Device Tree):描述硬件的"清单"
6.1 为什么需要设备树
传统做法在内核源码中为每块板子写 machine descriptor 和大量板级文件,维护成本高。设备树把硬件拓扑与配置抽成 .dts 源文件,编译为二进制 .dtb,由 Bootloader 随内核一起加载。
6.2 设备树里有什么
- CPU 核数、频率、enable-method
- 内存(
memory节点)基址与大小 - 外设寄存器基址、中断号、时钟源、GPIO 引脚
- 总线拓扑(I2C/SPI 下挂的设备)
内核驱动通过 compatible 字符串匹配驱动,例如:
uart0: serial@02020000 {
compatible = "vendor,uart";
reg = <0x02020000 0x1000>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_uart>;
status = "okay";
};
6.3 与启动的关系
若 DTB 中内存大小、串口节点错误,常见现象是:内核能打印早期 log,随后 panic 或串口无输出。排障时要核对 Bootloader 加载的 DTB 是否与当前板卡一致。
七、Linux 内核启动阶段
内核镜像被 Bootloader 加载到内存并跳转后,执行流程大致如下(不同架构细节不同,逻辑相通)。
7.1 架构相关早期代码
- 设置初始页表、开启 MMU(若尚未开启)
- 识别 CPU 类型、缓存行大小
- 解析设备树(
early_init_dt_scan)获取内存布局、cmdline
此阶段输出常出现在串口,关键字如 Booting Linux on physical CPU、内存 bank 信息。
7.2 start_kernel 与驱动子系统
进入 C 语言主初始化路径 start_kernel() 后,依次完成:
- 调度器、定时器、中断子系统
- 内存管理(buddy、slab)
- 平台设备与总线驱动注册(基于 DTB)
- 块设备、VFS、挂载 rootfs
7.3 根文件系统的挂载
root= 启动参数指定根设备,常见形式:
| 参数示例 | 含义 |
|---|---|
root=/dev/mmcblk0p2 | eMMC 第 2 分区 |
root=/dev/nfs nfsroot=192.168.1.10:/nfs | NFS 网络根文件系统(开发常用) |
root=/dev/ram0 | 内存盘,常与 initrd/initramfs 配合 |
initramfs:打包在内核镜像内或单独加载的临时根文件系统,内含必要驱动与挂载脚本,用于找到真实根分区(LVM、加密盘、USB 硬盘等场景)。
挂载成功后,内核打印:
VFS: Mounted root (ext4 filesystem) on device 179:2.
Run /sbin/init as init process
7.4 内核启动时间线(概念图)
汇编入口 → 早期汇编/C → start_kernel
→ rest_init → kernel_init 线程
→ 尝试执行用户空间 /sbin/init(或 init= 指定)
八、用户空间:init 与业务进程
8.1 init 进程的职责
内核启动的第一个用户态进程 PID=1,Traditionally /sbin/init。职责包括:
- 挂载
/proc、/sys、/dev等虚拟文件系统 - 按运行级别或 target 启动系统服务
- 回收僵尸进程
- 拉起 getty 登录或直接进入应用
8.2 常见 init 实现
| 实现 | 特点 |
|---|---|
| BusyBox init | 体积小,脚本 /etc/inittab 配置,适合资源受限设备 |
| systemd | 主流桌面/服务器与部分嵌入式发行版,依赖并行、单元文件 |
| 自定义脚本 | /etc/init.d/rcS 直接启动单一应用,工控、消费电子常见 |
嵌入式产品为缩短启动时间,常裁剪 systemd、减少不必要服务,甚至 init 直接 exec 业务程序。
8.3 应用程序启动
业务层可能是:
- Qt / GTK 图形界面
- 后台守护进程 + Web 配置
- ROS 节点、流媒体服务等
从系统角度看,它们只是 init 拉起的普通进程;启动优化往往要分析整条依赖链:网络就绪、数据库挂载、许可证校验等。
九、不同启动介质与存储布局
9.1 常见介质对比
| 介质 | 特点 | 典型用途 |
|---|---|---|
| SPI NOR Flash | 随机读快、可 XIP,容量较小 | Bootloader、内核、小 rootfs |
| NAND / eMMC | 容量大、成本低 | 大 rootfs、数据分区、OTA |
| SD 卡 | 开发方便 | 原型验证 |
| 网络(TFTP/NFS) | 无需本地烧录 | 内核与 rootfs 开发调试 |
9.2 典型 Flash 分区示例
+------------------+
| SPL / BootROM 加载区 |
+------------------+
| U-Boot 完整镜像 |
+------------------+
| 环境变量 (env) |
+------------------+
| 内核 (zImage) |
+------------------+
| 设备树 (dtb) |
+------------------+
| 根文件系统 (squashfs/ext4) |
+------------------+
| 用户数据 / OTA 备份 |
+------------------+
分区表可能来自:硬编码地址、GPT/MBR、U-Boot mtdparts 或厂商私有方案。升级时务必对照分区文档,避免擦除 Bootloader。
十、安全启动(Secure Boot)简述
安全启动在每一级只加载经过厂商密钥签名验证的下一级镜像:
BootROM 校验 SPL 签名
→ SPL 校验 U-Boot/TF-A 签名
→ Bootloader 校验内核与 dtb
→ (可选)内核校验 dm-verity / 模块签名
相关技术:
- ARM Trusted Firmware-A(TF-A):EL3 固件、PSCI、安全世界切换
- OP-TEE:安全 OS,密钥与生物特征等敏感逻辑
- dm-verity:根文件系统块级完整性,防篡改
开发与安全启动冲突的常见点:未签名内核无法启动、JTAG 被熔断。需在方案阶段规划密钥与签名流水线。
十一、ARM 与 x86 嵌入式启动差异(简表)
| 项目 | ARM 嵌入式 | x86 嵌入式(如 Atom) |
|---|---|---|
| 第一级代码 | BootROM + SPL | UEFI/BIOS 固件 |
| Bootloader | U-Boot 为主 | GRUB + UEFI |
| 硬件描述 | 设备树为主 | ACPI 为主 |
| 典型产品 | 路由器、摄像头、工控 | 网关、边缘服务器 |
x86 路线更接近 PC:UEFI 固件 → EFI 分区上的 GRUB → Linux,但"内核 → init → 应用"的后半段与 ARM 类似。
十二、启动失败排障思路
按阶段分层排查,先看串口最后一行 log 卡在哪里:
- 无任何打印:电源、Boot Mode、串口线/波特率、UART 引脚是否被 Bootloader 初始化
- 仅有 BootROM/SPL 信息:DDR 训练失败、Flash 读失败、镜像损坏
- U-Boot 停住:
bootcmd错误、分区无内核、环境变量损坏(可printenv/ 恢复默认 env) - 内核 panic:
bootargs根设备错误、缺少驱动、DTB 不匹配 - 内核挂起无 init:rootfs 损坏、init 不存在或不可执行
- init 起来但应用无响应:用户态脚本、依赖服务、许可证或配置问题
常用工具:
- 串口终端(minicom、picocom)
- JTAG/SWD(OpenOCD)查看 PC 指针与寄存器
- U-Boot
md、mmc、fatls、tftp等命令 - 内核
earlyprintk、initcall_debug启动参数
十三、启动时间优化(产品向)
| 层级 | 常见手段 |
|---|---|
| Bootloader | 跳过网络/USB 初始化、精简 bootdelay、并行加载 |
| 内核 | CONFIG_EMBEDDED、裁剪驱动、禁用不必要子系统、压缩镜像 |
| rootfs | initramfs 瘦身、squashfs 只读根、延迟挂载 |
| 用户态 | 减少 systemd 单元、应用懒加载、并行初始化无依赖模块 |
测量方式:GPIO 翻转打点、printk 时间戳、systemd-analyze(若适用)、示波器量关键电源轨上电时序。
十四、小结
嵌入式系统启动是一条从硬件复位到软件分层交付的严格流水线:
- BootROM 解决"从哪启动、加载谁"的问题
- SPL + Bootloader 解决"DDR 与外设就绪、把内核搬进内存"的问题
- 内核 解决"驱动硬件、挂载根文件系统"的问题
- init 解决"用户空间环境与守护进程"的问题
- 应用程序 才实现产品价值
掌握每一级的输入输出(谁加载谁、参数如何传递、日志长什么样),就能把"板子不亮"、“卡在 Starting kernel”、"根文件系统挂载失败"等问题快速定位到具体层级,而不是盲目换镜像或重烧整片 Flash。
参考资料
- Linux 内核文档:
Documentation/admin-guide/boot.rst、Documentation/devicetree/ - U-Boot 官方文档:https://docs.u-boot.org/
- ARM Architecture Reference Manual(复位、异常向量、TrustZone)
- 各 SoC 厂商 TRM(Technical Reference Manual)Boot 章节
本文为技术总结,具体芯片请以官方数据手册与 BSP 为准。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)