概述
AArch64执行状态提供31×64位通用寄存器,可在所有时间和所有异常级别访问。
每个寄存器的宽度为64位,通常称为寄存器X0-X30。
每个AArch64位通用寄存器(X0-X30)也有一个32位(W0-W30)形式。
32位的W寄存器是64位X寄存器的低位,也就是说,W0映射到X0的低位字,W1映射到X1的低位字。读取W寄存器会从X读取低32bit值,高位忽略,但是写入W寄存器,会将X寄存器高32bit置0,也就是说,将0xFFFFFF写入W0会将X0设置为0x00000000FFFFFF。
1、AArch64特殊寄存器
除了31个核心寄存器外,还有几个特殊寄存器,如下
没有名为X31或W31的寄存器。许多指令被编码为数字31表示零寄存器ZR(WZR/XZR)。还有一组受限制的指令,其中对一个或多个参数进行编码,使数字31表示堆栈指针(SP)。
访问零寄存器时,将忽略所有写入操作,所有读取操作返回0。请注意,SP寄存器的64位形式不使用X前缀。
在ARMv8体系结构中,在AArch64中执行时,对于每个异常级别,异常返回状态保存在以下专用寄存器中:
- Exception Link Register (ELR)
- Saved Processor State Register (SPSR)
每个异常级别都有一个专用SP,但它不用于保存返回状态(EL0的LR使用X30寄存器,EL0中没有权限访问PSR,因此没有,需要在其他特权级才可以)
1.1 Zero register
零寄存器用作源寄存器时读取为零,用作目标寄存器时写无效。多用于清除其他寄存器。
1.2 Stack pointer
在ARMv8体系结构中,各异常级别的SP是分开的。默认情况下,获取异常会选择目标异常级别SP_ELn的堆栈指针。例如,对EL1执行异常将选择SP_EL1。每个异常级别都有自己的堆栈指针SP_EL0、SP_EL1、SP_EL2和SP_EL3。
当AArch64处于EL0以外的异常级别时,处理器可以使用
- 与该异常级别(SP_ELn)关联的专用64位堆栈指针
- 与EL0(SP_EL0)关联的堆栈指针
EL0只能访问SP_EL0
t后缀表示选择了SP_EL0堆栈指针。h后缀表示选择了SP_ELn堆栈指针。
大多数指令都无法引用SP。但是,一些形式的算术指令,例如,ADD指令,可以读写当前堆栈指针,以调整函数中的堆栈指针。例如:
ADD SP, SP, #0x10 // Adjust SP to be 0x10 bytes before its current value
1.3 Program Counter
ARMv7使用R15作为PC指针,在ARMv8中取消了对于PC的直接访问,使得函数的返回更容易被预测。PC无法直接被使用,而是将其隐含在其他的指令中,如PC的相对地址加载。
1.4 Exception Link Register (ELR)
The Exception Link Register holds the exception return address
1.5 Saved Process Status Register
当程序产生异常时,程序的状态保存在Saved Program Status Register (SPSR)中,与ARMv7的CPSR类似。SPSR在发生异常之前保存PSTATE的值,并用于在执行异常返回时恢复PSTATE的值。
在ARMv8中,写入的SPSR取决于异常级别。如果在EL1中获取异常,则使用SPSR_EL1。如果EL2中出现异常,则使用SPSR_EL2,如果EL3中出现异常,则使用SPSR_EL3。当发生异常时,core将填充SPSR。
2、Processor state
在ARMv8中无法像ARMv7那样直接访问cpsr寄存器。在AArch64中,传统CPSR的组件作为可独立访问的字段提供,这些统称为处理器状态(PSTATE)。PSTATE不是一个寄存器,是一组。
在AArch64中,通过执行ERET指令从异常返回,这将导致SPSR_ELn被复制到PSTATE。这将恢复ALU标志、执行状态、异常级别和处理器分支。从这里,可以从ELR_ELn中的地址继续执行。PSTATE.{N,Z,C,V}字段可以在EL0访问。所有其他PSTATE字段可以在EL1或更高级别执行,并且在EL0未定义。
3、System registers
在AArch64中,系统配置通过系统寄存器进行控制,并使用MSR和MRS指令进行访问,而在ARMv7中使用协处理器CP15来处理,因此ARMv8移除了协处理器的概念,用系统寄存器来代替。寄存器的名称表明了访问它的最低级别。例如:
•TTBR0 EL1可从EL1、EL2和EL3访问。
•TTBR0 EL2可从EL2和EL3访问
后缀为_ELn的寄存器在部分或所有级别中都有一个单独的备份,通常不是EL0。
很少有系统寄存器可以从EL0访问,尽管缓存类型寄存器(CTR_EL0)就是一个可以访问的例外。
访问系统寄存器如下所示:
MRS x0, TTBR0_EL1 // Move TTBR0_EL1 into x0
MSR TTBR0_EL1, x0 // Move x0 into TTBR0_EL1
ARM体系结构的早期版本使用协处理器进行系统配置。但是,AArch64不包括对协处理器的支持。表4-5仅列出了本书中提到的系统寄存器。该表显示了具有每个寄存器的单独副本的异常级别。例如,单独的辅助控制寄存器(ACTLR)以ACTLR_EL1、ACTLR_EL2和ACTLR_EL3的形式存在。
3.1 The system control register
系统控制寄存器(SCTLR)是控制标准内存、系统设施和提供内核执行状态的寄存器。
- UCI:设置后,在AArch64中为DC CVAU、DC CIVAC、DC CVAC和IC IVAU指令启用EL0访问
- EE:异常大小端,0-小端 1-大端
- EOE:EL0中显示数据访问大小端。0-小端 1-大端
- WXN:0-写权限的区域不会被强制到“从不执行” 1-写权限的区域被设置为从不执行
- nTWE:为1表示WFE指令正常执行。不是陷入到WFE
- nTWI:为1表示WFI指令正常执行。不是陷入到WFI
- UCT:设置后,在AArch64模式下EL0可以访问CTR_EL0寄存器。
- DZE:在EL0处访问DC ZVA指令。0-禁止 1-使能
- I:指令缓存启用。这是EL0和EL1处指令缓存的启用位
- UMA:用户掩码访问。当EL0使用AArch64时,控制从EL0访问中断掩码
- SED:SETEND指令禁用。0-enable 1-disable
- ITD:IT指令禁用。0-IT指令有效 1-IT指令被视为16位指令。只有另一条16位指令,或32位指令的前半部分可以跟随。
- CP15BEN:CP15屏障启用。如果实现,它是AArch32 CP15 DMB、DSB和ISB屏障操作的启用位。
- SA0:EL0的堆栈对齐检测
- SA:堆栈对齐检测使能
- C:数据缓存使能。EL0和EL1的数据缓存使能位。
- A:对齐检测使能位
- M:使能MMU
访问SCTLR:
在任何异常级别中启用数据和指令缓存之前,必须先使处理器中的缓存失效
4、Endianness
在内存中存在的两种数据存储方式,小端及大端。小端中最低有效字节存储在低地址,而在大端中是最高有效字节存储在低地址。
对于每个异常级别,大小端的模式是相互独立的。对于EL3、EL2、EL1是通过SCTLR_ELn.EE 设置大小端。EL0的大小端控制位是在EL1的附加位SCTLR_EL1.E0E。在AArch64执行状态下,数据访问可以是大端或小端,而取指总是小端的。处理器是否同时支持LE和BE取决于处理器的实现。如果只支持小端点,则EE和E0E位始终为0。类似地,如果只支持大端性,则EE和E0E位处于静态1值。使用AArch32时,CPSR.E bit的值不同于系统控制寄存器的EE bit,现在不推荐这么来设置。也不推荐使用ARMv7 SETEND指令。设置SCTLR.SED位后,可能在执行SETEND指令时导致Undef异常。
5、Changing execution state (again)
在第3.8的“更改执行状态”中,我们描述了AArch64和AArch32之间在异常级别方面的更改。现在我们从寄存器的角度考虑这种变化。
从AArch32进入到AArch64:
- 在AArch32上可以访问的低异常等级的寄存器的值是未知的
- AArch32执行期间无法访问的寄存器保留AArch32执行之前的状态
- 在EL3的异常中,当EL2使用AArch32时,ELR_EL2的高32位的值是未知的
- AArch64状态下的SP和ELRs寄存器在AArch32状态下无法访问,在该异常级别,保留AArch32执行之前的状态,下列寄存器也是如此:SP_EL0、SP_EL1、 SP_EL2、 ELR_EL1。
5.1 Registers at AArch32
实际上与ARMv7相同意味着AArch32必须与ARMv7权限级别相匹配。这也意味着AArch32只处理ARMv7 32位通用寄存器。因此,ARMv8体系结构与AArch32执行状态提供的视图之间必须存在某种对应关系。
在ARMv7体系结构中,有16个32位通用寄存器(R0-R15)供软件使用。其中15个(R0-R14)可用于通用数据存储。剩余的寄存器R15是程序计数器(PC),其值随着内核执行指令而改变。软件也可以访问CPSR,先前执行模式下保存的CPSR副本为SPSR。在接受异常时,CPSR将复制到接受异常的模式的SPSR。
ARMv7中使用备份功能来减少异常的延迟。然而,这也意味着,在相当数量的寄存器中,一次可以使用的寄存器不到一半。相比之下,AArch64执行状态有31×64位通用寄存器,可在所有时间和所有异常级别访问。AArch64和AArch32之间执行状态的变化意味着AArch64寄存器必须映射到AArch32(ARMv7)寄存器集。如下图4-8
在AArch32中执行时,AArch64寄存器的高32位不可访问。如果处理器在AArch32状态下运行,它将使用32位W寄存器,这相当于32位ARMv7寄存器。
AArch32将备份寄存器映射到AArch64寄存器,否则将无法访问这些寄存器。
AArch32中的SPSR和ELR_Hyp寄存器是仅可使用系统指令访问的附加寄存器。它们没有映射到AArch64体系结构的通用寄存器空间。其中一些寄存器在AArch32和AArch64之间映射:
•SPSR_svc映射到SPSR_EL1。
•SPSR_hyp映射到SPSR_EL2。
•ELR_hyp映射到ELR_EL2
以下寄存器仅在AArch32执行期间使用。但是,由于使用AArch64在EL1处执行,因此尽管在该异常级别的AArch64执行期间无法访问它们,但它们仍保留其状态:
• SPSR_abt.
• SPSR_und.
• SPSR_irq.
• SPSR_fiq.
SPSR寄存器仅在AArch64在更高的异常级别上的上下文切换时可访问。
同样,如果将异常从AArch32中的异常级别进入AArch64中的异常级别,AArch64 ELR_ELn的前32位都为零
5.2 PSTATE at AArch32
在AArch64中PSTATE中的字段可以对应于CPSR的不同组件,可以独立访问。在AArch32中,存在与ARMv7 CPSR位相对应的额外字段。
6、NEON and floating-point registers
除了通用寄存器外,ARMv8还有32个128位浮点寄存器,标记为V0-V31。32个寄存器用于保存标量浮点指令的浮点操作数,以及NEON操作的标量和向量操作数。NEON和浮点寄存器也在第7章AArch64浮点和NEON中介绍。
6.1 Floating-point register organization in AArch64
在对标量数据进行操作的NEON指令和浮点指令中,浮点寄存器和NEON寄存器的行为类似于主要的通用整数寄存器。因此,只访问低位,读取时忽略未使用的高位,写入时设置为零。标量浮点和NEON名称的限定名表示有效位的数量,如下所示,其中n是寄存器编号0-31。
支持16位浮点,但仅作为要从或转换为的格式。数据处理操作不支持它。
6.2 Scalar register sizes
在AArch64中,整数标量的映射已从ARMv7-A中使用的映射更改为图4-11所示的映射:
在图4-11中,S0是D0的下半部分,D0是Q0的下半部分。S1是D1的下半部分,D1是Q1的下半部分,依此类推。
6.3 Vector register sizes
向量可以是64位宽的一个或多个元素,也可以是128位宽的两个或多个元素,如图4-12所示
6.4 NEON in AArch32 execution state
在AArch32中,较小的寄存器被打包成较大的寄存器(例如,D0和D1被组合成Q1)。这会引入一些复杂的循环携带依赖项,这会降低编译器对循环结构进行矢量化的能力。
AArch32中的浮点和高级SIMD寄存器映射到AArch64 FP和SIMD寄存器。
AArch64 V16-V31 FP和NEON寄存器无法从AArch32访问。与通用寄存器一样,在使用AArch32在异常级别执行期间,这些寄存器保留了以前使用AArch64执行时的状态。