文章说明:
-
Linux内核版本:5.0
-
架构:ARM64
-
参考资料及图片来源:《奔跑吧Linux内核》
-
Linux 5.0内核源码注释仓库地址:
zhangzihengya/LinuxSourceCode_v5.0_study (github.com)
1. 组调度
CFS的调度粒度是进程,但是在某些应用场景中,用户希望的调度粒度是用户组,如在一台服务器中有N个用户登录,希望这N个用户可以平均分配CPU时间。这在调度粒度为进程的CFS里是很难做到的,拥有进程数量多的登录用户将会被分配比较多的CPU资源,组调度可以满足这方面的应用需求。
CFS定义了一个数据结构task_group来抽象和描述组调度:
struct task_group {/* 任务组与cgroup子系统状态的基础结构 */struct cgroup_subsys_state css;#ifdef CONFIG_FAIR_GROUP_SCHED/* 下面的字段仅在启用公平调度组(CONFIG_FAIR_GROUP_SCHED)时才会存在 */struct sched_entity **se; /* 指向每个CPU上任务组可调度实体的指针数组 */struct cfs_rq **cfs_rq; /* 指向每个CPU上任务组所拥有的运行队列的指针数组 */unsigned long shares; /* 任务组的CPU份额 */#ifdef CONFIG_SMP/** 下面的字段仅在启用对称多处理(CONFIG_SMP)时才会存在,* load_avg 可能在时钟滴答时存在严重争用,因此将其放在自己的缓存行中,与上面的字段分开,这些字段在每个滴答都会访问。*/atomic_long_t load_avg ____cacheline_aligned;
#endif
#endif#ifdef CONFIG_RT_GROUP_SCHED/* 下面的字段仅在启用实时调度组(CONFIG_RT_GROUP_SCHED)时才会存在 */struct sched_rt_entity **rt_se; /* 指向每个CPU上任务组实时调度实体的指针数组 */struct rt_rq **rt_rq; /* 指向每个CPU上任务组实时运行队列的指针数组 */struct rt_bandwidth rt_bandwidth; /* 存储任务组实时调度的带宽信息 */
#endifstruct rcu_head rcu; /* 用于RCU(Read-Copy-Update)机制的头部,用于进行资源释放 */struct list_head list; /* 用于将任务组添加到全局链表中 */struct task_group *parent; /* 指向父任务组的指针 */struct list_head siblings; /* 用于将任务组添加到兄弟任务组的链表中 */struct list_head children; /* 用于将子任务组添加到任务组的链表中 */#ifdef CONFIG_SCHED_AUTOGROUP/* 下面的字段仅在启用自动分组(CONFIG_SCHED_AUTOGROUP)时才会存在 */struct autogroup *autogroup; /* 与自动分组相关的信息 */
#endifstruct cfs_bandwidth cfs_bandwidth; /* 存储任务组公平调度的带宽信息 */
};
2. 组调度的创建
组调度属于cgroup架构中的cpu子系统,在系统配置时需要打开CONFIG_CGROUP_SCHED和CONFIG_FAIR_GROUP_SCHED宏。我们直接从sched_create_group()
函数来看如何创建和组织—个组调度:
// 创建和组织一个组调度
// parent 指向上一级的组调度节点,系统中有一个组调度的根,命名为 root_task_group
struct task_group *sched_create_group(struct task_group *parent)
{...// 创建 CFS 需要的组调度数据结构if (!alloc_fair_sched_group(tg, parent))goto err;// 创建 realtime 调度器需要的组调度数据结构if (!alloc_rt_sched_group(tg, parent))goto err;return tg;err:...
}
sched_create_group()->alloc_rt_sched_group()
:
int alloc_fair_sched_group(struct task_group *tg, struct task_group *parent)
{...// cfs_rq 是一个指针数组,分配 nr_cpu_ids 个 cfs_rq 数据结构并将其存放到该指针数组中tg->cfs_rq = kcalloc(nr_cpu_ids, sizeof(cfs_rq), GFP_KERNEL);...// shares 成员表示该组的权重,这里暂时初始化为 nice 值为 0 的进程权重tg->shares = NICE_0_LOAD;// 初始化 CFS 中与带宽控制相关的信息init_cfs_bandwidth(tg_cfs_bandwidth(tg));// for 循环遍历系统中所有的 CPU,为每个 CPU 分配一个 cfs_rq 调度队列和 sched_entity 调度实体for_each_possible_cpu(i) {cfs_rq = kzalloc_node(sizeof(struct cfs_rq),GFP_KERNEL, cpu_to_node(i));if (!cfs_rq)goto err;se = kzalloc_node(sizeof(struct sched_entity),GFP_KERNEL, cpu_to_node(i));if (!se)goto err_free_rq;// 初始化 cfs_rq 调度队列中的 task_timeline 和 min_vruntime 等信息init_cfs_rq(cfs_rq);// 用于构建组调度结构的关键函数,对组调度的相关数据结构进行初始化init_tg_cfs_entry(tg, cfs_rq, se, i, parent->se[i]);init_entity_runnable_average(se);}...
}
下图所示为在一个双核处理器系统中组调度的数据结构关系:
3. 把进程添加到组调度
要把进程添加到组调度,需要调用cgroup里的接口函数cpu_cgroup_attach()
:
// 把进程添加到组调度
static void cpu_cgroup_attach(struct cgroup_taskset *tset)
{...// 遍历参数 tset 包含的进程链表cgroup_taskset_for_each(task, css, tset)// 将进程迁移到组调度中sched_move_task(task);
}
cpu_cgroup_attach()->sched_move_task()
:
void sched_move_task(struct task_struct *tsk)
{...// 判断该进程是否正在运行running = task_current(rq, tsk);// 判断进程是否在就绪队列里或者正在运行中// task_struct 数据结构中的 on-rq 成员表示该进程的状态,TASK_ON_RQ_QUEUED 表示该进程在就绪队列中或者正在运行中,// TASK_ON_RQ_MIGRATING 表示该进程正在迁移中queued = task_on_rq_queued(tsk);// 如果该进程处于就绪态,那么要让该进程暂时先退出就绪队列if (queued)dequeue_task(rq, tsk, queue_flags);// 如果该进程正在运行中,刚才已经调用 dequeue_task() 函数让进程退出就绪队列,现在只能将其添加回就绪队列中if (running)put_prev_task(rq, tsk);// sched_change_group() 函数调用CFS的调度类的操作方法集中的 task_change_group() 方法。另外,这里还调用// set_task_rq() 函数设置进程调度实体中的 cfs_rq 成员和 parent 成员,cfs_rq成员指问组调度中自身的 CFS // 就绪队列,parent 成员指向组调度中的 se 调度实体sched_change_group(tsk, TASK_MOVE_GROUP);if (queued)// 调用 enqueue_task() 函数把退出就绪队列的进程和组调度重新添加回就绪队列enqueue_task(rq, tsk, queue_flags);if (running)set_curr_task(rq, tsk);...
}
4. 组调度的基本策略
- 在创建组调度tg时,tg为每个CPU同时创建组调度内部使用的cfs_rq
- 组调度作为—个调度实体添加到系统的CFS就绪队列rq->cfs_rq中
- 程添加到一个组中后,进程就脱离了系统的CFS就绪队列,并且添加到组调度里的CFS就绪队列tg->cfs_rq[]中
- 在选择下一个进程时,从系统的CFS就绪队列开始,如果选中的调度实体是组调度tg,那么还需要继续遍历tg中的就绪队列,从中选择一个进程来运行。
注意,CFS的组调度机制可以支持N级,这里只以简单的两级为例。