Linux CPU拓扑

CPU拓扑相关的概念见这篇博客的介绍。这篇笔记重点关注内核部分的代码实现。CPU拓扑的管理是和体系结构相关的,这里我们以ARM64为例进行分析。CPU拓扑管理主要涉及如下几个文件:

|-------------------------------|--------------------------------------------------|
| 文件 | 描述 |
| arch/arm64/kernel/topology.c | ARM64的CPU拓扑实现文件,包含了核心的构建CPU拓扑表的流程 |
| drivers/base/topology.c | CPU拓扑驱动,通过sysfs向用户态暴露了CPU拓扑结构 |
| include/linux/topology.h | 体系结构无关的CPU拓扑头文件,提供了默认的CPU拓扑相关接口实现。外部模块总是应该引用该头文件 |
| include/linux/arch_topology.h | 体系结构相关的CPU拓扑信息 |

CPU拓扑表

ARM平台使用cpu_topology结构来保存CPU拓扑信息,每个CPU一个cpu_topology对象,所有的CPU拓扑信息保存到一个全局数组中。

cpp 复制代码
struct cpu_topology cpu_topology[NR_CPUS];

struct cpu_topology {
    int thread_id;
    int core_id;
    int cluster_id;
    cpumask_t thread_sibling;
    cpumask_t core_sibling;
};
  • 按照范围从大到小的顺序,cluster_id 用来编码系统有多少个socket组成;core_id 用来编码socket下的CPU核心;支持超线程技术时,thread_id用来编码同一个CPU核心下的线程(也叫逻辑CPU),ARM不支持超线程,所以thread_id永远是0。
  • thread_siblingcore_sibling是两个CPU掩码。thread_sibling表示属于同一个core下的CPU掩码;core_sibling表示属于同一个socket下所有的所有线程掩码。

CPU拓扑表会在开机时根据DTS中的配置进行初始化。此外,还提供了一组接口用来根据cpu id查询拓扑表中字段。

cpp 复制代码
#define topology_physical_package_id(cpu)    (cpu_topology[cpu].cluster_id)
#define topology_core_id(cpu)        (cpu_topology[cpu].core_id)
#define topology_core_cpumask(cpu)    (&cpu_topology[cpu].core_sibling)
#define topology_thread_cpumask(cpu)    (&cpu_topology[cpu].thread_sibling)

设置CPU拓扑表

一个系统的CPU拓扑结构应该是固定的,因此应该在开机初始化时就完成对其设置。开机初始化时,kernel_init()->kernel_init_freeable()->smp_prepare_cpus()->**init_cpu_topology()**会根据DTS中的cpu_map配置设置内核的cpu_topology[]。其中从smp_prepare_cpus()开始就是体系结构相关的实现。

cpp 复制代码
void __init init_cpu_topology(void)
{
    reset_cpu_topology();

    if (parse_dt_topology()) // 从DTS中解析cpu拓扑结构
        reset_cpu_topology();
}

static int __init parse_dt_topology(void)
{
    struct device_node *cn, *map;
    int ret = 0;
    int cpu;

    // 从/cpus/cpu_map节点中解析拓扑
    cn = of_find_node_by_path("/cpus");

    map = of_get_child_by_name(cn, "cpu-map");

    ret = parse_cluster(map, 0); // 解析过程是自顶向下完成的

    // 检查确保所有的CPU的拓扑信息都有被解析到
    for_each_possible_cpu(cpu)
        if (cpu_topology[cpu].cluster_id == -1)
            ret = -EINVAL;
...
    return ret;
}

解析cluster

cpp 复制代码
// 首次调用cluster参数传入的是/cpus/cpu-map
static int __init parse_cluster(struct device_node *cluster, int depth)
{
    char name[10];
    bool leaf = true;
    bool has_cores = false;
    struct device_node *c;
    static int cluster_id __initdata;
    int core_id = 0;
    int i, ret;

    // 这个循环递归的调用Parse_cluster()函数可以完成/cpus/cpu-map节点下所有cluster节点的解析
    i = 0;
    do {
        snprintf(name, sizeof(name), "cluster%d", i);
        c = of_get_child_by_name(cluster, name);
        if (c) {
            leaf = false;
            ret = parse_cluster(c, depth + 1);
            of_node_put(c);
            if (ret != 0)
                return ret;
        }
        i++;
    } while (c); // c为空时,表示当前节点下没有名为clusterXX的子节点,结束循环,尝试解析cluster下的core节点

    // 和上面类似,实现解析cluster节点下的所有core节点
    i = 0;
    do {
        snprintf(name, sizeof(name), "core%d", i);
        c = of_get_child_by_name(cluster, name);
        if (c) {
            has_cores = true;
    
            if (depth == 0) { // core节点只能存在cluster节点下,如果直接出现在cpu-map下属于配置错误
                pr_err("%s: cpu-map children should be clusters\n",
                       c->full_name);
                of_node_put(c);
                return -EINVAL;
            }
    
            if (leaf) {
                ret = parse_core(c, cluster_id, core_id++);
            } else {
                pr_err("%s: Non-leaf cluster with core %s\n",
                   cluster->full_name, name);
                ret = -EINVAL;
            }
            of_node_put(c);
            if (ret != 0)
                return ret;
        }
        i++;
    } while (c);

    if (leaf && !has_cores)
        pr_warn("%s: empty cluster\n", cluster->full_name);

    if (leaf)
        cluster_id++;
    return 0;
}

