
目录1. 简介2. 栈 - 代码运行的便签纸3. 寄存器一览”4. 通用寄存器4.1 R0 ~ R34.2 R4 ~ R114.3 R124.4 汇编示例5. 堆栈指针 R135.1 Banked5.2 栈指针 和 栈5.3 栈帧5.4 栈溢出与保护5.5 裸机与 RTOS 的栈使用差异5.6 栈操作示意图6. 链接寄存器 LR6.1 基本工作机制6.2 函数嵌套调用中的保护6.3 中断/异常发生时LR变身“特殊通行证”6.4 调试时用LR找Bug7. 程序计数器 PC7.1 基本特性与流水线效应7.2 PC的对齐要求与LSB规则7.3 PC不可直接修改8. 程序状态寄存器组 PSRs8.1 xPSR的组合与拆分8.2 APSR运算标志的晴雨表8.3 EPSR执行状态的监管者8.4 IPSR当前中断的身份牌9. 中断屏蔽寄存器组PRIMASK、FAULTMASK、BASEPRI9.1 功能概述9.2 BASEPRI 的典型应用场景10. 控制寄存器 CONTROL10.1 Handler 模式是什么10.2 位0 nPRIV特权级与用户级的身份切换10.3 位1 SPSEL双堆栈指针的选择开关10.4 复位后的默认状态与系统启动流程1. 简介本文主要讲解 ARM Cortex-M3 内核的寄存器系统主要包含三类寄存器通用寄存器R0-R15、程序状态寄存器组的三个子寄存器APSR、EPSR、IPSR的功能以及中断屏蔽寄存器组PRIMASK、FAULTMASK、BASEPRI和控制寄存器CONTROL。想读懂还是需要有点基础的建议阅读前要有 裸机开发 和 RTOS操作系统开发 经验。2. 栈 - 代码运行的便签纸在学习寄存器之前我们先要稍微了解一下 ----栈(stack)。在代码运作中会使用寄存器保存临时数据但是 Cortex‐M3 内核的寄存器又是有限的。当同时活跃的数据变多函数嵌套变深或者遇到中断时寄存器吃不下这么多数据该怎么办所以 CPU 需要临时把这些数据搬到内存里缓存起来到用的时候再搬回寄存器。这块专门用来临时存放寄存器数据的内存就是 ----栈 (Stack)。栈 是 后进先出(LIFO) 的结构完美符合函数调用和中断处理。举例A 调用 BB 再调用 CC 会先退出也就是最后调用的最先返回。3.寄存器一览”Cortex‐M3 处理器拥有R0~R15的寄存器组和若干特殊功能寄存器。R0~R7 是低组通用寄存器(Low Registers)R8~R12 是高组通用寄存器(High Registers)R13 是栈指针(SP)。 SP指向的是栈顶它有两个一个是主堆栈指针(MSPMain Stack Pointer)另一个是进程堆栈指针(PSPProcess Stack Pointer)。R14 是链接寄存器(LR)。函数A 调用 函数B 时LR会记下 “A的下一条指令地址”B执行完之后就能找到回来的路。R15 是程序计数器(PC)。始终指向下一个即将执行的指令的地址。特殊功能寄存器分别是程序状态寄存器组PSRs中断屏蔽寄存器组PRIMASK、FAULTMASK、BASEPRI控制寄存器CONTROL表中 xPSR 实际就是 PSRs 的一个“别名”也就是有两个名字。特殊功能寄存器有预定义的功能必须通过专用的指令 MSR/MRS 来访问而且它们也没有与之相关联的访问地址。MRSMove to Register From Special Register加载特殊功能寄存器的值到通用寄存器MSRMove to Special Register From Register存储通用寄存器的值到特殊功能寄存器4. 通用寄存器R0‐R12 都是 32 位通用寄存器用于数据操作。R0-R7与R8-R12的主要区别在于绝大多数 16 位的 Thumb 指令只能访问低寄存器。但是实际功能分组时不是按照低组或高组来区分的。4.1 R0 ~ R3R0~R3用于函数参数传入、返回值传出、临时值。参数传入调用函数时R0~R3 按顺序存放 前四个 整数/指针参数返回值传出函数返回时通过 R0(32位及以下) 或 R0R1(64位) 返回结果临时值函数内部的局部变量、中间计算结果也可暂存在 R0~R3 中当参数有很多寄存器不够用剩余的参数传递会使用栈空间。因此想让代码更高效在函数设计时要尽量控制参数不超过 4 个以利用寄存器传递而不是占用速度更慢的栈空间。当调用函数时R0~R3原本已经有数据了(上层函数使用)为避免这些数据被 被调函数 覆盖上层函数 需要先将R0~R3中 需要保留的值 保存在栈中(压栈)被调函数 返回后 再恢复寄存器的值。所以R0~R3属于调用者保存寄存器。4.2 R4 ~ R11R4~R11用于保存函数的局部变量和中间结果。在 函数A 调用 函数B如果 函数B 要使用R4~R11寄存器不管寄存器上面的值是否有效都必须先将寄存器原始值压栈保存直到到函数返回前再出栈恢复。由此可见R4~R11属于被调用者保存寄存器。R4~R11的压栈规则如上所以即使出现再多层函数嵌套调用调用前后寄存器的值也不会改变。(PS在很多编译器的标准调用约定中R11被用作栈帧指针(FPFrame Pointer也可以叫做栈底指针)它指向 当前函数栈的起始位置。配合 SP 可以方便地访问 栈 上的参数和局部变量并支持调试时的栈回溯。如果函数不使用 栈帧指针比如在高优化等级下R11 也可作为普通的通用寄存器使用)4.3 R12R12 不太重要实际可以不用关注。R12 在标准调用约定中也被称为IP (Intra-Procedure-call scratch register过程内调用暂存寄存器)主要用于动态链接、代码跳转以及函数入口/出口的临时存储。在函数调用过程中有时需要一小段“辅助代码”来完成跳转。例如当你要调用的函数不在当前程序里而在一个动态库.dll 或 .so中运行时才知道它的真实地址或者你的代码要从 Thumb 指令集切换到 ARM 指令集或者反过来中间需要几条额外的指令做状态切换。这段“辅助代码”很短它需要一个临时的地方存放某个地址或中间值。这个临时地方就是R12。4.4 汇编示例首先稍微了解一下表格的汇编指令示例等价执行结果MOVS/MOVr3, #0x04MOVS/MOVr4, r0r3 0x04r4 r0PUSH{r4-r5, lr}将 r4、r5、lr 压栈ADDS/ADDr0, r1ADDS/ADDr0, r1, r2ADDS/ADDr0, #5r0 r0 r1r0 r1 r2r0 r0 5POP{r4-r5, pc}将 r4、r5、pc 出栈(PSMOVS 和 MOV、ADDS 和 ADD基本格式是一样的表面执行效果也是一样的但是它们不是一个东西感兴趣可以去查查)如图结合 左边的C代码 和 右边的汇编代码只关注通用寄存器输入的参数 (1,2,3,4) 存入了R0~R3(参数传入)R4、R5用于存放中间结果所以在进入函数时将R4、R5压栈然后使用R4、R5作为中间值最终结果存放与R0(返回值传出)函数退出时又将R4、R5出栈5. 堆栈指针 R13SP 有两个一个是主堆栈指针(MSP,Main Stack Pointer)另一个是进程堆栈指针(PSPProcess Stack Pointer)。MSPCortex-M3 复位后默认使用的堆栈指针也是特权级下默认的堆栈。所有的异常处理例程包括中断服务函数、HardFault、系统服务调用等必须且只能使用 MSP。操作系统内核代码如任务调度器通常也使用 MSP。PSP专门为用户应用程序准备的堆栈指针。通常用于运行 RTOS 中的各个用户任务每个任务拥有自己独立的栈通过 PSP 来访问。所以裸机一般不会使用 PSP。MSP 和 PSP 实际指向不同的栈区所以切换栈指针就是切换当前使用的栈内存区域。5.1 Banked虽然 Cortex-M3 拥有两个堆栈指针但是它们是Banked。Banked在同一个寄存器名称R13/SP之下物理上存在两个独立的寄存器MSP和PSP。它们拥有各自独立的物理存储单元但软件层面统一通过SPR13来访问。硬件会根据当前CPU模式和CONTROL寄存器的状态自动决定将哪一个物理寄存器映射到SP这个名称上。Banked机制的核心是“上下文依赖的选择器”。内核通过CONTROL 寄存器和 当前的工作模式来决定把哪一个物理寄存器映射给SP这个名字。CONTROL 后面还会讲。5.2 栈指针 和 栈栈由一块连续的内存和栈顶指针SP组成。在 Cortex-M3 中栈是向下生长由高地址向低地址增长的压栈时 SP 的值减小出栈时 SP 的值增大。在 Cortex-M3 中栈操作主要通过两条指令完成PUSH将寄存器内容压入栈中。执行时SP先减小减去压入数据所占用的字节数然后将数据写入SP指向的新地址。POP从栈中弹出数据到寄存器。执行时先读取SP指向的地址内容然后SP增大加上弹出数据所占用的字节数。由于栈向下增长初始化时SP通常被设置为栈内存区域的最高地址 1即栈顶的初始位置。例如若栈区为0x2000 1000~0x2000 1FFF则初始SP应设为0x2000 2000。5.3 栈帧在Cortex-M3中“栈帧”是一个广义概念它指代保存在当前堆栈中的一组连续数据。但需要特别注意的是中断/异常产生的栈帧与函数嵌套调用产生的栈帧在形成机制和用途上有本质区别。中断栈帧当发生异常或中断时为了确保中断服务程序执行完毕后能精确还原被打断的现场Cortex-M3的硬件逻辑会自动执行压栈操作这一过程无需软件指令干预且具有原子性。硬件会自动将R0 ~ R3、R12、LR(R14)、PC(R15)、xPSR寄存器按固定顺序压入当前使用的堆栈(MSP 或 PSP)。函数调用栈帧当发生普通的函数嵌套调用例如funcA()调用funcB()时硬件不会自动压栈。所有栈帧的构建均由编译器生成的软件指令如PUSH、SUB SP完成。函数调用栈帧主要用于保存局部变量、被调用者需保存的寄存器、返回地址。中断栈帧是硬件为处理异步事件而强制创建的“快照”函数调用栈帧是编译器为管理同步执行流局部变量、函数嵌套而手动构建的数据结构。5.4 栈溢出与保护栈的大小是有限的若 函数嵌套过深或局部变量太多压栈操作导致栈指针低于栈的允许范围向低地址端生长过头就会发生栈溢出Stack Overflow。栈溢出可能导致破坏相邻内存区域的数据如全局变量、堆数据程序运行异常、HardFault难以调试的随机性故障5.5 裸机与 RTOS 的栈使用差异裸机全程仅使用 MSP初始化时由启动文件设置 SP 初始值。所有函数调用、中断都共用这一块主栈因此栈大小需覆盖“主循环最大嵌套深度 最大中断嵌套深度”的场景。RTOS 程序MSP 仅用于异常处理和调度器核心代码PSP 用于各用户任务。每个任务拥有独立的栈空间任务栈大小需根据该任务的调用深度和局部变量量单独配置。这样做的好处是任务间栈隔离一个任务的栈溢出不会立即污染其他任务但仍可能破坏全局数据。5.6 栈操作示意图假设栈区从0x2000 1000到0x2000 1FFF按字对齐每次压栈4字节初始 SP 指向栈顶边界0x20002000即最高地址 1。高地址 0x20002000 ┌─────────────┐ ← 初始 SP栈顶边界不存放数据 │ │ │ (空闲) │ │ │ │ │ │ │ │ │ 低地址 0x20001000 └─────────────┘ ← 栈底警戒区连续压入 R0、R1、R2每次4字节高地址 0x20002000 ┌─────────────┐ ← 初始 SP栈顶边界不存放数据 0x20001FFF ─ ─ ─ ─ ─ ─ ─┤ ← 栈区最高有效地址边界线 │ R0 │ ← 最先压入地址最高 0x20001FFC ├─────────────┤ ← 第1次PUSH后 SPR0 │ R1 │ 0x20001FF8 ├─────────────┤ ← 第2次PUSH后 SPR1 │ R2 │ ← 最后压入地址最低当前栈顶 0x20001FF4 ├─────────────┤ ← 第3次PUSH后 SPR2 │ │ │ .... │ 低地址 0x20001000 └─────────────┘ ← 栈底向下方向已被占用继续PUSH会溢出第1次 PUSH {R0}SP SP - 4 →0x20001FFC将 R0 写入该地址。第2次 PUSH {R1}SP SP - 4 →0x20001FF8将 R1 写入该地址。第3次 PUSH {R2}SP SP - 4 →0x20001FF4将 R2 写入该地址。可见先压入的数据在高地址后压入的数据在低地址SP 始终指向当前栈顶即最后一个压入的数据。弹出到 R2、R1、R0后进先出高地址 0x20002000 ┌─────────────┐ ← 第3次POP后 SP回到初始位置 0x20001FFF ─ ─ ─ ─ ─ ─ ─┤ │ R0 │ ← 第3次POP读取R0SP 4 0x20001FFC ├─────────────┤ ← 第2次POP后 SP指向R0 │ R1 │ ← 第2次POP读取R1SP 4 0x20001FF8 ├─────────────┤ ← 第1次POP后 SP指向R1 │ R2 │ ← 第1次POP读取R2SP 4 0x20001FF4 ├─────────────┤ ← 第1次POP前 SP指向R2起始状态 │ │ │ .... │ 低地址 0x20001000 └─────────────┘ ← 栈底第1次 POP {R2}从当前 SP (0x20001FF4) 读取数据到 R2然后 SP SP 4 →0x20001FF8。第2次 POP {R1}从当前 SP (0x20001FF8) 读取数据到 R1然后 SP SP 4 →0x20001FFC。第3次 POP {R0}从当前 SP (0x20001FFC) 读取数据到 R0然后 SP SP 4 →0x20002000。可以看出栈是由高地址向低地址增长压栈时 SP 的值减小出栈时 SP 的值增大。6. 链接寄存器 LR在Cortex-M3的寄存器生态中R14 扮演着一个看似简单却至关重要的角色 —— 它被专门设计为链接寄存器Link Register用于保存中断函数处理完成或函数调用完成后的返回地址。如果没有LR处理器在跳转到子程序后就“迷失”了归途。6.1 基本工作机制当你在C代码里调用一个函数时CPU实际会执行一条特殊的汇编指令叫BL或BLX。这条指令会自动做两件事1. 将当前程序计数器PC的值加上一个偏移量计算出下一条即将执行的指令地址。2. 将该地址自动存入LR。随后处理器跳转到目标子程序执行。当子程序执行完毕时只需执行一条BX LR指令处理器就会将LR中保存的地址重新加载到PC从而实现“从哪里来回哪里去”的返回。6.2 函数嵌套调用中的保护LR 虽然方便但是它只有一个。这就引发了一个问题函数A 调用了 函数B调用B之前LR里存的是“A执行完后回主程序的地址”。但当你调用B时CPU会自动把“B执行完后回A的地址”覆盖进LR这样A的返回地址就丢了。为了解决这个问题在调用B之前编译器会帮我们把LR里原来的地址压栈等B执行完了再出栈。再回看 4.4 章节的示例可以看到进入函数后会将 LR 与其他寄存器一起压栈防止内部再调用别的函数覆盖原本的 LR。6.3 中断/异常发生时LR变身“特殊通行证”这是LR最“分裂”的地方 ——在正常函数里它是地址但在中断服务程序里它变成了一个“魔数”神秘数字。因为当外部中断或硬件异常发生时CPU会1.硬件自动把当前的运行状态包括返回地址压入堆栈保存。2. 然后把一个特殊的魔数比如0xFFFFFFF9写入LR而不是写普通的返回地址。这个魔数其实是告诉CPU“我不是地址我是异常返回指令”。当你的中断处理程序执行完执行BX LR时CPU 看到 LR 里存的不是普通地址而是这类魔数就会触发硬件自动恢复机制把之前压入堆栈的寄存器全部弹出来回到中断前的状态。几个常见的魔数含义0xFFFFFFF1退出中断后回到“线程模式”用主堆栈指针MSP0xFFFFFFF9退出中断后回到“线程模式”用进程堆栈指针PSP—— RTOS任务切换常用这个0xFFFFFFFD退出中断后回到“Handler模式”用于中断嵌套6.4 调试时用LR找Bug当程序跑飞进HardFault了你可以在调试器里查看LR的值看它是从哪个函数跳过来的配合堆栈里的信息就能倒推出“死机前最后执行了哪一步”。7. 程序计数器 PC在Cortex-M3的所有寄存器中R15 —— 程序计数器Program Counter是最特殊的一个。它既是CPU执行流程的导航仪也是整个内核运行状态最直观的窗口。PC总是指向当前正在执行的指令地址其值的变化直接决定了程序的走向。7.1 基本特性与流水线效应Cortex-M3内核采用三级流水线架构取指Fetch→ 译码Decode→ 执行Execute这三个阶段并行工作使得指令吞吐率显著提升时刻取指阶段译码阶段执行阶段第1个时钟取指令N空闲空闲第2个时钟取指令N1译码指令N空闲第3个时钟取指令N2译码指令N1执行指令N当CPU在执行某条指令时取指阶段已经提前取到了 当前指令地址 8 处的指令。所以这种流水线设计给PC带来一个特性在Cortex-M3中当读取PC的值时得到的并非当前正在执行的指令地址而是当前指令地址 4。为什么是 4 而不是 8在Cortex-M3中读取PC时硬件返回的并不是真正的取指地址而是当前执行地址 4ARMv7-M架构规定。这是一个架构规定的返回值而不是物理取指地址的直接映射。(PSPC的读取值 ≠ 实际的取指地址。流水线中的取指地址可能更靠前这取决于流水线状态和指令对齐但架构层面规定PC读取返回 4为了软件提供一致的相对偏移基准。)7.2 PC的对齐要求与LSB规则Cortex-M3 要求 PC 值必须半字对齐即地址最低位LSB在物理上始终为0地址为偶数以保证指令按 16 位或 32 位边界正确取指。但在 Thumb 指令集体系中函数地址和返回地址LR的最低位被用作 “指令集状态标记”。由于 Cortex-M3 仅支持 Thumb-2 指令集该标记必须固定为1表示目标为 Thumb 代码。因此编译器在生成函数地址时会将真实入口地址的 LSB强制置 1。例如一个真实地址为0x080001CC的函数在代码获取地址时会显示为0x080001CD。当执行BX或BLX跳转时处理器会自动忽略该 LSB将其清 0后再送入 PC从而正确访问对齐的指令地址。7.3 PC不可直接修改PC作为R15在寄存器编号上与其他通用寄存器连续排列因此理论上可以通过MOV PC, Rn或LDR PC, [addr]直接修改PC的值实现代码跳转。但是在C语言层面永远不要用指针直接修改 PC做这种非标操作很容易造成问题。实际上即使在汇编代码中也不应该直接赋值 PC。8. 程序状态寄存器组 PSRsPSRs 寄存器直接反映了处理器的当前运算状态、执行模式以及中断嵌套层级是理解 Cortex-M3 内核运行机理的核心窗口。在Cortex-M3的寄存器体系中程序状态寄存器组Program Status RegistersPSRs是唯一 一组名不副实的寄存器。它看似一个独立的32位寄存器实则是三个功能各异的寄存器的逻辑组合。8.1 xPSR的组合与拆分PSR是一个32位的 虚拟寄存器实际由 三个物理寄存器 通过逻辑组合构成子寄存器全称位段位置职责APSR应用程序状态寄存器位[31:27]存储ALU运算产生的条件标志EPSR执行状态寄存器位[26:25] 位[24] 位[15:10]控制指令执行状态与中断可切状态IPSR中断状态寄存器位[8:0]指示当前正在服务的异常编号这三个子寄存器在物理上是独立的但在逻辑上被映射到同一个32位地址空间。开发者可以使用以下方式访问整体访问使用MRS Rn, xPSR读取完整的32位xPSR或MSR xPSR, Rn整体写入通常只有特权级代码才能写入单独访问使用MRS Rn, APSR、MRS Rn, EPSR或MRS Rn, IPSR分别读取各个子寄存器。8.2 APSR运算标志的晴雨表APSR应用程序状态寄存器存放着算术逻辑单元ALU执行运算后产生的条件标志位是条件分支指令如BEQ、BNE、BGT等的判断依据。位标志名称置位条件31N负标志运算结果为负数时置1最高位为130Z零标志运算结果为0时置129C进位/借位标志无符号加法溢出时置1无符号减法产生借位时清028V溢出标志有符号运算结果溢出时置127Q饱和标志发生饱和运算如SSAT/USAT指令时置1需要手动清零8.3 EPSR执行状态的监管者EPSR执行状态寄存器负责管理处理器在执行指令时的内部状态。EPSR不是给程序员看的是给硬件自己在中断“插队”时看的备忘录。EPSR不可写。T位告诉硬件 “我现在是Thumb模式” —— Cortex-M3永远为1不用管。IT位告诉硬件 “我正处在一个条件执行块里做到第几条了”。ICI位告诉硬件 “我正搬一车寄存器搬到第几个了”。8.4 IPSR当前中断的身份牌IPSR中断状态寄存器是一个9位寄存器位[8:0]用于标识当前正在服务的异常或中断编号。它的值直接反映了处理器的异常嵌套层级。IPSR是只读的任何写入操作均无效。IPSR值含义0线程模式Thread Mode未处理任何异常1复位Reset2不可屏蔽中断NMI3硬件错误HardFault4-10其他系统异常MemManage、BusFault、UsageFault、SVCall、DebugMonitor、PendSV、SysTick16-255外部中断IRQ0-IRQ239具体数量因芯片型号而异我们可以在共享的中断处理函数中通过读取 IPSR 判断当前是哪个中断触发了该函数适用于多个中断共用一个入口的场景。9. 中断屏蔽寄存器组PRIMASK、FAULTMASK、BASEPRI中断屏蔽寄存器常用于控制异常或中断的使能和除能。每个异常都有优先级数值越小优先级越高这些特殊寄存器可基于优先级屏蔽异常只有在特权访问模式才可以对它们操作。在用户模式下对中断屏蔽寄存器写操作会被忽略读操作会返回0。9.1 功能概述PEIMASK和FAULTMASK都是简单粗暴的中断屏蔽工具BASEPRI寄存器相较来说更加细腻。9.2 BASEPRI 的典型应用场景RTOS临界区RTOS的任务切换通常由PendSV异常触发。PendSV是一个可配置优先级的中断其优先级在系统初始化时被设置为所有可配置中断中最低的级别。RTOS 进入临界区时将BASEPRI寄存器设置为特定阈值该阈值一般会比 PendSV 大屏蔽 PendSV 中断就无法进行任务调度。中断服务函数中的临界段在中断处理过程中如果需要执行一段不被低优先级中断打断的代码但又希望允许更高优先级的中断如紧急错误中断嵌套BASEPRI是理想选择。RTOS 临界区的思路也是这样。驱动层的资源保护当多个中断共享同一个硬件资源如UART发送缓冲区时设置BASEPRI屏蔽那些也使用该资源的中断但保留更紧急的系统中断10. 控制寄存器 CONTROL在Cortex-M3的寄存器家族中CONTROL寄存器是唯一 一个不直接参与数据运算、不保存状态标志、也不控制中断屏蔽却深刻影响着整个系统运行模式的寄存器。CONTROL 寄存器只有两个有效位却决定了处理器在特权级与用户级之间的身份切换以及主堆栈与进程堆栈之间的选择 —— 这两项决策是操作系统实现 任务隔离 和 系统安全 的基础。10.1 Handler 模式是什么Handler模式已经出现过很多次了一直没讲解现在放在这里讲。Handler模式在响应任何异常或中断时自动进入。用专业的话来说就是处理器正在执行异常处理程序(包括所有中断服务函数和系统异常) 时的运行状态。与Handler模式相对的是线程模式(Thread Mode)即处理器在执行普通应用程序代码时的状态。10.2 位0 nPRIV特权级与用户级的身份切换Cortex-M3支持特权级(Privileged Level) 和 用户级(User Level)两种执行模式的特权级别划分特权级可以访问所有寄存器包括CONTROL、中断屏蔽寄存器、所有系统内存区域以及执行所有指令包括MRS/MSR对特殊寄存器的操作。这是系统复位后的默认状态。用户级不能修改CONTROL寄存器不能写PRIMASK、FAULTMASK、BASEPRI等中断屏蔽寄存器不能写xPSR中的APSR部分只读不能访问系统控制空间SCS中的某些关键寄存器对内存的访问受MPU内存保护单元的约束如果启用nPRIV位的实际作用nPRIV 0时线程模式享有全部权限nPRIV 1时线程模式被降级为用户级权限受限。Handler模式中断/异常处理始终运行在特权级不受nPRIV位影响。10.3 位1 SPSEL双堆栈指针的选择开关Cortex-M3拥有两个物理堆栈指针(MSP 和 PSP)但 R13 在任意时刻只能指向其中之一。SPSEL位决定了当前SP寄存器对应的是哪一个。将内核堆栈MSP与 应用程序堆栈PSP分离是一种安全隔离的硬件设计。当用户程序发生堆栈溢出时破坏的是PSP指向的用户堆栈区域而MSP指向的内核堆栈不受影响系统仍有机会通过 HardFault 或 MemManage 异常进行恢复或上报而不至于完全崩溃。10.4 复位后的默认状态与系统启动流程Cortex-M3复位后CONTROL寄存器的默认值为0x00000000nPRIV 0线程模式运行在特权级。SPSEL 0当前使用MSP作为堆栈指针。这意味着在系统启动的最初阶段所有代码包括启动代码、系统初始化、main函数都运行在特权级并使用MSP。直到操作系统初始化完成创建了第一个用户任务并准备好 PSP 堆栈后才会通过修改CONTROL 切换到用户级 PSP的组合。在RTOS的任务切换中CONTROL寄存器的管理是上下文切换的核心环节。