一、ARM架构概述:从处理器状态到寄存器模型

ARM(Advanced RISC Machine)架构是当前嵌入式、移动终端、物联网以及部分服务器领域使用最广泛的处理器架构之一。与 x86 采用的复杂指令集(CISC)不同,ARM 属于典型精简指令集架构(RISC),其核心设计思想包括:指令长度固定、寻址方式相对简单、大量使用寄存器、用加载/存储指令访问内存等。理解 ARM 架构的关键,首先在于理解处理器的工作状态和寄存器组织模型,它们是操作系统移植、裸机编程、异常处理以及汇编级调试的基础。

在 ARM 体系结构中,处理器状态决定了处理器当前执行哪一类指令集、以怎样的方式访问寄存器;工作模式则决定了处理器当前处于怎样的权限级别,以及能够访问哪些寄存器。对于准备架构方向面试的同学来说,有三个概念最常被问起:ARM 的三大基本状态七种处理器工作模式以及37 个物理寄存器的组织方式。这三个概念共同构成了 ARM 程序员模型的核心,也是理解后续异常向量表、上下文切换、中断处理等知识的前提。

本节先给出一个总体框架,后续章节会逐步展开:

  • 三大基本状态:ARM 状态、Thumb 状态、异常状态(以及扩展的调试状态、Thumb-2、Jazelle 等)。
  • 七种工作模式:用户模式、FIQ、IRQ、管理模式、中止模式、未定义模式、系统模式。
  • 寄存器模型:R0-R15 共 16 个通用寄存器,加上 CPSR 和 SPSR 状态寄存器;在带影子寄存器的模式下物理寄存器总数达到 37 个。

下面的内容将按照“状态”“模式”“寄存器”三个维度逐层展开,并配有寄存器映射表、汇编示例和面试题解析。文章以 ARMv7 的 32 位经典模型为主线,同时在最后单独说明 AArch64(64 位)的主要变化,帮助读者既掌握经典八股考点,也能覆盖 64 位体系结构的新增内容。

二、ARM 处理器的三大基本状态

ARM 处理器在工作时,会根据当前执行代码的类型和所处的执行环境,处于不同的处理状态。三大基本状态通常指:ARM 状态Thumb 状态异常状态。其中 ARM 状态和 Thumb 状态是两种正常的指令执行状态,异常状态则是处理器响应异常后进入的特殊执行状态。此外,ARM 还定义了调试状态、Jazelle 状态等扩展状态。下面逐一说明。

2.1 ARM 状态:32 位指令执行状态

ARM 状态是 ARM 处理器的默认执行状态。当处理器处于 ARM 状态时,执行的指令是 32 位宽度的 ARM 指令集,所有指令都按 32 位对齐,即指令地址最低两位为 00。ARM 指令集的特点是指令功能相对丰富,多数指令支持条件执行,例如可以用 ADDEQSUBNE 这样的条件后缀实现分支预测的替代方案。

ARM 状态下的几条典型指令如下:

/* ARM 状态示例:32 位指令,地址 4 字节对齐 */
        MOV     R0, #10         // R0 = 10
        ADD     R1, R0, #5      // R1 = R0 + 5
        LDR     R2, [R1]        // R2 = *(R1)
        CMP     R0, #10         // 比较 R0 与 10
        BLEQ    equal_label     // 若相等则跳转

ARM 状态的优势在于指令表达能力更强,单条指令可以完成更复杂的操作,例如带移位的加法 ADD R0, R1, R2, LSL #2 可以在一个周期内完成移位和加法。缺点则是代码密度较低,同样的程序在 ARM 状态下编译出的代码体积通常大于 Thumb 状态。

处理器复位后进入哪种状态,取决于具体实现和启动配置。在大多数 ARMv7 处理器中,复位后默认进入管理模式(Supervisor),执行状态通常由系统控制寄存器决定。对于裸机启动代码来说,一段典型的启动流程就是:复位后先进入 ARM 状态执行初始化,再根据需求切换到 Thumb 状态执行应用代码,以减小代码体积。

2.2 Thumb 状态:16 位指令执行状态

Thumb 状态是 ARM 为了解决代码密度问题而引入的一种执行状态。处于 Thumb 状态时,处理器执行的是 16 位宽度的 Thumb 指令集,指令按 16 位对齐,即指令地址最低一位为 0。Thumb 指令集可以理解为 ARM 指令集的一个子集,它将最常用的指令压缩为 16 位,从而显著减小代码体积,这对于 Flash 容量有限的嵌入式系统尤其重要。

Thumb 状态下的典型代码:

/* Thumb 状态示例:16 位指令,地址 2 字节对齐 */
        .thumb
        MOVS    R0, #10         // R0 = 10,并更新标志位
        ADDS    R1, R0, #5      // R1 = R0 + 5
        LDR     R2, [R1]        // R2 = *(R1)
        CMP     R0, #10
        BEQ     equal_label

与 ARM 状态相比,Thumb 状态的最大特点是:多数数据处理指令会隐式更新 CPSR 中的条件标志位(如 MOVSADDS 中的 S 后缀在 Thumb-1 里是常见写法);同时 Thumb-1 指令的条件执行能力较弱,例如只有跳转指令支持条件执行。因此,在纯 Thumb-1 时代,程序员经常需要在代码密度和指令表达力之间做权衡。

实际上,在现代 ARMv7 处理器中,大量应用代码运行在 Thumb 状态,因为 Thumb-2 技术(后文会继续说明)已经解决了 Thumb-1 表达力不足的问题,同时保留了较高的代码密度。

2.3 异常状态与调试状态

异常状态是处理器在响应异常时的特殊执行状态。当发生中断、数据中止、未定义指令、软件中断等异常时,处理器会暂停当前的正常指令流,保存现场,并根据异常类型跳转到对应的异常向量入口。异常处理过程中,处理器通常会切换到相应的异常模式,例如 IRQ 异常进入 IRQ 模式,数据中止进入 Abort 模式。异常状态的本质并不仅仅是“换一种指令集”,更重要的是“换一套执行上下文”:程序计数器、栈指针、链接寄存器和状态寄存器都可能切换到对应模式的影子寄存器。

