深入解析ARM Cortex-M3寄存器系统
2026/7/15 6:27:47 网站建设 项目流程

目录

1. 简介

2. 栈 - 代码运行的"便签纸"

3. 寄存器一览”

4. 通用寄存器

4.1 R0 ~ R3

4.2 R4 ~ R11

4.3 R12

4.4 汇编示例

5. 堆栈指针 R13

5.1 Banked

5.2 栈指针 和 栈

5.3 栈帧

5.4 栈溢出与保护

5.5 裸机与 RTOS 的栈使用差异

5.6 栈操作示意图

6. 链接寄存器 LR

6.1 基本工作机制

6.2 函数嵌套调用中的保护

6.3 中断/异常发生时,LR变身“特殊通行证”

6.4 调试时用LR找Bug

7. 程序计数器 PC

7.1 基本特性与流水线效应

7.2 PC的对齐要求与LSB规则

7.3 PC不可直接修改

8. 程序状态寄存器组 PSRs

8.1 xPSR的组合与拆分

8.2 APSR:运算标志的"晴雨表"

8.3 EPSR:执行状态的"监管者"

8.4 IPSR:当前中断的"身份牌"

9. 中断屏蔽寄存器组(PRIMASK、FAULTMASK、BASEPRI)

9.1 功能概述

9.2 BASEPRI 的典型应用场景:

10. 控制寄存器 CONTROL

10.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 调用 B,B 再调用 C,C 会先退出,也就是最后调用的最先返回。

3.寄存器一览”

Cortex‐M3 处理器拥有R0~R15的寄存器组,和若干"特殊功能寄存器"

R0~R7 是低组通用寄存器(Low Registers),R8~R12 是高组通用寄存器(High Registers)

R13 是栈指针(SP)。 SP指向的是栈顶,它有两个,一个是主堆栈指针(MSP,Main Stack Pointer)另一个是进程堆栈指针(PSP,Process Stack Pointer)

R14 是链接寄存器(LR)。"函数A" 调用 "函数B" 时,LR会记下 “A的下一条指令地址”,B执行完之后,就能找到回来的路。

R15 是程序计数器(PC)。始终指向下一个即将执行的指令的地址。

"特殊功能寄存器"分别是:

  • 程序状态寄存器组(PSRs)
  • 中断屏蔽寄存器组(PRIMASK、FAULTMASK、BASEPRI)
  • 控制寄存器(CONTROL)

表中 xPSR 实际就是 PSRs 的一个“别名”,也就是有两个名字。

特殊功能寄存器有预定义的功能,必须通过专用的指令 MSR/MRS 来访问,而且它们也没有与之相关联的访问地址。

MRS(Move to Register From Special Register)加载特殊功能寄存器的值到通用寄存器
MSR(Move to Special Register From Register)存储通用寄存器的值到特殊功能寄存器

4. 通用寄存器

R0‐R12 都是 32 位通用寄存器,用于数据操作R0-R7R8-R12的主要区别在于:绝大多数 16 位的 Thumb 指令只能访问低寄存器。

但是实际功能分组时,不是按照低组高组来区分的。

4.1 R0 ~ R3

R0~R3用于函数参数传入、返回值传出、临时值

参数传入调用函数时,R0~R3 按顺序存放 前四个 整数/指针参数
返回值传出函数返回时,通过 R0(32位及以下) 或 R0+R1(64位) 返回结果
临时值函数内部的局部变量、中间计算结果也可暂存在 R0~R3 中

当参数有很多,寄存器不够用,剩余的参数传递会使用栈空间。因此想让代码更高效,在函数设计时要尽量控制参数不超过 4 个,以利用寄存器传递,而不是占用速度更慢的栈空间。

当调用函数时,R0~R3原本已经有数据了(上层函数使用),为避免这些数据被 "被调函数" 覆盖,"上层函数" 需要先将R0~R3中 需要保留的值 保存在中(压栈),"被调函数" 返回后 再恢复寄存器的值。所以R0~R3属于"调用者保存寄存器"

4.2 R4 ~ R11

R4~R11用于保存函数的局部变量中间结果

在 "函数A" 调用 "函数B",如果 "函数B" 要使用R4~R11寄存器,不管寄存器上面的值是否有效,都必须先将寄存器原始值压栈保存,直到到函数返回前出栈恢复。由此可见R4~R11属于"被调用者保存寄存器"

R4~R11的压栈规则如上,所以即使出现再多层函数嵌套调用,调用前后寄存器的值也不会改变。

(PS:在很多编译器的标准调用约定中,R11被用作栈帧指针(FP,Frame Pointer,也可以叫做栈底指针),它指向 当前函数栈的起始位置配合 SP 可以方便地访问 栈 上的参数和局部变量,并支持调试时的栈回溯。如果函数不使用 栈帧指针,比如在高优化等级下,R11 也可作为普通的通用寄存器使用)

4.3 R12

R12 不太重要,实际可以不用关注。

