所谓 SoC 启动流程,本质上就是:让处理器从一个确定的位置取到第一条指令,然后一步步把硬件环境初始化好,最后把控制权交给真正的软件系统。

一、SoC 启动到底要解决什么问题?

处理器的本质工作很简单:

取指令 → 译码 → 执行 → 访问数据 → 写回结果。

但刚上电时,处理器面临几个最基本的问题:

1. 我从哪里取第一条指令?

2.  这需要“复位向量”。 我看到的地址对应哪块硬件?

3.  这需要“存储空间映射”。 我能不能直接访问 Flash、SRAM、DRAM?

4.  有些能直接访问,有些必须先初始化控制器。 异常、中断、缓存、总线是否已经可用?

默认往往不可用,必须逐步配置。 所以,SoC 启动不是“CPU 一上电就运行系统”,而是一个从最小硬件环境开始,逐步扩大可用资源的过程。

二、第一步:上电复位,让 SoC 回到确定状态

上电后,电源、时钟、复位电路先工作。

复位的意义是:把 SoC 里的关键寄存器、状态机、处理器状态拉回一个确定的初始状态。

否则,每个触发器里可能是 0,也可能是 1,系统行为不可预测。

复位完成后,处理器会进入一个预定义状态:

●  程序计数器 PC 被设置到固定位置; 

●  处理器进入特定模式; 

●  中断通常先关闭; 

●  缓存、MMU 等复杂机制通常还没开启; 

●  处理器准备从复位地址取第一条指令。 

这个固定位置,就是常说的Reset Vector,复位向量。

三、第二步:处理器从复位向量取第一条指令

处理器不会“思考”自己该去哪儿运行代码。它只会按照体系结构规定,从复位向量对应的地址开始取指令。

例如,一个典型 SoC 可能规定:

复位后 PC = 0x0000_0000

于是 CPU 第一件事就是访问地址 0x0000_0000,取出那里的指令。

但这里马上出现一个关键问题:

0x0000_0000 到底是谁?

可能是片上 ROM,也可能是 Flash,也可能是被重映射后的 SRAM。

系统存储空间映射与重映射。

四、第三步:通过存储映射找到启动代码

SoC 里有很多硬件模块:

●  ROM 

●  SRAM 

●  Flash 

●  DDR 

●  UART 

●  SPI 

●  I2C 

●  GPIO 

●  中断控制器 

●  调试模块 

处理器访问它们的方式,本质上都是访问地址。

比如:

0x0000_0000 - 0x0000_FFFF  启动 ROM
0x2000_0000 - 0x2001_FFFF  SRAM
0x4000_0000 - 0x4000_0FFF  UART
0x5000_0000 - 0x5000_0FFF  GPIO

这就是 存储空间映射。

从处理器视角看,它并不关心后面是真正的存储器,还是一个外设寄存器。它只知道:访问某个地址,就会得到某种响应。

启动时,SoC 通常会把一小段可靠的启动代码放在 Boot ROM 中。因为 ROM 是片上固化的,不依赖外部存储器初始化,最适合承担第一阶段启动任务。

五、第四步:Boot ROM 执行最小启动逻辑

Boot ROM 是 SoC 启动的第一段软件。

它的任务不是运行完整系统,而是完成“最低限度的硬件准备”。

典型工作包括:

●  判断启动模式; 

●  初始化必要时钟; 

●  配置基础 SRAM; 

●  设置栈指针; 

●  初始化外部存储控制器; 

●  从 Flash、eMMC、SD 卡、SPI NOR 或 UART 加载下一阶段程序; 

●  跳转到下一阶段启动代码。 

举个例子:

一颗 SoC 支持从 SPI Flash 启动。上电后,CPU 先执行片上 Boot ROM。Boot ROM 读取启动引脚,发现当前配置是 SPI 启动,于是初始化 SPI 控制器,从 SPI Flash 里读取 Bootloader,把它搬到 SRAM 或 DDR 中,然后跳过去执行。

流程大概是:

这一步的核心思想是:先用最可靠的片上资源,把更复杂的软件加载进来。

六、第五步:完成存储空间重映射

很多 SoC 启动初期,会把 Boot ROM 映射到低地址,比如 0x0000_0000。

但系统真正运行起来后,低地址可能需要给 RAM、异常向量表或操作系统使用。于是会发生 重映射。

所谓重映射,就是:

同一个处理器地址,在启动前后可以指向不同硬件。

例如启动初期:

0x0000_0000 → Boot ROM

启动完成后:

0x0000_0000 → SRAM 或 DDR

为什么要这么做?

因为处理器复位时必须从固定地址启动,但系统运行时又希望这个地址区域更加灵活。重映射机制就像门牌号不变,但门后面的房间换了。

这也是 SoC 启动比普通软件启动复杂的原因之一:软件看到的是地址,硬件决定地址背后是谁。

