ARM64 架构基础

原图

ARM64 架构介绍

ARMv8-A 架构介绍

ARMv8-A 是 ARM 公司发布的第一代支持 64 位处理器的指令集和架构。ARMv8-A 架构除了提高了处理能力外,还引入了很多吸引人的新特性。

  • 具有 64 位宽的虚拟地址空间。
  • 提供 31 个 64 位宽的通用寄存器,可以减少对栈的访问,从而提高性能。
  • 提供 16KB 和 64KB 的 page,有助于降低 TLB 的未命中率(miss rate)。
  • 具有全新的异常处理模型,有助于降低操作系统和虚拟化的实现复杂度。
  • 具有全新的加载-获取、存储-释放指令(load-acquire,store-release instruction),专为 C++11 以及 Java 内存模型而设计。

ARM64 的基本概念

ARM 处理器实现的是精简指令集计算机(Reduced Instruction Set Computer,RISC)架构。本节介绍 ARMv8-A 架构中的一些基本概念。

  • 处理单元: ARM 公司的官方技术手册中提到了一个概念,可以把处理器处理事务的过程抽象为处理单元(Processing Element,(PE)

  • 执行状态: 执行状态(execution state)是处理器运行时的环境,包括寄存器的位宽、支持的指令集异常模型、内存管理以及编程模型等。ARMv8 架构定义了两种执行模式
    • AArch64:64 位的执行状态。提供 31 个 64 位的通用寄存器、提供 64 位的程序计数(PC)寄存器、栈指针(SP)寄存器以及异常链接寄存器(ELR)。提供 A64 指令集、定义 ARMv8 异常模型,支持 4 个异常等级 ELO~EL3
    • AArch32:32 位的执行状态。提供 13 个 32 位的通用寄存器,再加上 PC 寄存器、SP 寄存器、链接寄存器(LR)。支持两套指令集,分别是 A32 和 T32 指令集(Thumb 指令集)
  • ARMv8 指令集:
    • A64 指令集:运行在 AArch64 状态,提供 64 位指令集支持
    • A32 指令集:运行在 AArch32 状态,提供 32 位指令集支持
    • T32 指令集:运行在 AArch32 状态,提供 16 和 32 位指令集支持
  • 系统寄存器命名: 在 AArch64 状态下,很多系统寄存器会根据不同的异常等级提供不同的变种寄存器

    _ELx, where x is 0,1, 2, or 3

比如,SP_ELO 表示 ELO 下的栈指针寄存器,SP_EL1 表示 EL1 下的栈指针寄存器。

ARMv8 处理器的运行状态

ARMv8 处理器支持两种运行状态--AArch64 状态和 AArch32 状态。AArch64 架构的异常等级(exception level)确定了处理器当前运行的特权级别,类似于 ARMv7 架构中的特权等级。

  • EL0:用户特权,用于运行普通用户程序。
  • EL1:系统特权,通常用于运行操作系统。
  • EL2:运行虚拟化扩展的虚拟监控程序(hypervisor)。
  • EL3:运行安全世界中的安全监控器(secure monitor)。

在 ARMv8 架构里允许切换应用程序的运行模式。比如,在运行 64 位操作系统的 ARMv8 处理器中,我们可以同时运行 A64 指令集的应用程序和 A32 指令集的应用程序。当需要执行 A32 指令集的应用程序时,需要通过管理员调用(Supervisor Call, SVC)指令切换到 EL1 操作系统会执行任务的切换并且返回到 AArch32 的 ELO,这时候系统便为这个应用程序备好了 AArch32 的运行环境。

不对齐访问

对齐访问有两种情况。一种是指令对齐,另一种是数据对齐。

  • A64 指令集要求指令存放的位置必须以字(word,32 位宽)为单位对齐。访问存储位置不是以字为单位对齐的指令会导致 PC 对齐异常(PCaligment fault)。
  • 对于数据访问,需要区分不同的内存类型。
    • 对内存类型是设备内存的不对齐访问会触发对齐异常(alignment fault)
    • 对于访问普通内存,除了使用独占、加载/独占-存储(load-exclusive/store-exclusive)指令或加载-获取/存储-释放(load-acquire/store-release)指令外,还可使用其他的用于加载或存储单个或多个寄存器的所有指令。如果访问地址和要访问的数据元素大小不对齐,那么可以根据以下两种情况进行处理:
      • 若对应的异常等级中的 SCTLR_ELx 寄存器的 A 域设置为 1,则说明打开了地址对齐检查功能,因而会触发对齐异常
      • 若对应的异常等级中的 SCTLRELx 寄存器的 A 域设置为 0,则说明处理器支持不对齐访问

当然,处理器支持的不对齐访问也有一些限制。

  • 不能保证单次访问原子地完成,有可能复制多次。
  • 不对齐访问比对齐访问需要更多的处理时间。
  • 不对齐的地址访问可能会引发中止(abort)。

ARMv8 寄存器

通用寄存器

AArch64 运行状态支持 31 个 64 位的通用寄存器,分别是 x0~x30 寄存器,而 AArch32 运行状态支持 16 个 32 位的通用寄存器。通用寄存器除了用于数据运算和存储之外,还可以在函数调用过程中起到特殊作用,ARM64 架构的函数调用标准和规范对此有所约定。

  • 传递参数和结果:x0 - x7。
  • 间接结果寄存器:x8。
  • 临时寄存器(调用的函数需要保存): x8 - x15。
  • 平台寄存器:x16(IP0) x17(IP1) x18(PR)。
  • 被调用函数需要保存:x19 - x28。
  • 栈帧寄存器:x29(FP)。
  • 链接寄存器:x30(LR)。

在 AArch64 运行状态下,使用 X 来表示 64 位通用寄存器,比如 X0、X30 等。另外,还可以使用 W 来表示低 32 位的寄存器,比如 W0 表示 X0 寄存器的低 32 位数据。

特殊寄存器

ARMv8 架构除了支持 31 个通用寄存器之外,还提供各个特殊的寄存器,如下图所示:

零寄存器

ARMv8 架构提供两个零寄存器(zero register),这两个零寄存器的内容全是 0,可以用作源寄存器,也可以当作目标寄存器。WZR 是 32 位的零寄存器,XZR 是 64 位的零寄存器。

栈指针寄存器

ARMv8 架构支持 4 个异常等级,每一个异常等级都有专门的 SP_ELn 寄存器,比如当处理器运行在 EL1 时就选择 SP_EL1 寄存器作为栈指针(Stack Pointer,SP)寄仔器。

  • SP EL0:EL0 下的栈指针寄存器。
  • SP EL1:EL1 下的栈指针寄存器。
  • SP EL2:EL2 下的栈指针寄存器。
  • SP EL3:EL3 下的栈指针寄存器。

当处理器运行在比 EL0 高的异常等级时:

  • 处理器可以访问当前异常等级对应的栈指针寄存器 SP_ELn。
  • EL0 对应的栈指针寄存器 SP_ELO 可以当作临时寄存器,比如 Linux 内核就使用这种临时寄存器存放进程的 task struct 数据结构的指针。
  • 当运行在 ELO 时,处理器只能访问 SP_EL0 寄存器,而不能访问其他高等级的 SP 寄存器。

PC 寄存器

PC (Program Counter)寄存器通常用来指向当前运行指令的下一条指令的地址,用于控制程序中指令的执行顺序,但是编程人员不能通过指令来直接访问。

异常链接寄存器

异常链接寄存器(Exception Link Register,ELR)用来存放异常返回地址。

保存处理状态寄存器

当我们进行异常处理时,处理器的处理状态会保存到保存处理状态寄存器(SavedProcess Status Register,(SPSR)里,这种寄存器非常类似于 ARMv7 架构中的 CPSR。当异常将要发生时,处理器会把处理状态寄存器(PSTATE)的值暂时保存到 SPSR 里;当异常处理完成并返回时,再把 SPSR 中的值恢复到处理器状态寄存器。SPSR 的布局如图所示:

  • CurrentEL 寄存器: PSTATE 寄存器中的 EL 字段保存了当前异常等级。使用 MRS 指令可以读取当前异等级。
    • 0:表示 ELO
    • 1:表示 EL1
    • 2:表示 EL2
    • 3:表示 EL3
  • DAIF 寄存器: 表示 PSTATE 寄存器中的{D,A,I,F}字段
    • D Debug exceptions mask
    • A SError interrupt Process state mask, for example, asynchronous external abort
    • I IRQ interrupt Process state mask
    • F FIQ interrupt Process state mask
  • SPSel 寄存器: 表示 PSTATE 寄存器中的 SP 字段,用来在 SP_EL0 和 SP_ELn 中选择栈指针寄存器

系统寄存器

除了上面介绍的通用寄存器和特殊寄存器之外,ARMv8 架构还定义了很多系统寄存器,可通过访问和设置这些系统寄存器来完成对处理器不同功能的配置。在 ARMv7 架构里,我们需要通过访问 CP15 协处理器来间接访问这些系统寄存器:而在 ARMv8 架构中没有协处理器,可直接访问系统寄存器。ARMv8 架构支持如下 7 大类的系统寄存器:

  • 通用系统控制寄存器
  • 调试寄存器
  • 性能监控寄存器
  • 活动监控寄存器
  • 统计扩展寄存器
  • RAS 寄存器
  • 通用定时器寄存器

系统寄存器支持不同异常等级下的访问,通常系统寄存器可使用“Reg_ELn”的方式来表示,示例如下。

  • Reg_EL1:处理器处于 EL1、EL2 以及 EL3 时可以访问该寄存器。
  • Reg_EL2:处理器处于 EL2 和 EL3 时可以访问该寄存器。

当处于 EL0 时,大部分系统寄存器不支持处理器访问,但也有一些例外,比如 CTR_ELO。可以通过 MSR 和 MRS 指令来访问系统寄存器,比如:

1
2
mrs x0, TTBRO_EL1 //把 TTBR0_EL1 的值复制到 x0 寄存器
msr TTBROEL1, xO //把 x0 寄存器的值复制到 TTBR0_EL1

A64 指令集

指令集架构(ISA)是处理器架构设计的重点之一。ARM 公司定义和实现的指令集架构一直在不断变化和发展。ARMv8 架构最大的改变是增加了一种新的 64 位的指令集,这是对早前 ARM 指令集的有益补充和增强,称为 A64 指令集。这种指令集可以处理 64 位宽的寄存器,并且使用 64 位的指针来访问内存,运行在 AArch64 状态下。ARMv8 兼容老的 32 位指令集,又称为 A32 指令集,运行在 AArch32 状态下。 A64 指令集和 A32 指令集是不兼容的,它们是两套完全不一样的指令集架构,它们的指令编码也不一样。需要注意的是,A64 指令集的指令为 32 位宽,而不是 64 位宽

aarch64 架构图

指令集概览:

详细可以查看原文 点击

ARM64 异常处理

在 arm64 架构里,中断属于异常的一种。中断是外部设备通知处理器的一种方式,他会打断处理器正在执行的指令流。在前面的描述中断的文章中已经介绍了关于异常的处理,这里不再细说。

ARM64 内存管理

如图所示,ARM 处理器的内存管理单元(Memory Management Unit,MMU)包括 TLB 和页表遍历单元(Table Walk Unit)两个部件。TLB 是一块高速缓存,用于缓存页表转换结果,从而减少页表查询的时间。完整的页表翻译和查找的过程叫作页表查询(TranslatonTable Walk),页表查询的过程由硬件自动完成,但是页表的维护需要由软件完成。

对于多任务操作系统,每个进程都拥有独立的进程地址空间。这些进程地址空间在虚地址范围内是相互隔离的,但是在物理地址空间内有可能映射到同一个物理页面,那么这些进程地址空间是如何和物理地址空间发生映射关系的呢?这就需要处理器的内存管理单元提供页表映射和管理的功能。

页表

AArch64 架构中的 MMU 不仅支持单一阶段的地址页表转换,还支持虚拟化扩展中的两阶段页表转换

  • 单一阶段页表:虚拟地址(VA)被翻译成物理地址(PA)
  • 两阶段页表(虚拟化扩展) 阶段 1 虚拟地址被翻译成中间物理地址(Intermediate Physical Adress,IPA) 阶段 2 中间物理地址被翻译成最终物理地址

页表映射

在 AArch64 架构中,因为地址总线位宽最多支持 48 位,所以虚拟地址被划分为两个空间,每个空间最多支持 256TB。

  • 低位的虚拟地址空间位于 0x00000000000000~0x0000FFFFFFFFF。如果虚拟地址的最高位等于 0,那就使用这个虚拟地址空间,并且使用 TTBRO_ELx (TranslationTable Base Register)来存放页表的基地址。
  • 高位的虚拟地址空间介于 0xFFFF00000000000~0xFFFFFFFFFFFFF。如果虚拟地址的最高位等于 1,那就使用这个虚拟地址空间,并且使用 TTBR1_ELx 来存放页表的基地址。

AArch64 架构中的页表支持如下特性。

  • 最多可以支持 4 级页表。
  • 输入地址的最大有效位宽为 48 位。
  • 输出地址的最大有效位宽为 48 位。
  • 翻译的页面粒度可以是 4KB、16KB 或 64KB。

当 TLB 未命中时,处理器查询页表的过程如下。

  • 处理器根据 TTBRx 和虚拟地址来判断使用哪个页表基地址寄存器,是使用 TTBR0 还是 TTBR1。当虚拟地址的第 63 位(简称 VA[63])为 1 时选择 TTBR1 当 VA[63] 为 0 时选择 TTBR0。页表基地址寄存器中存放着一级页表的基地址。
  • 处理器将虚拟地址的 VA[47:39] 作为 L0 索引,在一级页表(L0 页表)中找到页表项,一级页表一共有 512 个页表项。
  • 一级页表的页表项中存放有二级页表(L1 页表)的物理基地址。处理器将虚拟地址的 VA[38:30] 作为 L1 索引,在二级页表中找到相应的页表项,二级页表有 512 个页表项。
  • 二级页表的页表项中存放有三级页表(L2 页表)的物理基地址。处理器将虚拟地址的 VA[29:21] 作为 L2 索引,在三级页表(L2 页表)中找到相应的页表项,三级页表有 512 个页表项。
  • 三级页表的页表项中存放有四级页表(L3 页表)的物理基地址。处理器将虚拟地址的 VA[20:12] 作为 L3 索引,在四级页表(L3 页表)中找到相应的页表项,四级页表有 512 个页表项。
  • 四级页表的页表项里存放有 4KB 页的物理基地址,然后加上虚拟地址的 VA[11:0] 就构成了新的物理地址,于是处理器就完成了页表的查询和翻译工作。

参考文献

https://blog.51cto.com/u_15278218/2930963
https://courses.cs.washington.edu/courses/cse469/19wi/arm64.pdf
《ARM-Architecture Reference Manual,for ARMv8-A Architecture Profile,v8.4》
《奔跑吧 Linux 内核-入门篇》