ARMv7-A小系统实践(二):处理器模式、寄存器组与CPSR

本章记录什么

这一章解释 ARMv7-A 程序最先遇到的一组概念:处理器模式、可见寄存器、banked 寄存器、CPSR 和 SPSR。目标不是背诵一张寄存器表,而是能够回答三个工程问题:为什么启动代码要多次切换模式设置 sp,为什么 IRQ 到来后同名的 lrsp 会指向另一份物理寄存器,以及异常返回时为什么必须同时恢复程序地址和处理器状态。

在这一章之前做了什么

上一章确定了整个小系统的能力边界和资料分工,但还没有进入任何一条具体执行路径。现在从 CPU 的“当前身份”开始,因为启动、异常和调度最终都要落到寄存器可见性上。若这一层没有建立,看到 cps #0x12mrs r0, spsrrfeia 时只能记住指令表面,无法解释状态为什么能够恢复。

本章会先建立模式与寄存器组的基础,再把规则放进启动代码和一次 IRQ 中推演,最后列出可以在调试器里验证的观察点。任务切换只在结尾用于对比,不会提前混入。

系列目录 · 上一篇:平台、范围与路线

本章的阅读顺序

先读基础概念,确认每个术语解决什么问题;再看设计推导,理解实现为什么采用这种保存集合、初始化顺序或状态机;随后逐段阅读代码和执行轨迹;最后用验证方法与错误案例检查理解。文中保留寄存器名、指令名和标准缩写,叙述部分统一使用简体中文。

动手前的基础:模式、bank 与程序状态

适用平台:Zynq-7000 / 双核 Cortex-A9 / ARMv7-A
学习目的:读懂启动代码、异常入口和每核各模式栈,为上下文切换与故障定位建立底座
阅读方式:正文可以替代本节英文原文;只在“原图对照”处打开 PDF 看图
编写日期:2026-07-30

0. 这一目标到底要学会什么

原定目标所说的“寄存器切换”,第一层确实是读者理解的:

处理器模式改变后,同一个汇编寄存器名会映射到不同的物理寄存器 bank。

但为了能做移植和 Debug,不能只记一张 bank 图,还要把下面四件事连起来:

  1. 当前模式记录在 CPSR.M[4:0]
  2. 哪些模式拥有自己的 SP/LR/SPSR
  3. 异常发生时,硬件怎样保存旧状态并切换寄存器 bank。
  4. 异常返回时,怎样恢复 CPSR,从而重新看到原模式的寄存器 bank。

本目标不是 OS 任务切换。两者的区别是:

1
2
3
4
5
6
7
处理器模式切换
硬件异常入口或特权指令改变 CPSR.M
寄存器名切换到另一个硬件 bank

任务上下文切换
OS 把任务 A 的寄存器写入内存
再从内存恢复任务 B 的寄存器

二者会在 IRQ 尾部调度时汇合,但概念不能混用。


1. 本文覆盖的原始资料和准确范围

原始资料 准确范围 页数 本文用途
Programmer’s Guide 第 4 章 PDF/手册页 47–56 10 页 建立模式、权限、寄存器和 CPSR 的直观框架
ARM ARM B1.3 PDF/手册页 B1-1139–B1-1154 16 页 补齐架构定义、bank 关系和严格语义
ARM ARM B1.8.3–B1.8.5 B1-1169–B1-1182 中的相关内容 14 页范围内选读 说明模式切换怎样真正发生在异常入口

这不是逐句翻译,而是按上述原文逻辑整理的中文工程精读稿。B1.3 中与 Zynq 第一版移植关系很弱的 Jazelle、虚拟化路由边角和保留值,只说明用途,不要求背诵。

原图对照

第一次读正文时,只建议看以下 5 张图:

原文图名 所在页 看什么
Programmer’s Guide Figure 4-3: Privilege levels 49 User 与 PL1 特权模式的关系
Programmer’s Guide Figure 4-5: The ARM register set 51 最直观的 banked register 全图
Programmer’s Guide Figure 4-6: CPSR bits 53 CPSR 位域位置
ARM ARM Figure B1-1: Modes, privilege levels, and security states B1-1141 模式、特权级和安全状态的规范关系
ARM ARM Figure B1-2: ARM core registers, PSRs, and ELR_hyp, showing register banking B1-1144 最完整的寄存器 bank 规范图

