原图
ARM64架构介绍
ARMv8-A架构介绍
ARMv8-A是ARM公司发布的第一代支持64位处理器的指令集和架构。ARMv8-A架构除了提高了处理能力外,还引入了很多吸引人的新特性。
- 具有64位宽的虚拟地址空间
- 提供31个64位宽的通用寄存器,可以减少对栈的访问,从而提高性能
- 提供16KB和64KB的页面,有助于降低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 | mrs x0, TTBRO_EL1 //把TTBR0_EL1的值复制到x0寄存器 |
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内核-入门篇》