2. 调度实体与调度类:struct sched_entity 结构体、调度类(sched_class)接口、stop_sched_class与dl_sched_class
在 Linux 内核的 CFS 调度框架中,调度实体 (Scheduling Entity) 和 调度类 (Scheduling Class) 是两个核心抽象。调度实体是内核可调度的最小单元,而调度类则定义了不同调度策略的通用接口。理解这两者的关系,是掌握进程调度调优的基础。
2.1 调度实体:struct sched_entity
struct sched_entity 是 CFS 调度器用于管理可运行进程(或进程组)的核心数据结构。它不直接对应一个进程的 task_struct,而是嵌入在其中,专门供 CFS 使用。
该结构体主要包含以下关键字段:
| 字段名 | 类型 | 作用描述 |
|---|---|---|
load |
struct load_weight |
调度实体的权重,决定了其获得 CPU 时间的比例。权重越高,获得的 CPU 时间越多。 |
run_node |
struct rb_node |
用于将调度实体插入 CFS 运行队列的红黑树节点。CFS 通过红黑树按 vruntime 排序。 |
on_rq |
unsigned int |
标志位,指示该调度实体当前是否在运行队列上。 |
vruntime |
u64 |
虚拟运行时间。这是 CFS 调度的核心指标,用于记录实体在“理想多核处理器”上运行的时间。CFS 总是选择 vruntime 最小的实体来运行。 |
exec_start |
u64 |
当前实体开始执行的时间戳,用于计算本次运行的时间片。 |
sum_exec_runtime |
u64 |
该实体在 CPU 上实际执行的总时间(纳秒级)。 |
prev_sum_exec_runtime |
u64 |
上次被切换出 CPU 时的 sum_exec_runtime 快照,用于计算本次运行时间。 |
depth |
int |
在组调度层级中的深度。根调度实体深度为 0,其子实体深度为 1,以此类推。 |
parent |
struct cfs_rq * |
指向该实体所属的 CFS 运行队列(cfs_rq)的指针。 |
my_q |
struct cfs_rq * |
如果该实体是一个任务组(task group),则 my_q 指向该组自己的 CFS 运行队列。 |
vruntime 是 CFS 调度的“罗盘”。在调优时,理解 vruntime 的计算方式(受 nice 值和权重影响)至关重要。通过调整进程的 nice 值,可以间接改变其调度实体的权重,从而影响 CPU 时间分配。
2.2 调度类接口:sched_class
struct sched_class 是 Linux 内核调度框架的“多态”接口。它定义了一组函数指针,每个调度类(如 CFS、实时调度类、空闲调度类)都必须实现这些接口。调度核心代码通过调用这些统一的接口来执行调度操作,而无需关心底层具体的调度算法。
以下是 sched_class 中最重要的几个接口方法:
- enqueue_task:将调度实体加入运行队列。当进程变为可运行状态时调用。
- dequeue_task:将调度实体从运行队列中移除。当进程阻塞或退出时调用。
- yield_task:进程主动让出 CPU。
- check_preempt_curr:检查新唤醒的进程是否可以抢占当前正在运行的进程。
- pick_next_task:从运行队列中选择下一个要运行的调度实体。这是调度器的核心决策点。
- put_prev_task:将当前正在运行的进程放回运行队列。
- set_curr_task:当进程被选中运行时,更新调度器的内部状态。
- task_tick:周期性时钟中断处理函数。CFS 在此函数中更新
vruntime并检查是否需要调度。 - update_curr:更新当前运行进程的运行时统计信息。
下图展示了调度类接口在调度决策流程中的核心位置:
2.3 stop_sched_class 与 dl_sched_class
在 Linux 内核中,调度类按照优先级从高到低排列。其中 stop_sched_class 和 dl_sched_class 是两个特殊且优先级极高的调度类。
2.3.1 stop_sched_class(停止调度类)
stop_sched_class 是系统中优先级最高的调度类。它用于执行一些需要独占 CPU 且不能被中断的特殊任务,例如 migration 线程(用于在 CPU 之间迁移进程)和 stop 线程。
- 特性: 该调度类中的线程一旦被选中,将一直运行直到主动完成或放弃 CPU。它不会被任何其他调度类抢占。
- 典型用途: CPU 热插拔、进程迁移、内核调试中的单步执行。
- 调优影响: 普通调优场景下无需直接干预。但理解其存在有助于解释为什么在某些极端负载下,某些 CPU 核心会短暂“卡住”——这通常是
stop线程在执行关键操作。
2.3.2 dl_sched_class(Deadline 调度类)
dl_sched_class 实现了 SCHED_DEADLINE 策略,基于 Earliest Deadline First (EDF) 算法。它为实时任务提供了比普通实时调度类(rt_sched_class)更精确的时序保证。
| 特性 | 描述 |
|---|---|
| 调度算法 | Earliest Deadline First (EDF) + CBS (Constant Bandwidth Server) |
| 核心参数 | sched_runtime(运行时间)、sched_deadline(截止时间)、sched_period(周期) |
| 优先级 | 高于 rt_sched_class,低于 stop_sched_class |
| 适用场景 | 音视频编解码、工业控制、自动驾驶等对延迟有严格上限要求的实时任务 |
| 带宽隔离 | CBS 机制确保每个 Deadline 任务不会消耗超过其声明的 CPU 带宽,保护其他任务 |
dl_sched_class 需要非常谨慎。如果配置了过多的 Deadline 任务或设置了过大的 sched_runtime,可能会导致系统总 CPU 带宽超过 100%,从而造成系统不稳定甚至死锁。内核提供了 sched_dl_runtime_us 和 sched_dl_period_us 等 cgroup 参数来限制总带宽。
下图展示了 Linux 内核中所有调度类的优先级层次结构:
/proc/sched_debug 或 ftrace 查看当前系统中各个调度类的运行情况。如果发现 stop 或 dl 调度类占用了过多的 CPU 时间,通常意味着系统存在异常(如频繁的 CPU 热插拔操作或 Deadline 任务配置不当),需要进一步排查。