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 |