1. ARMv7架构概述:Cortex-A/R/M系列介绍、ARMv7-A处理器模式与寄存器组、异常与中断处理机制

各位同学,咱们今天正式开讲ARMv7。说实话,ARMv7这个架构在我职业生涯里占了很大比重。从早期的手机SoC到后来的工业控制器,我调试过的ARMv7芯片少说也有几十款。你想想看,一个架构能活这么多年,一定有它的道理。

ARMv7不是单一的产品线,它分成了三个系列:Cortex-A、Cortex-R和Cortex-M。这三个系列虽然都叫ARMv7,但设计哲学完全不同。我刚开始接触时也搞混过,后来踩了几个坑才真正理解它们的区别。

1.1 Cortex-A/R/M系列介绍

先看一张总览图,把三个系列的定位搞清楚。

ARMv7 三大系列架构总览 Cortex-A系列 Application 高性能应用处理器 MMU / 多级Cache Linux / Android 典型:A8/A9/A15 Cortex-R系列 Real-time 实时控制处理器 MPU / 紧耦合内存 RTOS / 硬实时 典型:R4/R5/R7 Cortex-M系列 Microcontroller 微控制器 无MMU/无MPU 裸机 / 轻量RTOS 典型:M3/M4/M7 核心差异速览 • 内存管理:A系列有MMU(虚拟地址),R系列有MPU(保护),M系列无硬件管理单元 • 中断延迟:A系列约50-200周期,R系列约10-20周期,M系列可做到6-12周期 • 调试痛点:A系列多核缓存一致性问题,R系列时序敏感,M系列堆栈溢出最常见

这张图我画了很多遍,每次给新团队培训都用它。三个系列的核心差异,说白了就是性能、实时性、功耗三个维度的取舍。

我的经验:选型时最容易犯的错误是用Cortex-A去做硬实时控制。我曾经见过一个项目,工程师用A8跑裸机做电机控制,结果中断抖动大到电机直接啸叫。后来换成R4,问题立刻解决。记住:A系列跑Linux,R系列跑RTOS,M系列跑裸机或轻量RTOS,这个分工别搞反了。

1.2 ARMv7-A处理器模式与寄存器组

接下来我们深入Cortex-A系列。ARMv7-A有9种处理器模式,我列个表方便你对照。

模式 缩写 权限等级 用途
User USR PL0 普通应用程序运行
FIQ FIQ PL1 快速中断处理
IRQ IRQ PL1 普通中断处理
Supervisor SVC PL1 操作系统内核、系统调用
Monitor MON PL1 安全扩展(TrustZone)
Abort ABT PL1 数据/指令异常
Hyp HYP PL2 虚拟化扩展(仅A15等)
Undefined UND PL1 未定义指令异常
System SYS PL1 特权任务(共享User寄存器)

这张表你得背下来。为什么?因为调试的时候,看当前模式就能猜出大概出了什么问题。比如程序跑飞进了UND模式,八成是指令编码错了或者PC指针跳到了数据区。

寄存器组这块,ARMv7-A有16个通用寄存器R0-R15,外加CPSR和SPSR。但不同模式下,部分寄存器是banked的——也就是有物理副本。

调试技巧:我调试中断嵌套问题时,经常遇到一种情况:在IRQ模式里修改了R13(SP),退出中断后系统崩溃。原因就是IRQ模式有自己的banked R13,和SVC模式的R13不是同一个物理寄存器。你改的是IRQ的栈指针,但内核用的是SVC的栈。这个坑我至少帮三个团队排查过。

寄存器banking的规则,我整理成了一张速查表:

寄存器 USR/SYS SVC IRQ FIQ ABT UND MON HYP
R0-R7 共用 共用 共用 共用 共用 共用 共用 共用
R8-R12 共用 共用 共用 FIQ独有 共用 共用 共用 共用
R13(SP) 共用 SVC_SP IRQ_SP FIQ_SP ABT_SP UND_SP MON_SP HYP_SP
R14(LR) 共用 SVC_LR IRQ_LR FIQ_LR ABT_LR UND_LR MON_LR HYP_LR
SPSR 无 SVC_SPSR IRQ_SPSR FIQ_SPSR ABT_SPSR UND_SPSR MON_SPSR HYP_SPSR