异常处理完成后,处理器通过恢复 PC、CPSR 等寄存器,返回到被打断的指令流继续执行。因此,异常状态和异常模式是紧密关联的:状态回答“执行什么”,模式回答“以什么权限执行”

调试状态则是处理器进入硬件调试时的状态。当调试器通过 JTAG 或 SWD 接口发出调试请求,或处理器执行到断点、观察点时,会进入调试状态。在调试状态下,处理器暂停正常取指执行,核心寄存器可以被调试器读写,内存和外围设备也可以被访问。调试状态对于理解寄存器模型很有帮助,因为调试器显示的所有寄存器值,正是本节要讨论的 37 个物理寄存器组织。

2.4 Thumb-2 技术:混合长度指令

Thumb-2 是 ARMv7 架构引入的重要扩展,它允许在 Thumb 状态下混合执行 16 位和 32 位指令。Thumb-2 技术从根本上解决了 Thumb-1 表达能力不足的问题:原来只能在 ARM 状态下使用的一些复杂指令和条件执行能力,现在也能在 Thumb 状态下通过 32 位 Thumb 指令来完成。

需要强调:Thumb-2 并不是一种独立于 Thumb 的“第三种正常状态”,它仍然属于 Thumb 状态,只是指令宽度突破了 16 位限制。很多面试题会设置这个陷阱,问“ARM 有哪几种状态”,如果回答“ARM 状态、Thumb 状态、Thumb-2 状态”就会出错,因为 Thumb-2 只是 Thumb 状态的扩展,不是并列的新状态。

Thumb-2 的典型指令示例:

/* Thumb-2 状态下可以使用 32 位指令,实现条件执行和移位等复杂操作 */
        ITE     EQ              // if-then-else 前缀
        MOVEQ   R0, #1
        MOVNE   R0, #0
        ADD     R1, R2, R3, LSL #2   // 32 位 Thumb 指令

由于 Thumb-2 兼具 ARM 状态的功能和 Thumb 状态的代码密度,现代 ARMv7 应用通常整体运行在 Thumb-2 状态下,而内核底层某些关键代码仍可能运行在 ARM 状态。这也解释了为什么面试中经常追问“为什么现代嵌入式程序大多编译成 Thumb/Thumb-2”。

2.5 Jazelle、ThumbEE 与安全扩展状态

除了 ARM 和 Thumb 两大基本执行状态,ARM 还定义了若干扩展执行状态。了解这些有助于在面试中体现知识广度,但应分清主次。

Jazelle 状态:面向 Java 字节码的硬件加速状态,处理器可以直接执行部分 Java 字节码。Jazelle 状态与 ARM/Thumb 状态并列,其启用和返回通过专门的指令 BXJ 控制。实际产品中使用较少,现代处理器更多通过软件 JIT 来执行 Java。

ThumbEE 状态:Thumb Execution Environment,是针对 JIT 执行环境优化的 Thumb 变体状态,增加了对空指针检查、数组越界检查等语义的支持。ThumbEE 已经处于被淘汰的边缘,在 ARMv8 中不再支持。

安全扩展状态:在支持 TrustZone 的 ARMv7 处理器中,存在安全世界(Secure World)和非安全世界(Normal World)两个执行环境,通过监控模式(Monitor Mode)进行切换。这里的“安全/非安全世界”不是指令集状态,而是执行环境状态,但面试中常与处理状态、工作模式一起被问到,读者需要能区分:安全扩展解决的是隔离问题,不改变 ARM/Thumb 指令集状态本身。

2.6 三大状态的总结对比

比较维度ARM 状态Thumb 状态异常状态/调试状态
指令宽度32 位16 位(Thumb-2 可混合 32 位)取决于处理程序代码
指令对齐4 字节对齐2 字节对齐与状态相关
代码密度较低较高不适用
条件执行绝大多数指令支持早期受限,Thumb-2 后增强不适用
典型用途启动代码、性能关键路径应用代码、固件主体中断、异常、调试处理

回到最初的八股考点:如果面试官问“ARM 处理器的三大基本状态是什么”,最稳妥的回答是:ARM 状态、Thumb 状态、异常状态。若面试官继续追问扩展状态,可以补充调试状态、Jazelle 状态、ThumbEE 状态,并说明 Thumb-2 属于 Thumb 状态的扩展而非并列状态,这个细节往往是加分项。

三、ARM 处理器的七种工作模式

工作模式决定了处理器的运行权限以及可访问的寄存器集合。ARMv7 经典模型定义了七种工作模式,其中用户模式为非特权模式,其余六种为特权模式。进入特权模式的主要途径是异常和模式切换指令。理解七种模式的名称、用途和寄存器差异,是掌握 ARM 异常处理与系统编程的基础。

3.1 用户模式(User Mode)

用户模式是普通应用程序运行的模式,是七种模式中唯一不具备特权、不能直接访问系统资源的模式。在用户模式下,程序不能修改 CPSR 中的模式位切换到其他特权模式,不能直接访问受保护的系统寄存器,也不能执行某些特权指令。用户模式的特点是没有对应的影子寄存器和 SPSR,应用需要系统调用(SWI/SVC)才能请求内核服务。

用户模式和系统模式共享完全相同的寄存器集合,区别仅在于:用户模式是非特权模式,系统模式是特权模式。很多操作系统在完成初始化后,会让内核任务运行在系统模式,让应用任务运行在用户模式。

3.2 快速中断模式(FIQ Mode)

