SoC 是怎么“醒来”的?
所谓 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 启动不是“软件跑起来”这么简单,而是硬件体系结构、存储映射、异常机制、缓存系统和启动软件共同配合的结果。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)