1. ARMv8架构概述:从ARMv7到ARMv8的演进、AArch64与AArch32执行状态、寄存器模型与异常等级

各位好,我是老李。做嵌入式十几年,从ARM7TDMI一路做到现在的Cortex-A76,可以说见证了ARM架构的整个进化史。今天咱们聊ARMv8,这个架构可以说是ARM历史上一次“脱胎换骨”的升级。

为什么这么说?因为ARMv8引入了一个全新的执行状态——AArch64。这不仅仅是把32位寄存器扩展到64位那么简单,整个异常模型、指令集、寻址方式都变了。我当年第一次接触Cortex-A53的时候,看着那套全新的异常等级体系,说实话,有点懵。

但别怕,咱们今天就把这块硬骨头啃下来。

1.1 从ARMv7到ARMv8:一次架构级的飞跃

ARMv7时代,我们用的最多的是Cortex-A系列处理器。那时候的架构设计,说白了还是32位的底子。虽然通过LPAE(大物理地址扩展)能支持到40位物理地址,但虚拟地址空间始终被限制在4GB以内。

ARMv8的出现,彻底打破了这堵墙。它带来了两个核心变化:

  • 64位计算能力:通用寄存器从16个扩展到31个,每个寄存器宽度变成64位
  • 全新的异常模型:用4个异常等级(EL0-EL3)替代了ARMv7的7种处理器模式

核心观点:ARMv8不是ARMv7的简单升级,而是一次架构重构。它保留了向后兼容性,但为64位高性能计算重新设计了底层。

我记得2013年做第一个Cortex-A53项目时,芯片手册里同时出现了AArch32和AArch64两套指令集。当时我就意识到,这玩意儿跟以前完全不是一个玩法了。

1.2 AArch64与AArch32:两种执行状态

ARMv8最巧妙的设计,就是同时支持两种执行状态。你可以在同一个处理器上,根据需要切换运行32位或64位代码。

特性 AArch64 AArch32
寄存器宽度 64位 32位
通用寄存器数量 31个(X0-X30) 15个(R0-R14)
指令集 A64(固定32位长度) A32/T32(ARM/Thumb)
虚拟地址空间 最大48位(256TB) 最大32位(4GB)
异常等级 EL0-EL3 EL0-EL3(兼容模式)
条件执行 大部分指令不支持 广泛支持

这里有个关键点:AArch32不是ARMv7的简单复制。它在ARMv8上运行时,底层异常模型已经变成了EL0-EL3的架构。我见过不少工程师以为AArch32就是原来的ARMv7,结果在中断处理上踩了坑。

实战建议:如果你的项目需要同时运行32位和64位代码(比如64位Linux内核+32位用户态应用),务必确认你的RTOS或Hypervisor对这两种执行状态的切换支持是否完善。我曾经在一个项目中,因为RTOS没有处理好AArch32到AArch64的异常路由,导致系统在模式切换时频繁崩溃。

1.3 寄存器模型:从15个到31个的进化

ARMv8的寄存器模型,是我个人觉得最值得花时间理解的部分。它直接决定了你的汇编代码怎么写、性能怎么优化。

在AArch64状态下,通用寄存器组是这样的:

  • X0-X30:31个64位通用寄存器
  • W0-W30:X寄存器的低32位别名(用于32位操作)
  • XZR/WZR:零寄存器(读取返回0,写入被忽略)
  • SP:堆栈指针寄存器(EL0/EL1/EL2/EL3各有一个独立SP)
  • PC:程序计数器(不再是通用寄存器)

重要变化:在ARMv7中,PC(R15)是可以直接读写的通用寄存器。但在ARMv8的AArch64中,PC被独立出来,不能直接通过指令修改。这意味着很多ARMv7上的跳转技巧在AArch64上不再适用。

我刚开始写AArch64汇编时,最不习惯的就是这一点。以前在ARMv7上,我经常用MOV PC, LR做函数返回,到了AArch64必须用RET指令。这个习惯我改了好几个星期。

