1. ARMv7架构概述:Cortex-A系列处理器介绍、ARMv7-A架构特性、寄存器模型与工作模式

好,咱们正式开始。ARMv7架构,说白了就是ARM公司在2010年前后推出的一个指令集架构版本。你想想看,现在市面上绝大多数手机、平板、甚至不少嵌入式设备,跑的都是ARMv7或它的变种。我个人习惯把ARMv7-A看作是「应用处理器」的起点——它真正让ARM有能力去跟x86抢地盘。

1.1 Cortex-A系列处理器:从手机到服务器的基石

Cortex-A系列是ARMv7-A架构的主力军。我当年第一次接触Cortex-A8时,还在做一款工业平板。那会儿从单片机跳到应用处理器,感觉就像从自行车换成了摩托车。

Cortex-A系列主要分三代:

处理器 架构版本 流水线 典型应用
Cortex-A8 ARMv7-A 13级整数 早期智能手机、MID
Cortex-A9 ARMv7-A 8级整数 多核平板、机顶盒
Cortex-A15 ARMv7-A 15-24级 服务器、高端移动设备

这里有个坑,我提醒你一下:Cortex-A8是顺序执行,Cortex-A9是乱序执行。别小看这个区别,它直接影响你写RTOS任务切换代码时的流水线刷新策略。我曾经在A8上跑得好好的上下文切换代码,搬到A9上就出问题——就是因为没处理乱序执行带来的指令重排。

我的经验:选型时别只看主频。Cortex-A9虽然流水线比A8短,但IPC(每时钟指令数)高得多。做RTOS调度时,A9的乱序执行会让你的临界区保护更复杂。

1.2 ARMv7-A架构特性:你真正需要关心的

ARMv7-A相比之前的ARMv5、ARMv6,加了几个硬核特性。我挑三个最重要的说:

  • Thumb-2技术:16位和32位指令混编。代码密度提升约30%,性能损失不到5%。做RTOS时,我建议关键任务用ARM指令,普通任务用Thumb-2。
  • NEON SIMD引擎:128位SIMD,适合音频、视频、信号处理。嗯,这里要注意——NEON寄存器在任务切换时必须保存,否则你的DSP算法会跑飞。
  • VFPv3/v4浮点单元:硬件浮点运算。Cortex-A9之前是可选,A9之后基本标配。
避坑指南:我曾经在一个项目中,因为没在RTOS的上下文切换里保存VFP寄存器,导致浮点运算结果时对时错。查了三天才发现——FPEXC寄存器的EN位没处理好。记住:启用VFP后,任务切换必须保存VFP寄存器组。

1.3 寄存器模型:16个通用寄存器背后的门道

ARMv7-A的寄存器模型,说白了就是一套「影子寄存器」机制。用户模式下你看到R0-R15,但切换到异常模式后,有些寄存器会被硬件自动替换。

核心寄存器一览:

寄存器 别名 作用 RTOS中的关键点
R0-R3 参数寄存器 传参、返回值 函数调用时自动使用
R4-R11 变量寄存器 局部变量保存 任务切换必须保存
R12 IP 内部过程调用 链接器临时使用
R13 SP 栈指针 每个任务有自己的SP
R14 LR 链接寄存器 保存返回地址
R15 PC 程序计数器 任务切换时修改
CPSR 状态寄存器 标志位、模式位 中断使能、条件码

你想想看,为什么RTOS的任务切换代码里,要保存R4-R11和LR、SP、PC?因为R0-R3是调用者保存的,函数返回后自然恢复。但R4-R11是被调用者保存的——你不保存,任务切回来就全乱了。

核心要点:ARMv7-A的寄存器模型是「分模式映射」的。FIQ模式有自己的R8-R12影子寄存器,IRQ模式有自己的SP和LR。这意味着中断响应时,硬件自动帮你做了部分上下文保存。但别高兴太早——影子寄存器只覆盖一部分,剩下的还得你手动来。

