ARMv7 处理器模式与寄存器组:七种模式详解

ARMv7 的处理器模式,说白了就是 CPU 的几套「工作服」。不同模式下,CPU 能看到不同的寄存器,权限也不一样。我刚开始学 ARM 时,总觉得模式切换很玄乎,后来调试一个中断嵌套的 bug,才真正体会到——不理解模式,你连 PC 怎么跳的都看不懂。

七种处理器模式

ARMv7 定义了 7 种运行模式。其中 6 种是特权模式,1 种是非特权模式。特权模式可以访问所有资源,非特权模式(用户模式)则受限。

模式 缩写 权限 典型用途
用户模式 USR 非特权 应用程序运行
快速中断模式 FIQ 特权 高速数据传输
普通中断模式 IRQ 特权 通用中断处理
管理模式 SVC 特权 系统调用(SWI)
中止模式 ABT 特权 数据/指令访问异常
未定义模式 UND 特权 未定义指令陷阱
系统模式 SYS 特权 操作系统内核任务
我的习惯:调试时先看 CPSR 的模式位(M[4:0])。如果发现 CPU 跑在 USR 模式,而你试图访问协处理器,那肯定要出异常。我曾经花了一下午查一个莫名其妙的死机,最后发现是用户模式下写了 CP15 寄存器。

通用寄存器组(R0-R15)

ARMv7 有 16 个通用寄存器,编号 R0 到 R15。但不同模式下,你看到的「R13」和「R14」可能不是同一个物理寄存器。这就是所谓的寄存器组备份。

R0-R12:通用数据寄存器

  • R0-R7:所有模式共享同一份物理寄存器。没有备份。
  • R8-R12:FIQ 模式有自己独立的 R8_fiq ~ R12_fiq。其他模式共享同一份。

为什么 FIQ 要单独备份 R8-R12?因为 FIQ 追求快速响应,省去了压栈保存的步骤。我做过一个高速数据采集的项目,FIQ 里直接操作 R8-R12,中断延迟能压到 10 个时钟周期以内。

R13(SP):栈指针

每种特权模式都有自己的 SP。用户模式和系统模式共享 SP_usr。这意味着:

  • IRQ 模式有自己的栈
  • FIQ 模式有自己的栈
  • SVC 模式有自己的栈
  • ……
我曾经踩过的坑:启动代码里只初始化了 SVC 模式的 SP,结果 IRQ 一来,CPU 切换到 IRQ 模式,SP 指向 0x00000000。写栈直接触发 Data Abort。所以,每个特权模式的 SP 都要单独初始化,别偷懒。

R14(LR):链接寄存器

LR 保存子程序调用的返回地址。每种异常模式也有自己的 LR。例如:

  • 执行 BL 指令时,LR = PC + 4
  • IRQ 触发时,LR_irq = 返回地址
  • SVC 触发时,LR_svc = 返回地址

嗯,这里要注意:异常模式的 LR 可能比实际返回地址多 4 或 8 个字节,具体要看异常类型。调试时别直接拿 LR 当返回地址用,得先修正。

R15(PC):程序计数器

PC 指向当前正在取指的指令地址。ARM 状态时,PC = 当前指令地址 + 8(因为三级流水线)。Thumb 状态时,PC = 当前指令地址 + 4。

调试技巧:看 PC 值的时候,记得减去流水线偏移。比如 ARM 状态下 PC = 0x8008,实际执行的指令在 0x8000。我见过不少新手对着 PC 值找代码,怎么都对不上,其实就是忘了这个 +8。

程序状态寄存器(CPSR / SPSR)

CPSR 是当前程序状态寄存器,SPSR 是备份程序状态寄存器。每种异常模式都有自己的 SPSR,用于保存进入异常前的 CPSR。

CPSR 位域详解

