35. 在用户空间应用程序中使用 XSTATE 功能

x86 架构支持通过 CPUID 枚举的浮点扩展。 应用程序会查阅 CPUID 并使用 XGETBV 来评估内核 XCR0 启用了哪些功能。

对于 AVX-512 和 PKRU 状态,如果可用,这些功能将由内核自动启用。 诸如 AMX TILE_DATA (XSTATE 组件 18) 之类的功能也由 XCR0 启用,但首次使用相关指令会被内核捕获,因为默认情况下,所需的大的 XSTATE 缓冲区不会自动分配。

35.1. 动态特性的目的

旧版用户空间库通常对备用信号堆栈具有硬编码的静态大小,通常使用 MINSIGSTKSZ,后者通常为 2KB。 该堆栈必须能够存储至少内核在跳转到信号处理程序之前设置的信号帧。 该信号帧必须包含 CPU 定义的 XSAVE 缓冲区。

但是,这意味着信号堆栈的大小是动态的,而不是静态的,因为不同的 CPU 具有不同大小的 XSAVE 缓冲区。 对于诸如 AMX 之类的新 CPU 功能,现有应用程序的 2KB 编译大小太小了。 内核不是普遍要求更大的堆栈,而是通过动态启用,内核可以强制用户空间应用程序具有适当大小的 altstacks。

35.2. 在用户空间应用程序中使用动态启用的 XSTATE 功能

内核提供了一个基于 arch_prctl(2) 的机制,供应用程序请求使用此类功能。 与此相关的 arch_prctl(2) 选项是

-ARCH_GET_XCOMP_SUPP

arch_prctl(ARCH_GET_XCOMP_SUPP, &features);

ARCH_GET_XCOMP_SUPP 将支持的功能存储在 uint64_t 类型的用户空间存储中。 第二个参数是指向该存储的指针。

-ARCH_GET_XCOMP_PERM

arch_prctl(ARCH_GET_XCOMP_PERM, &features);

ARCH_GET_XCOMP_PERM 将用户空间进程具有权限的功能存储在 uint64_t 类型的用户空间存储中。 第二个参数是指向该存储的指针。

-ARCH_REQ_XCOMP_PERM

arch_prctl(ARCH_REQ_XCOMP_PERM, feature_nr);

ARCH_REQ_XCOMP_PERM 允许请求动态启用的特性或特性集的权限。 特性集可以映射到工具,例如 AMX,并且可能需要启用一个或多个 XSTATE 组件。

特性参数是工具工作所需的最高 XSTATE 组件的编号。

请求某个特性的权限时,内核会检查其可用性。 内核确保进程任务中的 sigaltstacks 足够大,可以容纳生成的较大的信号帧。 它在 ARCH_REQ_XCOMP_SUPP 期间和任何后续的 sigaltstack(2) 调用期间都会强制执行此操作。 如果已安装的 sigaltstack 小于生成的 sigframe 大小,则 ARCH_REQ_XCOMP_SUPP 会导致 -ENOSUPP。 此外,如果请求的 altstack 对于允许的功能来说太小,则 sigaltstack(2) 会导致 -ENOMEM。

权限在授予后,每个进程都有效。 权限在 fork(2) 上继承,并在 exec(3) 上清除。

首次使用与动态启用的特性相关的指令会被内核捕获。 捕获处理程序会检查进程是否具有使用该特性的权限。 如果进程没有权限,则内核会向应用程序发送 SIGILL。 如果进程具有权限,则处理程序会为任务分配更大的 xstate 缓冲区,以便可以上下文切换大型状态。 如果分配失败(可能性不大),内核会发送 SIGSEGV。

35.2.1. AMX TILE_DATA 启用示例

下面是如何动态启用 TILE_DATA 的用户空间应用程序的示例

  1. 应用程序首先需要查询内核以获得 AMX 支持

    #include <asm/prctl.h>
    #include <sys/syscall.h>
    #include <stdio.h>
    #include <unistd.h>
    
    #ifndef ARCH_GET_XCOMP_SUPP
    #define ARCH_GET_XCOMP_SUPP  0x1021
    #endif
    
    #ifndef ARCH_XCOMP_TILECFG
    #define ARCH_XCOMP_TILECFG   17
    #endif
    
    #ifndef ARCH_XCOMP_TILEDATA
    #define ARCH_XCOMP_TILEDATA  18
    #endif
    
    #define MASK_XCOMP_TILE      ((1 << ARCH_XCOMP_TILECFG) | \
                                  (1 << ARCH_XCOMP_TILEDATA))
    
    unsigned long features;
    long rc;
    
    ...
    
    rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features);
    
    if (!rc && (features & MASK_XCOMP_TILE) == MASK_XCOMP_TILE)
        printf("AMX is available.\n");
    
  2. 之后,在确定对 AMX 的支持后,应用程序必须显式请求使用它的权限

    #ifndef ARCH_REQ_XCOMP_PERM
    #define ARCH_REQ_XCOMP_PERM  0x1023
    #endif
    
    ...
    
    rc = syscall(SYS_arch_prctl, ARCH_REQ_XCOMP_PERM, ARCH_XCOMP_TILEDATA);
    
    if (!rc)
        printf("AMX is ready for use.\n");
    

请注意,此示例不包括 sigaltstack 准备。

35.3. 信号帧中的动态特性

如果动态启用的特性处于其初始配置中,则不会在信号进入时写入信号帧。 这与非动态特性不同,无论其配置如何,非动态特性始终都会写入。 信号处理程序可以检查 XSAVE 缓冲区的 XSTATE_BV 字段,以确定是否写入了某个特性。

35.4. 虚拟机的动态特性

客户机状态组件的权限需要与主机分开管理,因为它们彼此互斥。 扩展了一对选项以控制客户机权限

-ARCH_GET_XCOMP_GUEST_PERM

arch_prctl(ARCH_GET_XCOMP_GUEST_PERM, &features);

ARCH_GET_XCOMP_GUEST_PERM 是 ARCH_GET_XCOMP_PERM 的变体。 因此,它为客户机组件提供了相同的语义和功能。

-ARCH_REQ_XCOMP_GUEST_PERM

arch_prctl(ARCH_REQ_XCOMP_GUEST_PERM, feature_nr);

ARCH_REQ_XCOMP_GUEST_PERM 是 ARCH_REQ_XCOMP_PERM 的变体。 它具有客户机权限的相同语义。 在提供类似功能的同时,这存在一个约束。 当创建第一个 VCPU 时,权限将被冻结。 在该点之后尝试更改权限将被拒绝。 因此,必须在创建第一个 VCPU 之前请求权限。

请注意,某些 VMM 可能已经建立了一组受支持的状态组件。 这些选项不被认为支持任何特定的 VMM。