3. CFS核心数据结构:cfs_rq运行队列、task_group组调度结构、sched_entity与cfs_rq的关联
完全公平调度器(CFS)的核心工程实现依赖于三个关键数据结构:cfs_rq(运行队列)、task_group(组调度结构)和sched_entity(调度实体)。理解这三者之间的关联,是掌握CFS调度机制和组调度配置的基础。
3.1 cfs_rq:CFS运行队列
cfs_rq 是每个CPU上CFS调度类的核心运行队列,负责管理该CPU上所有可运行的调度实体。其定义位于 kernel/sched/sched.h 中。
| 字段 | 类型 | 说明 |
|---|---|---|
nr_running |
unsigned int | 当前队列中可运行的调度实体数量 |
load |
struct load_weight | 队列的总负载权重,用于负载均衡 |
min_vruntime |
u64 | 队列中最小的虚拟运行时间,是CFS调度的核心基准 |
tasks_timeline |
struct rb_root_cached | 红黑树的根节点,用于按vruntime排序调度实体 |
curr |
struct sched_entity * | 当前正在执行的调度实体 |
next |
struct sched_entity * | 下一个将要被调度的实体(缓存优化) |
tg |
struct task_group * | 指向所属的任务组(若为根组则为NULL) |
关键概念:
min_vruntime 是CFS调度决策的“锚点”。新进程或唤醒进程的vruntime会被钳制在 min_vruntime 附近,防止它们因vruntime过小而长时间抢占CPU。
3.2 task_group:组调度结构
task_group 是实现CPU控制组(cgroup cpu子系统)的内核结构。它允许将多个任务组织成一个调度单位,实现资源的分层分配。
struct task_group {
struct cgroup_subsys_state css; // cgroup子系统状态
struct sched_entity **se; // 每个CPU上的调度实体数组
struct cfs_rq **cfs_rq; // 每个CPU上的CFS运行队列数组
struct sched_entity *parent_se; // 父组的调度实体
struct list_head children; // 子组列表
struct list_head siblings; // 兄弟组列表
unsigned long shares; // 该组获得的CPU时间份额
struct cfs_bandwidth cfs_bandwidth; // 带宽控制(CFS组限流)
};
每个 task_group 在每一个CPU上都维护一个独立的 cfs_rq 和 sched_entity。这种设计使得组调度可以自然地融入CFS的per-CPU架构。
组调度层级: 根任务组(root_task_group)包含所有系统进程。通过cgroup创建的子组形成树形结构,每个子组可以设置
cpu.shares 来控制其相对于兄弟组的CPU时间分配比例。
3.3 sched_entity:调度实体
sched_entity 是CFS调度的基本单位,它代表一个可以被调度的对象——可以是一个普通进程,也可以是一个任务组。
struct sched_entity {
struct load_weight load; // 调度实体的负载权重
struct rb_node run_node; // 红黑树节点
struct list_head group_node; // 组内链表节点
unsigned int on_rq; // 是否在运行队列上
u64 vruntime; // 虚拟运行时间
u64 prev_sum_exec_runtime; // 上次切换时的总执行时间
u64 sum_exec_runtime; // 总执行时间
struct sched_entity *parent; // 父调度实体(组调度时使用)
struct cfs_rq *cfs_rq; // 该实体所属的CFS运行队列
struct cfs_rq *my_q; // 若该实体代表一个组,指向组的cfs_rq
};
关键区分:
cfs_rq 和 my_q 是理解组调度的关键。cfs_rq 指向该实体被调度到的队列(父队列),而 my_q 仅当该实体代表一个任务组时才有效,指向该组自己的运行队列。
3.4 三者的关联关系
下图展示了在一个支持组调度的系统中,cfs_rq、task_group 和 sched_entity 之间的核心关联:
3.5 关联机制详解
三者的关联通过以下机制实现分层调度:
- 实体入队: 当一个进程(sched_entity)被唤醒时,它被插入到其所属
cfs_rq的红黑树中。如果该进程属于某个任务组,则插入到该组的cfs_rq中。 - 组实体调度: 当调度器选择了一个代表任务组的
sched_entity时,它会通过my_q找到该组的cfs_rq,然后在该组的红黑树中选择vruntime最小的进程执行。 - vruntime传递: 组实体的vruntime更新受其内部进程执行时间的影响,同时组实体本身的vruntime又参与父队列的调度决策,形成时间资源的递归分配。
- 负载传播: 组内进程的负载变化会通过
task_group的层级结构向上传播,影响父队列的负载均衡决策。
工程要点: 在组调度场景下,一个CPU上可能同时存在多个层级的
cfs_rq。调度器从根 cfs_rq 开始,逐层选择最优实体,直到选中一个普通进程。这种“递归选择”机制保证了组间和组内的公平性。
3.6 数据结构关系总结
| 组件 | 角色 | 关键关联字段 |
|---|---|---|
| cfs_rq | per-CPU/ per-group的运行队列 | tg → task_group;红黑树管理sched_entity |
| task_group | 任务分组与资源控制 | se[] → 每个CPU的组实体;cfs_rq[] → 每个CPU的组队列 |
| sched_entity | 可调度单位(进程或组) | cfs_rq → 所属队列;my_q → 若为组,指向组队列 |
理解这三者的关系,是后续配置组调度、进行CPU资源隔离和性能调优的前提。在实际工程中,通过 /sys/fs/cgroup/cpu/ 接口操作cgroup时,内核正是在动态创建和管理这些数据结构实例。