七、第六步:初始化栈、异常向量和中断系统

程序要正常运行,不能只会顺序执行指令,还要能处理异常和中断。

异常包括:

●  未定义指令; 

●  访问非法地址; 

●  总线错误; 

●  软中断; 

●  外部中断; 

●  复位异常。 

中断则来自外设,比如定时器、UART、网卡、GPIO 等。

所以启动代码必须设置:

异常向量表
中断控制器
中断优先级
中断入口函数
处理器运行模式

以 ARM 系统为例,异常向量表里存放的是不同异常入口地址。发生异常后,处理器不是乱跳,而是根据异常类型跳到指定入口。

这一步的本质是:

让系统从“只能按顺序跑”变成“能应对突发事件”。

不过在早期启动阶段,中断通常不会马上打开。因为外设和内存环境还不稳定,过早响应中断可能导致系统跑飞。

八、第七步:初始化缓存,但不能乱开

缓存的作用是缩短处理器访问存储器的时间。

处理器速度很快,外部存储器相对较慢。如果每条指令、每个数据都直接访问外部 DDR,性能会很差。

缓存就是放在处理器和存储器之间的高速缓冲区。

但启动时,缓存不能随便开启。

原因是:

●  地址映射可能还没稳定; 

●  MMU 可能还没配置; 

●  DDR 可能还没完成初始化; 

●  DMA 和 CPU 可能同时访问同一片内存; 

●  多核系统中还涉及缓存一致性。 

所以常见做法是:

早期启动:关闭缓存,保证行为简单可靠
中期初始化:配置内存属性和页表
系统稳定后:开启 I-Cache、D-Cache
多核系统:处理缓存一致性

I-Cache 缓存指令,D-Cache 缓存数据。

开缓存的目的很明确:提高性能。

但开缓存的前提也很明确:系统必须知道哪些地址能缓存,哪些不能缓存。例如外设寄存器就不能像普通内存一样缓存。否则 CPU 写了 UART 寄存器,但数据只停在缓存里,没有真正写到 UART,外设就不会工作。

九、第八步:Bootloader 接管,初始化更完整的系统

Boot ROM 通常很小,只负责“把下一棒交出去”。

真正复杂的初始化一般由 Bootloader 完成。

Bootloader 会做更多事情:

●  初始化 DDR; 

●  配置时钟树; 

●  配置 PLL; 

●  初始化串口,打印启动日志; 

●  建立内存布局; 

●  设置异常向量; 

●  配置 MMU 和 Cache; 

●  加载操作系统内核; 

●  加载设备树; 

●  跳转到内核入口。 

在嵌入式 Linux 系统里,Bootloader 常见代表是 U-Boot。

典型流程是:

如果是一个简单 MCU SoC,流程可能更短:

这两种系统规模不同,但第一性原理一样:

先建立最小运行环境,再加载更复杂的软件。

十、第九步:操作系统内核启动

当 Bootloader 把控制权交给操作系统内核后,启动进入新的阶段。

以内核为例,它会继续完成:

●  建立页表; 

●  开启 MMU; 

●  初始化调度器; 

●  初始化中断系统; 

●  初始化驱动; 

●  挂载根文件系统; 

●  启动第一个用户态进程。 

到这里,SoC 才真正从“硬件刚醒”进入“系统可用”。

可以把整个过程理解为一场接力:

复位电路:把系统拉到确定状态
CPU:从复位向量取第一条指令
Boot ROM:找到启动源
Bootloader:初始化大系统
操作系统:管理全部软硬件资源
应用程序:提供最终功能

十一、完整 SoC 启动流程总结

可以把 SoC 启动压缩成下面这条主线:

1. 上电
   ↓
2. 电源稳定,时钟产生,复位释放
   ↓
3. CPU 进入复位状态
   ↓
4. PC 指向复位向量
   ↓
5. 根据存储映射访问 Boot ROM
   ↓
6. Boot ROM 执行启动代码
   ↓
7. 判断启动模式和启动介质
   ↓
8. 初始化最小硬件环境
   ↓
9. 加载 Bootloader
   ↓
10. 必要时进行地址重映射
   ↓
11. 设置栈、异常向量、中断控制器
   ↓
12. 初始化 DDR、时钟、外设
   ↓
13. 配置 MMU、Cache 和缓存一致性
   ↓
14. 加载操作系统或应用程序
   ↓
15. 跳转到系统入口
   ↓
16. SoC 正式运行

结语:SoC 启动的本质

SoC 启动看起来复杂,但抓住一条线就很清楚:

处理器必须从确定地址取到第一条指令;

 启动代码必须建立最小运行环境;

 系统必须逐步初始化存储、异常、中断、缓存和外设;

 最后把控制权交给更高级的软件。

所以,SoC 启动不是“软件跑起来”这么简单,而是硬件体系结构、存储映射、异常机制、缓存系统和启动软件共同配合的结果。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