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 |

相关推荐
cominglately3 小时前
centos单机部署seata
linux·运维·centos
魏 无羡3 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse3 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
木子Linux3 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
mit6.8244 小时前
Ubuntu 系统下性能剖析工具: perf
linux·运维·ubuntu
鹏大师运维4 小时前
聊聊开源的虚拟化平台--PVE
linux·开源·虚拟化·虚拟机·pve·存储·nfs
watermelonoops4 小时前
Windows安装Ubuntu,Deepin三系统启动问题(XXX has invalid signature 您需要先加载内核)
linux·运维·ubuntu·deepin
滴水之功5 小时前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
ldinvicible5 小时前
How to run Flutter on an Embedded Device
linux
YRr YRr6 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu