3. ARM寄存器详解:通用寄存器、堆栈指针SP、链接寄存器LR、程序计数器PC、程序状态寄存器CPSR

寄存器,说白了就是CPU内部的高速缓存单元。我经常跟团队里的年轻人讲,理解寄存器就是理解ARM处理器的第一步。你想想看,CPU所有运算都要经过寄存器,它就像工程师手里的工具台——工具放得顺手,干活就快。

ARM处理器有37个寄存器,但咱们今天只讲最核心的那几个。嗯,这里要注意,不同工作模式下看到的寄存器集合是不一样的,这个后面会细说。

3.1 通用寄存器:R0-R12

通用寄存器R0到R12,一共13个。它们就像你桌上的便签纸,随手拿来用。ARM规定这些寄存器用途完全自由,没有硬件层面的限制。

但我个人习惯,在写汇编时会约定一些潜规则:

  • R0-R3:通常用来传参数。函数调用时,前4个参数就放这里
  • R4-R11:保存局部变量。我在项目中遇到过,有人把大循环的计数器放在R4里,性能提升很明显
  • R12:也叫IP寄存器,编译器经常拿它做临时中转

重要提醒:虽然硬件不限制用途,但如果你要跟C语言混编,就得遵守ATPCS(ARM-Thumb过程调用标准)。否则,函数调用时寄存器内容被覆盖,bug会非常难查。

举个例子,一个简单的加法函数:

; int add(int a, int b) — a在R0,b在R1
ADD R0, R0, R1    ; R0 = R0 + R1
BX  LR            ; 返回,结果在R0

你看,ARM的指令集设计得很优雅。操作数直接来自寄存器,结果也写回寄存器,没有多余的访存开销。

3.2 堆栈指针SP:R13

SP,全称Stack Pointer。它指向当前堆栈的栈顶。堆栈这东西,说白了就是一块后进先出的内存区域。函数调用、局部变量、中断现场保护,全得靠它。

ARM支持四种堆栈模式,但我工作中99%的情况只用一种:

模式 说明 常用场景
满递减 SP指向最后压入的数据,地址向下增长 最常用,C编译器默认
空递减 SP指向下一个空闲位置,地址向下增长 少见
满递增 SP指向最后压入的数据,地址向上增长 某些RTOS会用
空递增 SP指向下一个空闲位置,地址向上增长 几乎不用

我的经验:刚学ARM时,我总搞不清压栈出栈的方向。后来我记住一句话——「满递减,先减后存」。也就是先SP减4,再把数据存到SP指向的位置。出栈时反过来,先取数据,再SP加4。

堆栈操作指令也很直观:

PUSH {R0, R1}     ; 压栈,SP自动减8
POP  {R0, R1}     ; 出栈,SP自动加8

我曾经在一个中断服务程序里忘记平衡堆栈,结果程序跑飞了整整两天才找到原因。嗯,从那以后我写中断代码,第一件事就是检查PUSH和POP是否成对出现。

3.3 链接寄存器LR:R14

LR,Link Register。它保存了函数调用时的返回地址。你想想看,当CPU执行BL指令跳转到子函数时,下一条指令的地址就被自动保存到LR里了。

为什么需要这个寄存器?因为ARM没有硬件堆栈,函数嵌套全靠LR和SP配合。举个例子:

main:
    BL  func1       ; 跳转到func1,LR = 下一条指令地址
    ; 这里继续执行

func1:
    PUSH {LR}       ; 保存返回地址到堆栈
    BL  func2       ; 嵌套调用func2
    POP  {PC}       ; 恢复返回地址,等价于 BX LR
    ; 注意:POP {PC} 会直接跳转

这里有个细节:如果func1不嵌套调用其他函数,其实可以不用PUSH/POP,直接用BX LR返回就行。但一旦有嵌套,就必须保存LR。我在项目中见过有人偷懒没保存LR,结果嵌套两层后程序就乱跳了。