需要核对模式编码时看:

  • Programmer’s Guide Table 4-2: ARM Mode Encodings and Privilege Levels,第 49 页。
  • ARM ARM Table B1-1: ARM processor modes,B1-1139。

2. 处理器模式:为什么需要它们

ARMv7-A 不是让所有代码都以相同权限、使用同一套异常栈运行。不同模式承担不同职责:

模式 编码 典型来源和用途 特权
User (usr) 0x10 普通应用代码 PL0
FIQ (fiq) 0x11 快速中断 PL1
IRQ (irq) 0x12 普通外部中断 PL1
Supervisor (svc) 0x13 复位后、SVC 系统调用、内核主模式 PL1
Monitor (mon) 0x16 TrustZone 安全监控 PL1 Monitor
Abort (abt) 0x17 预取或数据访问异常 PL1
Hyp (hyp) 0x1A 虚拟化管理 PL2
Undefined (und) 0x1B 未定义指令异常 PL1
System (sys) 0x1F 特权软件使用 User 寄存器组 PL1

对 Zynq-7020 第一版单核移植,优先掌握:

1
SVC、IRQ、FIQ、Abort、Undefined、System/User

Monitor 要知道它属于 TrustZone;Hyp 要能识别,但没有明确虚拟化需求时不作为第一版实现重点。

User 与 System 最容易混淆

二者使用相同的 R0-R14 寄存器 bank,但权限不同:

  • User 是非特权模式,不能随意修改控制寄存器或中断屏蔽。
  • System 是 PL1 特权模式,通常供内核在“用户寄存器视图”下工作。
  • 两者都没有自己的 SPSR
  • System 不是异常目标模式,不会因为某种异常被硬件自动进入。

所以:

“共享寄存器”不等于“拥有相同权限”。


3. 可见寄存器与实际物理寄存器

汇编程序在任意时刻最多看到:

1
2
3
4
5
R0-R12    通用寄存器
R13/SP 栈指针
R14/LR 链接寄存器
R15/PC 程序计数器
CPSR 当前程序状态

但芯片内部的物理寄存器数量更多,因为部分名字在不同模式下有不同 bank。

3.1 必须记住的 bank 规律

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
R0-R7
所有模式共享

R8-R12
普通模式共享一组
FIQ 独享另一组 R8_fiq-R12_fiq

R13/SP
User/System 共享 SP_usr
FIQ、IRQ、SVC、Abort、Undefined、Monitor 各有自己的 SP
Hyp 有自己的 SP_hyp

R14/LR
User/System 共享 LR_usr
FIQ、IRQ、SVC、Abort、Undefined、Monitor 各有自己的 LR
Hyp 使用 ELR_hyp 保存异常返回地址

R15/PC
不分 bank

CPSR
每个 CPU 核只有一个“当前 CPSR”

SPSR
FIQ、IRQ、SVC、Abort、Undefined、Monitor、Hyp 等异常模式各有自己的保存状态
User/System 没有 SPSR

可压缩成一句口诀:

FIQ 多 bank R8-R12;异常模式各 bank SP/LR/SPSRR0-R7PC 不 bank;User/System 共用一组且没有 SPSR。

3.2 “切换”不是复制

假设:

1
2
SP_svc = 0x0010_8000
SP_irq = 0x0010_4000

在 SVC 模式执行:

1
mov r0, sp

r0 得到 0x00108000。切换到 IRQ 模式后再执行同一条指令,r0 得到 0x00104000

硬件没有把 SVC 栈指针复制到 IRQ 栈指针。发生的是:

1
2
3
4
5
CPSR.M 改变

名字 SP 的物理映射改变

当前看到 SP_irq,而不再是 SP_svc

这也是为什么启动代码必须逐个模式设置栈。如果 SP_irq 没初始化,第一次 IRQ 一执行 push 就会把现场写到未知地址。

3.3 为什么 FIQ 多出五个 bank

FIQ 额外拥有 R8_fiq-R12_fiq,目的是让小型快速处理程序少保存一些共享寄存器,降低入口开销。现代 OS 往往仍会建立规范现场,所以不要把它理解成“FIQ 永远不保存寄存器”,只能说架构给了更低延迟的可能。


4. CPSR:只按用途记,不要机械背 32 个格子