R12 在标准调用约定中也被称为IP (Intra-Procedure-call scratch register,过程内调用暂存寄存器),主要用于动态链接代码跳转以及函数入口/出口的临时存储

在函数调用过程中,有时需要一小段“辅助代码”来完成跳转。例如:当你要调用的函数不在当前程序里,而在一个动态库(.dll 或 .so)中,运行时才知道它的真实地址;或者你的代码要从 Thumb 指令集切换到 ARM 指令集(或者反过来),中间需要几条额外的指令做状态切换。这段“辅助代码”很短,它需要一个临时的地方存放某个地址或中间值。这个临时地方就是R12。

4.4 汇编示例

首先稍微了解一下表格的汇编指令:

示例等价执行结果

MOVS/MOVr3, #0x04

MOVS/MOVr4, r0

r3 = 0x04

r4 = r0

PUSH{r4-r5, lr}将 r4、r5、lr 压栈

ADDS/ADDr0, r1

ADDS/ADDr0, r1, r2

ADDS/ADDr0, #5

r0 = r0 + r1

r0 = r1 + r2

r0 = r0 + 5

POP{r4-r5, pc}将 r4、r5、pc 出栈

(PSMOVS 和 MOVADDS 和 ADD,基本格式是一样的,表面执行效果也是一样的,但是它们不是一个东西,感兴趣可以去查查)

如图,结合 左边的C代码 和 右边的汇编代码,只关注通用寄存器:

输入的参数 (1,2,3,4) 存入了R0~R3(参数传入)
R4、R5用于存放中间结果,所以在进入函数时将R4、R5压栈,然后使用R4、R5作为中间值
最终结果存放与R0(返回值传出)
函数退出时又将R4、R5出栈

5. 堆栈指针 R13

SP 有两个,一个是主堆栈指针(MSP,Main Stack Pointer)另一个是进程堆栈指针(PSP,Process Stack Pointer)

MSP

Cortex-M3 复位后默认使用的堆栈指针,也是特权级下默认的堆栈。

所有的异常处理例程(包括中断服务函数、HardFault、系统服务调用等)必须且只能使用 MSP。操作系统内核代码(如任务调度器)通常也使用 MSP。

PSP

专门为用户应用程序准备的堆栈指针。

通常用于运行 RTOS 中的各个用户任务,每个任务拥有自己独立的栈,通过 PSP 来访问。

所以裸机一般不会使用 PSP

MSP 和 PSP 实际指向不同的栈区,所以切换栈指针就是切换当前使用的栈内存区域。

5.1 Banked

虽然 Cortex-M3 拥有两个堆栈指针,但是它们是"Banked"

Banked

同一个寄存器名称(R13/SP)之下,物理上存在两个独立的寄存器(MSP和PSP)。

它们拥有各自独立的物理存储单元,但软件层面统一通过SP(R13)来访问。硬件会根据当前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()时,硬件不会自动压栈。所有栈帧的构建均由编译器生成的软件指令(如PUSHSUB SP)完成。

函数调用栈帧主要用于保存:局部变量被调用者需保存的寄存器返回地址。

  • 中断栈帧是硬件为处理异步事件而强制创建的“快照”;

  • 函数调用栈帧是编译器为管理同步执行流(局部变量、函数嵌套)而手动构建的数据结构。

5.4 栈溢出与保护

栈的大小是有限的,若 函数嵌套过深或局部变量太多,压栈操作导致栈指针低于栈的允许范围(向低地址端生长过头),就会发生栈溢出(Stack Overflow)

栈溢出可能导致:

  • 破坏相邻内存区域的数据(如全局变量、堆数据)

  • 程序运行异常、HardFault

  • 难以调试的随机性故障

5.5 裸机与 RTOS 的栈使用差异

裸机全程仅使用 MSP,初始化时由启动文件设置 SP 初始值。所有函数调用、中断都共用这一块主栈,因此栈大小需覆盖“主循环最大嵌套深度 + 最大中断嵌套深度”的场景。
RTOS 程序

MSP 仅用于异常处理和调度器核心代码,PSP 用于各用户任务。每个任务拥有独立的栈空间,任务栈大小需根据该任务的调用深度和局部变量量单独配置。这样做的好处是任务间栈隔离,一个任务的栈溢出不会立即污染其他任务(但仍可能破坏全局数据)。

5.6 栈操作示意图

假设栈区从0x2000 10000x2000 1FFF(按字对齐,每次压栈4字节),初始 SP 指向栈顶边界0x20002000(即最高地址 + 1)。

高地址 0x20002000 ┌─────────────┐ ← 初始 SP(栈顶边界,不存放数据) │ │ │ (空闲) │ │ │ │ │ │ │ │ │ 低地址 0x20001000 └─────────────┘ ← 栈底(警戒区)

连续压入 R0、R1、R2(每次4字节):

