前言
Arm-v8架构是在2011年10月宣布的,至今也有10年了,但网上大部分关于Arm处理器的内容主要是基于早期经典的Arm-v6架构下的,甚至Arm-v7架构下的资料都很少,关于这两个架构的部分已经在前一篇进行了整理,也是对网上关于Arm寄存器最常见的一些说法进行了总结和筛选。本篇,目标移至目前主流的Arm-v8架构进行简要介绍(由于内容太多了,指令的部分被阉割了),主要内容均参考这本2015年发布的Arm白皮书手册。
新增特性
禁止直接修改PC
在x86架构中,直接修改EIP(Extended Instruction Pointer,该寄存器指向即将执行的指令,类似Arm处理器中的PC)的值是不被允许的,只能通过间接的方式来实现地址的跳转,例如call, jmp, jcc
。但在Arm架构的处理器下,汇编代码是可以直接修改PC(R15)寄存器的值的,这确实给了程序设计者更大的自由,对于艺高胆大的人来说,甚至可以玩出多种花样来。
然而,这种自由的代价就是增加了编译器的复杂度,也阻碍了更深的指令流水线的设计,最终在Arm-v8架构中,直接访问PC的方式也同x86一样,被禁止了。
异常级别
在Arm-v8架构下,新增了异常级别(Exception Level)EL0~EL4,用来整合早期架构中的运行模式(Arm-v7架构中的9种运行模式,除了Usr模式以外均为特权模式)与特权级别PLn(Privilege Level,与特权模式对应,只有Usr模式属于非特权级别,其余模式均为特权级别,大多数情况下,软件都运行在用户模式)。有点类似x86架构上的Ring0-Ring3,不同的是x86架构上,Ring3权限最小,Ring0权限最大,并且Ring1-Ring2通常不被使用。Arm-v8架构的异常级别则是,EL4权限最大,EL0权限最小。
有了异常级别的概念后,运行模式与特权级别的概念也就淡化了,EL0属于非特权级别,EL1/2/3属于特权级别。异常级别的使用模型如下:
异常级别 | 运行的软件 |
---|---|
EL0 | Normal user applications |
EL1 | Linux OS kernel |
EL2 | Hypervisor(虚拟机监视器,可以跑多个虚拟OS) |
EL3 | Low-level firmware, including the Secure Monitor |
总的来说,任何一个软件,不论是应用程序、操作系统内核还是虚拟机监视器,都只能运行在一个异常等级中。但是这个机制也有例外,对于内核虚拟机监视器(in-kernel hypervisor),例如KVM,就可以横跨EL2和EL1这两个异常级别运行。
安全模型
Arm-v8架构提供2种安全状态,Secure与Non-secure。Non-secure状态也就是常说的Normal World,Secure状态则对应Secure World。Arm-v8的安全模型基本沿用了Arm-v7 security extension的思路,主要目的保护一些安全应用的数据,例如指纹信息,人脸信息以及加密用的私钥等。该模型不同于特权级别等软件逻辑上的保护,而是一种物理上的隔离,即不同安全状态下,可以访问的物理内存是不同的。和Arm-v7架构一样,Secure Monitor起到看门的作用,用于隔离Normal World和Secure World。
执行状态
Arm-v8架构定义了两种执行状态,AArch64与AArch32。这两种执行状态用的是同一套通用寄存器(General-Purpose Registers),AArch32状态是对Arm-v7架构的兼容,它只使用通用寄存器低32位那部分。当处于Arm-v8架构的AArch32状态下,保留了Arm-v7架构定义的特权级别,而在AArch64状态下,特权级别将被异常级别所替代。因此,在执行状态进行转换的时候,就需要根据ELn与PLn的对应关系了。
当处于AArch64状态时,处理器执行的是A64指令集。当进入AArch32状态,处理器会执行A32或T32(Thumb)指令集种的一种。
下图展示了异常级别在不同执行状态下的组织结构:
寄存器组织(AArch64)
寄存器组织这里主要以AArch64执行状态为主,AArch32与Arm-v7架构类似,在最后一部分会提到,关于Arm-v7架构的寄存器组织可以先参考前一篇文章进行了解。
通用寄存器
首先是通用寄存器,Arm-v8架构有31个通用寄存器X0-X30,而Arm-v7架构仅有16个通用寄存器R0-R15。下面按组介绍各通用寄存器的功能:
参数寄存器(X0~X7):
通常用来传递函数的参数或是保存函数返回值,最多可以用来暂存函数的前8个参数。
易失性寄存器(X9~X15):
Caller-saved temporary registers,顾名思义,调用者需要保存的临时寄存器,也叫做易失性寄存器。当调用者试图调用子程序时,如果想继续使用这几个寄存器中的值,就需要在调用前,将这几个寄存器的值暂存到堆栈中。如果子程序中修改了这几个寄存器的值,在返回前,是不会恢复这几个寄存器中的值的。
非易失性寄存器(X19~X28):
Callee-saved registers,同理,被调用的子程序需要保存的寄存器,也叫做非易失性寄存器。在进入子程序时,子程序需要先将这些寄存器保存到堆栈中,然后再执行主体程序。在返回上层函数之前,也需要将这些寄存器恢复后再返回。因此调用者,可以不必保存这些寄存器。
特殊用途寄存器(X8, X16~X18, X29, X30):
- X8:间接结果寄存器。用来保存间接结果的地址,例如返回值是一个结构体时,保存结构体的地址。
- 子程序内部调用寄存器(Intra-Procedure-Call Temporary Registers),IP0与IP1,这俩寄存器可被Veneers(实现Arm/Thumb状态切换,由链接器插入的一小段代码)或类似的代码所用。或是用于在子程序调用前,作为存储中间值的临时寄存器。使用时不需要保存,但尽量不要用。
- x18:平台寄存器(Platform Register),用于保存当前所用的平台的ABI,尽量不要使用。
- x29:帧指针寄存器(FP),用于连接栈帧,使用时必须保存。
- x30:链接寄存器(LR),用于保存子程序的返回地址。
特殊寄存器
除了31个核心寄存器外,还有一些特殊的寄存器。如下图所示:
这里注意一点,官方白皮书特意说明了,没有X31/W31寄存器,很多指令集用X31/W31寄存器来表示零寄存器(ZR)或者栈帧寄存器(SP),这个说法是错误的,但因为传播广泛,从而造成了误导,包括兰新宇这篇中也是。
零寄存器(ZR):
在Linux的根文件系统中,dev目录下有个特殊的设备文件”zero”。作为source,读取”/dev/zero”可以产生众多的null字符(0x00);作为sink,任何写入”/dev/zero”的字符都将被吞没。
事实上,这两种应用的需求都是广泛存在的,所以ARMv8也在硬件层面引入了一个新的Zero Register(XZR/WZR),效果和软件层面的”/dev/zero”类似,作为源寄存器产生0,作为目标寄存器丢弃传入的数据。
比如我们要将一些变量赋值为0,因为ARM不允许直接操作内存单元上的数据,所以需要先将一个寄存器置为0,然后再讲这个寄存器的内容store到变量所在的内存单元上,像这样:
Code1
2mov r0, #0
str r0, [...]有了zero register,一条指令就可以解决问题:
Code1
str wzr, [...]
栈指针寄存器(SP):
SP(Stack Pointer)寄存器,这个Arm-v7也有,很好理解。不同的是,Arm-v8架构下,随着异常级别的引入。每个异常级别都有自己的栈指针寄存器,SP_EL0、SP_EL1、SP_EL2、SP_EL3。对于不同的异常级别,可访问的SP寄存器情况如下:
表中EL1t/EL2t/EL3t指的就是SP_EL0,简单来说就是处于EL0只能访问SP_EL0,处于ELn(n>0)可以访问SP_ELn和SP_EL0。
程序计数器(PC):
PC(Program Counter)寄存器的作用还是和Arm-v7一样,这里不重复,只是不能被直接访问和修改了,这部分在开头新增特性那也提到过。
异常链接寄存器(ELR):
ELR(Exception Link Register)寄存器也很熟悉,在介绍Arm-v7时就提到过,ELR_hyp与LR_hyp在32位运行状态下共用一个。这里,ELR的作用是保存异常返回地址。
进程状态保存寄存器(SPSR):
SPSR(Saved Program Status Register)寄存器,Arm-v8里的SPSR与Arm-v7里的类似,区别在于Arm-v7的SPSR用来保存CPSR(Current Program Status Register)的值,而Arm-v8里的SPSR用来保存PSTATE(Processor STATE)的值。下图为SPSR的基本
AArch64状态下,SPSR各个位所代表的含义如下:
- N:负数标志位(Negative result)
- Z:零标志位(Zero result)
- C:进位标志位(Carry out)
- V:溢出标志位(oVerflow)
- SS:软件单步(Software Step),表示异常发生时是否开启软件单步,我的理解是类似EFlags的TF(Trap Flag)位。
- IL:非法(代码)执行状态位(IlLegal Execution State bit),异常一发生,PSTATE的IL位就会被设置。
- D:进程状态调试位(Process state Debug mask)。表示是否从检测点(断点处)开始调试异常,软件单步调试事件会根据异常发生时,所在异常级别的SPSR_ELn的D位,决定是否进行调试。
- A:系统错误标志位(System Error)
- I:IRQ标志位。
- F:FIQ标志位。
- M[4]:表示异常发生时的执行状态,值为0时表示执行状态为AArch64。
- M[3:0]:异常发生时,所在模式或所处的异常级别。
在Arm-v8架构下,被写入值的SPSR依赖当前所处的异常级别。例如异常发生在EL1,使用的就是SPSR_EL1,以此类推。
处理器状态(PSTATE):
AArch64没有CPSR,取而代之的是PSTATE(Processor STATE),注意一点,CPSR与PSTATE并不相等,PSTATE是一组特殊寄存器的组合。下图分别为PSTATE各字段单独的含义,以及各组寄存器单独的含义(包含仅AArch32才有的寄存器),这里呢不单独列出来了,直接看英文或者参考上面的SPSR。
系统寄存器
在AArch64状态下,系统配置是通过系统寄存器来控制的,主要通过MSR
与MRS
指令进行操作。在以前的Arm-v7架构上,是通过协处理器(CP15)来操作系统配置的,但是Arm-v8架构的AArch64运行状态不支持协处理器。完整的寄存器名会声明最低可访问的异常级别,例如:
- TTBR0_EL1,可被EL1,EL2,EL3的程序访问
- TTBR0_EL2,可被EL2,EL3的程序访问
访问系统寄存器代码形式如下:
1 | MRS x0, TTBR0_EL1 // Move TTBR0_EL1 into x0 |
以下为几个比较重要的系统寄存器,这里仅对功能作简要介绍,不展开。更详细的内容,可以参考白皮书,或者后期对Arm架构进一步深入时会介绍到。
寄存器 | 说明 | 作用 | n的可选值 |
---|---|---|---|
CTR_ELn | 缓存类型寄存器(Cache Type Register) | 有关集成缓存体系结构的信息 | 0 |
ELR_ELn | 异常链接寄存器(Exception Link Register) | 存储导致异常发生的指令的地址 | 1, 2, 3 |
ESR_ELn | 异常综合表征寄存器(Exception Syndrome Register) | 包含导致异常发生的原因等信息 | 1, 2, 3 |
HCR_ELn | Hypervisor配置寄存器(Hypervisor Configuration Register) | 控制虚拟化功能的配置以及EL2的异常捕获。捕获异常时,会将异常(IRQ/FIQ/SError)路由到hypervisor中处理。 | 2 |
SCR_ELn | 安全配置寄存器(Secure Configuration Register) | 控制安全状态以及EL3的异常捕获 | 3 |
SCTLR_ELn | 系统控制寄存器(System Control Register) | 控制体系结构的特征,例如MMU,缓存以及对齐检测 | 0, 1, 2, 3 |
SPSR_ELn | 进程状态保存寄存器(Saved Program Status Register) | 当异常发生,切换运行模式或者异常等级时,保存处理器状态 | abt, fiq, irq, und, 1, 2, 3 |
TPIDR_ELn | 用户读写线程ID寄存器(User Read/Write Thread ID Register) | 为执行在某个异常级别的软件,提供一个存储其线程身份信息的地方,以方便操作系统管理 | 0, 1, 2, 3 |
TTBR0_ELn | Hypervisor转换表基址寄存器0(Translation Table Base Register 0) | 保存着转换表0的基址,以及被占用的内存信息。在AArch64中,TTBR0有3个,TTBR1只有1个。在双系统切换时(Linux/Tee),需要在ATF中保存/恢复这些寄存器。 | 1, 2, 3 |
TTBR1_ELn | Hypervisor转换表基址寄存器1(Translation Table Base Register 1) | 保存着转换表1的基址,以及被占用的内存信息 | 1 |
VBAR_ELn | 向量基址寄存器(Vector Based Address Register) | 保存任意异常进入ELn时的跳转向量基地址 | 1, 2, 3 |
系统控制寄存器
系统控制寄存器(System ConTroL Register ),用来控制标准内存,系统设施,并为核心功能函数的实现提供状态信息。系统控制寄存器属于系统寄存器,同样用MSR
与MRS
指令进行操作。这里不展开介绍了,就先贴个图吧,等以后遇到了再展开讲讲。
NEON与浮点寄存器
Arm-v8有32个128-bit的浮点寄存器V0~V31,这32个寄存器用来处理标量浮点运算和NEON指令。需要注意一点,v8-v15是Callee需要保存的,v16-v31是Caller需要保存的,这与通用寄存器是反着的。
对Arm-v7架构的兼容
寄存器组织的映射
先上图,看的明白:
左图很熟悉了,就是Arm-v7架构下的寄存器组织。再看右图,右图则是Arm-v8架构下,将AArch32状态对应的寄存器组织(也就是Arm-v7架构的寄存器组织)映射到Arm-v8架构下的31个通用寄存器上的情况。所以现在很多说法是,Arm处理器拥有31个通用寄存器和6个状态寄存器,这种说法其实是有误区的。在Arm-v7架构下,仅有16个通用寄存器;而在Arm-v8架构下,有31个通用寄存器,并且在AArch32执行状态下,原先Arm-v7架构所对应的各个寄存器的位置(除状态寄存器外)全部映射到了这31个寄存器上。因此才会被说成,有31个通用寄存器和6个状态寄存器,这也是至今仍有37个寄存器说法的原因之一。
AArch32运行状态下的SPSR与ELR_hyp寄存器作为额外的可以访问并使用系统指令的寄存器,它们并不会映射到AArch64状态下的通用寄存器上。这里面有着一些其它的映射关系,具体如下:
- SPSR_svc映射到SPSR_EL1
- SPSR_hyp映射到SPSR_EL2
- ELR_hyp映射到ELR_EL2
还有一些寄存器,它们只在AArch32状态下才使用,但是会保留寄存器的状态,即便在AArch64执行状态下无法访问它们:
- SPSR_abt
- SPSR_und
- SPSR_irq
- SPSR_fiq
这里白皮书提到一点,如果一个异常导致从AArch32状态的异常级别切换到了AArch64状态的异常级别,那么AArch64状态下,ELR_ELn的高32位会被置零。
AArch32状态下的PSTATE
前面说到,在AArch64下,是没有CPSR寄存器的,取而代之的是名为PSTATE的一组寄存器。但是在AArch32状态下,PSTATE字段值的含义将有所改变,并利用空余的字段,构造成可以兼容Arm-v7的PSTATE。当然,这种拥有额外字段PSTATE只能在AArch32状态下可被访问,具体如下:
为了方便比较,下图为AArch64状态下的PSTATE与AArch32状态下PSTATE各字段的对比:
异常级别的切换
在Arm-v7架构上,处理器模式可以通过特权级别的软件控制,或者在发生异常时自动切换。当异常发生时,内核会保存当前的执行状态与返回地址,进入到指定的运行模式,并大概率屏蔽掉硬件中断。
下图是对Arm-v7架构上,运行模式与特权级别的总结。应用程序运行在最低特权等级的PL0上,也就是非特权模式。操作系统运行在PL1上,虚拟机监视器以及虚拟化扩展位于PL2。作为连接Secure World与 Normal World的安全监视器(Secure Monitor)同样运行在PL1。
在AArch64执行状态下,处理器模式被映射到了异常级别上(如下图所示)。当AArch32运行状态发生异常时,处理器会切换到支持当前异常处理的异常级别(运行模式)上。
异常级别的切换必须遵循如下规则:
- 当切换到更高等级的异常级别时,例如从EL0到EL1,表明提升了软件执行时的特权。
- 不能产生一个低于当前异常级别的异常,例如EL1不能产生一个EL0的异常。
- 异常处理不能在EL0进行,处理异常必须在更高的异常级别上。
- 异常会导致程序流程的改变,后面省略,参考白皮书P34。
- 异常处理结束,并返回先前异常级别时,是通过
ERET
指令实现的。 - 从一个异常返回时,可以仍然处于发生异常的异常级别,或者返回到一个更低的异常级别中,但是不能进入到更高的异常级别中。例如从EL1返回,可能仍然位于EL1,或者返回到EL0,但是不能进入到EL2。
- 安全状态通常不会随着异常级别的切换而发生改变,除非是EL3直接返回到一个Non-secure状态。(这部分白皮书好像也错了,少了一个not)
执行状态的切换
你的操作系统会经常发生执行状态的切换。它可能是这样一种情况,例如,你所运行的是64位操作系统,但你想运行一个32位的应用程序在EL0,这时,你不得不将系统切换成AArch32状态。当程序执行完,再将操作系统切回AArch64执行状态。
一般来说,只能通过切换异常级别来实现执行状态的切换。当发生一个异常时,或许会导致从AArch32切换到AArch64,当从这个异常返回时,又会从AArch64切换回AArch32。这样就实现了执行状态的切换。更多注意要点,这里不再翻译了,参考白皮书P35。
参考资料
- 《Arm Cortex-A Series Programmer’s Guide for ARMv8-A》2015
- Armv8架构寄存器组织
- AArch64异常(一):AArch64异常简介
- Arm体系结构知识:汇编、架构、异常级别和安全状态
- Winddong-Armv8-AArch64 寄存器和指令集
- 思否-Armv8架构虚拟化介绍
- 知乎-Armv8带来的变化
- CSDN-Armv8-a架构简介
- CSDN-Armv8体系架构简介: AArch64系统级体系结构之编程模型
- CSDN-Armv8异常处理简介
- CSDN-Armv8架构与指令集.学习笔记
- CSDN-Arm通用寄存器传递参数介绍
- CSDN-Armv8对CPU虚拟化的支持及L4_fiasco中实现