CPSR 同时回答四类问题:

  1. 上一条运算结果怎样:N/Z/C/V/Q
  2. 当前采用什么指令状态:主要是 T
  3. 哪些异步异常被屏蔽:A/I/F
  4. 当前处于什么处理器模式:M[4:0]

4.1 CPSR 位域

1
2
31  30  29  28  27  26:25 24  23:20 19:16 15:10 9  8 7 6 5  4:0
N Z C V Q IT J - GE IT E A I F T M
第一优先级:移植和异常代码天天使用
名称 工程含义
9 E 数据访问端序
8 A 异步 Abort 屏蔽,1 表示屏蔽
7 I IRQ 屏蔽,1 表示屏蔽
6 F FIQ 屏蔽,1 表示屏蔽
5 T 1 为 Thumb 状态,0 为 ARM 状态
4:0 M 当前处理器模式

先牢牢记:

1
低 10 位:E A I F T MMMMM
第二优先级:读普通汇编经常出现
含义
N 结果为负
Z 结果为零
C 加减的进位/借位语义,也用于移位移出的位
V 有符号溢出
Q 饱和运算累计溢出

CMP r0, r1 本质上更新标志而不保存减法结果,随后 BEQ/BNE/BHI/... 根据这些位判断。

第三优先级:知道存在即可
  • IT:Thumb IT 条件执行块状态,分成两段存放。
  • GE[3:0]:某些并行 SIMD 指令的比较结果。
  • J:Jazelle 状态,当前 Zynq OS 移植不需要作为记忆重点。
  • 保留位:按手册要求保留,不自行写入任意值。

4.2 修改 CPSR 时要避免的错误

不能为了切模式而随手构造整个 CPSR 常量,覆盖条件标志、指令状态或屏蔽位。常用方法是:

1
2
3
4
mrs   r0, cpsr        @ 读取
bic r0, r0, #0x1f @ 只清 M 域
orr r0, r0, #0x12 @ 选择 IRQ
msr cpsr_c, r0 @ 只写控制域

或者在允许的特权环境使用:

1
2
3
cps #0x12             @ 主动进入 IRQ 模式
cpsid i @ 设置 I,屏蔽 IRQ
cpsie i @ 清除 I,允许 IRQ

cps #0x12 只改变模式,不会自动生成一次 IRQ 异常现场。


5. SPSR:异常前 CPSR 的停车位

进入普通 PL1 异常时,硬件做的关键动作之一是:

1
异常前 CPSR → 目标模式的 SPSR

例如 IRQ:

1
CPSR_old → SPSR_irq

因此在 IRQ handler 中:

  • 当前 CPSR 描述“正在 IRQ 模式执行”的状态。
  • SPSR_irq 描述“被打断代码当时”的模式、T 位、屏蔽位和标志。

二者不能互换。

5.1 为什么每种异常模式各有 SPSR

IRQ、Abort、Undefined 等异常可能在不同时间保存不同的旧状态。如果只有一个共享 SPSR,它们之间会互相覆盖。独立 bank 让每个异常模式至少拥有自己的架构保存位置。

这并不自动解决任意嵌套问题:同一种模式再次嵌套,仍可能覆盖自己的 LR_mode/SPSR_mode。所以 OS 入口通常尽快把它们压入内存异常帧。

5.2 异常返回的本质

异常返回必须同时完成:

1
2
恢复返回 PC
恢复异常前 CPSR

一旦旧 CPSR.M 被恢复,当前寄存器映射也随之回到旧模式。例如从 IRQ 回到 SVC 后,名字 sp 再次表示 SP_svc

常见形式:

1
2
subs pc, lr, #4        @ 某些 IRQ 返回序列
rfeia sp! @ 从内存同时恢复 PC 和 CPSR

具体 LR 修正量属于异常主题,不能把所有异常都机械写成 lr - 4


6. 一次 IRQ 中真正发生的寄存器切换

假设 CPU0 正在 SVC 模式运行,IRQ 未屏蔽。一个外设 IRQ 到来后,架构入口可理解为:

1
2
3
4
5
6
1. CPSR_svc → SPSR_irq
2. 首选返回地址 → LR_irq
3. CPSR.M → IRQ
4. CPSR.I → 1,防止普通 IRQ 立即再次进入
5. 当前可见 SP 从 SP_svc 变成 SP_irq
6. PC → vector_base + 0x18

