2. 调度实体与调度组:task_struct 中的调度字段、调度组(task_group)的概念、组调度与带宽控制
在 CFS 调度器的设计中,调度的基本单位并非直接是进程(task),而是调度实体(Scheduling Entity, sched_entity)。这一抽象层使得 CFS 能够统一调度单个进程和进程组,为组调度(Group Scheduling)和带宽控制(Bandwidth Control)奠定了数据结构基础。
2.1 task_struct 中的调度字段
每个进程在内核中对应一个 task_struct 结构体,其中包含了与 CFS 调度直接相关的关键字段。这些字段是调度器进行虚拟时间计算、入队出队、负载跟踪的依据。
| 字段名 | 类型 | 作用说明 |
|---|---|---|
se |
struct sched_entity |
CFS 调度实体,包含 vruntime、权重、负载等核心调度数据 |
rt |
struct sched_rt_entity |
实时调度实体(RT 类使用) |
dl |
struct sched_dl_entity |
Deadline 调度实体(DL 类使用) |
prio |
int |
动态优先级,影响 CFS 权重计算 |
static_prio |
int |
静态优先级,由 nice 值决定(-20 ~ 19) |
normal_prio |
int |
常规优先级,综合静态优先级和调度策略计算 |
sched_class |
const struct sched_class * |
指向该进程所属调度类(如 fair_sched_class) |
cpus_ptr |
const struct cpumask * |
进程可运行的 CPU 集合 |
其中 se(sched_entity)是 CFS 调度的核心,其内部结构如下:
struct sched_entity {
struct load_weight load; // 调度权重,由 nice 值映射
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; // 总执行时间
u64 nr_migrations; // 迁移次数
struct sched_avg avg; // PELT 负载跟踪数据
};
关键理解:
vruntime 是 CFS 选择下一个运行进程的唯一依据。调度器总是选择红黑树中 vruntime 最小的调度实体投入运行。权重(load.weight)决定了进程实际执行时间与虚拟时间的换算比例。
2.2 调度组(task_group)的概念
调度组(task_group)是 CFS 实现组调度的核心数据结构。它允许将多个进程组织成一个逻辑组,组内的所有进程共享一个 CPU 时间配额。组调度使得资源隔离和容器化成为可能。
每个 task_group 包含以下关键元素:
- 自己的调度实体:在父调度组或根任务组的红黑树中,该组以一个
sched_entity的身份参与调度。 - 自己的运行队列:组内包含一个 CFS 运行队列(
cfs_rq),用于管理组内进程的调度。 - 权重与配额:组可以设置
cpu.shares(权重)和cpu.cfs_quota_us/cpu.cfs_period_us(带宽限制)。
实际应用: 在 Kubernetes 或 Docker 容器环境中,每个容器对应一个
task_group。通过调整组的 cpu.shares,可以实现容器间的 CPU 时间比例分配,而 cpu.cfs_quota_us 则限制容器的 CPU 使用上限。
下图展示了根任务组与两个子调度组之间的层级关系:
2.3 组调度与带宽控制
组调度解决了资源隔离问题,而带宽控制则解决了资源上限问题。两者结合,构成了 Linux 容器 CPU 资源管理的完整方案。
2.3.1 组调度的核心机制
组调度的实现基于以下原理:
- 层级时间分配:调度器先按权重在根任务组和子组之间分配时间,子组再在其内部进程间分配时间。
- 嵌套红黑树:每个
cfs_rq维护自己的红黑树,组调度实体作为父级红黑树的节点。 - 权重传播:子组的权重会向上传播到父组,影响父组在更高层级中的时间分配。
性能注意事项: 过多的调度组层级(深度超过 3-4 层)会增加调度器的遍历开销。在生产环境中,建议保持扁平化的组结构,避免深度嵌套。
2.3.2 带宽控制参数
带宽控制通过 CGroup 的 CPU 子系统实现,主要参数如下:
| 参数文件 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cpu.cfs_period_us |
微秒 | 100000 (100ms) | 带宽控制周期 |
cpu.cfs_quota_us |
微秒 | -1 (无限制) | 每个周期内允许的运行时间上限 |
cpu.shares |
权重 | 1024 | CPU 时间相对权重(非硬限制) |
cpu.stat |
状态 | - | 显示带宽控制统计信息(如 throttled 次数) |
带宽控制的工作流程如下:
2.3.3 带宽控制配置示例
以下示例展示如何通过 CGroup 限制一个容器组使用 2 个 CPU 核心(200ms/100ms 周期):
# 创建 CGroup
mkdir -p /sys/fs/cgroup/cpu/my_container
# 设置带宽限制:每 100ms 周期最多运行 200ms(即 2 个 CPU 核心)
echo 200000 > /sys/fs/cgroup/cpu/my_container/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/my_container/cpu.cfs_period_us
# 设置权重(与其他组相比的相对比例)
echo 2048 > /sys/fs/cgroup/cpu/my_container/cpu.shares
# 将进程加入组
echo 1234 > /sys/fs/cgroup/cpu/my_container/cgroup.procs
# 查看带宽控制统计
cat /sys/fs/cgroup/cpu/my_container/cpu.stat
# 输出示例:
# nr_periods 0
# nr_throttled 0
# throttled_time 0
生产环境建议:
- 对于延迟敏感型服务(如数据库、Web 服务器),建议设置
cpu.cfs_quota_us为物理 CPU 核心数的 80%-90%,预留突发处理能力。 - 对于批处理任务,可以设置
cpu.shares较低(如 512),确保不抢占关键业务资源。 - 监控
cpu.stat中的nr_throttled值,如果频繁出现 throttled,说明配额设置过紧,需要调整。
2.4 调度实体与调度组的性能影响
理解调度实体和调度组的结构,对于性能调优至关重要:
| 场景 | 问题表现 | 调优方向 |
|---|---|---|
| 大量短生命周期进程 | 频繁创建/销毁调度实体,增加红黑树操作开销 | 使用线程池复用进程,减少调度实体数量 |
| 深度嵌套的 CGroup | 调度遍历路径变长,上下文切换延迟增加 | 保持组层级 ≤ 3 层,使用扁平结构 |
| 带宽配额设置过小 | 频繁 throttled,服务吞吐量下降 | 根据实际负载计算配额,预留 20% 缓冲 |
| 权重分配不合理 | 高优先级组得不到足够 CPU 时间 | 使用 cpu.shares 按业务重要性比例分配 |