4. x86 拓扑

本文档记录并澄清了内核中 x86 拓扑建模和表示的主要方面。在对相应代码进行更改时,请同时更新/修改本文档。

与架构无关的拓扑定义位于 How CPU topology info is exported via sysfs。本文件包含 x86 特有的差异/特性,这些差异/特性不一定适用于通用定义。因此,了解 x86 上的 Linux 拓扑的方法是:先阅读通用的拓扑文档,并同时结合本文档来了解 x86 的具体特性。

不用说,代码应当使用通用函数——本文件用于记录 x86 拓扑的内部工作原理。

由 Thomas Gleixner <tglx@kernel.org> 和 Borislav Petkov <bp@alien8.de> 创建。

拓扑设施的主要目的是为需要了解/查询/使用运行中系统的线程、核心、封装等结构的各种代码提供适当的接口。

内核不关心物理插槽(socket)的概念,因为插槽与软件无关,它是一个机电组件。过去,一个插槽总是包含一个封装(参见下文),但随着多芯片模块(MCM)的出现,一个插槽可以容纳多个封装。因此,代码中可能仍然存在对插槽的引用,但它们属于历史遗留产物,应当清理掉。

系统的拓扑结构以以下单位进行描述:

  • 封装

  • 核心

  • 线程

4.1. 封装

封装包含多个核心以及共享资源,例如 DRAM 控制器、共享缓存等。

现代系统也可能使用“裸片(Die)”一词来指代封装。

AMD 对封装的称呼是“节点(Node)”。

内核中与封装相关的拓扑信息

  • topology_num_threads_per_package()

    封装中的线程数。

  • topology_num_cores_per_package()

    封装中的核心数。

  • topology_max_dies_per_package()

    封装中的最大裸片数。

  • cpuinfo_x86.topo.die_id

    裸片的物理 ID。

  • cpuinfo_x86.topo.pkg_id

    封装的物理 ID。该信息通过 CPUID 获取,并从封装中各核心的 APIC ID 推导得出。

    现代系统将此值用于插槽。一个插槽内可能有多个封装。该值可能与 topo.die_id 不同。

  • cpuinfo_x86.topo.logical_pkg_id

    封装的逻辑 ID。由于我们不信任 BIOS 以一致的方式枚举封装,因此引入了逻辑封装 ID 的概念,以便我们能够合理地计算系统中可能的最大封装数,并使封装呈线性枚举。

  • topology_max_packages():

    系统中可能的最大封装数。有助于按封装的设施(per package facilities)预分配每个封装的信息。

  • cpuinfo_x86.topo.llc_id

    • 在 Intel 处理器上,这是共享末级缓存的 CPU 列表中的第一个 APIC ID

    • 在 AMD 处理器上,这是包含末级缓存的节点 ID 或核心复合体 ID。通常,它是一个在系统上唯一标识 LLC 的数字。

4.2. 核心

一个核心由 1 个或多个线程组成。这些线程是 SMT 还是 CMT 类型并不重要。

AMD 对 CMT 核心的称呼是“计算单元(Compute Unit)”。内核一律使用“核心(core)”。

4.3. 线程

一个线程是一个单独的调度单元。它相当于一个逻辑 Linux CPU。

AMD 对 CMT 线程的称呼是“计算单元核心(Compute Unit Core)”。内核一律使用“线程(thread)”。

内核中与线程相关的拓扑信息

  • topology_core_cpumask():

    该 cpumask 包含线程所属封装中的所有在线线程。

    在线线程数也会打印在 /proc/cpuinfo 的“siblings”中。

  • topology_sibling_cpumask():

    该 cpumask 包含线程所属核心中的所有在线线程。

  • topology_logical_package_id():

    线程所属的逻辑封装 ID。

  • topology_physical_package_id():

    线程所属的物理封装 ID。

  • topology_core_id();

    线程所属的核心 ID。它也会打印在 /proc/cpuinfo 的“core_id”中。

  • topology_logical_core_id();

    线程所属的逻辑核心 ID。

4.4. 系统拓扑枚举

x86 系统上的拓扑可以通过组合使用枚举处理器拓扑和缓存层级的特定厂商 CPUID 叶子节点来发现。

