Arm 定义的是一系列指令集架构(ISA),而不是一种处理器实现。架构规定了程序员可见的状态、指令语义、异常、特权和内存顺序。内核的流水线、缓存层次结构和分支预测器属于微架构选择。
架构配置文件用于区分部署需求。A-profile 面向带有虚拟内存的应用处理器,R-profile 面向实时系统,M-profile 面向微控制器。除非另有说明,下面介绍的执行状态和特权模型均针对 A-profile 系统。
执行状态和指令集 #
AArch32 提供 32 位通用寄存器,可以执行 A32 指令集,或执行历史上称为 Thumb 的可变长度 T32 指令集。由 Armv8-A 引入的 AArch64 提供 31 个 64 位通用寄存器和定长的 A64 指令集。X0 到 X30 表示 64 位寄存器;W0 到 W30 表示对应寄存器的低 32 位。写入 W 寄存器会清除其对应 X 寄存器的高半部分。
处理器可以实现多个执行状态,但不能根据产品是否支持 64 位来推断其支持哪些状态。Armv8-A 允许 AArch64 操作系统支持 AArch32 应用,而后续架构版本允许实现选择在特定异常级别不支持 AArch32。因此,软件必须使用平台文档和操作系统接口来确定可用的执行状态。
执行状态不只由寄存器宽度决定。AArch32 和 AArch64 具有不同的指令编码、异常模型、系统寄存器和 ABI。代码不能通过普通分支切换状态;必须通过异常返回或其他由架构定义的转换来建立目标状态。
寄存器和过程调用 #
A64 是一种加载-存储 ISA:算术运算在寄存器上执行,而显式的加载和存储指令访问内存。
ldr w0, [x1] // 加载 32 位值
add w0, w0, #1
str w0, [x1]若干寄存器名称具有架构规定或约定俗成的作用:
| 寄存器 | AAPCS64 中的作用 |
|---|---|
X0-X7 | 整数参数和结果 |
X8 | 间接结果位置寄存器 |
X9-X15 | 调用者保存的临时寄存器 |
X16-X17 | 过程内调用临时寄存器 |
X18 | 平台寄存器或临时寄存器,具体取决于平台 ABI |
X19-X28 | 被调用者保存的寄存器 |
X29 | 按约定作为帧指针 |
X30 | 链接寄存器 |
SP | 栈指针 |
BL 将返回地址写入 X30,而 RET 通常通过 X30 进行分支。Arm 64 位架构的过程调用标准(AAPCS64)还定义了浮点/向量参数寄存器、聚合类型传递、被调用者保存状态以及 16 字节栈对齐。这些规则属于 ABI 契约,而不是 A64 指令语法的属性。与编译代码互操作的手写汇编必须遵循平台的完整 ABI。
异常级别和转换 #
A-profile 系统使用异常级别,数字越大表示特权越高:
- EL0 运行非特权应用程序。
- EL1 通常运行操作系统内核。
- EL2 为虚拟机监控程序提供虚拟化控制。
- EL3 在实现该级别的系统上提供安全监控器功能。
实现不必提供所有级别。安全状态和虚拟化也会影响某个 EL 能够访问的资源,因此仅凭 EL 编号无法描述完整的保护域。
异常包括同步异常、IRQ、FIQ 和系统错误。发生异常时,返回地址和保存的处理器状态会记录在 ELR_ELx 和 SPSR_ELx 中,系统会从目标 EL 的向量表中选择一个入口,并屏蔽或改变选定的状态。处理程序验证保存的状态后,ERET 会恢复该状态。系统调用通常使用 SVC 进入 EL1;启用后,虚拟机监控程序调用和安全监控器调用分别使用 HVC 和 SMC。
转换表和访问控制寄存器负责强制实施内存隔离。EL2 可以在 EL1 客户机的第一级映射之上增加第二级转换,而 TrustZone 安全状态还可以施加另一层访问边界。具体功能是否可用以及寄存器的确切行为取决于所实现的架构版本。
内存顺序、屏障和原子操作 #
Arm 使用弱顺序内存模型。当没有架构同步机制施加约束时,一个内核可能会以不同于程序顺序的顺序观察相互独立的加载和存储。缓存一致性确保参与观察者对同一位置的写入顺序达成一致;但仅有一致性并不能规定对不同位置的访问顺序。
并发代码应表达其协议所需的最小顺序约束:
- Acquire 加载可防止后续访问移动到 Acquire 之前。
- Release 存储可防止先前访问移动到 Release 之后。
- 读-改-写原子操作提供带有指定顺序语义的不可分更新。
DMB在选定的共享性和访问域内为相关内存观察排序。DSB等待指定的内存效果完成。ISB刷新指令流水线,使后续指令在先前上下文变更操作所建立的上下文中执行。
AArch64 支持 LDXR/STXR 等独占加载/独占存储循环。支持 Large System Extensions 的系统还可以提供单指令原子读-改-写操作。两种形式都需要重试或竞争处理,并且都无法修复语言层面的数据竞争。C 和 C++ 程序应使用语言提供的原子操作,因为编译器会将这些语义映射到适当的指令和屏障。
评论
在 GitHub 上阅读或评论(发表评论需要登录)