另外,AArch64还引入了一组系统寄存器,用于配置MMU、异常处理、性能监控等。这些寄存器只能通过MSR和MRS指令访问,不能像ARMv7那样通过协处理器指令操作。

// AArch64系统寄存器访问示例
// 读取当前异常等级
MRS X0, CurrentEL

// 配置系统控制寄存器
MSR SCTLR_EL1, X1

// 读取时钟周期计数器(性能优化常用)
MRS X2, CNTPCT_EL0

1.4 异常等级:全新的安全与虚拟化模型

ARMv8的异常等级(Exception Levels)设计,是我认为整个架构中最优雅的部分。它用4个等级清晰地划分了软件栈的权限层次:

  • EL0:用户态应用(权限最低)
  • EL1:操作系统内核
  • EL2:Hypervisor(虚拟化)
  • EL3:安全监控器(Secure Monitor,权限最高)

每个异常等级都有独立的:

  • 堆栈指针(SP_ELx)
  • 异常向量表(VBAR_ELx)
  • 系统控制寄存器(SCTLR_ELx)
  • 页表基地址寄存器(TTBR0_ELx/TTBR1_ELx)

避坑指南:我曾经在一个项目中,因为EL2的异常向量表没有正确配置,导致虚拟机一触发中断就跳到了EL1的向量表里。排查了整整两天才发现,是VBAR_EL2的地址写错了。记住:每个异常等级的向量表必须独立配置,不能共用。

异常等级之间的切换,遵循一个基本原则:低等级不能直接访问高等级的资源。比如EL0的应用想调用系统服务,必须通过SVC指令触发同步异常,由EL1的内核来处理。

这种设计带来的好处很明显:

  • 安全性:EL3的Secure Monitor可以隔离安全世界和非安全世界
  • 虚拟化:EL2的Hypervisor可以管理多个EL1的虚拟机
  • 可扩展性:新增的异常等级不会影响现有软件栈
ARMv8 异常等级与软件栈分层 EL3 - Secure Monitor EL2 - Hypervisor EL1 - OS Kernel EL0 - User Applications 关键说明: • 异常等级从EL0到EL3,权限逐级递增 • 低等级不能直接访问高等级资源,必须通过异常机制(SVC/HVC/SMC) • 每个等级有独立的堆栈指针、异常向量表和系统控制寄存器

1.5 异常路由与返回机制

理解了异常等级,接下来就是异常怎么路由、怎么返回。ARMv8的异常处理流程,比ARMv7要清晰得多。

当一个异常发生时,处理器会:

  1. 根据异常类型和当前异常等级,确定目标异常等级
  2. 保存返回地址到ELR_ELx(异常链接寄存器)
  3. 保存当前PSTATE到SPSR_ELx(保存的程序状态寄存器)
  4. 跳转到对应异常等级的向量表项

异常返回使用ERET指令,它会从ELR_ELx恢复PC,从SPSR_ELx恢复PSTATE。这个设计比ARMv7的SUBS PC, LR, #4要优雅得多。

性能优化技巧:在AArch64中,异常返回的开销比ARMv7要小。因为ERET是一条原子指令,不需要手动恢复PC和PSTATE。我在做RTOS上下文切换优化时,利用这个特性把中断延迟降低了大约15%。

异常路由的规则也很直观:

  • Synchronous(同步异常):如SVC指令、数据中止、未定义指令,路由到当前异常等级或更高等级
  • IRQ/FIQ(异步异常):路由到当前异常等级或更高等级,由SCR_EL3/Routing控制
  • SError(系统错误):通常路由到EL3,由Secure Monitor处理

嗯,到这里,ARMv8架构的核心概念就讲完了。你可能会问:这些跟实际项目移植有什么关系?关系大了去了。后面几章我们会深入每个细节,从启动代码到MMU配置,从中断处理到性能调优,一步步把ARMv8吃透。

记住一句话:理解异常等级,就理解了ARMv8的魂。