注意硬件没有自动保存 R0-R12。入口汇编必须在改写这些寄存器前,把所需现场放入 IRQ 栈或任务异常帧。

返回时:

1
2
3
异常帧中的状态 → 通用寄存器
保存的 CPSR → CPSR
保存的返回地址 → PC

恢复 CPSR 后模式映射回到异常前的 bank,于是原任务继续。

cps #mode 与异常入口的对照

行为 cps #mode 真正异常入口
改变 CPSR.M
切换可见 bank
旧 CPSR 自动存入 SPSR
自动产生返回地址
自动跳到向量

这是本目标最重要的辨析题。


7. 双核 Zynq:模式 bank 之外还有“每核一套”

Zynq-7020 的两个 Cortex-A9 核,各自拥有完整架构状态:

1
2
3
4
5
6
7
CPU0
CPSR、PC、R0-R12
SP/LR/SPSR 的各模式 bank

CPU1
CPSR、PC、R0-R12
SP/LR/SPSR 的各模式 bank

所以要同时理解两个维度:

1
2
横向:CPU0 与 CPU1 各有自己的寄存器
纵向:每个 CPU 内部又有 SVC/IRQ/FIQ/... bank

第一版单核启动只初始化 CPU0;启动 CPU1 前,必须给 CPU1 单独准备各模式栈和 per-CPU 数据。CPU0 的 SP_irq 不能供 CPU1 共用。


8. 启动代码应该怎样初始化各模式栈

典型思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cpsid if               @ 初始化期间先屏蔽 IRQ/FIQ

cps #0x12 @ IRQ
ldr sp, =cpu0_irq_stack_top

cps #0x11 @ FIQ
ldr sp, =cpu0_fiq_stack_top

cps #0x17 @ Abort
ldr sp, =cpu0_abt_stack_top

cps #0x1b @ Undefined
ldr sp, =cpu0_und_stack_top

cps #0x1f @ System/User bank
ldr sp, =cpu0_sys_stack_top

cps #0x13 @ SVC
ldr sp, =cpu0_svc_stack_top

这段代码的意义不是反复修改同一个 SP,而是逐一选择并初始化不同的物理 SP_mode

实际移植还要:

  • 根据 MPIDR 选择 CPU0/CPU1 对应栈;
  • 保证栈顶满足 AAPCS 8 字节对齐;
  • 栈之间不能重叠;
  • 在向量和 IRQ 栈准备好之前不能开放 IRQ;
  • 记录链接脚本中栈区边界,便于越界诊断。

9. 移植与 Debug:从症状反推哪一层错了

症状 1:一开 IRQ 就 Data Abort 或跑飞

优先检查:

  1. SP_irq 是否初始化。
  2. 当前 CPU 是否用了自己的 IRQ 栈。
  3. IRQ 栈地址是否可写、对齐且未与 .bss/其他栈重叠。
  4. 向量基址是否正确。

症状 2:异常返回后进入错误模式

优先检查:

  1. 异常帧里的 SPSR 是否被破坏。
  2. MSR SPSR...RFE 使用的值是否正确。
  3. 返回现场的 PC/CPSR 顺序是否与保存布局一致。
  4. 是否把当前 CPSR 错当成异常前 CPSR。

症状 3:返回地址差 4 或 8 字节

这是异常类型和 LR 修正问题,不是 GIC 问题。记录:

1
2
3
4
异常类型
异常发生时 ARM/Thumb 状态
硬件写入的 LR_mode
软件实际使用的返回指令

再查 ARM ARM B1.8.3 的 Table B1-6: Exception return addresses 和 B1-1172 的 Table B1-7: Offsets applied to Link value for exceptions taken to PL1 modes

症状 4:改了 sp,切回模式后值“消失”

通常不是写失败,而是读者写的是另一个 bank。调试器停下后先读取 CPSR.M,再判断当前显示的是哪一个 SP_mode

最小现场打印

异常诊断至少记录:

1
2
3
4
5
6
CPU ID
当前 CPSR
异常前 SPSR
当前模式 SP/LR
修正后的返回 PC
异常类型

不要在尚未确认 IRQ 栈有效时直接调用复杂 printf


10. 本目标的掌握标准