1.4 工作模式:7种模式与异常处理

ARMv7-A有7种工作模式。我刚开始学的时候觉得太多,后来做RTOS才发现——每种模式都有它的设计意图。

  1. 用户模式(USR):普通程序运行,权限最低。
  2. 系统模式(SYS):特权模式,共享用户寄存器组。
  3. 快速中断模式(FIQ):高优先级中断,有自己的影子寄存器。
  4. 普通中断模式(IRQ):普通中断。
  5. 管理模式(SVC):系统调用入口。
  6. 中止模式(ABT):数据/指令异常。
  7. 未定义模式(UND):未定义指令异常。

这里有个关键点:用户模式不能直接修改CPSR来切换到其他模式。必须通过异常或系统调用。这就是RTOS实现系统调用的硬件基础——你通过SWI/SVC指令触发异常,硬件自动切换到SVC模式,然后执行内核代码。

我的习惯:做RTOS时,我把内核代码跑在SVC模式,用户任务跑在USR模式。这样用户任务无法直接修改内核数据——硬件帮你做了隔离。比软件保护靠谱多了。

下面这张图,是我画的一个ARMv7-A工作模式切换关系图。你仔细看看,异常优先级和模式切换路径都在里面了:

ARMv7-A工作模式切换关系图 ARMv7-A 工作模式切换关系 用户模式 (USR) 系统模式 (SYS) 管理模式 (SVC) IRQ模式 FIQ模式 中止模式 (ABT) 未定义模式 (UND) 软件控制 SVC指令 IRQ异常 数据/指令异常 未定义指令 FIQ异常 说明: 1. 用户模式只能通过异常或SVC指令切换到其他模式 2. FIQ优先级最高,IRQ次之,其他异常优先级递减 3. 系统模式是唯一能通过软件直接进入的特权模式

你看这张图,用户模式在中间,其他模式围绕它。异常发生时,硬件自动完成模式切换和PC跳转。RTOS的任务调度,本质上就是利用这些异常机制——定时器中断触发IRQ,IRQ处理函数里做任务切换,然后返回用户模式的新任务。

曾经踩过的坑:我在一个项目中,FIQ和IRQ都用了。FIQ处理高速数据流,IRQ做普通外设。结果发现FIQ处理时间太长,导致IRQ响应延迟。后来才明白——FIQ模式下中断是屏蔽的,你在FIQ里待太久,IRQ就进不来。所以FIQ里只做最紧急的事,其他丢给IRQ。

1.5 异常向量表:系统启动的第一道门

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

典型的向量表布局:

; ARMv7-A 异常向量表示例
.section .vectors, "ax"
.global _start

_start:
    ldr pc, =reset_handler      ; 0x00: 复位
    ldr pc, =undef_handler      ; 0x04: 未定义指令
    ldr pc, =svc_handler        ; 0x08: 系统调用
    ldr pc, =prefetch_handler   ; 0x0C: 预取中止
    ldr pc, =data_handler       ; 0x10: 数据中止
    ldr pc, =unused_handler     ; 0x14: 保留
    ldr pc, =irq_handler        ; 0x18: IRQ
    ldr pc, =fiq_handler        ; 0x1C: FIQ

这里有个细节:为什么用ldr pc, =xxx而不是b xxx?因为b指令有±32MB的跳转范围限制,而ldr pc, =xxx可以从文字池加载绝对地址,不受范围限制。做RTOS时,你的代码可能分布在不同的内存区域,用ldr pc更安全。

我的建议:向量表放在0x00000000,但把中断处理函数放在高地址。这样低地址区域留给Bootloader和异常入口,高地址放应用代码。内存管理更清晰。

好了,ARMv7-A架构的概览就到这里。你掌握了寄存器模型和工作模式,后面讲RTOS任务调度时,你会发现——任务切换的本质就是「保存当前模式的寄存器,恢复目标模式的寄存器」。这个道理贯穿整个课程。