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 中缺少某个标志本身对最终用户来说几乎没有任何意义。

一方面,诸如 “vaes” 之类的特性可能在未定义 X86_FEATURE_VAES 的内核上对用户应用程序完全可用,因此 /proc/cpuinfo 中没有 “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,并找出 hypervisor 支持什么和不支持什么。如前所述,/proc/cpuinfo 不是无用特性标志的垃圾场。

3.2. 如何创建特性标志?

3.2.1. a:特性标志可以从 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. b:标志可以来自分散的基于 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. c:标志可以在某些硬件特性的条件下综合创建。

条件的示例包括 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”。标志 “ring3mwait” 仅在 INTEL_XEON_PHI_[KNL|KNM] 处理器上运行时才会显示。

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

这些标志不代表硬件特性。相反,它们代表在内核中实现的软件特性。例如,内核页表隔离是纯软件特性,其特性标志 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. a:默认情况下,标志的名称来自 X86_FEATURE_<name> 中的字符串。

默认情况下,/proc/cpuinfo 中的标志 <name> 是从 cpufeatures.h 中相应的 X86_FEATURE_<name> 中提取的。例如,标志 “avx2” 来自 X86_FEATURE_AVX2。

3.3.2. b:可以覆盖命名。

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

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

3.3.3. c:命名覆盖可以是 “”,这意味着它不会出现在 /proc/cpuinfo 中。

如果将特性暴露给用户空间没有意义,则应从 /proc/cpuinfo 中省略该特性。例如,X86_FEATURE_ALWAYS 在 cpufeatures.h 中定义,但该标志是替代运行时修补功能中使用的内部内核特性。因此,它的名称被覆盖为 “”。它的标志不会出现在 /proc/cpuinfo 中。

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

3.4.1. a:硬件没有枚举对它的支持。

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

3.4.2. b:内核不知道该标志。

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

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

例如,如果在构建时未启用 5 级分页(即,未选择 CONFIG_X86_5LEVEL),则不会显示标志 “la57” [1]。即使该特性仍将通过 CPUID 检测到,内核也会通过 setup_clear_cpu_cap(X86_FEATURE_LA57) 来禁用它。

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

可以通过命令行参数禁用某个特性,或者由于该特性未能成功启用而被禁用。命令行参数 clearcpuid= 可以用来禁用特性,使用 /arch/x86/include/asm/cpufeatures.h 中定义的特性编号。例如,可以使用 clearcpuid=514 来禁用用户模式指令保护 (User Mode Instruction Protection)。数字 514 是通过 #define X86_FEATURE_UMIP (16*32 + 2) 计算得出的。

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

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

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