FIQ 是 Fast Interrupt Request 的缩写,用于处理需要快速响应的中断。FIQ 模式提供了最多的私有影子寄存器(R8 到 R14 共 7 个),这使得 FIQ 处理程序可以在不保存太多现场的情况下立即使用这些私有寄存器,从而降低中断延迟。此外,在异常向量表中,FIQ 向量位于最后,可以紧接向量表之后直接放置 FIQ 处理函数,避免一次额外跳转。

FIQ 模式的典型用途包括实时性要求极高的外设,例如某些通信控制器的缓冲区管理。不过在现代 SoC 中,是否使用 FIQ 取决于中断控制器的配置和系统设计,许多平台甚至只使用 IRQ。

3.3 普通中断模式(IRQ Mode)

IRQ 是普通外部中断的处理模式。当外设产生中断时,处理器若允许响应,会进入 IRQ 模式并在 IRQ 异常向量处执行处理程序。IRQ 模式拥有自己的 R13_irq、R14_irq 和 SPSR_irq,其中 R13_irq 是 IRQ 模式专用栈指针,R14_irq 保存异常返回地址,SPSR_irq 保存被打断时的 CPSR。

与 FIQ 相比,IRQ 模式私有的影子寄存器较少,因此 IRQ 处理程序通常需要在入口处手动保存更多现场。实际系统中,绝大多数外设中断都通过 IRQ 处理。

3.4 管理模式(Supervisor / SVC Mode)

管理模式是 ARM 处理器复位后的默认模式,也是操作系统内核常用的特权模式。在 ARMv7 中,原来的 SWI(软件中断)指令改名为 SVC,但管理模式名称仍沿用 Supervisor。软件通过执行 SWI/SVC 指令触发软件中断,处理器进入管理模式,内核由此执行系统调用服务。管理模式拥有私有的 R13_svc、R14_svc 和 SPSR_svc。

由于管理模式权限最高、操作最自由,启动代码通常运行在管理模式下完成时钟、内存、栈和中断向量表的初始化,然后再切换到其他模式,并为各种模式建立自己的栈。

3.5 中止模式(Abort Mode)

中止模式用于处理取指失败或数据访问失败。具体分为两类异常:预取中止(Prefetch Abort),发生在取指令阶段;数据中止(Data Abort),发生在读写数据阶段。中止模式拥有私有的 R13_abt、R14_abt 和 SPSR_abt。

数据中止是内存保护和虚拟内存管理的基础。例如,当进程访问了无权限或未映射的地址时,MMU 会产生数据中止,处理器进入 Abort 模式,内核可以在异常处理程序中识别缺页故障并完成页面加载。预取中止则通常与非法指令地址有关,但很多系统对预取中止采取延迟处理策略。

3.6 未定义模式(Undefined Mode)

未定义模式在处理器遇到无法识别的指令时进入。典型场景包括:执行了未定义的指令编码、执行了协处理器指令但协处理器不存在或未使能、某些扩展指令在未授权状态下被执行。未定义模式拥有私有的 R13_und、R14_und 和 SPSR_und。

未定义指令异常有一个重要用途:指令集模拟。内核对未定义指令做解码后,可以在软件中模拟协处理器功能或扩展指令,模拟完成后返回被中断的指令流。早期的浮点模拟就是通过这种方式实现的。

3.7 系统模式(System Mode)

系统模式是与用户模式共享同一套寄存器的特权模式,它的引入是为了让内核代码可以在不切换寄存器视图的情况下运行在特权级别。系统模式没有私有的影子寄存器,也没有自己的 SPSR。操作系统内核的任务切换常借助系统模式:在系统模式下,内核仍使用与用户任务相同的 R0-R15,便于上下文操作,同时拥有执行特权指令的能力。

需要特别注意:系统模式不能通过异常自然进入,它只能通过修改 CPSR 模式位主动切换进入。这也是系统模式与用户模式虽然寄存器相同、但权限不同的一个关键点。

3.8 七种模式总结

模式名称缩写权限进入方式私有寄存器
用户模式usr非特权正常执行
FIQ 模式fiq特权FIQ 异常R8-R14、SPSR
IRQ 模式irq特权IRQ 异常R13、R14、SPSR
管理模式svc特权复位、SWI/SVCR13、R14、SPSR
中止模式abt特权取指/数据中止R13、R14、SPSR
未定义模式und特权未定义指令R13、R14、SPSR
系统模式sys特权修改 CPSR 主动切换

这七种模式是 ARMv7 32 位体系结构的基础。后续章节会看到,寄存器分组正是围绕这七种模式展开的:不同模式的私有影子寄存器组合叠加在公共寄存器之上,构成整个程序员可见寄存器模型。

四、ARM 寄存器组织总览

ARM 架构的寄存器模型是面试中的绝对重点,尤其是“37 个寄存器”这个数字。先澄清一个常见误区:37 指的是物理寄存器总数,但程序员在任意一个模式下能够同时看到的只是其中一部分,也就是“当前模式的寄存器视图”。不同模式共享一部分寄存器,同时拥有自己的影子寄存器。

为便于理解,可以从三个层次拆解寄存器模型:

  • 通用寄存器:R0-R15,其中 R13 通常用作栈指针 SP,R14 是链接寄存器 LR,R15 是程序计数器 PC。
  • 状态寄存器:CPSR 是当前程序状态寄存器,SPSR 是各特权模式下保存中断前 CPSR 的备份寄存器。
  • 影子寄存器:各特权模式私有的一组寄存器,用于减少异常处理时的现场保存开销。

下面先介绍通用寄存器,再深入状态寄存器,最后给出完整的模式与寄存器映射关系。

4.1 通用寄存器 R0-R12

R0 到 R12 是真正的通用寄存器,可用于数据运算、地址计算和临时存储。从体系结构角度看,它们没有特殊的硬件功能,但在软件开发中有约定俗成的用途。按照 AAPCS(ARM 体系结构过程调用标准),R0-R3 用于传递函数参数和保存返回值,R4-R11 为被调用者保存寄存器,R12 通常作为暂存寄存器,R13-R15 则有特殊作用。

