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拓扑信息保存到一个全局数组中。

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查询拓扑表中字段。

#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()开始就是体系结构相关的实现。

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

// 首次调用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

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()函数完成的。

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

你可能感兴趣的:(Linux进程调度,linux,CPU,拓扑)