不要求现在背下 CPSR 所有冷门位。达到下面标准即可进入后续实践:

  • 能默写主要模式编码:usr=10, fiq=11, irq=12, svc=13, abt=17, und=1b, sys=1f
  • 能画出 R0-R7R8-R12_fiq、各模式 SP/LR/SPSR 的 bank 关系。
  • 能解释模式切换是“映射变化”,不是寄存器值复制。
  • 能区分 CPSRSPSR_irq
  • 能说明 IRQ 硬件入口保存什么、没有保存什么。
  • 能说明为什么每个 CPU、每种异常模式都需要自己的栈。
  • 能区分 cps #0x12 与真正 IRQ 入口。
  • 看到 RFE 或带 S 的异常返回序列时,知道它在恢复 PC 和 CPSR。

30 秒复述版

ARMv7-A 当前模式由 CPSR.M 决定。模式改变时,同名 SP/LR 可能映射到不同的 bank;FIQ 还额外 bank 了 R8-R12。异常进入会把旧 CPSR 保存到目标模式 SPSR,把返回地址放到该模式 LR,切模式并跳向量,但不会自动保存 R0-R12。入口汇编用该模式 SP 建现场,返回时同时恢复 PC 和 CPSR;旧模式恢复后,原来的寄存器 bank 自动重新可见。双核则每个核各自拥有完整的一套上述状态。


11. 下一主题的接口

本目标解决的是“硬件为什么会切换当前可见寄存器”。下一主题 AAPCS32 解决:

1
2
3
4
5
C 函数调用时
哪些寄存器可以被被调函数改写
哪些必须保持
参数和返回值放在哪里
栈在函数边界必须满足什么条件

之后才能合理回答:普通任务切换为什么常保存 R4-R11/SP/LR,而异常入口为什么需要保存更完整的现场。

把概念放回真实启动与异常路径

五、目前已经明确的核心概念

1. “寄存器切换”的含义

当前目标中的“寄存器切换”,主要指:

ARMv7-A 改变处理器模式后,相同的寄存器名会映射到不同的物理 banked register。

例如,在汇编中始终写 sp,但是:

  • IRQ 模式下对应 SP_irq
  • SVC 模式下对应 SP_svc
  • Abort 模式下对应 SP_abt
  • User/System 模式下对应 SP_usr

这不是把一个 SP 的值自动复制到另一个 SP,而是当前模式改变了寄存器名到物理寄存器的映射。

2. 模式切换与任务上下文切换不同

  • 处理器模式切换:
    • ARM 架构硬件/特权指令机制
    • 例如 SVC → IRQ
    • 重点是 CPSR.M、SPSR、banked SP/LR
  • 操作系统任务上下文切换:
    • OS 软件机制
    • 把一个任务的寄存器保存到内存,再恢复另一个任务
    • 对应后续的“上下文与汇编指令”学习目标

两者都涉及寄存器,但不能混为一谈。

3. banked register 规则

1
2
3
4
5
6
7
8
R0-R7      所有模式共享
R8-R12 只有 FIQ 模式有独立 bank
R13/SP 各异常模式拥有独立 bank
R14/LR 各 PL1 异常模式拥有独立 bank
R15/PC 所有模式共享
CPSR 每个 CPU 核只有一个当前 CPSR
SPSR 每个异常模式各有一个
User/System 共享核心寄存器,并且没有 SPSR

4. CPSR 的当前理解

CPSR 保存当前处理器状态,重点字段包括:

1
2
3
4
5
6
7
8
9
10
N/Z/C/V    运算条件标志
Q 累积饱和标志
IT Thumb IT 块状态
J/T 当前指令集状态
GE SIMD 并行比较结果
E 数据访问端序
A 异步中止屏蔽
I IRQ 屏蔽
F FIQ 屏蔽
M[4:0] 当前处理器模式

当前最需要优先记忆的是低 10 位:

1
E A I F T M[4:0]

5. SPSR 的当前理解

异常进入时,处理器把异常前的 CPSR 自动保存到目标异常模式的 SPSR,例如:

1
2
3
4
IRQ: CPSR -> SPSR_irq
SVC: CPSR -> SPSR_svc
ABT: CPSR -> SPSR_abt
UND: CPSR -> SPSR_und

SPSR 的两个主要用途:

  • 异常处理程序检查异常发生前的状态。
  • 异常返回时恢复原 CPSR,从而恢复原模式、屏蔽状态和指令集状态。

