1. ARMv7架构概述:从历史到核心寄存器

各位同学,今天咱们正式进入ARMv7的世界。在开始动手配置MMU之前,我觉得有必要先把ARM的来龙去脉讲清楚。毕竟,你连CPU的“家底”都不了解,后面调起寄存器来心里肯定没底。

ARM公司成立于1990年,最初是Acorn、Apple和VLSI的合资企业。我当年刚入行时,ARM还只是嵌入式圈子里的小众选择,谁能想到今天它几乎统治了移动设备和物联网。说白了,ARM的成功靠的不是性能极致,而是低功耗和灵活的授权模式——你买IP核自己改,比自己从头造CPU划算太多了。

1.1 ARMv7三大系列:A/R/M

ARMv7架构分成了三个方向,每个方向针对不同的应用场景。我经常看到新手搞混它们,这里先列个表:

系列 全称 典型应用 关键特性
ARMv7-A Application 手机、平板、路由器 MMU、多核、NEON、TrustZone
ARMv7-R Real-time 汽车刹车、工业控制 MPU、低延迟中断、紧耦合内存
ARMv7-M Microcontroller STM32、传感器节点 无MMU/MPU、硬件堆栈、位带操作

咱们这门课主要讲ARMv7-A,因为只有A系列才有完整的MMU,才能跑Linux这样的操作系统。R系列虽然也有MPU,但那是给实时系统用的,不支持虚拟地址。M系列嘛……说实话,我早期做单片机项目时经常用,但它的寄存器模型和A系列差别很大,后面我们不会涉及。

核心观点:ARMv7-A是唯一支持虚拟内存和操作系统的系列。如果你要做嵌入式Linux或Android底层,必须吃透A系列。

1.2 ARMv7核心寄存器组详解

好了,咱们来看ARMv7-A的寄存器。这是你写汇编、配置MMU、处理异常的基础。ARMv7-A有16个通用寄存器(R0-R15),外加CPSR和SPSR。嗯,这里要注意:R15其实不是通用寄存器,它是PC(程序计数器)。

1.2.1 通用寄存器 R0-R14

  • R0-R3:参数传递和临时变量。函数调用时,前4个参数放这里。我习惯把R0当累加器用,但要注意别被函数调用冲掉。
  • R4-R11:变量寄存器。函数内部要保存的变量通常放这里。R11是FP(帧指针),不过现在编译器更喜欢用R7做FP。
  • R12:IP(过程间调用暂存寄存器)。链接器有时会用,别乱动。
  • R13:SP(栈指针)。这个不用多说,栈操作全靠它。
  • R14:LR(链接寄存器)。调用子程序时,返回地址自动存这里。

个人经验:我调试过一块板子,中断服务程序里忘了保存LR,结果返回时直接飞了。从那以后,我写中断处理第一件事就是压栈R0-R3和LR。

1.2.2 R15:PC(程序计数器)

R15就是PC。ARMv7-A里,PC指向当前指令+8(因为流水线)。你读PC的值时,得到的是当前指令地址+8;写PC就是跳转。我曾经见过新手直接写MOV R15, #0x8000来跳转,结果因为没对齐导致异常——ARM指令必须4字节对齐。

; 正确的跳转方式
LDR R0, =0x8000
BX  R0          ; 跳转并切换指令集(如果R0的bit0=1,切Thumb)

; 错误的写法(不推荐)
MOV R15, #0x8000  ; 可能产生对齐异常

1.2.3 CPSR(当前程序状态寄存器)

CPSR是ARMv7里最重要的控制寄存器。它记录了当前处理器的状态、中断屏蔽位、条件标志。我把它拆成几个字段:

位域 名称 作用
[31:28] N, Z, C, V 条件标志:负数、零、进位、溢出
[27:25] Q, IT, J 饱和标志、If-Then块状态、Jazelle状态
[24:8] 保留/GE SIMD大于等于标志,一般不用管
[7:6] E, A 大小端控制、异步中止屏蔽
[5] T Thumb状态:0=ARM指令,1=Thumb指令
[4:0] M[4:0] 处理器模式:0x10=User, 0x11=FIQ, 0x12=IRQ, 0x13=SVC, 0x17=Abort, 0x1B=Undef, 0x1F=System

避坑指南:我曾经在中断里直接修改CPSR的M位来切换模式,结果忘了保存旧模式,返回时系统崩溃。正确的做法是:通过异常进入自动切换模式,不要手动改M位,除非你很清楚自己在做什么。

1.2.4 SPSR(保存的程序状态寄存器)

SPSR是异常模式的专属寄存器。当发生异常(比如IRQ、FIQ、SVC)时,硬件会自动把当前CPSR复制到SPSR_。这样你处理完异常后,可以用MOVS PC, LR或SUBS PC, LR, #4恢复CPSR。说白了,SPSR就是CPSR的“快照”,帮你安全返回。

每个异常模式都有自己的SPSR,但User和System模式没有。为什么?因为这两个模式不会主动触发异常,不需要保存状态。

1.3 知识结构总览

下面这张图把ARMv7-A的寄存器体系和异常模式串起来了。我建议你把它存下来,后面讲MMU配置时会反复用到这些概念。

ARMv7-A 核心寄存器与异常模式结构图 ARMv7-A 核心寄存器与异常模式 通用寄存器 R0 - R3 (参数/临时) R4 - R11 (变量) R12 (IP) R13 (SP) R14 (LR) R15 (PC) 状态寄存器 CPSR (当前状态) SPSR_<mode> (保存状态) 每个异常模式独有 异常模式 SVC (管理模式) IRQ (普通中断) FIQ (快速中断) Abort (中止) Undef (未定义指令) User/System (非异常) 说明:异常发生时,硬件自动保存CPSR到SPSR_<mode>,并切换PC到异常向量表 返回时通过 MOVS PC, LR 恢复CPSR和PC R0-R15 + CPSR + SPSR = ARMv7-A 核心寄存器全集

1.4 寄存器使用注意事项

最后,我总结几个实际项目中容易踩的坑:

  • 不要直接修改CPSR的T位:切换ARM/Thumb状态要用BX或BLX指令,直接写CPSR会导致流水线不一致。
  • SPSR只在异常模式下有效:在User模式下读SPSR,结果是不可预测的。我见过有人用MRS R0, SPSR来获取状态,结果读回来一堆垃圾。
  • PC对齐:ARM指令必须4字节对齐,Thumb指令2字节对齐。跳转前检查地址的bit0,否则会触发对齐异常。
  • LR在嵌套中断中要小心:如果中断里又来了中断,LR会被覆盖。我的习惯是:进中断先压栈LR,出中断再恢复。

总结:ARMv7-A的寄存器设计很精妙——通用寄存器负责数据操作,CPSR控制状态,SPSR保证异常安全返回。理解这套机制,是后面配置MMU、处理中断、编写启动代码的基础。别急,咱们一章一章来。