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拓扑信息 |
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;
- };
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拓扑结构应该是固定的,因此应该在开机初始化时就完成对其设置。开机初始化时,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参数传入的是/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;
- }
- 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;
- }
从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);
- }
- }
用户态可以通过/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 |