36. 在用户空间应用程序中使用 XSTATE 特性

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

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

36.1. 动态特性的目的

传统的用户空间库通常为备用信号栈设定了硬编码的静态大小,通常使用 MINSIGSTKSZ,一般为 2KB。该栈必须能够存储至少内核在跳转到信号处理函数之前设置的信号帧。该信号帧必须包含由 CPU 定义的 XSAVE 缓冲区。

然而,这意味着信号栈的大小是动态的而不是静态的,因为不同 CPU 具有不同大小的 XSAVE 缓冲区。对于像 AMX 这样新的 CPU 特性,现有应用程序中编译时确定的 2KB 大小显得太小了。通过动态启用,内核无需强制所有程序都使用更大的栈,而是可以要求用户空间应用程序具备适当大小的 altstack。

36.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 组件。

feature 参数是功能模块正常工作所需的最高 XSTATE 组件的编号。

当请求某个特性的权限时,内核会检查其可用性。内核会确保进程任务中的 sigaltstack 足够大,以容纳由此产生的大信号帧。无论是在 ARCH_REQ_XCOMP_SUPP 期间还是在随后的任何 sigaltstack(2) 调用期间,内核都会强制执行此规则。如果已安装的 sigaltstack 小于生成的 sigframe 大小,ARCH_REQ_XCOMP_SUPP 将返回 -ENOSUPP。此外,如果请求的 altstack 对于所允许的特性而言太小,sigaltstack(2) 将返回 -ENOMEM。

权限一旦被授予,即对每个进程有效。权限会在 fork(2) 时被继承,在 exec(3) 时被清除。

首次使用与动态启用特性相关的指令时会被内核捕获。陷阱处理程序会检查进程是否具有使用该特性的权限。如果进程没有权限,内核将向应用程序发送 SIGILL。如果进程拥有权限,处理程序会为任务分配一个更大的 xstate 缓冲区,以便能够对该大型状态进行上下文切换。如果分配失败(这种情况极少发生),内核将发送 SIGSEGV。

36.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 的准备工作。

36.3. 信号帧中的动态特性

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

36.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。