AArch64 Linux 的可扩展矩阵扩展支持¶
本文档简要概述了 Linux 提供给用户空间的接口,以支持 ARM 可扩展矩阵扩展 (SME) 的使用。
这只是最重要功能和问题的概述,并非旨在详尽无遗。应与 AArch64 Linux 的可扩展向量扩展支持 中的 SVE 文档结合阅读,该文档提供了 SME 中包含的流式 SVE 模式的详细信息。
本文档不旨在描述 SME 架构或程序员模型。为了帮助理解,附录 A 中包含了 SME 相关程序员模型功能的最低限度描述。
1. 概述¶
PSTATE.SM、PSTATE.ZA、流式模式向量长度、ZA 和(如果存在)ZTn 寄存器状态以及 TPIDR2_EL0 是按线程跟踪的。
SME 的存在通过 aux vector AT_HWCAP2 条目中的 HWCAP2_SME 报告给用户空间。此标志的存在意味着 SME 指令和寄存器的存在,以及本文档中描述的特定于 Linux 的系统接口。SME 在 /proc/cpuinfo 中报告为“sme”。
SME2 的存在通过 aux vector AT_HWCAP2 条目中的 HWCAP2_SME2 报告给用户空间。此标志的存在意味着 SME2 指令和 ZT0 的存在,以及本文档中描述的特定于 Linux 的系统接口。SME2 在 /proc/cpuinfo 中报告为“sme2”。
还可以通过使用 MRS 指令读取 CPU ID 寄存器 ID_AA64PFR1_EL1 并检查 SME 字段的值是否非零来检测用户空间中 SME 指令的执行支持。[3]
它不保证存在以下各节中描述的系统接口:需要验证这些接口是否存在的软件必须检查 HWCAP2_SME。
有许多可选的 SME 功能,这些功能的存在通过 AT_HWCAP2 报告:
HWCAP2_SME_I16I64 HWCAP2_SME_F64F64 HWCAP2_SME_I8I32 HWCAP2_SME_F16F32 HWCAP2_SME_B16F32 HWCAP2_SME_F32F32 HWCAP2_SME_FA64 HWCAP2_SME2
随着 SME 架构的发展,此列表可能会随着时间的推移而扩展。
这些扩展也通过 CPU ID 寄存器 ID_AA64SMFR0_EL1 报告,用户空间可以使用 MRS 指令读取该寄存器。有关详细信息,请参阅 ARM64 ELF hwcaps 和 ARM64 CPU 功能寄存器。
调试器应将自身限制为通过 NT_ARM_SVE、NT_ARM_SSVE、NT_ARM_ZA 和 NT_ARM_ZT regset 与目标进行交互。检测对这些 regset 的支持的推荐方法是首先连接到目标进程,然后尝试
ptrace(PTRACE_GETREGSET, pid, NT_ARM_<regset>, &iov).
每当 ZA 寄存器值在用户空间和内核之间在内存中交换时,寄存器值在内存中编码为一系列从 0 到 VL/8-1 的水平向量,存储在与 SVE 向量相同的字节序不变格式中。
在线程创建时,PSTATE.ZA 和 TPIDR2_EL0 会被保留,除非指定了 CLONE_VM,在这种情况下,PSTATE.ZA 设置为 0,TPIDR2_EL0 设置为 0。
2. 向量长度¶
SME 定义了第二个向量长度,类似于 SVE 向量长度,它控制流式模式 SVE 向量和 ZA 矩阵数组的大小。ZA 矩阵是正方形的,每条边都具有与流式模式 SVE 向量一样多的字节。
4. 系统调用行为¶
在 syscall 上,PSTATE.ZA 被保留,如果 PSTATE.ZA==1,则 ZA 矩阵和 ZTn(如果存在)的内容会被保留。
在 syscall 上,PSTATE.SM 将被清除,SVE 寄存器将按照标准 SVE ABI 处理。
SVE 寄存器、ZA 或 ZTn 均不用于将参数传递给任何 syscall 或从任何 syscall 接收结果。
在进程创建时(例如,clone()),新创建的进程的 PSTATE.SM 将被清除。
线程的所有其他 SME 状态,包括当前配置的向量长度、PR_SME_VL_INHERIT 标志的状态以及延迟向量长度(如果有),都会在所有 syscall 中保留,但第 6 节中描述的 execve() 的特定例外情况除外。
5. 信号处理¶
信号处理程序以 PSTATE.SM=0、PSTATE.ZA=0 和 TPIDR2_EL0=0 调用。
添加了一个新的信号帧记录 TPIDR2_MAGIC,格式化为 struct tpidr2_context,允许从信号处理程序访问 TPIDR2_EL0。
一个新的信号帧记录 za_context 在信号传递时编码 ZA 寄存器内容。[1]
ZA 的信号帧记录始终包含基本元数据,特别是线程的向量长度(在 za_context.vl 中)。
ZA 矩阵可能包含在记录中,也可能不包含,具体取决于 PSTATE.ZA 的值。当且仅当以下情况时,寄存器才存在:za_context.head.size >= ZA_SIG_CONTEXT_SIZE(sve_vq_from_vl(za_context.vl)),在这种情况下,PSTATE.ZA == 1。
如果矩阵数据存在,则记录的其余部分具有 vl 相关的大小和布局。定义了宏 ZA_SIG_* [1] 以方便访问它们。
矩阵存储为一系列水平向量,格式与 SVE 向量使用的格式相同。
如果 ZA 上下文太大而无法放入 sigcontext.__reserved[] 中,则会在堆栈上分配额外的空间,并在 __reserved[] 中写入一个 extra_context 记录,引用此空间。然后将 za_context 写入额外空间。有关此机制的更多详细信息,请参阅 [1]。
如果支持 ZTn 且 PSTATE.ZA==1,则将为 ZTn 生成信号帧记录。
ZTn 的信号记录具有 magic ZT_MAGIC (0x5a544e01),由标准信号帧标头组成,后跟一个 struct zt_context,指定系统支持的 ZTn 寄存器的数量,然后是 zt_context.nregs 个数据块,每个寄存器 64 字节。
5. 信号返回¶
从信号处理程序返回时
如果信号帧中没有 za_context 记录,或者如果记录存在但不包含上一节中描述的任何寄存器数据,则 ZA 被禁用。
如果 za_context 存在于信号帧中并且包含矩阵数据,则 PSTATE.ZA 设置为 1,ZA 用指定的数据填充。
无法通过信号返回更改向量长度。如果信号帧中的 za_context.vl 与当前向量长度不匹配,则信号返回尝试被视为非法,导致强制 SIGSEGV。
如果不支持 ZTn 或 PSTATE.ZA==0,则具有 ZTn 的信号帧记录是非法的,导致强制 SIGSEGV。
6. prctl 扩展¶
添加了一些新的 prctl() 调用,以允许程序管理 SME 向量长度
prctl(PR_SME_SET_VL, unsigned long arg)
设置调用线程的向量长度和相关标志,其中 arg == vl | flags。调用进程的其他线程不受影响。
vl 是所需的向量长度,其中 sve_vl_valid(vl) 必须为真。
标志
PR_SME_VL_INHERIT
在 execve() 期间继承当前向量长度。否则,向量长度会在 execve() 处重置为系统默认值。(请参阅第 9 节。)
PR_SME_SET_VL_ONEXEC
将请求的向量长度更改延迟到此线程执行的下一个 execve()。
效果等效于在线程执行的下一个 execve()(如果有)之后立即隐式执行以下调用
prctl(PR_SME_SET_VL, arg & ~PR_SME_SET_VL_ONEXEC)
这允许启动具有不同向量长度的新程序,同时避免调用者中的运行时副作用。
如果没有 PR_SME_SET_VL_ONEXEC,则请求的更改会立即生效。
- 返回值:成功时为非负值,错误时为负值
- EINVAL:不支持 SME,请求的向量长度无效,或者
标志无效。
成功时
调用线程的向量长度或将在线程的下一个 execve() 处应用的延迟向量长度(取决于 arg 中是否存在 PR_SME_SET_VL_ONEXEC),设置为系统支持的最大值,该值小于或等于 vl。如果 vl == SVE_VL_MAX,则设置的值将是系统支持的最大值。
取消调用线程中任何先前未完成的延迟向量长度更改。
返回的值描述了结果配置,编码为 PR_SME_GET_VL。如果 arg 中不存在 PR_SME_SET_VL_ONEXEC,则此值中报告的向量长度是此线程的新当前向量长度;否则,报告的向量长度是将在此调用线程的下一个 execve() 处应用的延迟向量长度。
更改向量长度会导致所有 ZA、ZTn、P0..P15、FFR 和 Z0..Z31 的所有位(除了 Z0 位 [127:0] .. Z31 位 [127:0])变得未指定,包括流式和非流式 SVE 状态。使用等于线程当前向量长度的 vl 调用 PR_SME_SET_VL,或者使用 PR_SME_SET_VL_ONEXEC 标志调用 PR_SME_SET_VL,并不构成此目的的向量长度更改。
更改向量长度会导致 PSTATE.ZA 被清除。使用等于线程当前向量长度的 vl 调用 PR_SME_SET_VL,或者使用 PR_SME_SET_VL_ONEXEC 标志调用 PR_SME_SET_VL,并不构成此目的的向量长度更改。
prctl(PR_SME_GET_VL)
获取调用线程的向量长度。
以下标志可以 OR 运算到结果中
PR_SME_VL_INHERIT
向量长度将在 execve() 中继承。
无法确定是否存在未完成的延迟向量长度更改(这通常只会在 fork() 或 vfork() 与典型使用中相应的 execve() 之间发生)。
要从结果中提取向量长度,请将其与 PR_SME_VL_LEN_MASK 进行按位与运算。
- 返回值:成功时为非负值,错误时为负值
EINVAL:不支持 SME。
7. ptrace 扩展¶
定义了一个新的 regset NT_ARM_SSVE,用于通过 PTRACE_GETREGSET 和 PTRACE_SETREGSET 访问流式模式 SVE 状态,这在 AArch64 Linux 的可扩展向量扩展支持 中有记录。
定义了一个新的 regset NT_ARM_ZA,用于通过 PTRACE_GETREGSET 和 PTRACE_SETREGSET 访问 ZA 状态。
有关定义,请参阅 [2]。
regset 数据以 struct user_za_header 开头,包含
size
完整 regset 的大小,以字节为单位。这取决于 vl,并且可能取决于未来的其他因素。
如果对 PTRACE_GETREGSET 的调用请求的数据少于 size 的值,则调用者可以分配更大的缓冲区并重试以读取完整的 regset。
max_size
regset 可以为目标线程增长到的最大大小(以字节为单位)。即使目标线程更改其向量长度等,regset 也不会变得更大。
vl
目标线程的当前流式向量长度,以字节为单位。
max_vl
目标线程的最大可能流式向量长度。
标志
以下标志中的零个或多个,其含义和行为与相应的 PR_SET_VL_* 标志相同
SME_PT_VL_INHERIT
SME_PT_VL_ONEXEC(仅 SETREGSET)。
更改向量长度和/或标志的效果等效于 PR_SME_SET_VL 的文档中记录的效果。
如果调用者需要知道 SETREGSET 实际设置的 VL 是什么,则必须进行进一步的 GETREGSET 调用,除非事先知道支持请求的 VL。
有效负载的大小和布局取决于标头字段。提供了 ZA_PT_ZA*() 宏以方便访问数据。
在任何一种情况下,对于 SETREGSET,允许省略有效负载,在这种情况下,向量长度和标志会更改,并且 PSTATE.ZA 设置为 0(以及这些更改的任何后果)。如果提供了有效负载,则 PSTATE.ZA 将设置为 1。
对于 SETREGSET,如果不支持请求的 VL,则效果将与省略有效负载的效果相同,除了报告 EIO 错误。不会尝试将有效负载数据转换为实际设置的向量长度的正确布局。由调用者负责将有效负载布局转换为实际的 VL 并重试。
写入部分、不完整的有效负载的效果未指定。
定义了一个新的 regset NT_ARM_ZT,用于通过 PTRACE_GETREGSET 和 PTRACE_SETREGSET 访问 ZTn 状态。
NT_ARM_ZT regset 由单个 512 位寄存器组成。
当 PSTATE.ZA==0 时,读取 NT_ARM_ZT 会将 ZTn 的所有位报告为 0。
写入 NT_ARM_ZT 将 PSTATE.ZA 设置为 1。
如果提供了任何寄存器数据以及 SME_PT_VL_ONEXEC,则寄存器数据将以当前向量长度解释,而不是配置为在 exec 上使用的向量长度。
8. ELF 核心转储扩展¶
NT_ARM_SSVE 注释将添加到每个核心转储中,用于已转储进程的每个线程。这些内容将等效于如果为每个线程执行相应类型的 PTRACE_GETREGSET 时将读取的数据(在生成核心转储时)。
NT_ARM_ZA 注释将添加到每个核心转储中,用于已转储进程的每个线程。这些内容将等效于如果为每个线程执行 NT_ARM_ZA 的 PTRACE_GETREGSET 时将读取的数据(在生成核心转储时)。
NT_ARM_ZT 注释将添加到每个核心转储中,用于已转储进程的每个线程。这些内容将等效于如果为每个线程执行 NT_ARM_ZT 的 PTRACE_GETREGSET 时将读取的数据(在生成核心转储时)。
NT_ARM_TLS 注释将扩展到两个寄存器,第二个寄存器将在支持 SME 的系统上包含 TPIDR2_EL0,否则将读取为零,写入将被忽略。
9. 系统运行时配置¶
为了缓解扩展信号帧对 ABI 的影响,提供了一种策略机制,供管理员、发行版维护者和开发人员设置用户空间进程的默认向量长度
/proc/sys/abi/sme_default_vector_length
将整数的文本表示形式写入此文件会将系统默认向量长度设置为指定的值,该值使用与通过 PR_SME_SET_VL 设置向量长度相同的规则四舍五入为支持的值。
可以通过重新打开该文件并读取其内容来确定结果。
在启动时,默认向量长度最初设置为 32 或最大支持的向量长度,以较小且受支持者为准。这决定了 init 进程 (PID 1) 的初始向量长度。
读取此文件返回当前系统默认向量长度。
在每次 execve() 调用时,新进程的新向量长度设置为系统默认向量长度,除非
为调用线程设置了 PR_SME_VL_INHERIT(或等效的 SME_PT_VL_INHERIT),或者
存在延迟向量长度更改,通过 PR_SME_SET_VL_ONEXEC 标志(或 SME_PT_VL_ONEXEC)建立。
修改系统默认向量长度不会影响任何现有进程或线程的向量长度,这些进程或线程不会进行 execve() 调用。
附录 A. SME 程序员模型(信息性)¶
本节提供 SME 对 ARMv8-A 程序员模型所做的添加的最低限度描述,这些添加与本文档相关。
注意:本节仅供参考,并非旨在完整或取代任何架构规范。
A.1. 寄存器¶
在 A64 状态下,SME 添加了以下内容
一种新模式,即流式模式,在该模式下,可以使用普通 FPSIMD 和 SVE 功能的子集。如果支持,EL0 软件可以随时进入和离开流式模式。
为了获得最佳系统性能,强烈建议软件仅在主动使用时才启用流式模式。
一个新的向量长度,用于控制 ZA 和 Z 寄存器在流式模式下的大小,与 SVE 在非流式模式下使用的向量长度分开。不要求给定系统中两种模式支持的当前选定向量长度或向量长度集之间存在任何关系。流式模式向量长度称为 SVL。
一个新的 ZA 矩阵寄存器。这是一个 SVLxSVL 位的正方形矩阵。ZA 上的大多数操作都需要启用流式模式,但可以在没有流式模式的情况下启用 ZA,以便加载、保存和保留数据。
为了获得最佳系统性能,强烈建议软件仅在主动使用时才启用 ZA。
当 SME2 存在时,会引入一个新的 ZT0 寄存器。这是一个 512 位寄存器,当设置了 PSTATE.ZA 时可以访问它,就像 ZA 本身一样。
PSTATE 中的两个新的 1 位字段,可以通过 SMSTART 和 SMSTOP 指令或访问 SVCR 系统寄存器来控制
PSTATE.ZA,如果为 1,则 ZA 矩阵是可访问的并且具有有效数据,而如果为 0,则无法访问 ZA。当 PSTATE.ZA 从 0 更改为 1 时,ZA 中的所有位都会被清除。
PSTATE.SM,如果为 1,则 PE 处于流式模式。当 PSTATE.SM 的值更改时,由实现定义在两种模式下都有效的浮点寄存器位的子集是否可以保留。任何其他位将被清除。
参考文献¶
- [1] arch/arm64/include/uapi/asm/sigcontext.h
AArch64 Linux 信号 ABI 定义
- [2] arch/arm64/include/uapi/asm/ptrace.h
AArch64 Linux ptrace ABI 定义
[3] ARM64 CPU 功能寄存器