七、多核与模式 bank 的关系

已经明确这是两个不同维度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CPU0 有自己完整的一套架构寄存器
├── SP_irq
├── SP_fiq
├── SP_svc
├── SP_abt
├── SP_und
└── SP_usr/sys

CPU1 也有自己完整的一套架构寄存器
├── SP_irq
├── SP_fiq
├── SP_svc
├── SP_abt
├── SP_und
└── SP_usr/sys
  • “CPU0/CPU1”表示每个核独立的处理器状态。
  • “IRQ/SVC/FIQ 等”表示一个核内部的模式 bank。
  • 工程启动代码按“CPU ID × 处理器模式”分别初始化栈。

九、交流中得到的重要纠正

纠正 1:cps #mode 不等于异常进入

cps #mode

  • 是特权软件主动修改模式。
  • 改变 CPSR.M。
  • 使当前可见的 banked register 随模式变化。
  • 不会自动保存旧 CPSR。
  • 不会自动生成异常返回地址。

异常进入:

  • 由处理器硬件按照异常类型改变模式。
  • 自动把旧 CPSR 保存到目标模式 SPSR。
  • 自动把首选返回地址保存到目标模式 LR,Hyp 除外。
  • 跳到相应异常向量。

纠正 2:硬件不会自动保存所有通用寄存器

异常发生时,硬件主要保存 CPSR 和返回地址,并切换模式。R0-R12 等工作寄存器仍需异常入口汇编根据软件设计保存。

纠正 3:System 模式不是异常模式

System 与 User 共享核心寄存器,但 System 是 PL1 特权模式。没有异常会默认进入 System,它通常由操作系统显式切换使用。

从规则走到实际实现

下面回到本章对应的小系统实现。这里不再假定读者已经打开源码,而是把关键地址、数据结构、指令序列和返回路径直接放在文章中解释。

模式、指令状态、安全状态是三个问题

处理器模式由 CPSR.M[4:0] 表示,它影响权限与 banked 寄存器的选择。ARM/Thumb 指令状态主要由 CPSR.T 等状态位体现,决定指令如何解释。TrustZone 的 Secure/Non-secure 是安全状态,不能从“正在 SVC 模式”推导出来。

例如一个任务可以处于 SVC 模式、ARM 指令状态,并使用普通小端数据访问。切到 IRQ 模式是异常处理;从 ARM 函数调用 Thumb 函数是指令状态切换;进入安全监控路径又涉及另一套规则。

当前工程统一使用 -marm,任务运行在 SVC 模式,没有实现用户态和安全监控固件。

Cortex-A9 需要认识哪些模式

模式 M 编码 寄存器特点 典型用途
User 0x10 使用 usr 组 SP/LR,无 SPSR 非特权应用
FIQ 0x11 独立 r8-r14、SPSR_fiq 快速中断
IRQ 0x12 独立 SP/LR、SPSR_irq 普通中断
SVC 0x13 独立 SP/LR、SPSR_svc 内核与 SVC 异常
Monitor 0x16 安全扩展相关 bank 安全状态切换
Abort 0x17 独立 SP/LR、SPSR_abt 取指或数据访问异常
Undefined 0x1b 独立 SP/LR、SPSR_und 未定义指令
System 0x1f 与 User 共享寄存器组,无 SPSR 特权下操作 usr 组寄存器

User 与 System 的寄存器组相同,权限不同。System 是特权模式,但不是某类异常的自动入口目标。

ARMv7-A 架构手册还会列出 Hyp 模式。那属于实现虚拟化扩展的处理器范围,Cortex-A9 不实现该扩展。不能因为 ARM ARM 画出了 ELR_hyp,就给 Zynq-7020 安排一条 Hyp 启动路径。

同一个寄存器名,可能对应不同存储

对本工程涉及的经典模式,可按下面的规律记:

1
2
3
4
5
6
7
r0-r7     普通模式间共享
r8-r12 FIQ 有自己的 bank,其他这里讨论的模式共享
r13/sp User/System 共用;各异常模式有自己的 bank
r14/lr User/System 共用;各异常模式有自己的 bank
r15/pc 不是每个模式各保存一份任务 PC
CPSR 当前 CPU 正在执行的状态
SPSR_xxx 对应异常模式保存的异常前状态