对于各个 x86 厂商,其解析首选顺序的 CPUID 叶子节点如下

  1. AMD

    1. CPUID 叶子节点 0x80000026 [扩展 CPU 拓扑] (Core::X86::Cpuid::ExCpuTopology)

      扩展 CPUID 叶子节点 0x80000026 是 CPUID 叶子节点 0xB 的扩展,并在各个层级提供核心、复合体、CCD(裸片)和插槽的拓扑信息。

      是否支持该叶子节点可以通过检查最大扩展 CPUID 级别是否 >= 0x80000026,然后检查特定层级(从 0 开始)中 EBX[15:0]LogProcAtThisLevel 是否为非零来发现。

      该层级中 ECX[15:8]LevelType 提供了该层级所描述的拓扑域——核心、复合体、CCD(裸片)或插槽。

      内核使用来自 EAX[4:0]CoreMaskWidth 来计算需要将 EDX[31:0] 中的 ExtendedLocalApicId 右移多少位,以便为该拓扑层级获取唯一的拓扑 ID。具有相同拓扑 ID 的 CPU 共享该层级的资源。

      CPUID 叶子节点 0x80000026 还提供了有关功耗和能效排名、以及在具有异构特征的 AMD 处理器上的核心类型的更多信息。

      如果支持 CPUID 叶子节点 0x80000026,则无需进行进一步解析。

    2. CPUID 叶子节点 0x0000000B [扩展拓扑枚举] (Core::X86::Cpuid::ExtTopEnum)

      扩展 CPUID 叶子节点 0x0000000B 是扩展 CPUID 叶子节点 0x80000026 的前身,仅描述处理器拓扑的核心和插槽域。

      是否支持该叶子节点可以通过检查最大支持的 CPUID 级别是否 >= 0xB,然后检查特定层级(从 0 开始)中的 EBX[31:0] 是否为非零来发现。

      该层级中 ECX[15:8]LevelType 提供了该层级所描述的拓扑域——线程或处理器(插槽)。

      内核使用来自 EAX[4:0]CoreMaskWidth 来计算需要将 EDX[31:0] 中的 ExtendedLocalApicId 右移多少位,以便为该拓扑层级获取唯一的拓扑 ID。共享该拓扑 ID 的 CPU 共享该层级的资源。

      如果支持 CPUID 叶子节点 0xB,则无需进行进一步解析。

    3. CPUID 叶子节点 0x80000008 ECX [大小标识符] (Core::X86::Cpuid::SizeId)

      如果 CPUID 叶子节点 0x80000026 和 0xB 均不支持,则使用大小标识符叶子节点 0x80000008 ECX 来检测封装上的 CPU 数量。

      是否支持该叶子节点可以通过检查支持的扩展 CPUID 级别是否 >= 0x80000008 来发现。

      如果 ECX[15:12] 中的 ApicIdSize 字段非零,则从 APIC ID 计算插槽 ID 所需的移位位数由此字段计算得出。

      如果报告 ApicIdSize 为零,则移位位数根据由 ECX[7:0] 中的 NC 字段计算得出的线程数的阶数来计算,该字段描述了封装上的线程数 - 1

      除非支持扩展 APIC ID,否则用于查找插槽 ID 的 APIC ID 来自 CPUID 叶子节点 0x00000001 EBX[31:24]LocalApicId 字段。

      拓扑解析继续检测是否支持扩展 APIC ID。

    4. CPUID 叶子节点 0x8000001E [扩展 APIC ID、核心标识符、节点标识符] (Core::X86::Cpuid::{ExtApicId,CoreId,NodeId})

      是否支持扩展 APIC ID 可以通过检查 CPUID 叶子节点 0x80000001 [特征标识符] (Core::X86::Cpuid::FeatureExtIdEcx) 的 ECX[22] 中是否存在 TopologyExtensions 来检测。

      如果支持拓扑扩展,在进行拓扑枚举时,应优先使用来自 CPUID 叶子节点 0x8000001E EAX[31:0]ExtendedApicId 中的 APIC ID,而不是来自 CPUID 叶子节点 0x00000001 EBX[31:24]LocalApicId 字段中的 APIC ID。

      在不支持 CPUID 叶子节点 0x80000026 或 CPUID 叶子节点 0xB 的 0x17 及以上家族处理器上,从 APIC ID 计算核心 ID 的移位位数,是通过使用 EBX[15:8] 中的 ThreadsPerCore 字段(描述每核心线程数 - 1)计算得出的每核心线程数的阶数来计算的。

      在 0x15 家族的处理器上,来自 EBX[7:0] 的核心 ID 被用作 cu_id(计算单元 ID),以检测共享计算单元的 CPU。

    所有支持 TopologyExtensions 特性的 AMD 处理器都将来自 CPUID 叶子节点 0x8000001E (Core::X86::Cpuid::NodeId) 的 ECX[7:0] 中的 NodeId 存储为每个 CPU 的 node_id。在较旧的处理器上,node_id 是通过使用 MSR_FAM10H_NODE_ID MSR(MSR 0x0xc001_100c)发现的。NODE_ID MSR 的存在是通过检查 CPUID 叶子节点 0x80000001 [特征标识符] (Core::X86::Cpuid::FeatureExtIdEcx) 的 ECX[19] 来检测的。

  2. Intel

    在 Intel 平台上,枚举处理器拓扑的 CPUID 叶子节点如下

    1. CPUID 叶子节点 0x1F (V2 扩展拓扑枚举叶子节点)

      CPUID 叶子节点 0x1F 是 CPUID 叶子节点 0xB 的扩展,并在各个层级提供核心、模块、瓦片、裸片、裸片组和插槽的拓扑信息。

      是否支持该叶子节点可以通过检查支持的 CPUID 级别是否 >= 0x1F,然后检查特定层级(从 0 开始)中的 EBX[31:0] 是否为非零来发现。

      子叶子节点中 ECX[15:8]Domain Type 提供了该层级所描述的拓扑域——核心、模块、瓦片、裸片、裸片组和插槽。

      内核使用来自 EAX[4:0] 的值来计算需要将 EDX[31:0] 中的 x2APIC ID 右移多少位,以便为该拓扑层级获取唯一的拓扑 ID。具有相同拓扑 ID 的 CPU 共享该层级的资源。

      如果支持 CPUID 叶子节点 0x1F,则无需进行进一步解析。

    2. CPUID 叶子节点 0x0000000B (扩展拓扑枚举叶子节点)

      扩展 CPUID 叶子节点 0x0000000B 是 V2 扩展拓扑枚举叶子节点 0x1F 的前身,仅描述处理器拓扑的核心和插槽域。

      是否支持该叶子节点可以通过检查支持的 CPUID 级别是否 >= 0xB,然后检查特定层级(从 0 开始)中的 EBX[31:0] 是否为非零来发现。

      CPUID 叶子节点 0x0000000B 具有与 CPUID 叶子节点 0x1F 相同的布局,并且应以类似的方式进行枚举。

      如果支持 CPUID 叶子节点 0xB,则无需进行进一步解析。

    3. CPUID 叶子节点 0x00000004 (确定性缓存参数叶子节点)

      在既不支持 CPUID 叶子节点 0x1F、也不支持 CPUID 叶子节点 0xB 的 Intel 处理器上,SMT 域的移位位数是使用共享 L1 缓存的 CPU 数量来计算的。

      是否具有超线程特性的处理器是通过 CPUID 叶子节点 0x1(基本 CPUID 信息)的 EDX[28] 来检测的。

      来自 CPUID 0x4 的 0 级 EAX[25:14]共享此缓存的逻辑处理器的最大可寻址 ID 数的阶数,提供了计算核心 ID 所需的相对于 APIC ID 的移位位数。

      APIC ID 和封装信息使用来自 CPUID 叶子节点 0x1 的数据进行计算。

    4. CPUID 叶子节点 0x00000001 (基本 CPUID 信息)

      用于推导物理封装(插槽)ID 的掩码和移位位数,是使用来自 CPUID 叶子节点 0x1 的 EBX[23:16] 中的此物理封装中的逻辑处理器的最大可寻址 ID 数计算得出的。

    传统平台上的 APIC ID 派生自 CPUID 叶子节点 0x1 的 EBX[31:24] 中的 Initial APIC ID 字段。

  3. Centaur 和兆芯

    与 Intel 类似,Centaur 和兆芯使用 CPUID 叶子节点 0x00000004(确定性缓存参数叶子节点)和 CPUID 叶子节点 0x00000001(基本 CPUID 信息)的组合来推导拓扑信息。