AAPCS 分配如下:

寄存器别名调用约定说明
R0-R3a1-a4参数/返回值前四个参数;R0 兼作返回值
R4-R11v1-v8被调用者保存函数使用前需保存原值
R12ip调用者/暂存过程内调用暂存寄存器
R13sp栈指针指向当前栈顶
R14lr链接寄存器保存函数返回地址
R15pc程序计数器保存当前取指地址

了解这些约定对阅读汇编代码和应对面试都有帮助。例如,面试官可能会问:“为什么函数调用时 R4-R11 需要被调用者保存?”答案正是为了在函数调用链中维持上层函数的局部变量,降低调用开销的同时保持寄存器分配的清晰性。

4.2 程序计数器 PC(R15)

R15 是程序计数器,保存着处理器即将执行的指令地址。在 ARM 状态下,PC 的值通常比当前执行指令的地址大 8 字节(由于三级流水线:取指、译码、执行),在 Thumb 状态下通常大 4 字节。这个细节在汇编编程中非常重要,例如读取 PC 用于位置无关代码、计算跳转地址等。

示例:读取 PC 实现位置无关寻址:

/* ARM 状态下读取 PC 获取当前地址 */
        ADR     R0, target_label    // 伪指令,汇编器转换为基于 PC 的加法
        LDR     R1, =target_label   // 通过常量池加载绝对地址

由于 PC 直接参与取指,修改 PC 本质上就是执行跳转。几乎所有跳转指令最终都是通过修改 PC 完成的:BBL 修改 PC 的同时,BL 还先把返回地址写入 LR;BX 修改 PC 并可切换 ARM/Thumb 状态。

4.3 栈指针 SP(R13)

R13 在体系结构层面虽然也是通用寄存器,但按约定作为栈指针使用。ARM 的栈操作支持四种增长方向:满递增、空递增、满递减、空递减。其中嵌入式系统最常用的是满递减栈(Full Descending),栈指针指向最后一个压入的数据,向低地址增长。

各特权模式都拥有自己的 R13 影子寄存器,以保证异常处理时使用独立的内核栈,而不会破坏被打断程序的用户栈。这正是 R13_irq、R13_svc、R13_fiq 等寄存器存在的意义。初始化时,启动代码必须为每一种用到的模式设置自己的栈区,否则一旦发生异常,处理器会使用未初始化甚至非法的栈地址。

典型压栈和出栈指令:

/* 满递减栈示例 */
        PUSH    {R0-R3, LR}     // 将 R0-R3、LR 压栈,SP 递减
        POP     {R0-R3, PC}     // 出栈并直接恢复 PC,实现函数返回

4.4 链接寄存器 LR(R14)

R14 是链接寄存器,用于保存子程序调用的返回地址。当执行 BL 指令时,处理器自动把下一条指令的地址写入 LR,随后修改 PC 跳转到目标函数;函数返回时,通过 MOV PC, LRBX LR 恢复 PC,实现返回。

在异常处理中,各异常模式拥有自己的 R14 影子寄存器,用于保存异常返回地址。对于不同的异常,R14 保存的返回地址需要经过不同修正:例如 IRQ 异常返回时通常将 R14 减 4 作为返回地址,数据中止可能需要减 8。这也是异常处理代码中经常出现 SUBS PC, LR, #4 这类指令的原因所在。

函数调用与返回示例:

        BL      my_func         // 跳转并保存返回地址到 LR
        // 返回后继续执行

my_func:
        PUSH    {R0-R3, LR}     // 保存现场
        // 函数体
        POP     {R0-R3, LR}     // 恢复现场
        BX      LR              // 返回调用点

五、状态寄存器 CPSR 与 SPSR 详解

状态寄存器是 ARM 异常处理和模式切换的核心。CPSR(Current Program Status Register)保存当前处理器的状态信息,包括条件码标志、中断使能位、Thumb 状态位、工作模式位等;SPSR(Saved Program Status Register)在异常发生时用于保存异常前 CPSR 的内容,便于异常返回时恢复现场。

在七种经典模式中,用户模式和系统模式没有 SPSR,其余五种异常模式各有一个 SPSR。从物理寄存器角度看,共有 1 个 CPSR 和 5 个 SPSR,因此在 16 个通用寄存器(R0-R15)加 6 个状态寄存器的基础上,叠加上各模式的影子寄存器,就构成了 37 个物理寄存器。下一节会具体拆解这个数字。

5.1 CPSR 的位域划分

CPSR 共 32 位,划分为四个区域:标志位区、状态位区、扩展区和控制区。核心位域如下:

位域名称含义
N(bit31)负标志结果为负数时置 1
Z(bit30)零标志结果为零时置 1
C(bit29)进位/借位标志加法进位或移位移出位时置 1
V(bit28)溢出标志有符号数运算溢出时置 1
Q(bit27)饱和标志饱和运算发生饱和时置 1
I(bit7)IRQ 禁止位为 1 时禁止 IRQ 中断
F(bit6)FIQ 禁止位为 1 时禁止 FIQ 中断
T(bit5)状态位为 1 表示 Thumb 状态,为 0 表示 ARM 状态
M[4:0](bit4-0)模式位编码当前处理器工作模式

其中 N、Z、C、V 四个条件码标志是条件执行的基础。比较指令 CMP 和带 S 后缀的数据处理指令会更新这些标志,随后条件分支指令根据标志决定是否跳转。

5.2 条件码标志详解

条件码标志的理解不能死记硬背,要结合具体运算:

  • N:运算结果的最高位。对于有符号数,结果最高位为 1 表示负数。
  • Z:运算结果是否为 0。相等比较 CMP R0, R1 后,若 R0 等于 R1,则 Z 置 1。
  • C:加法产生进位、减法不产生借位、移位移出 1 时置位。无符号比较时,若发生借位则 C 清零。
  • V:有符号运算溢出。例如两个正数相加结果为负,或两个负数相加结果为正。