“共享”限定在同一个核上。CPU0 与 CPU1 各自拥有寄存器文件;CPU0 的 r0 不会因为 CPU1 写 r0 而变化。寄存器银行切换与多核是两个不同维度。

假定 SVC 模式下 sp_svc=0x00120000sp_irq=0x00130000r4=0x12345678。执行:

1
cps #0x12

之后通过名字 sp 看到的是 0x00130000r4 仍是 0x12345678sp_svc 没有消失,只是当前名字 sp 不再选中它。切回 SVC 又能看到原值。

这一步没有把寄存器压进内存,也没有保存一个软件任务。硬件 bank 的数量是固定的,不可能为每一个线程分配一个 SVC bank。

启动代码怎样初始化这些 bank

基础版 src/start.S 先读 MPIDR,随后按模式和 CPU 选择栈。下面摘出两个模式:

1
2
3
4
5
6
7
8
9
cps #0x12
cmp r4, #0
ldreq sp, =__stack_irq_cpu0_top
ldrne sp, =__stack_irq_cpu1_top

cps #0x13
cmp r4, #0
ldreq sp, =__stack_svc_cpu0_top
ldrne sp, =__stack_svc_cpu1_top

第一段写当前 CPU 的 sp_irq,第二段写 sp_svc。链接脚本为两个核分别预留内存,两份栈既不能地址相同,也不能互相重叠。

这里有两个条件需要同时满足:处理器已处于允许执行 CPS 的特权环境;对应 RAM 已经能访问,链接符号落在实际可用内存里。SP 初始化只意味着寄存器装入了一个数,能否安全压栈,要到第一次访存才真正验证。

CPSR 各字段怎样参与系统运行

名称 在本系列中的意义
31 N 算术结果符号
30 Z 结果为零
29 C 加法进位;减法通常表示没有借位
28 V 有符号溢出
27 Q 饱和运算的粘滞标志
26:25、15:10 IT Thumb IT 块状态
24 J 与特定指令执行状态有关,当前代码不用
19:16 GE 部分并行算术指令的比较状态
9 E 数据访问端序控制
8 A 异步 Abort 屏蔽控制
7 I IRQ 屏蔽,1 表示屏蔽
6 F FIQ 屏蔽,1 表示屏蔽
5 T Thumb 状态指示
4:0 M 处理器模式

保留位与未实现扩展不能随意写固定值。尤其不能把 A 当成“所有 Abort 的总开关”:同步地址转换故障不会因为 A=1 就消失。

前四位很容易在异常代码中被忽略。假设任务执行:

1
2
3
cmp r0, #0
@ 此处到来 IRQ
beq zero_case

IRQ handler 中也会执行比较和算术。如果返回时只恢复 PC,不恢复原来的 N/Z/C/V,beq 就可能使用 handler 留下的标志。这解释了为什么异步返回必须恢复 CPSR,而普通 C 函数调用一般不承诺保留条件标志。

另一个容易读反的是 I:I=1 表示屏蔽 IRQ。调试时看到 CPSR 低字节为 0xd3,可拆成:

1
2
3
4
0x13  SVC 模式
0x40 FIQ 屏蔽
0x80 IRQ 屏蔽
T=0 ARM 状态

因此 0xd3 常用于暂时不接受 IRQ 的 SVC 现场;0x53 则允许 IRQ,仍屏蔽 FIQ。这里只解释这些位,不意味着完整 CPSR 一定只有这一字节。

MRS、MSR、CPS 的职责

1
2
3
4
5
mrs r0, cpsr        @ 读取当前状态
mrs r1, spsr @ 读取当前异常模式的保存状态
cpsid i @ 屏蔽 IRQ
cpsie i @ 允许 IRQ
cps #0x13 @ 切到 SVC

mrs r1, spsr 的含义依赖当前模式。在 IRQ 模式读取的是 SPSR_irq;切到 SVC 后再读,得到的是 SPSR_svc。User/System 没有自己的 SPSR,不要把它当成任意位置都可读取的“上一个状态”。

MSR 还能选择写 CPSR 的哪些字段。例如工程普通 context 恢复使用:

1
msr cpsr_c, r1

这里写控制字段,不是恢复整份算术标志。对一个遵守 ABI 的普通函数调用边界,这与“条件标志不保证跨调用保持”的约定相符。但它不能替代异步中断的完整状态恢复。

