CPU 通用寄存器组
R15 — PC (Program Counter,程序计数器)
PC 里存的永远是下一条要执行的指令地址。
CPU 每执行完一条指令,就去 PC 指向的地方取下一条,然后 PC 自动指向再下一条。所以 PC 永远比当前正在执行的指令领先一步。
往 PC 里写个地址,CPU 就会跳到那里去执行——这就是跳转的本质。
R14 — LR (Link Register,链接寄存器)
调用函数前,先把函数下面那条指令的地址存到 LR 里,再去执行函数。
函数执行完了,CPU 回到 LR 存的地址,继续往下跑。
调用函数 → 把返回地址存 LR → 执行函数 → 函数结束 → 跳回 LR 里的地址
如果函数里又调了别的函数(嵌套调用),LR 会被覆盖,所以要先 PUSH {LR} 把 LR 塞进栈里保护起来,回来再弹出来。
发生异常时,LR 存的不是普通返回地址,而是一个特殊值
EXC_RETURN,告诉 CPU 该怎么返回(回到线程模式还是 Handler 模式、用 MSP 还是 PSP)。
R13 — SP 栈顶指针
SP 指向栈的栈顶
栈在ram里,用来临时存放数据,比如函数调用时的返回地址、局部变量。
压栈,SP 往低地址移动;出栈,SP 往高地址 → 详见 栈模型(满递减栈)
普通情况下编译器在函数 prologue 里自动保证 SP 对齐,但在freertos里手动建栈的指针,需要注意遵守 AAPCS栈顶指针8字节对齐,避免后续浮点或 64 位访问产生硬件异常
Cortex-M 有两根栈指针,都是真实存在的寄存器,不过根据情况映射到sp寄存器中去↓
- MSP:主栈指针,复位后默认用这个,操作系统内核和异常处理也用这个。初始会在栈顶一定满足
if (((*(__IO uint32_t*)APPLICATION_ADDR) & 0x2FFE0000 ) == 0x20000000),bootloader开始用于检验栈顶指针是否合法 - PSP:进程栈指针,跑用户线程时用。线程各自的栈互相隔离,一个线程炸了不会拖垮内核。
R7 — BP 栈底指针
BP 指向当前函数栈帧的栈底(高地址端)。SP 随着压栈出栈来回移动,BP 在函数执行期间固定不动,作为访问局部变量的基准。
函数内部的每个局部变量都会被压在栈帧里,编译期就算好了每个变量距离 BP 的偏移量,运行时直接用 BP ± 偏移读写:
函数返回时,SP 回到 BP 的位置,弹出旧 BP 恢复调用者的栈帧。
R0、R1、R2、R3 — 临时计算寄存器
调用函数的时候,会先记录前4个参数,变量,用完再覆盖
R4、R5、R6 — 标准局部变量寄存器
存放函数内部需要长期跨函数保存的局部变量、循环变量(如 for (int i = 0... 中的 i)。
如果一个局部变量在当前函数里频繁被读写,而且中途当前函数还要调用其他子函数,编译器会优先把它分配在 R4、R5 或 R6 中。
R8、R9、R10、R11 — 高寄存器组(特殊约定与扩展)
Callee-saved 寄存器:上层函数保存值,下层函数不写这个寄存器
R8:标准 Callee-saved 寄存器。当 R4 ~ R7 用满后,复杂的函数会继续动用 R8 来存放局部变量。
R9:在不同的操作系统或编译模式下,R9 有特殊用途,os里指向当前线程的数据结构,裸机里存变量
R10:有栈溢出检查的编译器中用来记录栈空间的最底边界,现在一般是普通的Callee-saved 局部变量寄存器
R11:在ARM指令集里,当做R7来用
硬件压栈
是易失性寄存器,中断服务函数随时会覆盖它们,所以硬件强制压栈保护
异常发生时,硬件自动把 8 个寄存器的值按顺序压进栈里,异常返回时自动弹出恢复现场。全程不用写一条指令。
这 8 个寄存器是:
R0、R1、R2、R3:函数的前 4 个参数 / 临时计算结果
**R12:**内部临时抓取寄存器 (IP)
LR:链接寄存器保存中断处理完后要回哪里去
**PC:程序计数器 (Program Counter)**保存中断被打断那一刻正在执行的代码地址
xPSR:程序状态寄存器保存当时的进位、零标志等状态
软件压栈
- R4–R11 属于 高寄存器组,在函数调用约定(ABI)里,它们被称为 callee-saved registers(被调用者保存寄存器)。
- 硬件中断自动保存时不会保存 R4–R11,因为 Cortex-M 内核假设你可能并不需要它们。
- 是否要保存 R4–R11 取决于场景:
普通中断:如果 ISR(中断服务函数)用到了 R4–R11,编译器会在进入 ISR 时生成 PUSH {R4-R11} 指令,退出时 POP {R4-R11}(编译器AAPCS智能添加)。
RTOS 上下文切换:RTOS 的 PendSV handler 必须保存完整上下文(R4–R11 + 硬件自动压栈的 8 个寄存器),这样任务才能恢复到完全一致的 CPU 状态。
换句话说,不是每次中断都保存 R4–R11,而是根据需要决定是否软件压栈
📎 栈帧与压栈出栈详见 函数栈帧与压栈出栈