条件后缀示例:

        CMP     R0, R1          // 比较
        BEQ     equal_case      // Z=1,相等
        BNE     not_equal       // Z=0,不相等
        BHI     higher_case     // C=1 且 Z=0,无符号大于
        BLS     lower_same      // C=0 或 Z=1,无符号小于等于
        BGE     greater_equal   // N=V,有符号大于等于
        BLT     less_than       // N!=V,有符号小于

有符号与无符号使用不同条件后缀,这是面试中的高频考点。无符号数比较看 C 和 Z,有符号数比较看 N、V 和 ZBHI 对应无符号大于,BGT 对应有符号大于,两者不能混用。

5.3 控制位与模式位

CPSR 的低 8 位通常称为控制域,包括 I、F、T 和 M[4:0]:

  • I 位:置 1 时处理器不再响应普通 IRQ 中断。典型用法是在临界区或异常处理入口关闭 IRQ,防止嵌套干扰。
  • F 位:置 1 时禁止 FIQ 中断。FIQ 优先级通常高于 IRQ,用于更紧急的任务。
  • T 位:标志当前是 ARM 状态还是 Thumb 状态。T=0 为 ARM 状态,T=1 为 Thumb 状态。
  • M[4:0]:工作模式编码,对应七种模式。

模式位编码如下:

M[4:0]模式说明
10000用户模式usr
10001FIQ 模式fiq
10010IRQ 模式irq
10011管理模式svc
10111中止模式abt
11011未定义模式und
11111系统模式sys

修改 CPSR 模式位即可切换工作模式。典型模式切换代码:

/* 切换到管理模式并设置 SVC 栈 */
        MRS     R0, CPSR
        BIC     R0, R0, #0x1F
        ORR     R0, R0, #0x13    // SVC 模式
        MSR     CPSR_c, R0
        LDR     SP, =svc_stack_top

这里 MSR CPSR_c 中的 _c 表示只更新控制域,避免误改条件码标志,是系统编程中的常见写法。

5.4 SPSR:异常现场保存

进入异常模式时,处理器会自动把异常发生前的 CPSR 复制到对应模式的 SPSR 中,同时把返回地址写入对应模式的 LR。异常处理程序结束返回时,需要通过恢复 SPSR 来恢复被打断程序的状态寄存器,通过恢复 PC 来返回断点。

例如 IRQ 中断返回的典型序列:

irq_handler:
        SUB     LR, LR, #4      // 修正返回地址
        PUSH    {R0-R3, R12, LR}
        // 处理中断
        POP     {R0-R3, R12, LR}
        SUBS    PC, LR, #4      // 同时恢复 PC 和 CPSR(带 S 后缀)

SUBS PC, LR, #4 中的 S 后缀在将目标寄存器写为 PC 时,会同时把 SPSR 恢复到 CPSR,这是一次性完成返回和现场恢复的核心机制。如果写程序时漏掉 S,异常返回后 CPSR 不会被恢复,中断屏蔽位和处理器模式都可能处于错误状态,导致严重问题。

六、37 个寄存器的完整映射关系

这一节把前面分散的内容汇总起来,给出 ARMv7 经典模型中 37 个物理寄存器与七种模式的完整对应关系。面试官最喜欢问的题目之一就是:“请画出 ARM 的寄存器组织图”或“说明 37 个寄存器分别是哪些”。掌握本节内容,这类题目可以完整作答。

6.1 用户模式与系统模式的寄存器视图

用户模式和系统模式共享完全相同的寄存器集合,这是七种模式中唯一不需要影子寄存器的一组:

R0  R1  R2  R3  R4  R5  R6  R7
R8  R9  R10 R11 R12 SP  LR  PC
CPSR

用户模式与系统模式的区别仅在于权限:系统模式是特权模式,可以执行特权指令、访问受保护资源,并且可以通过修改 CPSR 切换到其他模式;用户模式则受到严格限制。

6.2 FIQ 模式的寄存器视图

FIQ 模式拥有最多的影子寄存器,从 R8 到 R14 共 7 个私有寄存器,加上私有的 SPSR_fiq。这使得 FIQ 处理程序可以在几乎不保存现场的情况下使用 R8-R12 作为工作寄存器,极大降低响应延迟。

R0  R1  R2  R3  R4  R5  R6  R7
R8_fiq  R9_fiq  R10_fiq R11_fiq R12_fiq
SP_fiq  LR_fiq  PC
CPSR
SPSR_fiq

FIQ 为什么私有寄存器最多?因为 FIQ 的设计目标是“最快响应”。中断处理时,如果处理器需要先保存 R0-R12 才能进入工作,延迟会明显增加。FIQ 通过提供 R8-R12 的私有副本,让处理程序可以直接使用这些寄存器而不破坏被打断程序的上下文。

6.3 IRQ 模式的寄存器视图

IRQ 模式只有 R13_irq、R14_irq 和 SPSR_irq 三个私有寄存器:

R0  R1  R2  R3  R4  R5  R6  R7
R8  R9  R10 R11 R12
SP_irq  LR_irq  PC
CPSR
SPSR_irq

由于 IRQ 模式没有私有的通用数据寄存器,进入 IRQ 处理程序时必须先保存要使用的寄存器。保存现场通常使用独立 IRQ 栈,通过压栈指令完成。这也体现了 IRQ 和 FIQ 在响应速度上的本质差异。

6.4 管理模式、中止模式和未定义模式的寄存器视图

管理模式、中止模式、未定义模式结构相同,都只有 R13、R14 和 SPSR 三个私有寄存器,只是模式名称不同,分别以 svc、abt、und 作为下标区分。

管理模式:

R0-R12 共享
SP_svc  LR_svc  PC
CPSR
SPSR_svc

中止模式:

R0-R12 共享
SP_abt  LR_abt  PC
CPSR
SPSR_abt

