基础知识
ARMv8新特性
① 4GB超大物理内存
② 64位虚拟地址空间
③ **31个64位宽的通用寄存器,**可以减少对栈的访问,从而提高了性能
④ 提供16KB和64KB的页面,有助于降低TLB的未命中率
⑤ 具有全新的异常处理模型,有助于降低操作系统和虚拟化的实现复杂度
⑥ 具有全新的加载-获取指令、存储-释放指令,专门为C++11、C11以及Java内存模型设计
专业术语
① 处理机(PE,Processing Element):把处理器处理事务的过程抽象为处理机,是 ARM 体系结构中对处理器核心的抽象描述。
② 执行状态(Execution State):处理器运行时的环境,包括寄存器的位宽、支持的指令集、异常模型、内存管理以及编程模型。ARMv8 有两个执行状态:
- AArch64:64位的执行状态
- 31个64位通用寄存器
- 64位PC、SP以及异常链接寄存器ELR
- 提供A64指令集
- 定义ARMv8异常模型,支持4个异常等级,即EL0~EL3
- 提供64位的内存模型
- 定义一组处理器状态PSTATE用来保存PE状态
- AArch32:32位的执行状态
- 提供13个32位通用寄存器、PC、SP、LR
- 支持A32和T32两套指令集
- 支持ARMv7-A异常模型,基于PE模型并映射到ARMv8的异常模型中
- 提供32位的虚拟内存访问机制
- 定义一组PSTATE来保存PE状态
③ ARMv8指令集:ARMv8体系结构根据不同的执行状态提供不同指令集的支持。
- A64指令集:AArch64下运行的64位指令集(指令还是32位,知识可以操作64位的寄存器)
- A32指令集:AArch32下运行的32位指令集
- T32指令集:AArch32下运行的16/32位指令集
④ 系统寄存器命名:在AArch64状态下,很多系统寄存器会根据不同的异常等级提供不同的变种寄存器,命名格式为<reg_name>_ELx,比如SP_EL0。
处理器执行状态
① ARM支持两种执行状态------AArch64和AArch32,分别对应64位执行状态和32位执行状态。处理器在复位后默认进入AArch64状态,并可通过异常返回(ERET)或异常陷入(Exception Entry)在不同执行状态之间切换。
② AArch64状态的异常等级确定了处理器当前运行的特权等级:
- EL0:用户特权,普通用户程序运行
- EL1:系统特权,用于操作系统内核,如果扩展了虚拟化扩展,运行虚拟机操作系统内核
- EL2:运行虚拟化扩展的虚拟机监控器
- EL3:运行安全世界中的安全监控器
③ 异常等级与安全状态的关系:ARMv8-A 将运行环境划分为安全世界(Secure World)和非安全世界(Non-secure World)。EL3 始终运行在安全世界,负责安全世界与非安全世界之间的切换;EL2 和 EL1 可分别运行在安全或非安全世界,而 EL0 的特权等级由所处的异常等级决定。
④ ARMv8-A 架构中,处理器通过异常级别和指令集状态实现了灵活的兼容性。在 AArch64 状态下,每个异常等级(如 EL1、EL0)可独立配置为运行 AArch64 或 AArch32 指令集。这种设计允许 64 位操作系统内核同时兼容 32 位和 64 位应用程序。
- 当 64 位操作系统(运行于 EL1)调度一个 AArch32 应用程序时,操作系统在任务切换过程中通过 SVC 异常陷入 EL1,保存当前 AArch64 上下文后将 EL0 切换为 AArch32 状态,并通过 ERET 返回到 AArch32 的 EL0 执行该应用。
- 相反,由于 32 位操作系统运行于 AArch32 的 EL1,无法提供 64 位寄存器环境,因此不能直接运行 AArch64 应用。
- 执行状态切换的代价:AArch64 与 AArch32 之间的切换需要保存和恢复完整的寄存器上下文,并涉及 PSTATE 的更新,因此频繁切换会带来一定的性能开销。操作系统通常只在任务调度或异常处理时进行状态切换,以降低开销。
ARMv8支持的数据宽度
ARMv8 架构支持多种数据宽度,从最小的字节到最大的四字,每种数据宽度都有明确的位宽定义,适用于不同的应用场景:
- 字节(Byte):8位,是 ARM 体系结构中最小的数据单位,常用于字符存储和位操作。
- 半字(Half-word):16位,常用于存储 16 位整数或 Unicode 字符。
- 字(Word):32位,是 AArch32 状态下的标准寄存器宽度,也是大多数指令操作的基本数据单位。
- 双字(Double-word):64位,是 AArch64 状态下通用寄存器的标准宽度,支持 64 位整数运算和 64 位地址寻址。
- 四字(Quad-word):128位,通常由两个 64 位寄存器组合使用,用于 SIMD 向量运算或 128 位数据操作。
ARMv8寄存器
通用寄存器
① 64位执行状态支持31个通用寄存器,分别是X0~X30寄存器,而32位支持16个32位通用寄存器。AArch64 执行状态下,通用寄存器(X0~X30)均为 64 位宽,同时支持通过 W0~W30 访问其低 32 位,从而实现 32 位与 64 位数据操作的灵活切换。除了数据运算和存储之外,通用寄存器还可以在函数调用中起到特殊作用,ARM64体系结构的函数调用标准和规范对此有所约定。下表按照寄存器在函数调用中的不同用途,对31个通用寄存器进行分类说明:
| REG | 低32位 | 用途分类 | 说明 |
|---|---|---|---|
| X0 | W0 | 传递参数和结果 | 用于传递函数第1个参数,同时用于返回函数结果。 |
| X1 | W1 | 传递参数和结果 | 用于传递函数第2个参数,同时用于返回函数结果。 |
| X2 | W2 | 传递参数和结果 | 用于传递函数第3个参数。 |
| X3 | W3 | 传递参数和结果 | 用于传递函数第4个参数。 |
| X4 | W4 | 传递参数和结果 | 用于传递函数第5个参数。 |
| X5 | W5 | 传递参数和结果 | 用于传递函数第6个参数。 |
| X6 | W6 | 传递参数和结果 | 用于传递函数第7个参数。 |
| X7 | W7 | 传递参数和结果 | 用于传递函数第8个参数,也可用于返回间接结果。 |
| X8 | W8 | 间接结果寄存器 | 用于传递函数返回的间接结果地址,返回结构体或较大数据时使用 |
| X9 | W9 | 调用函数需要保存 | 调用者保存寄存器,由调用方负责保存,可自由使用。 |
| X10 | W10 | 调用函数需要保存 | 调用者保存寄存器,可自由使用。 |
| X11 | W11 | 调用函数需要保存 | 调用者保存寄存器,可自由使用。 |
| X12 | W12 | 调用函数需要保存 | 调用者保存寄存器,可自由使用。 |
| X13 | W13 | 调用函数需要保存 | 调用者保存寄存器,可自由使用。 |
| X14 | W14 | 调用函数需要保存 | 调用者保存寄存器,可自由使用。 |
| X15 | W15 | 调用函数需要保存 | 调用者保存寄存器,可自由使用。 |
| X16 | W16 | 平台寄存器 | IP0,过程内调用临时寄存器,用于PLT跳转等场景。 |
| X17 | W17 | 平台寄存器 | IP1,过程内调用临时寄存器,用于PLT跳转等场景。 |
| X18 | W18 | 平台寄存器 | 平台寄存器,保留给平台ABI使用,应用层代码一般不使用。 |
| X19 | W19 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X20 | W20 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X21 | W21 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X22 | W22 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X23 | W23 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X24 | W24 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X25 | W25 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X26 | W26 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X27 | W27 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X28 | W28 | 被调用函数需要保存 | 被调用者保存寄存器,由被调用函数负责保存和恢复。 |
| X29 | W29 | 栈帧寄存器 | 帧指针(FP),用于指向当前函数的栈帧基址,维护栈回溯链。 |
| X30 | W30 | 链接寄存器 | 链接寄存器(LR),保存函数调用返回地址,BL指令执行时自动写入。 |
② 在上述31个通用寄存器中,X0~X7 用于传递函数参数和返回结果,X8 用于返回间接结果,X9~X15 为调用者保存寄存器,X16~X18 为平台相关寄存器,X19~X28 为被调用者保存寄存器,X29 作为栈帧指针,X30 作为链接寄存器。这种分工既保证了函数调用的高效性,也明确了各寄存器的保存责任,是 ARM64 函数调用规范(AAPCS64)的核心约定。
处理器状态
PSTATE(Processor State)是 ARMv8 架构中用于保存处理器当前状态的一组标志位,它反映了处理器的运行状态和控制信息。PSTATE 中的标志位主要分为四类:条件标志符号、执行状态控制、异常掩码标志位和访问权限,下面分别进行说明。
① 条件标志符号(Condition Flags)
- N(Negative):负数标志位,当最近一次运算结果为负数时置 1,否则清 0。
- Z(Zero):零标志位,当最近一次运算结果为零时置 1,否则清 0。
- C(Carry):进位标志位,当运算产生进位(无符号溢出)时置 1,否则清 0。
- V(Overflow):溢出标志位,当有符号运算发生溢出时置 1,否则清 0。
这四个条件标志位共同决定了条件分支指令(如 B.EQ、B.NE、B.GT 等)的执行结果,是程序流程控制的重要依据。
② 执行状态控制(Execution State Control)
- EL(Exception Level):当前异常等级,表示处理器运行在 EL0~EL3 中的哪一个特权等级。
- SP(Stack Pointer Select):栈指针选择位,用于选择当前使用的是 SP_EL0 还是当前异常等级对应的栈指针。
- SS(Software Step):软件单步执行标志位,用于调试场景下的单步执行控制。
- IL(Instruction Length):指令长度标志位,指示当前执行的指令是 A64(32 位)还是 T32(16 位)指令。
这些控制位决定了处理器当前的执行环境和指令集状态,是处理器状态切换的核心控制信息。
③ 异常掩码标志位(Exception Mask Bits)
- D(Debug Mask):调试异常掩码位,置 1 时屏蔽调试异常。
- A(SError Mask):异步异常掩码位,置 1 时屏蔽 SError 异步异常。
- I(IRQ Mask):IRQ 中断掩码位,置 1 时屏蔽 IRQ 中断请求。
- F(FIQ Mask):FIQ 中断掩码位,置 1 时屏蔽 FIQ 快速中断请求。
这四个掩码位分别控制不同类型异常的使能状态,操作系统在进入临界区或关键代码段时,通常会设置相应的掩码位来屏蔽中断,以保证操作的原子性。
④ 访问权限(Access Permissions)
- PAN(Privileged Access Never):特权访问禁止位,置 1 时禁止特权模式访问用户空间内存,用于增强系统安全性。
- UAO(User Access Override):用户访问覆盖位,置 1 时允许在特权模式下使用用户空间的访问权限,常用于系统调用中的用户数据拷贝。
- DIT(Data Independent Timing):数据无关时序位,置 1 时指示某些指令的执行时间与数据无关,用于防止时序侧信道攻击。
访问权限相关的标志位主要用于控制特权模式对内存的访问行为,是 ARMv8 安全机制的重要组成部分。
特殊寄存器
在 AArch64 执行状态下,除了通用寄存器之外,还有一组特殊寄存器用于控制处理器状态、管理异常处理和系统配置。下面介绍几个常用的特殊寄存器。
① 零寄存器(Zero Register,XZR/WZR)
零寄存器是一个只读寄存器,读取时始终返回 0,写入时数据被丢弃。在 AArch64 状态下,XZR 表示 64 位零寄存器,WZR 表示 32 位零寄存器。它常用于初始化寄存器、比较操作或作为占位操作数,例如 MOV X0, XZR 可将 X0 清零。
② 程序计数器(PC,Program Counter)
PC 寄存器保存当前正在执行指令的地址。在 AArch64 状态下,PC 为 64 位宽,且不能像通用寄存器那样直接读写,只能通过分支指令(如 B、BL、BR)或异常返回指令(ERET)间接修改。PC 是程序执行流程控制的核心。
③ 栈指针(SP,Stack Pointer)
SP 寄存器指向当前栈顶地址,用于函数调用时的压栈和出栈操作。AArch64 为每个异常等级提供了独立的栈指针:SP_EL0 用于 EL0,SP_EL1、SP_EL2、SP_EL3 分别对应各特权等级。当前使用哪个 SP 由 PSTATE 中的 SPSel 位决定,这样设计可以避免不同特权等级之间栈的相互干扰。
④ 备份程序状态寄存器(SPSR,Saved Program Status Register)
SPSR 用于在异常发生时保存被中断程序的 PSTATE 状态。当处理器进入异常处理时,当前 PSTATE 会被自动保存到对应异常等级的 SPSR_ELx 中;当异常返回(ERET)时,SPSR 中的值会被恢复回 PSTATE,从而恢复被中断程序的执行状态。每个异常等级都有独立的 SPSR_ELx。
⑤ 异常链接寄存器(ELR,Exception Link Register)
ELR 用于保存异常发生时被中断指令的返回地址。当异常陷入时,处理器将当前 PC 值保存到对应异常等级的 ELR_ELx 中;异常返回时,ERET 指令将 ELR 中的地址写回 PC,使程序从被中断的位置继续执行。ELR 与 SPSR 配合,共同完成异常的保存与恢复。
⑥ 当前异常等级寄存器(CurrentEL)
CurrentEL 寄存器用于读取当前处理器所处的异常等级(EL0~EL3)。它是一个只读寄存器,操作系统可以读取 CurrentEL 来判断当前运行的特权级别,从而执行相应的权限检查或分支逻辑。可以通过MRS指令来读取。
⑦ 中断屏蔽寄存器(DAIF)
PSTATE寄存器中的DAIF字段
⑧ 栈指针选择寄存器(SPSel)
PSTATE的SP字段
⑨ 特权访问禁止寄存器(PAN)
PSTATE的PAN字段
⑩ 用户访问覆盖寄存器(UAO)
PSTATE的UAO字段
⑪ 条件标志寄存器(NZCV)
PSTATE的NZCV字段
附带AI生成问题
基础概念
1. ARMv8 相比 ARMv7 有哪些主要新特性?
主要新特性包括:支持 4GB 超大物理内存;提供 64 位虚拟地址空间;拥有 31 个 64 位宽的通用寄存器,减少对栈的访问从而提升性能;提供 16KB 和 64KB 大页面,降低 TLB 未命中率;引入全新的异常处理模型,降低操作系统和虚拟化的实现复杂度;新增加载-获取、存储-释放指令,专门为 C++11、C11 以及 Java 内存模型设计。
2. 什么是 AArch64 和 AArch32?两者有哪些区别?
AArch64 是 64 位执行状态,提供 31 个 64 位通用寄存器、64 位 PC/SP/ELR,支持 A64 指令集,定义 ARMv8 异常模型(EL0~EL3);AArch32 是 32 位执行状态,提供 13 个 32 位通用寄存器、PC、SP、LR,支持 A32 和 T32 两套指令集,采用 ARMv7-A 异常模型并映射到 ARMv8 异常模型中。
3. A64、A32、T32 指令集分别对应什么执行状态?A64 的指令编码本身是多少位?
A64 指令集在 AArch64 下运行,A32 和 T32 指令集在 AArch32 下运行。A64 的指令编码本身仍是 32 位,只是可以操作 64 位寄存器,这是一个常见易错点。
4. 字节、半字、字、双字、四字分别是多少位?Word 在 AArch64 下是多少位?
字节为 8 位,半字为 16 位,字为 32 位,双字为 64 位,四字为 128 位。即使处于 AArch64 状态,Word 仍然是 32 位,而不是 64 位。
异常等级与执行状态
5. EL0~EL3 分别运行什么?
EL0 为用户特权,运行普通用户程序;EL1 为系统特权,运行操作系统内核,若启用虚拟化扩展则运行虚拟机操作系统内核;EL2 运行虚拟化扩展的虚拟机监控器;EL3 运行安全世界中的安全监控器。
6. EL3 的特殊性是什么?什么是安全世界和非安全世界?
EL3 始终运行在安全世界,负责安全世界与非安全世界之间的切换。安全世界用于运行可信固件和安全监控器,非安全世界运行普通操作系统和应用程序;EL2 和 EL1 可分别运行在安全或非安全世界,EL0 的特权等级由所处异常等级决定。
7. AArch64 和 AArch32 之间如何切换?什么指令/机制触发?
处理器复位后默认进入 AArch64 状态,可通过异常返回(ERET)或异常陷入(Exception Entry)在不同执行状态之间切换。例如 64 位操作系统调度 AArch32 应用时,通过 SVC 异常陷入 EL1,保存 AArch64 上下文后将 EL0 切换为 AArch32 状态,再通过 ERET 返回执行。
8. 复位后处理器默认进入什么执行状态?
复位后处理器默认进入 AArch64 执行状态。
通用寄存器与函数调用约定
9. ARM64 有多少个通用寄存器?说出 X0~X30 的用途分类。
ARM64 有 31 个通用寄存器(X0~X30)。X0~X7 用于传递函数参数和返回结果,X8 用于返回间接结果,X9~X15 为调用者保存寄存器,X16~X18 为平台相关寄存器,X19~X28 为被调用者保存寄存器,X29 作为栈帧指针,X30 作为链接寄存器。
10. X 寄存器和 W 寄存器是什么关系?W0 和 X0 是两个独立寄存器吗?
W0~W30 是 X0~X30 的低 32 位视图,W0 和 X0 不是两个独立寄存器,而是同一个寄存器的不同访问宽度。写入 W0 只会更新 X0 的低 32 位,并将高 32 位清零。
11. 哪些是调用者保存寄存器?哪些是被调用者保存寄存器?各由谁负责保存?
X9~X15 为调用者保存寄存器,由调用方负责保存;X19~X28 为被调用者保存寄存器,由被调用函数负责保存和恢复。这是 AAPCS64 函数调用规范的核心约定。
12. X29 和 X30 分别是什么?各自的作用?
X29 是帧指针(FP),用于指向当前函数的栈帧基址,维护栈回溯链;X30 是链接寄存器(LR),保存函数调用返回地址,BL 指令执行时自动写入。
13. BL 指令执行时,返回地址保存在哪里?
BL 指令执行时,返回地址自动保存到 X30(链接寄存器 LR)中。
PSTATE 与标志位
14. N、Z、C、V 标志位分别表示什么?C 和 V 的区别?
N 为负数标志位,运算结果为负时置 1;Z 为零标志位,结果为零时置 1;C 为进位标志位,无符号运算产生进位(无符号溢出)时置 1;V 为溢出标志位,有符号运算发生溢出时置 1。C 反映无符号溢出,V 反映有符号溢出,两者判断的溢出类型不同。
15. D、A、I、F 四个异常掩码位分别屏蔽什么?
D 屏蔽调试异常,A 屏蔽 SError 异步异常,I 屏蔽 IRQ 中断请求,F 屏蔽 FIQ 快速中断请求。操作系统进入临界区或关键代码段时,通常设置相应掩码位屏蔽中断以保证原子性。
特殊寄存器
16. XZR/WZR 是什么?读它和写它分别是什么效果?一般用在什么场景?
XZR/WZR 是零寄存器,读取时始终返回 0,写入时数据被丢弃。常用于初始化寄存器、比较操作或作为占位操作数,例如 MOV X0, XZR 可将 X0 清零。
17. AArch64 下 PC 能像通用寄存器那样直接读写吗?怎么修改 PC?
不能。PC 为 64 位宽,不能像通用寄存器那样直接读写,只能通过分支指令(如 B、BL、BR)或异常返回指令(ERET)间接修改。
18. SP 有几个?为什么每个异常等级要有独立的栈指针?由什么决定当前用哪个?
AArch64 为每个异常等级提供独立栈指针:SP_EL0 用于 EL0,SP_EL1、SP_EL2、SP_EL3 分别对应各特权等级。独立栈指针可避免不同特权等级之间栈的相互干扰。当前使用哪个 SP 由 PSTATE 中的 SPSel 位决定。
23. SPSR 保存什么?ELR 保存什么?两者如何配合完成异常的保存与恢复?
SPSR 保存被中断程序的 PSTATE 状态,ELR 保存被中断指令的返回地址。异常陷入时,处理器将当前 PSTATE 保存到 SPSR_ELx、将当前 PC 保存到 ELR_ELx;异常返回时,ERET 将 SPSR 恢复回 PSTATE、将 ELR 写回 PC,从而恢复被中断程序的执行。
24. ERET 指令执行时发生了什么?
ERET 执行时恢复 SPSR 到 PSTATE,并将 ELR 写回 PC,完成异常返回,使程序从被中断的位置继续执行。
25. CurrentEL 是什么?怎么读取?
CurrentEL 是当前异常等级寄存器,用于读取处理器当前所处的异常等级(EL0~EL3)。它是只读寄存器,操作系统可通过 MRS 指令读取,以判断当前特权级别并执行相应权限检查或分支逻辑。
26. 异常发生时,PSTATE 和返回地址分别被自动保存到哪里?
异常发生时,PSTATE 被自动保存到对应异常等级的 SPSR_ELx 中,返回地址(当前 PC 值)被自动保存到对应异常等级的 ELR_ELx 中。