也不能通过随意写 T 位,把正在执行的 ARM 指令流原地变成 Thumb。普通代码的 ARM/Thumb 互操作应使用相应分支机制;异常返回则使用架构规定的状态恢复路径。

SPSR 是快照,但只有一份

IRQ 发生时,硬件把异常前 CPSR 保存到 SPSR_irq,把返回相关地址放进 LR_irq,并进入 IRQ 模式。软件因此能取得异常前的执行状态。

每个核只有一份 SPSR_irq 和 LR_irq。若允许下一次 IRQ 在它们尚未转移到内存前到来,新异常会覆盖旧信息。支持嵌套中断的实现,必须先解决这个所有权问题,再重新允许 IRQ。

仅仅执行 cps #0x12 不会自动完成上述异常快照。手动切模式和硬件异常入口不能当成完全相同的操作。

一次 IRQ 的寄存器视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
任务 A:SVC
sp → A 的任务栈
lr → A 的函数返回位置
CPSR → A 的执行状态

硬件接收 IRQ
CPSR_old → SPSR_irq
返回信息 → LR_irq
M → IRQ,使用 sp_irq/lr_irq
PC → IRQ 向量

入口汇编
把 PC/CPSR 转移到 A 的 SVC 栈
切回 SVC,保存 A 的通用寄存器
切到独立的软件中断栈调用 C

异常返回
从选中任务现场恢复 PC/CPSR
恢复对应模式及其寄存器视图

“切到独立的软件中断栈”不一定意味着当前模式再次变成 IRQ。本工程在 SVC 模式下,把 SVC 的 SP 临时设置到一块专用于中断处理的内存。栈区的名字和 CPU 当前模式必须分别观察。

模式切换和任务切换的分界

动作 改变什么 是否足以换线程
cps #IRQ 模式与 bank 可见性
硬件 IRQ 入口 保存有限返回信息,切模式与向量
_CPU_Context_switch 软件保存旧状态,恢复新任务栈与继续点 是,在其约束内
IRQ 尾部选择另一 frame 从新任务的 PC/CPSR 返回 是,在完整现场已保存的前提下

两个任务都运行在 SVC 时,也能进行软件切换;它们轮流使用同一个硬件 SVC bank,把暂时不用的状态保存在不同内存对象中。

复查时用三个小实验

第一,在 _start 的两个 CPS 附近单步,分别记录 CPSR & 0x1f 和 SP。切模式会改变当前 SP 视图,但不会自动改变普通 r4 的值。

第二,在 IRQ 入口第一条指令停住,读取 SPSR 与 LR;继续执行到 cps #SVC 后再看 CPSR。不要把此时的 SVC LR 当成刚才的 IRQ LR。

第三,在 RFE 前检查内存里的 return_pc 与 cpsr,再单步返回。此时恢复的不只是地址,也包括中断屏蔽、模式与条件标志。

原图可对照 Programmer’s Guide 第 4 章的 The ARM register setCPSR bits,以及 ARM ARM B1.3 的 ARM core registers, PSRs, and ELR_hyp, showing register banking。后者画的是更广的架构集合,阅读时要排除 A9 未实现的扩展。

下一篇:AAPCS32 与关键汇编

重点回看

处理器模式切换改变的是 banked 寄存器的可见窗口,不是把一组寄存器复制到另一组。CPSR 描述当前执行状态,异常模式的 SPSR 保存异常前状态;IRQ 返回必须同时恢复地址与 CPSR。双核又在模式 bank 之外增加了每核独立性。

本章总结

处理器模式切换改变的是 banked 寄存器的可见窗口,不是把一组寄存器复制到另一组。CPSR 描述当前执行状态,异常模式的 SPSR 保存异常前状态;IRQ 返回必须同时恢复地址与 CPSR。双核又在模式 bank 之外增加了每核独立性。

下一章进入 AAPCS32,说明不发生异常时,C 与汇编如何约定寄存器和栈。

ARMv7-A小系统实践(二):处理器模式、寄存器组与CPSR

https://goko-son626.github.io/post/armv7a-01-registers-modes.html

作者

GoKo Mell

发布于

2026-06-13

更新于

2026-06-13

许可协议

评论

:D 一言句子获取中...