3. x86 特性标志

3.1. 介绍

/proc/cpuinfo 中的特性标志列表并不完整,并且代表了很久以前试图将特性标志放在用户空间易于查找的地方的一个不幸尝试。

然而,特性标志的数量随着 CPU 的发展而增长,导致 /proc/cpuinfo 难以解析和笨拙。

更重要的是,这些特性标志甚至不需要在该文件中,因为用户空间并不关心它们 - glibc 等已经使用 CPUID 来找出目标机器支持什么,不支持什么。

即使它没有显示特定的特性标志 - 尽管 CPU 仍然支持相应的硬件功能并且该 CPU 支持 CPUID 故障 - 用户空间也可以简单地探测该特性并确定它是否受支持,而不管它是否在某个地方被宣传。

此外,这些标志字符串一旦出现在那里就成为 ABI,并且在没有任何东西使用它们的情况下永久维护它们是一种巨大的浪费。

因此,当前 /proc/cpuinfo 的用途是显示内核已启用支持的特性。例如:CPUID 特性标志在那里,内核在启动时完成了额外的设置,并且该功能已准备好使用。一个完美的例子是“user_shstk”,其中内核中存在额外的代码启用,以支持用户程序的影子栈。

因此,如果用户想知道某个特性在给定的系统上是否可用,他们会尝试在 /proc/cpuinfo 中找到该标志。如果存在给定的标志,则意味着

  • 内核足够了解该特性,拥有一个 X86_FEATURE 位

  • 内核支持它,并且目前正在将其提供给用户空间或内核的某些其他部分

  • 如果该标志代表硬件特性,则硬件支持它。

/proc/cpuinfo 中缺少标志本身对最终用户来说几乎没有任何意义。

一方面,在没有定义 X86_FEATURE_VAES 的内核上,“vaes”等特性可能完全可供用户应用程序使用,因此 /proc/cpuinfo 中没有“vaes”。

另一方面,在非 VAES 硬件上运行的新内核在 /proc/cpuinfo 中也没有“vaes”。应用程序或用户无法区分差异。

最终结果是 /proc/cpuinfo 中的 flags 字段对内核调试略有帮助,但对其他任何事情都没有真正帮助。应用程序应该使用 glibc 设施等来查询 CPU 支持。用户应该依赖 tools/arch/x86/kcpuid 和 cpuid(1) 等工具。

关于实现,出现在 /proc/cpuinfo 中的标志在 arch/x86/include/asm/cpufeatures.h 中有一个 X86_FEATURE 定义。这些标志代表硬件特性以及软件特性。

如果内核关心一个特性或者 KVM 想要将该特性暴露给 KVM 访客,那么只有在访客需要解析 /proc/cpuinfo 时才应该将其暴露给访客。如上所述,这种情况极不可能发生。KVM 可以合成 CPUID 位,KVM 访客可以简单地查询 CPUID 并找出虚拟机监控程序支持什么,不支持什么。正如已经声明的那样,/proc/cpuinfo 不是无用特性标志的转储场。

3.2. 如何创建特性标志?

3.2.1. 特性标志可以从 CPUID 叶的内容派生

这些特性定义按照 CPUID 叶的布局进行组织,并以偏移量分组在字中,如 cpufeatures.h 中的 enum cpuid_leafs 中映射的那样(有关详细信息,请参阅 arch/x86/include/asm/cpufeatures.h)。如果在 cpufeatures.h 中使用 X86_FEATURE_<name> 定义定义了一个特性,并且如果在运行时检测到该特性,则标志将相应地显示在 /proc/cpuinfo 中。例如,标志“avx2”来自 cpufeatures.h 中的 X86_FEATURE_AVX2。

3.2.2. 标志可以来自分散的基于 CPUID 的特性

在稀疏填充的 CPUID 叶中枚举的硬件特性获得软件定义的值。尽管如此,仍然需要查询 CPUID 才能确定是否存在给定的特性。这在 init_scattered_cpuid_features() 中完成。例如,X86_FEATURE_CQM_LLC 定义为 11*32 + 0,并且在运行时在相应的 CPUID 叶 [EAX=f, ECX=0] 位 EDX[1] 中检查其存在。