避坑指南:中断处理函数里,LR会被硬件自动修改。如果你在中断里调用了其他函数,一定要先保存LR。我曾经...嗯,有一次在UART中断里调了个打印函数,忘了保存LR,结果打印完就回不去了,系统直接死机。

3.4 程序计数器PC:R15

PC,Program Counter。它指向当前正在执行的指令地址。ARM有个特点:PC的值是当前指令地址+8(在ARM状态下)。为什么是+8?因为ARM是三级流水线——取指、译码、执行。PC指向的是取指阶段,而当前执行的是两条指令之前的地址。

这个特性在调试时特别容易让人困惑。我刚开始做底层开发时,看PC值总觉得不对,后来才明白是流水线在作怪。

PC可以直接读写吗?可以,但要注意:

  • 读PC:得到当前指令地址+8
  • 写PC:相当于跳转,会刷新流水线
MOV PC, LR        ; 等价于 BX LR,函数返回
ADD PC, PC, #4    ; 跳过下一条指令(危险操作,不推荐)

核心要点:PC是ARM体系里最特殊的寄存器。它既是通用寄存器(R15),又有着固定的硬件行为。写PC就是跳转,这个特性让ARM的指令执行流非常灵活,但也容易出错。

3.5 程序状态寄存器CPSR

CPSR,Current Program Status Register。它记录了处理器的当前状态。说白了,就是CPU的「仪表盘」——告诉你现在跑在什么模式、运算结果有没有进位、中断是否开启等等。

CPSR的位定义如下:

位域 名称 说明
[31:28] N, Z, C, V 条件标志位:负、零、进位、溢出
[27:25] Q, IT, J 饱和标志、Thumb状态、Jazelle状态
[24:8] 保留 系统保留,不要动
[7:6] I, F IRQ和FIQ中断屏蔽位
[5] T Thumb状态位:0=ARM,1=Thumb
[4:0] M[4:0] 处理器模式:10011=Supervisor,10010=IRQ等

条件标志位是ARM指令集的一大特色。几乎所有指令都可以条件执行:

CMP R0, #5        ; 比较R0和5,设置CPSR
ADDGT R1, R1, #1  ; 如果R0 > 5,才执行这条加法

这个特性在编译条件分支时非常有用。我做过一个性能对比测试,用条件执行代替分支跳转,代码执行时间减少了30%左右。为什么?因为跳转会清空流水线,而条件执行不会。

实用技巧:调试时想查看CPSR?在GDB里输入 info registers cpsr 或者 p $cpsr。我习惯用 p/t $cpsr 以二进制形式查看,这样每个标志位一目了然。

最后说一句,CPSR在异常处理时会自动保存到SPSR(Saved Program Status Register)中。每个异常模式都有自己的SPSR,用来保存被中断前的CPSR值。这个机制保证了异常返回后,程序能无缝恢复执行。

ARM寄存器核心结构图 ARM 处理器核心 通用寄存器 R0 - R12 13个 32位寄存器 用途:数据运算、参数传递 R0-R3:参数 / R4-R11:局部 特殊功能寄存器 SP (R13) 堆栈指针 LR (R14) 链接寄存器 PC (R15) 程序计数器 CPSR 状态寄存器 通用寄存器特性 • 完全通用,无硬件限制 • 遵循ATPCS调用规范 • 不同模式有独立Bank 特殊寄存器特性 • SP:堆栈操作,自动增减 • LR:保存返回地址 • PC:流水线相关,+8偏移 CPSR状态位 N/Z/C/V 条件标志 I/F 中断屏蔽 T Thumb状态 注:不同工作模式下,SP、LR、CPSR有独立的备份寄存器

这张图把今天讲的五个寄存器组串起来了。你看,通用寄存器是运算的主力,SP和LR负责函数调用和返回,PC控制程序流向,CPSR则时刻监控着处理器的状态。它们配合起来,构成了ARM处理器的执行核心。

嗯,寄存器这块内容比较多,但都是基础中的基础。我建议你动手写几个小例子,比如用汇编实现一个函数调用,观察SP和LR的变化。实践出真知,光看文章是记不住的。

专注资料整理