4.5. 系统拓扑示例

注意

备用的 Linux CPU 枚举取决于 BIOS 如何枚举线程。许多 BIOS 首先枚举所有的线程 0,然后枚举所有的线程 1. 这带来的“优势”是,无论是否启用线程,线程 0 的逻辑 Linux CPU 编号都保持不变。这仅仅是一个实现细节,没有实际影响。

  1. 单封装,单核心

    [package 0] -> [core 0] -> [thread 0] -> Linux CPU 0
    
  2. 单封装,双核心

    1. 每核心一个线程

      [package 0] -> [core 0] -> [thread 0] -> Linux CPU 0
                  -> [core 1] -> [thread 0] -> Linux CPU 1
      
    2. 每核心两个线程

      [package 0] -> [core 0] -> [thread 0] -> Linux CPU 0
                              -> [thread 1] -> Linux CPU 1
                  -> [core 1] -> [thread 0] -> Linux CPU 2
                              -> [thread 1] -> Linux CPU 3
      

      备用枚举

      [package 0] -> [core 0] -> [thread 0] -> Linux CPU 0
                              -> [thread 1] -> Linux CPU 2
                  -> [core 1] -> [thread 0] -> Linux CPU 1
                              -> [thread 1] -> Linux CPU 3
      

      AMD 对 CMT 系统的称呼

      [node 0] -> [Compute Unit 0] -> [Compute Unit Core 0] -> Linux CPU 0
                                   -> [Compute Unit Core 1] -> Linux CPU 1
               -> [Compute Unit 1] -> [Compute Unit Core 0] -> Linux CPU 2
                                   -> [Compute Unit Core 1] -> Linux CPU 3
      
  1. 双封装,双核心

    1. 每核心一个线程

      [package 0] -> [core 0] -> [thread 0] -> Linux CPU 0
                  -> [core 1] -> [thread 0] -> Linux CPU 1
      
      [package 1] -> [core 0] -> [thread 0] -> Linux CPU 2
                  -> [core 1] -> [thread 0] -> Linux CPU 3
      
    2. 每核心两个线程

      [package 0] -> [core 0] -> [thread 0] -> Linux CPU 0
                              -> [thread 1] -> Linux CPU 1
                  -> [core 1] -> [thread 0] -> Linux CPU 2
                              -> [thread 1] -> Linux CPU 3
      
      [package 1] -> [core 0] -> [thread 0] -> Linux CPU 4
                              -> [thread 1] -> Linux CPU 5
                  -> [core 1] -> [thread 0] -> Linux CPU 6
                              -> [thread 1] -> Linux CPU 7
      

      备用枚举

      [package 0] -> [core 0] -> [thread 0] -> Linux CPU 0
                              -> [thread 1] -> Linux CPU 4
                  -> [core 1] -> [thread 0] -> Linux CPU 1
                              -> [thread 1] -> Linux CPU 5
      
      [package 1] -> [core 0] -> [thread 0] -> Linux CPU 2
                              -> [thread 1] -> Linux CPU 6
                  -> [core 1] -> [thread 0] -> Linux CPU 3
                              -> [thread 1] -> Linux CPU 7
      

      AMD 对 CMT 系统的称呼

      [node 0] -> [Compute Unit 0] -> [Compute Unit Core 0] -> Linux CPU 0
                                   -> [Compute Unit Core 1] -> Linux CPU 1
               -> [Compute Unit 1] -> [Compute Unit Core 0] -> Linux CPU 2
                                   -> [Compute Unit Core 1] -> Linux CPU 3
      
      [node 1] -> [Compute Unit 0] -> [Compute Unit Core 0] -> Linux CPU 4
                                   -> [Compute Unit Core 1] -> Linux CPU 5
               -> [Compute Unit 1] -> [Compute Unit Core 0] -> Linux CPU 6
                                   -> [Compute Unit Core 1] -> Linux CPU 7