/* TODO 本系列文章是对 ARMv8 Cortex-a 系列编程向导手册拙劣的翻译和注解,若有出入,以官方文档为准 */
AArch64 执行状态提供了 31 个 64 位宽的通用目的寄存器,可以在所有异常等级访问,每一个异常等级任一寄存器的访问都是相同的,这 31 个寄存器被称为 X0 - X30.
其中 W0-W30 是 X0 - X30 的 32 位格式,对应低32位(X30 保存子函数的返回地址?ELR_ELn 保存异常返回地址)。
如果写 W0 寄存器,那么 X0 寄存器的高 32 位会置 0.
除了 31 个内核通用寄存器,ARMv8 也提供了一些特殊寄存器。
寄存器名 | 寄存器描述 |
---|---|
WZR | 32 位宽的 0 寄存器,读寄存器的值恒为 0 |
XZR | 64 位宽的 0 寄存器,同上 |
SP_ELn | 堆栈指针,每一个异常等级都有专用的堆栈指针,n 表示异常等级 |
ELR_ELn | 保存异常返回地址的寄存器,当执行 ERET 指令时,该寄存器为异常返回后的执行地址,n 取值范围1-3,因为没有异常会在 EL0 中处理 |
SPSR_ELn | 用于保存异常发生时的处理器状态的寄存器,该寄存器备份的是 PSTATE 各位域的值,当执行 ERET 指令时,该寄存器会设置 PSTATE 各位域,n 取值范围1-3,因为没有异常会在 EL0 中处理 |
当该寄存器用作源寄存器时,读出的数据是0,当该寄存器用作目的寄存器时,写入的数据被丢弃。
在ARMv8架构中,当异常发生时,通常会伴随堆栈指针的切换,每一个异常等级一般都会使用该异常等级专用的寄存器,但除了 EL0 之外的异常等级,这些特权异常等级所使用的堆栈指针是可选的:
AArch64 堆栈指针选择如下:
其中 t 后缀表示选择 SP_EL0 堆栈指针,h 后缀表示选择 SP_ELn 堆栈指针(目前 t 与 h 的应用不懂,t 与 h 可以不用尝试理解)。
大多数指令不能直接使用 SP 指针,但是一些算数指令可以使用 SP 指针,比如 ADD 指令:
ADD SP, SP, #0x10 // Adjust SP to be 0x10 bytes before its current value
在 ARMv7 架构中, PC 寄存器可以做为一个通用寄存器使用。在 ARMv8 架构中,移除了对 PC 寄存器的直接访问,这可以让程序流更加可预测。
在 ARMv8 架构中, PC 不像任何一个已命名的寄存器那样可访问,而是使用隐形的 PC 相关的加载和地址生成指令(比如 B,BL,BLR). PC 绝不能做为一个数据处理指令和加载指令的目的寄存器。
该寄存器保存了异常返回地址,当执行 ERET
指令从异常中返回时,该寄存器的值会赋值给 PC
当异常发生时,处理器状态被保存到 SPSR_ELn 寄存器中(类似 ARMv7 的 CPSR 寄存器)。SPSR_ELn 寄存器保存异常发生时 PSTATE 的值,当异常返回时,SPSR_ELn 寄存器用于恢复 PSTATE 的值(PSTATE 用于保存处理器状态,这不是一个寄存器,更像是一块专用内存,该内存的各位域可单独访问)。
SPSR_ELn 寄存器结构如下:
位域 | 描述 |
---|---|
N | 负值标志位 |
Z | 0值标志位 |
C | 进位标志位 |
V | 溢出标志位 |
SS | software step |
IL | 非法执行状态位 |
D | 调用屏蔽位 |
A | SError(System error ) 中断屏蔽位 |
I | IRQ 中断屏蔽位 |
F | FIQ 中断屏蔽位 |
M | 异常发生时的执行状态指示位,0 表示异常发生时的执行状态为 AArch64 (注意,不通过该位修改执行状态) |
M[3:0] | 异常发生时的处理器模式或处理器异常等级 |
在 ARMv8 架构中,SPSR 寄存器的选择依据异常等级(类似 ELR 寄存器)。当异常在 EL1 中处理,那么使用 SPSR_EL1, 其他异常等级同理。 |
AArch64 并没有与 ARMv7 等同的 CPSR, 而是将 CPSR 的各部分作为可单独访问的不同的位域,这些位域统称为 PSTATE.
PSTATE 的位域定义如下:
在 AArch64 中,当我们执行 ERET
从异常中返回时,cpu 会将 SPSR_ELn 寄存器的值拷贝到 PSTATE 的各位域中,恢复各算术标志位,执行状态,异常等级,最后处理器跳转到 ELR_ELn 保存的地址处继续执行。
PSTATE.{N, Z, C, V} 位域可以在 EL0 异常等级中访问,其余位域只能 EL1 或更高异常等级中访问,如果在 EL0 中访问,那么是未定义的。
在 AArch64 中,系统配置也是通过系统寄存器实现,使用 MSR
与 MRS 指令访问。
而在 ARMv7 中,系统寄存器的访问通过 cp15 实现。在 ARMv8 中,系统寄存器的命名也表明了最低可访问的异常等级,比如:
对于不同异常等级的相同名称的寄存器(即拥有不同的后缀 ELn),它们被认为是单独的寄存器。系统寄存器的访问如下:
MRS x0, TTBR0_EL1 // Move TTBR0_EL1 into x0
MSR TTBR0_EL1, x0 // Move x0 into TTBR0_EL1
AArch64 不支持协处理器的使用,而是直接使用系统寄存器名来访问寄存器。
下表列出了常用的系统控制寄存器:
寄存器名 | 寄存器描述 |
---|---|
ACTRL_ELn | 复用控制寄存器,类似于 ARMv7 的 ACTRL, n 取值 1-3 |
CCSIDR_ELn | 当前所选择操作的 cache 的大小寄存器,通常在 clean 和 invalid 操作中使用,n 取值为 1 |
… | |
(后续寄存器描述请参考 4.3 章节内容,有个了解即可,表太多了,懒得翻译了,系统寄存器的名称与功能类似于 ARMv7) |
类似于 ARMv7 的 SCTLR, ARMv8 的 SCTLR_ELn 通用提供 cpu 内核功能控制,比如 cache 使能,MMU 使能。在 AArch64 中,SCTLR 寄存器位定义如下:
我们可以看出,在 EL2 和 EL3 异常等级中,并不是所有的位都可用,SCTLR 位域描述如下:
位域 | 位域描述 |
---|---|
UCI | 当此位设置时,EL0 可以使用一些 cache 管理指令,比如 DC CVAU , DC CIVAC , IC ICAU |
EE | 当前异常等级端序选择,0 表示选择小端序,1 表示选择大端序 |
E0E | EL0 数据访问端序选择,0表示小端序,1表示大端序 |
WXN | 写权限管理,0 表示内存写权限不被强制为不可执行 |
nTWE | WFE 指令相关 |
nTWI | WFI 指令相关 |
UCT | 此位置位后,EL0 可以访问 CTR_EL0 寄存器 |
DZE | EL0 是否可以访问 DC ZVA 指令,1 表示允许 |
I | EL0/ EL1 instrcution cache 使能位 |
UMA | |
SED | |
ITD | |
CP15BEN | |
SA0 | EL0 堆栈对齐检查使能位 |
SA | 堆栈对齐检查使能位 |
C | data cache 使能位 |
A | 对齐检查使能位 |
M | MMU 使能位 |
上述 SCTLR 寄存器位域中,重点关注{I, C, A, M }位域即可。
SCTLR_EL1 访问示例如下:
MRS X0, SCTLR_EL1 // Read System Control Register configuration data
ORR X0, X0, #(1 << 2) // Set [C] bit and enable data caching
ORR X0, X0, #(1 << 12) // Set [I] bit and enable instruction caching
MSR SCTLR_EL1, X0 // Write System Control Register configuration data
内存中存在两种基本的字节布局方式:
如下:
一个更有效的记忆方式是:大端序对应阅读习惯。而 cpu 一般选择小端序存储数据。
每一个异常等级的端序控制通过当前异常等级的 SCTLR_ELn.SS 位域实现,而 EL0 的端序通过 SCTLR_EL1.E0E 位域控制。
上文我们已经知道了只有在异常发生时或从异常返回时才能改变 CPU 执行状态, 现在我们讨论一下执行状态改变时寄存器的变化。
当我们从 AArch32 状态更改为 AArch64 时,我们必须清楚:
通常,应用程序开发人员编写的应用程序为 AArch32 或 AArch64 。只有操作系统必须考虑两种执行状态以及它们之间的切换。
AArch32 用于兼容 ARMv7 架构,这意味着 AArch32 必须匹配 ARMv7 的特权分级,也意味着 AArch32 状态下只处理 ARMv7 的 32 位通用目的寄存器。因此,ARMv8 架构与 AArch32 执行状态提供的观点之间必须存在一些对应关系。
ARMv7 存在16个 32 位的通用目的寄存器(R0-R15),R14 是 LR 寄存器,R15 作为 PC寄存器。内核存在 CPSR 寄存器用于保存处理器状态,也存在 SPSR 寄存器用于在异常发生时备份此时的 CPSR。
存在一些寄存器是 banking (不同的模式下,虽然寄存器名一样,但实际的物理缓存地址不同),如下图:
ARMv7 中 Banking 寄存器的使用减少了异常的延时,但是这也表示在某些模式下,存在一部分寄存器无法使用。
相比较之下,AArch64 拥有 31 个 64 位宽的通用目的寄存器,且这些寄存器在任一异常等级下都可访问。AArch64 与 AArch32 执行状态的改变意味着我们必须把 AArch64 的寄存器映射到 ARMv7 寄存器集合中(包括通用寄存器,特殊寄存器,系统寄存器?) 。
AArch64 与 AArch32 的通用寄存器映射关系如下:
在上图中:
在 AArch64 状态下,ARMv7 的 CPSR 的不同组成部分,作为 PSTATE 的不同位域,可被单独访问。
而在 AArch32 状态下,可直接访问 CPSR 寄存器,此时 CPSR 位域定义如下:
一些额外的位域只能在 AArch32 下访问:
位域 | 位域描述 |
---|---|
Q | |
GE(4) | 大于或等于标志位 |
IT(8) | if-then 执行位 |
J | |
T | T32 bit |
E | 端序选择位 |
M | 执行状态,为1,表示 AArch32 |
除了通用目的寄存器,ARMv8 也拥有 32 个 128 位的浮点寄存器 V0-V31, 这 32 个浮点寄存器用于浮点操作与向量操作
(后续略,暂不了解浮点操作与向量操作)