未定义模式:

R0-R12 共享
SP_und  LR_und  PC
CPSR
SPSR_und

这三种模式都用于异常处理,因此它们都具备独立的栈指针和链接寄存器,并通过 SPSR 保存异常前状态,这是所有异常模式的共同特征。

6.5 完整寄存器统计

现在可以把 37 个物理寄存器数清楚:

  • R0-R12:13 个通用数据寄存器(各模式共享)。
  • R13/R14/R15:主模式(用户/系统)3 个,加上各异常模式的影子寄存器。
  • R13/R14 的影子寄存器:FIQ、IRQ、SVC、Abort、Undefined 五个模式各 2 个,共 10 个。
  • R8-R12 的 FIQ 影子寄存器:5 个。
  • CPSR:1 个。
  • SPSR:FIQ、IRQ、SVC、Abort、Undefined 五个模式各 1 个,共 5 个。

合计:13 + 3 + 10 + 5 + 1 + 5 = 37 个。这个数字常被直接当作面试题考察,能背出数字只是入门,能完整拆解每个寄存器的归属才是真正掌握。

完整映射表如下:

寄存器usr/sysfiqirqsvcabtund
R0-R7共享共享共享共享共享共享
R8-R12共享R8_fiq-R12_fiq共享共享共享共享
R13(SP)SPSP_fiqSP_irqSP_svcSP_abtSP_und
R14(LR)LRLR_fiqLR_irqLR_svcLR_abtLR_und
R15(PC)PCPCPCPCPCPC
CPSRCPSRCPSRCPSRCPSRCPSRCPSR
SPSRSPSR_fiqSPSR_irqSPSR_svcSPSR_abtSPSR_und

七、状态切换与模式切换实战

理解了状态和模式之后,还需要掌握它们之间的关系以及切换方法。状态切换和模式切换是两个不同维度的问题:状态切换改变的是执行 ARM 还是 Thumb 指令;模式切换改变的是权限和寄存器视图。

7.1 ARM 状态与 Thumb 状态的切换

状态切换通常通过 BXBLX 等带状态切换的跳转指令实现。其规则是:跳转目标地址的最低有效位决定切换后的状态。目标地址 bit0 为 1 时切换到 Thumb 状态,bit0 为 0 时切换到 ARM 状态。

示例:

        // ARM 状态
        LDR     R0, =thumb_func
        BX      R0              // R0 的最低位为 1,切换到 Thumb 状态

        .thumb
thumb_func:
        // Thumb 状态代码
        LDR     R1, [R2]
        BX      LR

之所以用地址最低位标记状态,是因为 ARM 指令和 Thumb 指令分别按 4 字节、2 字节对齐,地址最低位在指令地址中本来就没有实际寻址意义,正好可以复用来编码状态。异常返回时,异常链接寄存器中的返回地址同样携带这一状态信息,确保返回后能恢复到异常前的状态。

7.2 模式切换与异常响应流程

处理器进入异常时会自动完成一系列硬件动作:

  1. 把当前 CPSR 保存到对应异常模式的 SPSR。
  2. 根据异常类型把返回地址写入对应模式的 LR。
  3. 将 CPSR 的模式位切换为对应异常模式,并置位中断屏蔽位(如 IRQ 异常进入后通常禁止 IRQ)。
  4. PC 指向异常向量表中的对应入口。

异常向量表通常位于内存起始处,ARM 状态下每个向量占 4 字节,典型布局为:

        B       reset_handler       // 0x00 复位
        B       undef_handler       // 0x04 未定义指令
        B       swi_handler         // 0x08 软件中断
        B       prefetch_abort      // 0x0C 预取中止
        B       data_abort          // 0x10 数据中止
        B       .                   // 0x14 保留
        B       irq_handler         // 0x18 IRQ
        B       fiq_handler         // 0x1C FIQ

可以看出 FIQ 向量位于最后,因此 FIQ 处理代码可以直接放在向量表之后,省去一次跳转,进一步缩短中断响应时间,这也是 FIQ 的设计细节之一。

7.3 一个完整的模式初始化示例

下面给出一个常见的启动代码片段,演示如何从复位后的管理模式出发,依次为各特权模式建立自己的栈。这段代码展示了 MSR/MRS、BIC/ORR、LDR 等指令在模式切换中的配合使用。

        // 复位后处于 SVC 模式
        LDR     SP, =svc_stack_top

        // 设置 IRQ 模式栈
        MRS     R0, CPSR
        BIC     R0, R0, #0x1F
        ORR     R0, R0, #0x12    // IRQ 模式
        MSR     CPSR_c, R0
        LDR     SP, =irq_stack_top

        // 设置 FIQ 模式栈
        MRS     R0, CPSR
        BIC     R0, R0, #0x1F
        ORR     R0, R0, #0x11    // FIQ 模式
        MSR     CPSR_c, R0
        LDR     SP, =fiq_stack_top

        // 返回 SVC 模式继续初始化
        MRS     R0, CPSR
        BIC     R0, R0, #0x1F
        ORR     R0, R0, #0x13
        MSR     CPSR_c, R0

如果跳过上述步骤,当系统首次发生 IRQ 时,处理器会使用 IRQ 模式下尚未初始化的 SP,导致栈指针错误。这类问题在裸机开发中非常典型,也常被用来考察面试者对模式与寄存器关系的实际理解。

八、异常处理中的寄存器使用流程

异常处理是寄存器模型落地最频繁的场景。下面以 IRQ 为例,完整走一遍“进入异常、保存现场、处理、恢复现场、返回”的寄存器流转过程,帮助读者把前面的知识点串起来。

8.1 进入异常时的硬件动作

