AArch64 Linux 的可扩展矩阵扩展支持

本文档简要概述了 Linux 为支持使用 ARM 可扩展矩阵扩展 (SME) 而向用户空间提供的接口。

这只是最重要特性和问题的概述,并非旨在详尽无遗。应结合 AArch64 Linux 的可扩展向量扩展支持 中的 SVE 文档进行阅读,该文档提供了 SME 中包含的流式 SVE 模式的详细信息。

本文档并非旨在描述 SME 架构或程序员模型。为了帮助理解,附录 A 中包含了 SME 相关程序员模型特性的最简描述。

1. 概述

  • PSTATE.SM、PSTATE.ZA、流式模式向量长度、ZA 和(如果存在)ZTn 寄存器状态以及 TPIDR2_EL0 按线程进行跟踪。

  • SME 的存在通过辅助向量 AT_HWCAP2 条目中的 HWCAP2_SME 报告给用户空间。此标志的存在意味着存在 SME 指令和寄存器,以及本文档中描述的特定于 Linux 的系统接口。SME 在 /proc/cpuinfo 中报告为“sme”。

  • SME2 的存在通过辅助向量 AT_HWCAP2 条目中的 HWCAP2_SME2 报告给用户空间。此标志的存在意味着存在 SME2 指令和 ZT0,以及本文档中描述的特定于 Linux 的系统接口。SME2 在 /proc/cpuinfo 中报告为“sme2”。

  • 用户空间中执行 SME 指令的支持也可以通过使用 MRS 指令读取 CPU ID 寄存器 ID_AA64PFR1_EL1 并检查 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 hwcapsARM64 CPU 功能寄存器

  • 调试器应将自己限制为通过 NT_ARM_SVE、NT_ARM_SSVE、NT_ARM_ZA 和 NT_ARM_ZT 寄存器集与目标进行交互。检测这些寄存器集支持的推荐方法是首先连接到目标进程,然后尝试

    ptrace(PTRACE_GETREGSET, pid, NT_ARM_<寄存器集>, &iov)。

  • 每当在用户空间和内核之间在内存中交换 ZA 寄存器值时,寄存器值在内存中编码为一系列水平向量,这些向量从 0 到 VL/8-1 存储,采用与 SVE 向量相同的字节序不变格式。

  • 在线程创建时,TPIDR2_EL0 会被保留,除非指定了 CLONE_SETTLS,在这种情况下,它会被设置为 0。

2. 向量长度

SME 定义了第二个类似于 SVE 向量长度的向量长度,它控制流式模式 SVE 向量和 ZA 矩阵数组的大小。ZA 矩阵是正方形的,每条边都有与流式模式 SVE 向量一样多的字节。

3. 流式和非流式模式 SVE 状态的共享

SVE 状态的哪些部分(如果有)在流式和非流式模式之间共享是实现定义的。当通过 ptrace 等软件接口在模式之间切换时,如果未提供任何寄存器内容作为切换的一部分,则不会假设任何状态被共享,所有内容都将被清零。

4. 系统调用行为

  • 在系统调用时,PSTATE.ZA 会被保留,如果 PSTATE.ZA==1,则 ZA 矩阵和 ZTn(如果存在)的内容会被保留。

  • 在系统调用时,PSTATE.SM 将被清除,SVE 寄存器将按照标准 SVE ABI 进行处理。

  • SVE 寄存器、ZA 或 ZTn 均不用于向任何系统调用传递参数或接收结果。

  • 在进程创建时(例如,clone()),新创建的进程将清除 PSTATE.SM。

  • 线程的所有其他 SME 状态(包括当前配置的向量长度、PR_SME_VL_INHERIT 标志的状态以及延迟的向量长度(如果有))都会在所有系统调用中保留,但受限于第 6 节中描述的 execve() 的特定例外情况。

5. 信号处理

  • 信号处理程序在禁用流式模式和 ZA 的情况下被调用。

  • 添加了一个新的信号帧记录 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 的信号记录具有魔术 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) 必须为 true。

flags

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 状态和非流式 SVE 状态。调用 PR_SME_SET_VL 时,vl 等于线程的当前向量长度,或者调用 PR_SME_SET_VL 时带有 PR_SME_SET_VL_ONEXEC 标志,不构成此目的的向量长度更改。

  • 更改向量长度会导致 PSTATE.ZA 和 PSTATE.SM 被清除。调用 PR_SME_SET_VL 时,vl 等于线程的当前向量长度,或者调用 PR_SME_SET_VL 时带有 PR_SME_SET_VL_ONEXEC 标志,不构成此目的的向量长度更改。

prctl(PR_SME_GET_VL)

获取调用线程的向量长度。

以下标志可以与结果进行“或”运算

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

目标线程的最大可能流式向量长度。

flags

以下标志中的零个或多个,其含义和行为与相应的 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 功能寄存器