解析core

cpp 复制代码
static int __init parse_core(struct device_node *core, int cluster_id,
 int core_id)
{
    char name[10];
    bool leaf = true;
    int i = 0;
    int cpu;
    struct device_node *t;

    // 如果有配置threadXXX,解析它,支持SMT才会配置
    do {
        snprintf(name, sizeof(name), "thread%d", i);
        t = of_get_child_by_name(core, name);
        if (t) {
            leaf = false;
            cpu = get_cpu_for_node(t);
            if (cpu >= 0) {
                cpu_topology[cpu].cluster_id = cluster_id;
                cpu_topology[cpu].core_id = core_id;
                cpu_topology[cpu].thread_id = i;
            } else {
                pr_err("%s: Can't get CPU for thread\n",
                   t->full_name);
                of_node_put(t);
                return -EINVAL;
            }
            of_node_put(t);
        }
        i++;
    } while (t);

    // 根据配置找到对应的CPU ID
    cpu = get_cpu_for_node(core);
    if (cpu >= 0) {
        if (!leaf) {
            pr_err("%s: Core has both threads and CPU\n",
                  core->full_name);
            return -EINVAL;
        }
        cpu_topology[cpu].cluster_id = cluster_id;
        cpu_topology[cpu].core_id = core_id;
    } else if (leaf) {
        pr_err("%s: Can't get CPU for leaf core\n", core->full_name);
        return -EINVAL;
    }
    return 0;
}

设置mask

从DTS解析拓扑结构时并未设置thread_sibling和core_sibling。这两个字段是在后面启动完毕后调用**store_cpu_topology()**函数完成的。

cpp 复制代码
void store_cpu_topology(unsigned int cpuid)
{
    struct cpu_topology *cpuid_topo = &cpu_topology[cpuid];
    u64 mpidr;

    if (cpuid_topo->cluster_id != -1) // DTS有配置时直接根据拓扑表信息计算
        goto topology_populated;

    // DTS未配置的情况下根据硬件的寄存器设置CPU拓扑表,这里我们不关注    
    mpidr = read_cpuid_mpidr();

...
topology_populated:
    update_siblings_masks(cpuid);
}

static void update_siblings_masks(unsigned int cpuid)
{
    struct cpu_topology *cpu_topo, *cpuid_topo = &cpu_topology[cpuid];
    int cpu;

    /* update core and thread sibling masks */
    for_each_possible_cpu(cpu) {
        cpu_topo = &cpu_topology[cpu];

        // 同一个cluster下的CPU为core_cibling
        if (cpuid_topo->cluster_id != cpu_topo->cluster_id)
            continue;
        cpumask_set_cpu(cpuid, &cpu_topo->core_sibling);
        if (cpu != cpuid)
            cpumask_set_cpu(cpu, &cpuid_topo->core_sibling);

        // 同一个core下面的CPU为thread_sibling
        if (cpuid_topo->core_id != cpu_topo->core_id)
            continue;
        cpumask_set_cpu(cpuid, &cpu_topo->thread_sibling);
        if (cpu != cpuid)
            cpumask_set_cpu(cpu, &cpuid_topo->thread_sibling);
    }
}

sysfs接口

用户态可以通过**/sys/devices/system/cpu/cpuX/topology**目录下的内容来获取到整个系统的CPU拓扑信息。下面是一个2大核+6小核手机上的各cpu拓扑信息值:

|---------|-------------------------|-------------|-------------------|------------------------|---------------------|--------------------------|
| cpu | physical_package_id | core_id | core_siblings | core_siblings_list | thread_siblings | thread_siblings_list |
| 0 | 0 | 0 | 3F | 0-5 | 01 | 0 |
| 1 | 0 | 1 | 3F | 0-5 | 02 | 1 |
| 2 | 0 | 2 | 3F | 0-5 | 04 | 2 |
| 3 | 0 | 3 | 3F | 0-5 | 08 | 3 |
| 4 | 0 | 4 | 3F | 0-5 | 10 | 4 |
| 5 | 0 | 5 | 3F | 0-5 | 20 | 5 |
| 6 | 0 | 0 | C0 | 6-7 | 40 | 6 |
| 7 | 0 | 1 | C0 | 6-7 | 80 | 7 |

相关推荐
意疏1 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu1 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
我的K84093 小时前
Flink整合Hudi及使用
linux·服务器·flink
1900433 小时前
linux6:常见命令介绍
linux·运维·服务器
Camellia-Echo3 小时前
【Linux从青铜到王者】Linux进程间通信(一)——待完善
linux·运维·服务器
Linux运维日记3 小时前
k8s1.31版本最新版本集群使用容器镜像仓库Harbor
linux·docker·云原生·容器·kubernetes
我是唐青枫3 小时前
Linux dnf 包管理工具使用教程
linux·运维·服务器
编程修仙4 小时前
Collections工具类
linux·windows·python
芝麻团坚果4 小时前
对subprocess启动的子进程使用VSCode python debugger
linux·ide·python·subprocess·vscode debugger
写点什么啦4 小时前
[debug]不同的window连接ubuntu的vscode后无法正常加载kernel
linux·vscode·ubuntu·debug