3. 调度实体与调度组:task_struct与sched_entity结构体、调度组概念、组调度与层级结构
在CFS(完全公平调度器)中,调度的基本单位并非直接是进程,而是调度实体(Scheduling Entity, SE)。这一抽象层使得CFS能够统一调度单个任务和任务组,从而构建出灵活、可控制的层级调度结构。本章将深入剖析task_struct与sched_entity的关系、调度组的概念以及组调度如何实现资源隔离与共享。
3.1 task_struct与sched_entity结构体
每个进程在内核中由一个task_struct表示,它包含了进程的所有信息(PID、状态、内存空间、文件描述符等)。而调度相关的信息则封装在sched_entity结构体中,作为task_struct的一个成员存在。
task_struct 包含一个 sched_entity 实例,CFS调度器操作的是 sched_entity,而非直接操作 task_struct。
以下为sched_entity结构体中的关键字段(Linux 5.x 内核简化版):
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 nr_migrations; // 迁移次数
struct sched_avg avg; // PELT负载跟踪数据结构
};
关键字段解析:
- load:权重值,决定该实体在CFS红黑树中的位置和CPU时间分配比例。
- vruntime:虚拟运行时间,CFS通过比较各实体的vruntime来选择下一个运行的任务(vruntime最小的优先)。
- avg:PELT(Per-Entity Load Tracking)负载跟踪结构,用于计算该实体的历史负载贡献。
- on_rq:标记该实体是否已入队到CFS运行队列(rq)。
sched_entity 既可以代表一个普通线程,也可以代表一个任务组(通过 task_group 结构)。这是实现组调度的基础。
3.2 调度组概念与task_group结构
调度组(Scheduling Group)是CFS实现资源分组管理的核心机制。通过将多个任务放入一个组,可以对该组整体分配CPU时间,组内任务再按各自的权重竞争组内的时间配额。
调度组在内核中由task_group结构体表示:
struct task_group {
struct cgroup_subsys_state css; // cgroup子系统状态
struct sched_entity **se; // 每个CPU上的调度实体(组调度实体)
struct cfs_rq **cfs_rq; // 每个CPU上的CFS运行队列(组内队列)
struct sched_entity my_se; // 该组在父调度域中的调度实体
struct cfs_rq my_cfs_rq; // 该组在父调度域中的CFS运行队列
unsigned long shares; // 组权重(CPU份额)
// ... 其他字段
};
组调度的工作机制:
- 每个
task_group在每一个CPU上拥有一个独立的cfs_rq(组内运行队列)和一个sched_entity(组调度实体)。 - 组调度实体
my_se被挂入父级调度域的CFS红黑树中,参与父级的调度竞争。 - 组内的任务(或子组)则挂在组内的
cfs_rq上,竞争组分配到的CPU时间。
3.3 组调度与层级结构
CFS支持多层级的组调度结构,通常通过cgroup(控制组)来配置。用户可以创建嵌套的cgroup树,每一层对应一个task_group,形成从根组到叶子任务的树形调度层级。
以下是一个典型的层级调度结构示意图:
3.4 调度实体与运行队列的映射关系
理解调度实体(SE)与CFS运行队列(cfs_rq)之间的映射关系是掌握组调度的关键。每个CPU核心有一个根CFS运行队列,而每个task_group在每个CPU上也有一个独立的CFS运行队列。
| 组件 | 每个CPU实例数 | 作用 | 包含内容 |
|---|---|---|---|
| 根cfs_rq | 1 | 管理该CPU上所有根级调度实体 | 根组SE、普通任务SE、子组SE |
| 子组cfs_rq | 每个task_group在每个CPU上1个 | 管理该组内任务的调度 | 组内任务SE、子子组SE |
| 根组SE | 1(全局) | 代表根组参与调度竞争 | 权重为所有子组权重之和 |
| 子组SE | 每个task_group在每个CPU上1个 | 代表子组在父级cfs_rq中竞争 | 权重由shares参数决定 |
| 任务SE | 每个任务1个 | 代表单个线程参与调度 | 权重由nice值决定 |
3.5 组调度对PELT与调度延迟的影响
组调度引入了调度层级,这对PELT负载跟踪和调度延迟产生了直接影响:
- PELT负载聚合:每个
sched_entity的avg结构独立跟踪负载。子组SE的负载是其内部所有任务负载之和。根组SE的负载是所有子组SE负载之和。这种逐层聚合保证了负载信息的完整性。 - 调度延迟放大:当调度器选择了一个组SE后,需要递归进入该组的cfs_rq选择下一个SE。层级越深,调度决策路径越长,调度延迟(从触发调度到任务实际运行)可能增加。
- 时间片分配:组调度使得时间片分配变为两级甚至多级。父级按shares分配时间给组,组内再按权重分配时间给任务。这可能导致某些任务在短时间内获得的时间片不连续,影响PELT的负载衰减计算。
3.6 本章小结
调度实体(sched_entity)是CFS调度器的核心抽象,它将调度信息从进程描述中解耦,使得组调度成为可能。task_group结构通过在每个CPU上维护独立的cfs_rq和SE,实现了层级化的资源分组管理。这种层级结构在提供灵活的资源控制能力的同时,也增加了调度延迟的复杂性和PELT负载跟踪的层级聚合需求。理解这些基础结构是后续进行PELT与调度延迟联合优化的前提。