AArch64 Linux 的可扩展矩阵扩展(SME)支持

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

本文仅概述最重要的特性和问题,并不旨在穷举。阅读本文时,应结合 Scalable Vector Extension support for AArch64 Linux 中的 SVE 文档一同阅读,该文档提供了有关 SME 中包含的流式 SVE(Streaming SVE)模式的详细信息。

本文档不旨在描述 SME 架构或程序员模型。为便于理解,附录 A 中包含了对 SME 相关程序员模型特征的简要描述。

1. 概述

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

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

  • SME2 的存在通过 aux 向量 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 hwcapsARM64 CPU Feature Registers

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

    ptrace(PTRACE_GETREGSET, pid, NT_ARM_<regset>, &iov).

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

  • 在创建线程时,除非指定了 CLONE_VM,否则 PSTATE.ZA 和 TPIDR2_EL0 会被保留;如果指定了 CLONE_VM,则 PSTATE.ZA 被设置为 0,TPIDR2_EL0 被设置为 0。

2. 向量长度

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

3. 系统调用行为

  • 在系统调用时,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() 的特定例外情况除外。

4. 信号处理

  • 信号处理程序在调用时会带有 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 寄存器数量),然后是每个寄存器 64 字节数据的 zt_context.nregs 个块。

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) 必须为真。

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 状态。使用等于线程当前向量长度的 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)

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

以下标志可以按位或到结果中

PR_SME_VL_INHERIT

向量长度将在 execve() 期间被继承。

无法确定是否存在未完成的延迟向量长度更改(在典型用法中,这通常只存在于 fork() 或 vfork() 与对应的 execve() 之间)。

要从结果中提取向量长度,请将其与 PR_SME_VL_LEN_MASK 进行按位与操作。

返回值:成功时为非负值,出错时为负值

EINVAL:不支持 SME。

7. ptrace 扩展

  • 定义了一个新的寄存器集 NT_ARM_SSVE,用于通过 PTRACE_GETREGSET 和 PTRACE_SETREGSET 访问流式模式 SVE 状态,这在 Scalable Vector Extension support for AArch64 Linux 中有文档记录。

  • 定义了一个用于 ZA 状态的新寄存器集 NT_ARM_ZA,用于通过 PTRACE_GETREGSET 和 PTRACE_SETREGSET 访问 ZA 状态。

    有关定义,请参阅 [2]。

寄存器集数据以 struct user_za_header 开始,包含

size

完整寄存器集的大小,以字节为单位。这取决于 vl,将来可能还取决于其他因素。

如果对 PTRACE_GETREGSET 的调用请求的数据少于 size 的值,调用方可以分配更大的缓冲区并重试,以便读取完整的寄存器集。

max_size

目标线程的寄存器集可以增长到的最大大小(以字节为单位)。即使目标线程更改其向量长度等,寄存器集也不会增长得比这更大。

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 转换有效负载布局并重试。

  • 写入部分、不完整的有效负载的效果是未定义的。

  • 定义了一个新的寄存器集 NT_ARM_ZT,用于通过 PTRACE_GETREGSET 和 PTRACE_SETREGSET 访问 ZTn 状态。

  • NT_ARM_ZT 寄存器集由单个 512 位寄存器组成。

  • 当 PSTATE.ZA==0 时,对 NT_ARM_ZT 的读取将报告 ZTn 的所有位均为 0。

  • 对 NT_ARM_ZT 的写入将把 PSTATE.ZA 设置为 1。

  • 如果随 SME_PT_VL_ONEXEC 一起提供了任何寄存器数据,则该寄存器数据将使用当前的向量长度来解释,而不是使用为 exec 配置的向量长度。

8. ELF coredump 扩展

  • 将为被转储进程的每个线程的每个 coredump 添加 NT_ARM_SSVE 附注。其内容将等同于在生成 coredump 时为每个线程执行相应类型的 PTRACE_GETREGSET 所读取的数据。

  • 将为被转储进程的每个线程的每个 coredump 添加一个 NT_ARM_ZA 附注。其内容将等同于在生成 coredump 时为每个线程执行 NT_ARM_ZA 的 PTRACE_GETREGSET 所读取的数据。

  • 将为被转储进程的每个线程的每个 coredump 添加一个 NT_ARM_ZT 附注。其内容将等同于在生成 coredump 时为每个线程执行 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 Feature Registers