高地址 0x20002000 ┌─────────────┐ ← 初始 SP(栈顶边界,不存放数据) 0x20001FFF ─ ─ ─ ─ ─ ─ ─┤ ← 栈区最高有效地址(边界线) │ R0 │ ← 最先压入,地址最高 0x20001FFC ├─────────────┤ ← 第1次PUSH后 SP(R0) │ R1 │ 0x20001FF8 ├─────────────┤ ← 第2次PUSH后 SP(R1) │ R2 │ ← 最后压入,地址最低(当前栈顶) 0x20001FF4 ├─────────────┤ ← 第3次PUSH后 SP(R2) │ │ │ .... │ 低地址 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:读取R0,SP += 4 0x20001FFC ├─────────────┤ ← 第2次POP后 SP(指向R0) │ R1 │ ← 第2次POP:读取R1,SP += 4 0x20001FF8 ├─────────────┤ ← 第1次POP后 SP(指向R1) │ R2 │ ← 第1次POP:读取R2,SP += 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退出中断后,回到“线程模式”,用主堆栈指针(MSP)
0xFFFFFFF9退出中断后,回到“线程模式”,用进程堆栈指针(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个时钟取指令N+1译码指令N(空闲)
第3个时钟取指令N+2译码指令N+1执行指令N

当CPU在执行某条指令时,取指阶段已经提前取到了 "当前指令地址 + 8" 处的指令。所以这种流水线设计给PC带来一个特性:

在Cortex-M3中,当读取PC的值时,得到的并非当前正在执行的指令地址,而是"当前指令地址 + 4"

为什么是 +4 而不是 +8?在Cortex-M3中,读取PC时,硬件返回的并不是真正的取指地址,而是当前执行地址 + 4(ARMv7-M架构规定)。这是一个架构规定的返回值,而不是物理取指地址的直接映射。

(PS:PC的读取值 ≠ 实际的取指地址。流水线中的取指地址可能更靠前,这取决于流水线状态和指令对齐,但架构层面规定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。当执行BXBLX跳转时,处理器会自动忽略该 LSB(将其清 0)后再送入 PC,从而正确访问对齐的指令地址。

7.3 PC不可直接修改

PC作为R15,在寄存器编号上与其他通用寄存器连续排列,因此理论上可以通过MOV PC, RnLDR PC, [addr]直接修改PC的值实现代码跳转。

但是在C语言层面,永远不要用指针直接修改 PC,做这种非标操作很容易造成问题。实际上即使在汇编代码中,也不应该直接赋值 PC。

8. 程序状态寄存器组 PSRs

PSRs 寄存器直接反映了处理器的当前运算状态、执行模式以及中断嵌套层级,是理解 Cortex-M3 内核运行机理的核心窗口。

在Cortex-M3的寄存器体系中,程序状态寄存器组(Program Status Registers,PSRs)是唯一 一组"名不副实"的寄存器。它看似一个独立的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, APSRMRS Rn, EPSRMRS Rn, IPSR分别读取各个子寄存器。

8.2 APSR:运算标志的"晴雨表"

APSR(应用程序状态寄存器)存放着算术逻辑单元(ALU)执行运算后产生的条件标志位,是条件分支指令(如BEQBNEBGT等)的判断依据。

标志名称置位条件
31N负标志运算结果为负数时置1(最高位为1)
30Z零标志运算结果为0时置1
29C进位/借位标志无符号加法溢出时置1;无符号减法产生借位时清0
28V溢出标志有符号运算结果溢出时置1
27Q饱和标志发生饱和运算(如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复位(Reset)
2不可屏蔽中断(NMI)
3硬件错误(HardFault)
4-10其他系统异常(MemManage、BusFault、UsageFault、SVCall、DebugMonitor、PendSV、SysTick)
16-255外部中断(IRQ0-IRQ239,具体数量因芯片型号而异)

我们可以在共享的中断处理函数中,通过读取 IPSR 判断当前是哪个中断触发了该函数(适用于多个中断共用一个入口的场景)。

9. 中断屏蔽寄存器组(PRIMASK、FAULTMASK、BASEPRI)

中断屏蔽寄存器常用于控制异常或中断的使能和除能。

每个异常都有优先级,数值越小优先级越高,这些特殊寄存器可基于优先级屏蔽异常,只有在特权访问模式才可以对它们操作。

在用户模式下,对中断屏蔽寄存器写操作会被忽略,读操作会返回0。

9.1 功能概述

PEIMASKFAULTMASK都是简单粗暴的中断屏蔽工具,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寄存器的默认值为0x00000000,nPRIV = 0:线程模式运行在特权级。SPSEL = 0:当前使用MSP作为堆栈指针。

这意味着在系统启动的最初阶段,所有代码(包括启动代码、系统初始化、main函数)都运行在特权级并使用MSP。

直到操作系统初始化完成,创建了第一个用户任务并准备好 PSP 堆栈后,才会通过修改CONTROL 切换到用户级 + PSP的组合。

在RTOS的任务切换中,CONTROL寄存器的管理是上下文切换的核心环节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询