当外设触发 IRQ 且 CPSR 中 I 位为 0 时,处理器在完成当前指令后进入 IRQ 异常,硬件自动执行:

  1. SPSR_irq = CPSR,保存被打断程序的状态寄存器。
  2. LR_irq = 被打断指令的下一条指令地址加状态修正值。
  3. CPSR 的模式位改为 IRQ 模式,同时 I 位自动置 1,禁止嵌套 IRQ。
  4. PC 跳转到 IRQ 异常向量。

8.2 保存与恢复现场

IRQ 处理程序的典型代码框架:

irq_handler:
        SUB     LR, LR, #4        // 修正返回地址,指向被打断指令的下一条
        PUSH    {R0-R3, R12, LR}  // 保存要使用的寄存器,已存在 IRQ 栈
        BL      do_irq_service    // 调用 C 处理函数
        POP     {R0-R3, R12, LR}  // 恢复现场
        SUBS    PC, LR, #4        // 返回并恢复 CPSR

这段代码中的几个关键点:

  • SUB LR, LR, #4 是 IRQ 返回地址修正,与流水线结构和异常响应时序相关。
  • PUSH/POP 使用 R13_irq 作为栈顶,即异常处理使用 IRQ 私有栈。
  • SUBS PC, LR, #4 带 S 后缀,写 PC 的同时将 SPSR_irq 恢复到 CPSR,一次完成返回和现场恢复。
  • R0-R3、R12 作为临时寄存器需要保存,因为 C 函数可能破坏它们;R4-R11 则按 AAPCS 由被调用函数自行保存。

8.3 软中断示例:从用户模式进入内核

用户程序通过 SWI/SVC 指令触发软中断进入管理模式,实现系统调用:

        // 用户模式
        MOV     R0, #SYSCALL_NUM  // 系统调用号
        SVC     #0                // 触发软件中断

        // 管理模式下的处理
