2.6.3.1 组调度 - 数据结构
我们先将任务组的总体数据结构放在一边,再来回顾一下CFS的调度逻辑:调度是每个CPU单独进行的,每个CPU有自己的runqueue, 对CFS而言,所有的调度实体都存放在一个cfsrq中,当调度发生时,调度器从该cfsrq中选择vruntime最小的调度实体对应的任务来执行。
如果调度器选择到的调度实体表示一个任务组的话,该怎么办呢?那么调度器需要从该任务组中继续选择vruntime最小的任务来执行。
这里存在一个问题:在多核系统上,一个任务组的所有任务可以分布在多个CPU上,如下图所示:

该图只是一个示意图,用来帮助我们理解调度组的概念,请不要将图中结构与任何内核的数据结构做直接的对应。
红色的5个任务隶属于同一个调度组,其中 SE_G0, SE_G1, SE_G2 这个 sub-group 被分配到了 CPU0 上,而 SE_G3, SE_G4 这个 sub-group 被分配到了 CPU1 上,SER0 与 SE_R1 分别是用来管理两边 sub-group 的根节点,也就是说当CPU0发生调度并选择到 SE_R0 时,会发现它其实代表一个任务组,此时需要进一步从该调度组中挑出最合适的任务来执行,这里应该从 SE_G0, SE_G1, SE_G2 中再选一个出来。
这里有两个问题:
如何区分一个 se 是任务还是任务组
如何管理在同一个CPU上并隶属于同一个任务组的多个任务
要回答这些问题,我们又需要回到数据结构 sched_entity 中来。在讨论调度实体时我们提到, sched_entity 存在的原因就是设计者需要一个既可以表示单个任务、又可以封装一个任务组的数据结构。现在我们来看一下该结构体的完整定义:
预编译指令 #ifdef CONFIG_FAIR_GROUP_SCHED 中间的几个字段都是用来封装任务组的。判断一个 se 是否是任务组的字段是 my_q: 如果该字段为 NULL, 则表示一个任务,否则就是一个任务组。而 my_q 是我们熟悉的结构体 cfs_rq, 也就是说同一个CPU上并且隶属于同一个组的任务又通过另一个cfsrq组织了起来,放在另一棵红黑树中。与之前讲解过的结构全部结合起来,单个CPU 的运行队列结构如下:

弄清楚了单个CPU上的结构之后,我们接下来看一下任务组的总体结构。系统专门定义了一个结构体来封装任务组的信息:
这里我们暂时只关心两个字段:
struct sched_entity **sestruct cfs_rq **cfs_rq
通过前文我们知道,系统将一个任务组分布到各个CPU上时,同一个CPU上所分配到的所有任务会形成一个子组(sub-group)并通过一个cfsrq来管理;而CPU本身的cfsrq中会有一个代表任务组 se 指向该子组; task_group 的这两个字段分别用来保存这两方面的内容:se[i]用来保存CPU[i]队列上代表任务组的se,而cfsrq[i]用来保存CPU[i]所分配到的任务子组,其中 i 表示CPU的索引下标。总体结构的示意图如下:

除了 sched_entity 与 task_group 之外,cfs_rq与 rq 中也有与组调度相关的字段,由于并不影响我们从总体上理解任务组的结构,因此在这里不再细究。
Last updated
Was this helpful?