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 |

相关推荐
HLC++8 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室9 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy12 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕12 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫13 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生14 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1115 小时前
linux基础IO
linux·运维·服务器
BelongPanda17 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯17 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983417 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器