svc_handler:
        LDR     R1, [LR, #-4]     // 取出 SVC 指令编码
        BIC     R1, R1, #0xFF000000  // 提取立即数
        // 根据调用号分发处理
        MOVS    PC, LR            // 返回用户模式并恢复 CPSR

从 SVC 处理程序返回时使用 MOVS PC, LR 而非 SUBS PC, LR, #4,是因为不同异常类型的返回地址修正方式不同:SVC 异常返回不需要修正,而 IRQ 需要减 4。面试中若被问到“各类异常的返回地址修正分别是多少”,可以从异常发生时机切入分析:SVC 在指令执行前触发,IRQ 在当前指令完成后触发,数据中止在指令执行中途触发,因此修正值分别是 0、-4、-8。

九、AAPCS 调用约定与寄存器的关系

AAPCS(ARM Architecture Procedure Call Standard)规定了函数调用时寄存器的使用规则。它虽然不是体系结构强制要求,但在编译器、操作系统和手写汇编配合时是事实标准,理解它有助于阅读反汇编代码和分析函数调用开销。

9.1 参数传递规则

前四个 32 位参数通过 R0、R1、R2、R3 传递;多于四个的参数通过栈传递。对于 64 位参数,传递时会占用两个寄存器或两个栈槽。返回值通过 R0 返回,64 位返回值通过 R0 和 R1 返回。

9.2 寄存器保存责任

  • R0-R3、R12 为调用者保存寄存器,被调用函数可随意修改,无需恢复原值。
  • R4-R11 为被调用者保存寄存器,被调用函数若需要使用,必须先把原值压栈,返回前恢复。
  • R13、R14、R15 有特殊作用,函数返回时 SP 必须恢复平衡,LR 保存返回地址,PC 完成跳转。

9.3 栈帧与常见函数序言

典型函数开头会先把 LR 和需要保留的寄存器压栈,结尾再恢复:

func:
        PUSH    {R4-R7, LR}     // 保存被调用者保存寄存器和返回地址
        // 函数体
        POP     {R4-R7, PC}     // 恢复寄存器并返回

如果函数很简单且不会再调用其他函数,可以通过尾调用优化省略 LR 的保存,例如直接 BX LR。这些细节在性能分析和安全上下文(如栈溢出防护)中都有实际意义。

十、AArch64(64 位 ARM)寄存器的变化

ARMv8 引入了 AArch64 执行状态,寄存器模型发生了显著变化。面试中若岗位面向 ARMv8/ARMv9 平台,这一节的内容属于必答题。AArch64 虽然延续了 ARM 的设计思路,但寄存器数量、命名和异常模型都有调整。

10.1 通用寄存器 X0-X30

AArch64 提供 31 个 64 位通用寄存器 X0-X30,其中 X30 用作链接寄存器,保存函数返回地址。64 位寄存器的低 32 位可以通过 W0-W30 访问。与 32 位 ARM 不同,AArch64 的 PC 不再是通用寄存器,不能直接通过普通指令读写,只能通过跳转、ADR 等指令间接操作。

10.2 专用寄存器

AArch64 中栈指针不再是通用寄存器。X31 根据指令上下文可以解读为零寄存器 XZR 或栈指针 SP。这一设计简化了指令编码,也明确区分了零寄存器和栈指针。

函数调用约定使用 X0-X7 传递参数,X0 保存返回值,X29 通常用作帧指针,X30 是链接寄存器。

10.3 状态寄存器与异常等级

AArch64 没有单一的大 CPSR,而是将状态信息拆分为多个系统寄存器,例如 NZCV 条件标志、当前异常等级寄存器 CurrentEL、栈指针选择寄存器 SPSel 等。异常模型也从 32 位时的七种模式演变为 EL0-EL3 四个异常等级:

异常等级典型用途对应关系
EL0用户应用类似用户模式
EL1操作系统内核类似 SVC/Abort/IRQ 等特权模式
EL2虚拟机监控器虚拟化场景
EL3安全监控固件TrustZone 安全世界切换

AArch64 的异常返回通过 ERET 指令完成,现场保存使用 ELR 寄存器保存返回地址,使用 SPSR 保存异常前状态。这些变化体现了 64 位架构对特权级管理的重新抽象——从“按异常类型分模式”转变为“按特权级分层”。

10.4 32 位与 64 位的对比要点

  • 32 位模型强调“模式 + 影子寄存器”,64 位模型强调“异常等级 + 专用系统寄存器”。
  • 32 位有 FIQ/IRQ 等模式化中断,64 位中断通过异常等级和中断控制器路由。
  • 32 位 PC 可直接读写,64 位 PC 是独立内部寄存器。
  • 64 位新增零寄存器,编码更简洁。

面试时要能根据岗位平台选择侧重:如果公司产品仍以 Cortex-M 或经典 ARM7/ARM9 为主,重点讲 32 位七种模式和 37 个寄存器;如果面向 Cortex-A53/A72 等 64 位 SoC,则需要同时掌握 AArch64 异常等级和 X/W 寄存器模型。

十一、ARM 寄存器与状态高频面试题解析

本章汇总架构方向面试中关于 ARM 状态和寄存器的高频题目,并给出答题思路,供读者自测和复习。

11.1 基础题

Q1:ARM 处理器有哪几种基本状态?

答:三大基本状态为 ARM 状态(执行 32 位 ARM 指令)、Thumb 状态(执行 16 位 Thumb 指令)和异常状态(响应异常时的处理状态)。还可以提及调试状态;Thumb-2 是 Thumb 状态的扩展,不是并列的新状态。

Q2:ARM 有哪几种工作模式?各自用途是什么?

答:共七种:用户模式(应用程序)、FIQ(快速中断)、IRQ(普通中断)、管理模式(复位默认和系统调用)、中止模式(取指/数据中止)、未定义模式(未定义指令)、系统模式(特权且与用户共享寄存器)。

Q3:为什么 ARM 有 37 个寄存器?请说明其组成。

答:37 个包含 16 个通用寄存器(R0-R15)加 6 个状态寄存器(1 个 CPSR 加 5 个 SPSR)构成的 22 个基本寄存器,再加影子寄存器:FIQ 模式的 R8-R12(5 个)和五个异常模式的 R13/R14(10 个),共 37 个。

11.2 进阶题

Q4:CPSR 的各个标志位分别代表什么?C 标志和 V 标志有什么区别?

答:N 为符号位,Z 为零标志,C 为进位/借位/移位移出标志,V 为有符号溢出标志。C 服务于无符号运算和移位,V 服务于有符号运算,两者不能混用。

Q5:为什么 FIQ 比 IRQ 响应更快?

答:其一,FIQ 模式拥有 R8-R12 私有影子寄存器,处理程序可少保存现场;其二,FIQ 向量位于异常向量表最后,可紧跟处理代码,少一次跳转;其三,FIQ 通常在中断约束上拥有更高优先级。

Q6:异常返回时为什么要修正 LR?各类异常修正值是多少?

答:因为异常响应发生在不同的指令执行阶段,LR 中保存的值与被期望的返回地址之间存在偏移。SVC 等指令未完成即进入异常,修正值为 0;IRQ 在当前指令完成后处理,修正值为 -4;数据中止在指令执行中触发,修正值为 -8。具体实现中需按不同异常分别处理。

Q7:如何切换 ARM 状态和 Thumb 状态?

答:通过 BX、BLX 等指令,以跳转目标地址最低位表示目标状态:bit0 为 0 切到 ARM 状态,bit0 为 1 切到 Thumb 状态。异常返回地址最低位同样携带状态信息,保证返回后恢复原状态。

11.3 综合题

Q8:从发生 IRQ 中断到中断处理完成返回,处理器状态和寄存器是如何变化的?

答:硬件自动完成 SPSR_irq 保存 CPSR、LR_irq 保存返回地址、切换 CPSR 为 IRQ 模式并禁止 IRQ,PC 跳到向量表;软件再保存要使用的寄存器、修正 LR、调用处理函数;返回时恢复寄存器,最后用带 S 后缀的写 PC 指令同时恢复 CPSR,完成返回。

Q9:AArch64 相比 AArch32 在寄存器模型上有哪些主要变化?

答:通用寄存器变为 X0-X30,PC 不再是通用寄存器;调用约定使用 X0-X7 传参;栈指针与零寄存器共享 X31 编码;状态信息拆分为 NZCV 等系统寄存器;异常从七种模式变为 EL0-EL3 四个等级。

Q10:如何为各异常模式建立独立栈?为什么要这样做?

答:在特权模式下依次修改 CPSR 的模式位,为每个模式设置自己的 R13 栈指针。原因是各异常模式使用独立的 R13 影子寄存器,如果不初始化,异常发生时会使用非法栈地址;同时独立栈可以避免异常处理破坏被打断程序的用户栈,保证嵌套安全和隔离性。

十二、总结

ARM 架构的三大基本状态、七种工作模式和 37 个寄存器模型,是整个 ARM 体系结构学习的基石。三者的关系可以概括为:状态决定执行什么宽度的指令,模式决定以什么权限和寄存器视图运行,寄存器模型则为状态切换和模式切换提供硬件载体。理解这一点后,再去看异常向量表、中断处理、任务切换和 64 位异常等级,都会有更清晰的脉络。

从面试准备角度,建议读者重点掌握以下链条:三大状态的定义与切换条件 → 七种模式的进入原因与私有寄存器 → CPSR/SPSR 各位域含义 → 37 个寄存器的完整拆解 → IRQ/SVC 异常处理的寄存器流转 → AArch64 的主要变化。把这些内容串起来,就可以应对绝大多数关于 ARM 程序员模型的问题。

实际工程项目中,这些知识也并非纸上谈兵:正确初始化各异常模式的栈、正确处理异常返回地址、正确使用带 S 后缀的返回指令、正确遵循 AAPCS 调用约定,任何一处出错都可能造成难以排查的系统崩溃。建议读者在理解本文的基础上,结合真实启动代码或裸机中断实验加深体会,形成“理论—动手—再回看理论”的闭环。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