引入调度域的讨论可以参考这篇文章。这篇笔记重点分析了内核调度域相关的数据结构以及内核用于构建调度域的代码实现,以此来加深对调度域的理解。调度域是调度器进行负载均衡的基础。
调度域拓扑层级
整个系统的调度域组成一个层级结构,内核设计了struct sched_domain_topology_level来描述一层调度域拓扑。
typedef const struct cpumask *(*sched_domain_mask_f)(int cpu);
typedef int (*sched_domain_flags_f)(void);
struct sched_domain_topology_level {
sched_domain_mask_f mask;
sched_domain_flags_f sd_flags;
int flags;
int numa_level;
struct sd_data data;
#ifdef CONFIG_SCHED_DEBUG
char *name;
#endif
};
- mask:该回调用于指定该层级的调度域的CPU掩码。
- sd_flags:该回调用于获取该层级的调度域标记。
- data:该层级的调度域对象,见下面单独分析。
- name:层级名字,如MC、DIE,这些名字会在用户态的/proc/sys/kernel/sched_domain目录中体现。
系统的调度域拓扑用sched_domain_topology_level[]数组表示,保存在全局变量sched_domain_topology中,数组的每一个元素代表一个层级。default_topology是系统定义的默认调度域拓扑层级数组。各体系结构可以定义自己的调度域拓扑层级数组,然后通过set_sched_topology()函数替换该默认值。
// 不考虑超线程,默认的由MC(多核)和DIE(socket)两个层级组成,手机产品基本上只有这两个层级
static struct sched_domain_topology_level default_topology[] = {
#ifdef CONFIG_SCHED_SMT
{ cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) }, // 支持超线程时开启
#endif
#ifdef CONFIG_SCHED_MC
{ cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) },
#endif
{ cpu_cpu_mask, SD_INIT_NAME(DIE) },
{ NULL, },
};
struct sched_domain_topology_level *sched_domain_topology = default_topology;
// MC层级包含了属于同一个cluster的所有CPU
const struct cpumask *cpu_coregroup_mask(int cpu)
{
return &cpu_topology[cpu].core_sibling;
}
// DIE层级包含了系统所有的CPU(不考虑NUMA)
static inline const struct cpumask *cpu_cpu_mask(int cpu)
{
return cpumask_of_node(cpu_to_node(cpu));
}
sd_data
该结构用于辅助定义调度域拓扑层级,包含了一个拓扑层级所有的调度域对象,这些对象每个CPU一份。由于Per-CPU变量也是动态分配的,所以类型是二级指针。此外,由于整个调度域拓扑层级一旦建立就基本不会再发生变化,每个CPU一份可以提高访问效率。
struct sd_data {
struct sched_domain **__percpu sd;
struct sched_group **__percpu sg;
struct sched_group_capacity **__percpu sgc;
};
构建系统调度域拓扑结构时,会使用__sdt_alloc()为sd_data分配内存,对应的释放函数为__sdt_free()。
static int __sdt_alloc(const struct cpumask *cpu_map)
{
struct sched_domain_topology_level *tl;
int j;
for_each_sd_topology(tl) { // 遍历所有的拓扑层级,为每一个tl->sd_data分配空间
struct sd_data *sdd = &tl->data;
// 分配调度域指针对象,这些指针每个CPU一份
sdd->sd = alloc_percpu(struct sched_domain *);
sdd->sg = alloc_percpu(struct sched_group *);
sdd->sgc = alloc_percpu(struct sched_group_capacity *);
// 为每个CPU分配这三个调度域对象
for_each_cpu(j, cpu_map) {
struct sched_domain *sd;
struct sched_group *sg;
struct sched_group_capacity *sgc;
sd = kzalloc_node(sizeof(struct sched_domain) + cpumask_size(),
GFP_KERNEL, cpu_to_node(j));
*per_cpu_ptr(sdd->sd, j) = sd;
sg = kzalloc_node(sizeof(struct sched_group) + cpumask_size(),
GFP_KERNEL, cpu_to_node(j));
sg->next = sg;
*per_cpu_ptr(sdd->sg, j) = sg;
sgc = kzalloc_node(sizeof(struct sched_group_capacity) + cpumask_size(),
GFP_KERNEL, cpu_to_node(j));
*per_cpu_ptr(sdd->sgc, j) = sgc;
}
}
return 0;
}
调度域对象
有3个调度域对象:
- struct sched_domain:调度域代表可以共享属性和调度参数的一组CPU。每个CPU在每一个调度域拓扑层级中都有一个shced_domain对象。
- struct sched_group:调度域可以由一个或多个调度组构成,每个调度组也代表可以共享属性和调度参数的一组CPU,属于同一个调度域的调度组的集合组成了调度域代表的CPU。调度域进行负载均衡的目的就是要保证其内部各个调度组之间的负载均衡。
- struct sched_capacity:包含了调度组的ca