(本系列从硬件 → 架构 → 中断 → 上下文 → OS → 车规 OS → 编译器 → 上电流程 → 全栈图 升级推进)

目录

10.1 MCU 上电到底发生了什么?

10.2 MCU 的启动顺序(抽象结构)

10.3 第一步:BootROM(MCU 厂商固化代码)

10.4 第二步:读取复位向量(Reset Vector)

10.5 第三步:执行 Startup(启动文件)

10.6 第四步:C 运行时初始化(crt0)

10.7 第五步:调用 main()

10.8 第六步:操作系统开始接管(RTOS 或 AUTOSAR OS)

10.9 第七步:中断向量与异常表的实际工作方式

10.10 从上电到 OS:完整流程图(抽象)

10.11 为什么理解启动流程至关重要?


10.1 MCU 上电到底发生了什么?

当 MCU 上电的那一刻,用户的 C 代码、main()、RTOS、AUTOSAR OS 都还没有出现。
最先运行的永远是芯片内部固化的 BootROM

BootROM 是 MCU 厂商写死在芯片里的“最底层初始化逻辑”,主要负责:

电源稳定检测  
时钟初始化  
安全检查(例如 ECC / Boot 校验)  
读取复位向量  
跳转到用户程序入口

上电流程高度架构化,跟操作系统完全无关。


10.2 MCU 的启动顺序(抽象结构)

无论 Cortex‑M、RH850、TriCore,整体流程都类似:

上电
  ↓
BootROM(固化代码)
  ↓
设置栈指针(SP)
  ↓
读取复位向量(Reset Vector)
  ↓
跳转到 Startup(启动文件)
  ↓
C 运行时初始化(crt0)
  ↓
初始化 .data / .bss
  ↓
调用 main()
  ↓
如果是 OS 系统 → 启动 OS
  ↓
进入任务调度器

下面逐步拆开。


10.3 第一步:BootROM(MCU 厂商固化代码)

BootROM 是芯片内部只读区域,由 MCU 厂商实现,用于:

  • 早期硬件上电检查
  • 时钟源设置
  • 寄存器初始值配置
  • 安全机制(ECC、Flash 校验)
  • 选择 Boot 模式(Normal Boot / Flash Boot / Serial Boot)
  • 跳转到用户程序的 Reset Handler

用户看不到 BootROM,也无法修改它。


10.4 第二步:读取复位向量(Reset Vector)

当芯片完成 BootROM 的基本初始化后,会读取一个固定地址作为初始入口点

举例(假设地址,不对应任何实际 MCU):

0x0000_0000  → 初始栈指针(Initial SP)
0x0000_0004  → Reset_Handler 地址

CPU 做两件事:

SP ← *(0x0000_0000)
PC ← *(0x0000_0004)

程序计数器(PC)加载 Reset_Handler 后开始执行你的“启动文件”。

Cortex‑M、RH850、TriCore 均类似,只是向量表布局不同。


10.5 第三步:执行 Startup(启动文件)

Startup 通常是 .s(汇编)文件,是整个系统的“真正入口”。

Startup 做的事情包括:

设置堆栈(SP)
初始化 FPU(若有)
中断向量表搬运(若需要)
调用 C 运行时初始化函数(__init / __crt0)

典型启动文件结构(抽象示例):


Reset_Handler:
    ldr   sp, =_stack_top
    bl    SystemInit
    bl    __crt0_init
    b     main

Startup 是整个系统承上启下的关键位置。


10.6 第四步:C 运行时初始化(crt0)

crt0 是由编译器工具链生成的初始化代码,包括:

  • 初始化 .data 段
    • 把 Flash 中的初值复制到 RAM
  • 清零 .bss 段
    • 全部未初始化的全局变量归零
  • 设置 RAM 中的运行环境
  • 调用构造函数(C++)
  • 准备 main() 所需的语言环境

如果没有 crt0:

  • 全局变量不会有默认值
  • 静态变量不会清零
  • main() 会在混乱的内存状态下运行

crt0 保证 main() 执行时的世界是可预期的。


10.7 第五步:调用 main()

上述步骤完成后,系统终于运行到用户的 C 代码:

int main(void)
{
    // 用户逻辑
}

如果是裸机:

  • 在 main() 内部通常是死循环或驱动初始化

如果是 RTOS:

  • 通常在 main() 中创建任务、启动调度:
main()
{
    xTaskCreate(...);
    vTaskStartScheduler();  // RTOS 接管 CPU
}

如果是 AUTOSAR OS:

  • main() 通常调用 StartOS(AppMode),由 OS 接管控制
main()
{
    StartOS(OSDEFAULTAPPMODE);
}

10.8 第六步:操作系统开始接管(RTOS 或 AUTOSAR OS)

在 OS 接管后:

  • SysTick 或 OSTM 启动节拍
  • 中断向量表配置完毕
  • 上下文切换机制启动
  • 第一个任务进入 Running 状态

这时,系统已经从“引导流程”进入“操作系统运行期”。

FreeRTOS:

vTaskStartScheduler()
   ↓
创建 Idle Task / Timer Task
   ↓
启动 SysTick
   ↓
调度第一个任务

AUTOSAR OS:

StartOS()
   ↓
OS 初始化 + Counter 初始化
   ↓
ScheduleTable / Alarm 启动
   ↓
Ready 最高优先级任务运行

10.9 第七步:中断向量与异常表的实际工作方式

中断向量表的核心作用:

  • 提供所有异常/中断入口地址
  • 供 CPU 在异常发生时跳转
  • OS 在启动时必须重定向/初始化向量表
  • 每个中断最终进入 ISR(ISR1 或 ISR2)

向量表示例(假设地址):

0x0000_0000: 初始 SP
0x0000_0004: Reset_Handler
0x0000_0008: NMI_Handler
0x0000_000C: HardFault_Handler
...
0x0000_0050: UART0_RX_Handler
0x0000_0054: TIMER0_Handler

OS(尤其是 AUTOSAR OS)会在中断入口加入:

保存上下文(或由硬件保存)
调用 OS 中断服务子系统
触发事件或任务激活
调度器判断是否切换任务

10.10 从上电到 OS:完整流程图(抽象)

上电
 ↓
BootROM(内部固化代码)
 ↓
加载向量表(SP & Reset_Handler)
 ↓
进入 Startup.s(启动文件)
 ↓
C 运行时初始化(crt0)
    - 初始化 .data
    - 清零 .bss
 ↓
main()
 ↓
StartOS() 或 vTaskStartScheduler()
 ↓
启动调度器(SysTick/TAUJ/OSTM)
 ↓
进入任务调度 → 运行应用

这就是 MCU 世界完整的引导流程。


10.11 为什么理解启动流程至关重要?

因为它关系到你之后的所有内容:

  • 为什么 main() 之前必须跑 crt0?
  • interrupt vector table 为什么要放在固定地址?
  • 操作系统为什么要接管中断?
  • RTOS 如何开始第一次任务切换?
  • AUTOSAR OS 为什么要在 StartOS() 内做初始化?
  • 链接脚本为什么必须精确分布 MEMORY?
  • 为什么 .data 要搬到 RAM?

启动流程串起:

硬件 → BootROM → 启动文件 → C init → OS → 应用

是整个系统的生命线。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