分散 CPUID 叶的目的是避免不必要地膨胀 struct cpuinfo_x86.x86_capability[]。例如,CPUID 叶 [EAX=7, ECX=0] 有 30 个特性并且是密集的,但 CPUID 叶 [EAX=7, EAX=1] 只有一个特性,并且会在 x86_capability[] 数组中浪费 31 位空间。由于每个可能的 CPU 都有一个 struct cpuinfo_x86,因此浪费的内存并非微不足道。

3.2.3. 在某些条件下,可以为硬件特性合成创建标志

条件的示例包括 MSR_IA32_CORE_CAPS 中是否存在某些特性或是否识别出特定的 CPU 型号。如果满足所需的条件,则通过 set_cpu_cap 或 setup_force_cpu_cap 宏启用这些特性。例如,如果在 MSR_IA32_CORE_CAPS 中设置了位 5,则将启用特性 X86_FEATURE_SPLIT_LOCK_DETECT 并且将显示“split_lock_detect”。只有在 INTEL_XEON_PHI_[KNL|KNM] 处理器上运行时才会显示标志“ring3mwait”。

3.2.4. 标志可以表示纯软件特性

这些标志不代表硬件特性。相反,它们代表内核中实现的软件特性。例如,内核页面表隔离纯粹是软件特性,其特性标志 X86_FEATURE_PTI 也在 cpufeatures.h 中定义。

3.3. 标志的命名

脚本 arch/x86/kernel/cpu/mkcapflags.sh 处理来自 cpufeatures.h 的 #define X86_FEATURE_<name> 并生成 kernel/cpu/capflags.c 中的 x86_cap/bug_flags[] 数组。生成的 x86_cap/bug_flags[] 中的名称用于填充 /proc/cpuinfo。 x86_cap/bug_flags[] 中标志的命名如下

3.3.1. 默认情况下,标志不会出现在 /proc/cpuinfo 中

默认情况下,/proc/cpuinfo 中省略了特性标志,因为在大多数情况下,将特性暴露给用户空间没有意义。例如,X86_FEATURE_ALWAYS 在 cpufeatures.h 中定义,但该标志是替代运行时修补功能中使用的内部内核特性。因此,该标志不会出现在 /proc/cpuinfo 中。

3.3.2. 如果绝对需要,请指定标志名称

如果 #define X86_FEATURE_* 行的注释以双引号字符(“”)开头,则双引号字符内的字符串将是标志的名称。例如,标志“sse4_1”来自 X86_FEATURE_XMM4_1 定义后面的注释“sse4_1”。

在某些情况下,需要覆盖标志的显示名称。例如,/proc/cpuinfo 是一个用户空间接口,必须保持不变。如果由于某种原因,X86_FEATURE_<name> 的命名发生变化,则应使用 /proc/cpuinfo 中已使用的名称覆盖新命名。

3.4. 发生以下一种或多种情况时,标志会丢失

3.4.1. 硬件不支持枚举它

例如,当新内核在旧硬件上运行时,或者引导固件未启用该特性时。即使硬件是新的,在运行时启用该特性也可能存在问题,该标志将不会显示。

3.4.2. 内核不知道该标志

例如,当旧内核在新硬件上运行时。

3.4.3. 内核在编译时禁用了对它的支持

例如,如果在构建时未启用线性地址屏蔽 (LAM)(即,未选择 CONFIG_ADDRESS_MASKING),则不会显示标志“lam”。即使仍然可以通过 CPUID 检测到该特性,内核也会通过 setup_clear_cpu_cap(X86_FEATURE_LAM) 清除来禁用它。

3.4.4. 该特性在启动时被禁用

可以使用命令行参数禁用特性,或者因为它未能启用。可以使用命令行参数 clearcpuid= 来使用 /arch/x86/include/asm/cpufeatures.h 中定义的特性编号禁用特性。例如,可以使用 clearcpuid=514 禁用用户模式指令保护。数字 514 是从 #define X86_FEATURE_UMIP (16*32 + 2) 计算得出的。

此外,还存在各种自定义命令行参数,用于禁用特定特性。参数列表包括但不限于 nofsgsbase、nosgx、noxsave 等。还可以使用“no5lvl”禁用 5 级分页。

3.4.5. 已知该特性无法正常工作

由于运行时缺少依赖项,已知该特性无法正常工作。例如,如果 XSAVE 特性被禁用,AVX 标志将不会显示,因为它们依赖于 XSAVE 特性。另一个例子是损坏的 CPU 并且它们缺少微码补丁。因此,内核决定不启用某个特性。