1. ARMv8架构概述:从ARMv7到ARMv8的演进

各位好,我是老李。做嵌入式系统移植这行快十五年了,从ARMv4一直跟到现在的ARMv9。今天咱们聊聊ARMv8架构,这是ARM历史上一次翻天覆地的变化。说实话,当年我第一次看到ARMv8的手册时,心里也是一惊——这跟以前玩的ARMv7完全不是一个路子。

ARMv8不是简单的升级,它是一次架构重构。为什么这么说?因为ARMv8引入了64位指令集,同时保留了32位兼容能力。说白了,就是让你既能跑64位的Linux,也能跑老旧的32位RTOS。这种"双模"设计,在项目移植时既是福音也是坑。

1.1 从ARMv7到ARMv8:到底变了什么?

我习惯把ARMv7到ARMv8的变化归纳为三个核心点:

  • 地址空间翻倍:从32位虚拟地址变成64位,理论上支持16EB地址空间。当然,实际芯片一般只实现48位或52位。
  • 寄存器全面升级:通用寄存器从16个变成31个,每个都是64位宽。这个变化对编译器优化特别友好。
  • 异常模型重构:引入了EL0-EL3四级异常等级,替代了ARMv7的七种处理器模式。

我在一个项目中遇到过这种情况:团队把Cortex-A9上的代码直接往Cortex-A53上搬,结果异常处理全崩了。为什么?因为ARMv7的IRQ/FIQ模式在ARMv8里根本不存在了。嗯,这里要注意,移植时千万别想当然。

核心认知:ARMv8不是ARMv7的补丁,它是重新设计的架构。64位执行状态AArch64和32位执行状态AArch32,两者在指令编码、寄存器模型、异常处理上完全不同。

1.2 AArch64与AArch32:两种执行状态

ARMv8最巧妙的设计就是支持两种执行状态。你想想看,一个芯片既能跑64位Linux,又能跑32位的老系统,这对产品迭代多友好。

特性 AArch64 AArch32
指令集 A64(固定32位指令) A32/T32(ARM/Thumb)
通用寄存器 31个64位(X0-X30) 16个32位(R0-R15)
虚拟地址 48位或52位 32位
异常等级 EL0-EL3 EL0-EL3(但EL3可选)
兼容性 全新设计 向后兼容ARMv7

我个人习惯在项目开始时先确定:目标系统跑在哪个执行状态?如果是纯64位环境,那就用AArch64,性能最好。如果必须兼容老驱动或老应用,那就得考虑AArch32。但要注意,AArch32在ARMv8里只是"兼容模式",不是原生32位,有些细节不一样。

实战技巧:在U-Boot或ATF启动时,可以通过SCR_EL3.RW位控制切换执行状态。0表示EL3以下都是AArch32,1表示EL3以下都是AArch64。这个位一旦设定,整个系统就定死了,不能动态切换。

1.3 异常等级模型(EL0-EL3)

ARMv8的异常等级模型,说白了就是把系统分成四个权限层。我刚开始学的时候觉得这玩意儿挺绕,后来做安全启动项目时才真正理解它的价值。

ARMv8 异常等级模型(EL0-EL3) EL3(最高权限) 安全监控器(Secure Monitor) EL2(虚拟化层) Hypervisor / 虚拟化扩展 EL1(操作系统层) Linux内核 / RTOS / 特权固件 EL0(用户层) 用户应用程序 异常等级越高,权限越大。EL3可以访问所有资源,EL0只能访问用户空间。 异常发生时,处理器会提升到更高的异常等级来处理。

这张图我画了很多遍,每次给团队新人讲都从这张图开始。四个等级各司其职:

  • EL3:最高权限,运行安全监控器(如ARM Trusted Firmware)。负责安全启动、电源管理、安全世界切换。
  • EL2:虚拟化层,运行Hypervisor。如果你做虚拟化项目,这里就是主战场。
  • EL1:操作系统内核。Linux内核就跑在这里,处理中断、内存管理、进程调度。
  • EL0:用户应用程序。权限最低,不能直接访问硬件。

避坑指南:我曾经在一个安全启动项目中,把EL3的异常向量表地址配错了,结果系统一启动就跳转到随机地址。查了两天才发现是VBAR_EL3寄存器没初始化。记住:每个异常等级都有自己的向量表基址寄存器,必须单独配置。

1.4 寄存器模型变化

寄存器模型的变化,是移植时最需要关注的地方。ARMv7有16个通用寄存器(R0-R15),其中R13是栈指针、R14是链接寄存器、R15是程序计数器。到了ARMv8的AArch64,全变了。

我列个对比表,大家一目了然:

寄存器类型 ARMv7(32位) ARMv8 AArch64
通用寄存器 R0-R15(16个32位) X0-X30(31个64位)
栈指针 R13(SP) SP_EL0 / SP_ELx(每个等级独立)
链接寄存器 R14(LR) X30(LR)
程序计数器 R15(PC) PC(不可直接访问)
状态寄存器 CPSR SPSR_ELx / NZCV
异常返回 MOVS PC, LR ERET指令

这里有个关键点:ARMv8的PC不能直接读写。你想想看,在ARMv7里我们经常用MOV PC, LR做函数返回,但在ARMv8里必须用RET指令。我见过有人移植汇编代码时直接复制粘贴,结果编译都过不了。

寄存器变化要点:

  • X0-X7:参数传递和返回值(与ARMv7的R0-R3类似,但更多)
  • X8:间接结果寄存器(用于系统调用)
  • X9-X15:临时寄存器(调用者保存)
  • X16-X17:IP0/IP1(过程内调用临时寄存器)
  • X18:平台寄存器(OS保留)
  • X19-X28:被调用者保存寄存器
  • X29:帧指针(FP)
  • X30:链接寄存器(LR)

另外,ARMv8还引入了系统寄存器的概念。以前ARMv7用协处理器CP15访问系统控制功能,现在全部改用MSR/MRS指令读写系统寄存器。比如:

/* ARMv7: 读SCTLR */
MRC p15, 0, r0, c1, c0, 0

/* ARMv8 AArch64: 读SCTLR_EL1 */
MRS x0, SCTLR_EL1

这个变化对底层移植影响很大。我建议你在移植启动代码时,先把所有CP15访问全部替换成对应的系统寄存器访问。别偷懒,一个一个查手册确认。

个人习惯:我每次做新平台移植,都会先整理一份"寄存器映射表",把ARMv7的CP15寄存器对应到ARMv8的系统寄存器。这样写代码时直接查表,不容易出错。

1.5 小结

ARMv8架构的变化,说白了就是三个核心:64位地址空间、两种执行状态、四级异常等级。寄存器模型的变化是这些核心的体现。做移植时,我建议你先搞清楚目标系统跑在哪个异常等级、用AArch64还是AArch32,然后再动手改代码。

嗯,这一章就到这里。记住:ARMv8不是ARMv7的简单升级,它是重新设计的架构。尊重差异,才能少踩坑。