Cortex-M启动流程与内存布局(从复位到main)

📅 2026-09-04
#来源/立芯星球 #类型/专题提炼 #技术/ARM体系结构 #技术/内存布局 #技术/启动流程

Cortex-M 启动流程与内存布局(从复位到 main)

上电到 main 之间发生了什么,是理解一切"玄学问题"(启动失败、栈溢出、代码搬移、优化错乱)的基础。本文按时间线串起星球的系列答疑。

一、上电第一拍:硬件取栈顶,BootROM 接管

复位后硬件自动把向量表第 [0] 项(initial_stack,初始栈顶地址)加载进 MSP——不需要任何软件参与;随后从向量表 [1] 取复位向量开始执行。取栈顶和第一条指令是在 BootROM 中完成的:BootROM 依据 BOOT0/BOOT1 引脚配置,去特定位置加载栈顶并执行复位语句 | Jack,老师,在第一步过程中,取栈顶指针到msp中,是在谁的指导下完成

理解取指路径还要知道总线分工:I-Code Bus 专门取指令(从 Flash 或映射到 0 地址的 SRAM,送译码后进 ALU,运算更新 LR/PC),D-Code Bus 专门取数据(如 Flash 中的 const 常量读入通用寄存器,但不包括 PC/LR)| Jack,I-Code-Bus, D-Code-Bus有什么区别?它们在如何取指令和数据

二、栈的方向:满减栈

Cortex-M 用满减栈(Full Descending):"满"指 SP 始终指向有效数据的栈地址——若不先移动指针就写入,会破坏栈中已有数据;"减"指 SP 向低地址方向移动。对应的"空栈"则 SP 指向空位,可以先写后移 | Jack,老师,满减栈什么意思? 满意。栈到底预留多大、在哪设置,由链接器的堆栈配置决定 | Jack,Linker Setting中;栈水位的粗测手段之一是直接打印栈指针 | Jack,栈水位的直接打印栈指针来检测

三、__main:C 运行时在 main 之前做的事

很多人以为栈是在 __main 里初始化的——其实到能执行 __main 时调用栈早就可用了(否则函数调用、保存返回地址都无从谈起)。__main(ARMCC)或 __libc_init_array(GCC/Newlib)真正做的是:把 .data 从 Flash 复制到 RAM、清零 .bss、C++ 工程调用全局构造,最后跳进用户的 main()。栈只是链接脚本静态预留的一块内存;想做 0xA5A5A5A5 填充测水位是你自己在启动文件里做的事。堆则不同:边界符号(_end 等)早已确定,但分配器要到第一次 malloc 才建立索引结构 | Jack,关于堆栈和__main()在什么时候初始化

四、异常返回与 Handler/Thread 切换

复位后 CPU 先处于 handler 模式,LR 被装入 0xFFFFFFF9 这类"伪返回值"——它不是内存地址,而是异常返回行为的编码(0xFFFFFFF9 表示返回 thread 模式并使用 MSP)。启动代码 LDR R0, =__main 后 BX R0 跳转,执行完毕靠 LR 中的编码自动切回 thread 模式运行 main | Jack,在复位异常后,CPU进入handle模式,LR被赋值0xFFFFFFF9。同理,SVC 这类软件触发的异常需要软件用 BX R14 显式返回,而大多数中断由硬件自动完成异常返回 | Jack,老师,这里的BX R14是指啥,通过异常返回值退出异常。写 PC 分支时数值必须保持 LSB=1(Thumb 状态),写成 0 会被当作企图切换 ARM 模式而触发 fault | Jack,老师,我不太能理解这里的含义

五、段与地址的确定时机

六、加速启动的思路

快速启动有两条路:其一,把数据直接编进 hex 由 Flash 加载并借 cache 提速;其二,对 RAM 中的函数指针挂载点,用 DMA 在系统启动时批量填充 RAM,尽快达到系统就绪状态 | Jack,飞秒启动起始它分两种方式: 1

小结

启动链条可以浓缩为:硬件装 MSP → BootROM 定入口 → __main 搬段清段 → 异常返回切 thread → main。凡是"改一行代码就死机/好了"的现象,都值得回到这条链上检查:地址是否合法(预取指)、栈是否溢出、段是否被搬到位。

来源笔记