位域 名称 说明
[31:28] N, Z, C, V 条件标志位(负、零、进位、溢出)
[27] Q 饱和运算溢出标志(DSP 扩展)
[26:25] IT[1:0] Thumb-2 IT 指令状态
[24:23] J, GE[3:0] Jazelle 状态 / SIMD 大于等于标志
[9:8] E, A 大小端控制 / 异步中止屏蔽
[7] I IRQ 中断屏蔽(1=屏蔽)
[6] F FIQ 中断屏蔽(1=屏蔽)
[5] T Thumb 状态(1=Thumb,0=ARM)
[4:0] M 处理器模式编码

M[4:0] 的编码值:

  • 10000 = 用户模式
  • 10001 = FIQ 模式
  • 10010 = IRQ 模式
  • 10011 = SVC 模式
  • 10111 = 中止模式
  • 11011 = 未定义模式
  • 11111 = 系统模式
我常用的调试命令:在 GDB 里用 info registers 看 CPSR,或者直接 p/x $cpsr。如果看到 T 位是 1,但反汇编显示的是 ARM 指令,那肯定是模式不匹配。这种问题在混合编程时特别常见。

SPSR:异常现场的「快照」

当异常发生时,硬件自动将 CPSR 拷贝到当前异常模式的 SPSR 中。异常返回时,用 SPSR 恢复 CPSR。举个例子:

; IRQ 异常入口
IRQ_Handler:
    ; 此时 SPSR_irq 保存了被中断任务的 CPSR
    ; 处理中断...
    SUBS PC, R14_irq, #4   ; 返回并恢复 CPSR

注意那个 SUBS PC, LR, #4。S 后缀表示操作结果会更新 CPSR,但这里实际上是从 SPSR 恢复。ARM 的这条指令是特例——当 PC 为目标寄存器且带 S 后缀时,自动将 SPSR 拷贝到 CPSR。

我曾经犯过的错:在中断处理函数里不小心修改了 SPSR。结果返回后,CPSR 被恢复成一个错误的值,CPU 直接跑飞。所以,除非你明确知道自己在做什么,否则别碰 SPSR。

知识结构图:ARMv7 模式与寄存器关系

ARMv7 处理器模式与寄存器关系图 ARMv7 处理器模式与寄存器关系 用户模式 (USR) 管理模式 (SVC) 普通中断 (IRQ) 快速中断 (FIQ) 中止模式 (ABT) 未定义模式 (UND) 系统模式 (SYS) 寄存器组分配 R0 - R7(所有模式共享) R8 - R12(FIQ 独享备份) R13 (SP) 每个模式独立 R14 (LR) 每个模式独立 R15 (PC) 所有模式共享 CPSR + SPSR(异常备份) 注:异常发生时,硬件自动保存 CPSR 到当前模式的 SPSR

实战要点总结

  1. 模式切换:用户模式不能直接切换模式,必须通过异常或 SWI 指令。系统模式可以自由切换,常用于 OS 内核。
  2. 栈初始化:每个特权模式都要分配独立的栈空间。我一般给 IRQ 和 FIQ 分配 256 字节,SVC 分配 1KB,ABT 和 UND 分配 128 字节。
  3. CPSR 保护:中断处理函数里不要修改 CPSR 的 I/F 位,除非你清楚后果。我曾经在 FIQ 里清了 I 位,结果 IRQ 嵌套进来,栈直接爆了。
  4. SPSR 只读:异常模式下,SPSR 由硬件自动维护。手动修改 SPSR 会导致返回后状态异常。
  5. 调试利器:用 JTAG/SWD 读 CPSR 的 M[4:0] 位,可以快速判断 CPU 当前在哪个模式。如果发现 CPU 卡在 ABT 或 UND 模式,那基本就是访问了非法地址或执行了非法指令。
一句话总结:ARMv7 的七种模式 + 寄存器组备份,本质上是为不同优先级的中断和异常提供独立的运行环境。理解了这个设计哲学,你就能写出更健壮、更高效的底层代码。