注意FIQ模式,它连R8-R12都是banked的。为什么?因为FIQ设计目标就是极速响应,省去保存和恢复寄存器的开销。我在做高速数据采集时用过FIQ,中断响应能压到几十纳秒级别。

1.3 异常与中断处理机制

ARMv7-A的异常向量表固定在0x00000000或0xFFFF0000(由SCTLR.V位控制)。每个异常入口占4字节,通常是一条跳转指令。

; 典型的异常向量表(基址0x00000000)
0x00000000:  LDR   PC, Reset_Addr      ; 复位
0x00000004:  LDR   PC, Undef_Addr      ; 未定义指令
0x00000008:  LDR   PC, SWI_Addr        ; 软中断(SVC)
0x0000000C:  LDR   PC, Prefetch_Addr   ; 预取指中止
0x00000010:  LDR   PC, Data_Addr       ; 数据中止
0x00000014:  LDR   PC, Hyp_Addr        ; 虚拟化(可选)
0x00000018:  LDR   PC, IRQ_Addr        ; 普通中断
0x0000001C:  LDR   PC, FIQ_Addr        ; 快速中断

异常处理的流程,我画个流程图帮你理解:

ARMv7-A 异常处理流程 异常发生 硬件自动完成: 保存PC到LR_<mode>,保存CPSR到SPSR_<mode> 切换模式并跳转: CPSR模式位更新,PC指向异常向量表 软件处理: 保存现场 → 处理异常 → 恢复现场 执行 SUBS PC, LR, #4

这里有个关键点:LR在异常入口保存的是返回地址,但不同异常需要不同的偏移量。我列个表:

异常类型 LR保存的值 返回指令 偏移原因
复位 不适用 不适用 —
未定义指令 当前指令地址+4 MOVS PC, LR LR指向下一条未执行指令
SVC 当前指令地址+4 MOVS PC, LR 同上
预取指中止 当前指令地址+4 SUBS PC, LR, #4 LR指向未执行的指令,需回退
数据中止 当前指令地址+8 SUBS PC, LR, #8 LR指向下下条,需回退两条
IRQ 当前指令地址+4 SUBS PC, LR, #4 LR指向未执行的指令
FIQ 当前指令地址+4 SUBS PC, LR, #4 同上

我曾经踩过的坑:刚做嵌入式那会儿,写中断服务程序返回时用了MOVS PC, LR,结果程序跑飞。查了半天才发现IRQ返回必须用SUBS PC, LR, #4。为什么?因为IRQ发生时,当前指令还没执行完,LR保存的是下一条指令的地址,直接返回会跳过当前指令。这个bug我调了整整一个通宵。

中断优先级方面,ARMv7-A硬件只区分FIQ和IRQ两级。FIQ优先级高于IRQ。但实际SoC中,中断控制器(GIC)会扩展出更多优先级。GIC支持最多256级优先级,但具体实现看芯片。

嵌套中断处理是个难点。默认情况下,ARM进入IRQ模式后会屏蔽IRQ(CPSR的I位置1)。如果你想支持嵌套,需要在ISR里手动开中断。但要注意:开中断前必须确保栈空间足够,否则嵌套几次就栈溢出了。

我的建议:做嵌套中断时,先切到SVC模式再开中断。因为IRQ模式的栈通常只分配了很小的空间(很多BSP只给256字节),而SVC模式的栈是内核栈,空间大得多。具体做法:在ISR入口保存现场后,执行CPS #0x13切换到SVC模式,然后开中断。返回时先关中断,再切回IRQ模式,最后用SUBS PC, LR, #4返回。

最后提一下FIQ和IRQ的选择。FIQ的优势不仅是优先级高,更重要的是它有独立的R8-R12,省去了保存这些寄存器的开销。但FIQ的向量表入口只有一个,不像IRQ可以挂多个中断源。实际项目中,我通常把最紧急、最频繁的中断分配给FIQ,比如定时器滴答、高速ADC转换完成等。