4. 调度实体与调度类:task_struct中的调度字段、sched_entity结构体、调度类(sched_class)接口与层次结构

Linux内核的进程调度并非直接操作庞大的 task_struct,而是通过抽象层将调度逻辑与进程描述解耦。本章深入剖析调度实体(sched_entity)与调度类(sched_class)的设计,揭示CFS调度器如何通过这两层结构实现高效、可扩展的调度决策。

4.1 task_struct中的调度相关字段

每个进程的 task_struct 中包含一组关键的调度字段,它们是调度器与进程交互的入口。下表列出了核心字段及其作用:

字段名 类型 说明
prio int 动态优先级,用于实时进程和CFS的nice值映射
static_prio int 静态优先级,由nice值计算得出(范围100~139)
normal_prio int 常规优先级,基于静态优先级和调度策略调整
rt_priority unsigned int 实时优先级(0~99),仅用于SCHED_FIFO/SCHED_RR
sched_class const struct sched_class * 指向该进程所属调度类的指针
se struct sched_entity CFS调度实体,嵌入在task_struct中
rt struct sched_rt_entity 实时调度实体,用于RT调度类
dl struct sched_dl_entity Deadline调度实体,用于SCHED_DEADLINE
policy unsigned int 调度策略(如SCHED_OTHER、SCHED_FIFO等)
nr_cpus_allowed int 允许运行的CPU数量
cpus_allowed struct cpumask CPU亲和性掩码
关键设计点: 每个进程同时包含CFS、RT、DL三种调度实体,但同一时刻只有一个调度类处于活跃状态。调度器通过 sched_class 指针动态切换调度行为。

4.2 sched_entity结构体:CFS的调度单元

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                     exec_start;     /* 当前调度周期开始时间 */
    u64                     sum_exec_runtime; /* 总执行时间 */
    u64                     vruntime;       /* 虚拟运行时间(核心指标) */
    u64                     prev_sum_exec_runtime; /* 上次调度时的总执行时间 */

    u64                     nr_migrations;  /* 迁移次数 */
    struct sched_statistics statistics;     /* 调度统计信息 */

#ifdef CONFIG_FAIR_GROUP_SCHED
    struct sched_entity     *parent;        /* 父调度实体(组调度层级) */
    struct cfs_rq           *cfs_rq;        /* 所属CFS就绪队列 */
    struct cfs_rq           *my_q;          /* 子CFS就绪队列(仅对组有效) */
#endif
};
vruntime 的核心地位: CFS调度器通过维护每个调度实体的 vruntime 值,并选择红黑树中最小的节点作为下一个运行进程,从而实现“完全公平”。vruntime的增长速率受进程权重影响:权重越高,vruntime增长越慢,获得更多CPU时间。

4.3 调度类(sched_class)接口与层次结构

调度类是Linux内核调度子系统实现多态的核心机制。每个调度类实现一组标准接口,内核通过 sched_class 指针统一调用。下图展示了调度类的层次结构与核心接口:

调度类(sched_class)层次结构与核心接口 核心调度器 sched_class 指针动态选择 stop_sched_class 停止调度类(最高优先级) dl_sched_class Deadline调度类 rt_sched_class 实时调度类 fair_sched_class CFS完全公平调度类 sched_class 核心接口方法 • enqueue_task / dequeue_task — 入队/出队 • pick_next_task — 选择下一个运行进程 • put_prev_task — 将当前进程放回就绪队列 • task_tick — 周期性调度时钟滴答 • set_curr_task — 设置当前运行进程 • yield_task — 主动让出CPU • check_preempt_curr — 检查是否可抢占当前进程

4.4 调度类优先级与选择顺序

调度类按照严格的优先级顺序进行选择。内核在 __pick_next_task 中按以下顺序遍历调度类:

  1. stop_sched_class — 停止调度类,用于CPU停止操作,优先级最高
  2. dl_sched_class — Deadline调度类,处理SCHED_DEADLINE策略
  3. rt_sched_class — 实时调度类,处理SCHED_FIFO和SCHED_RR
  4. fair_sched_class — CFS调度类,处理SCHED_OTHER、SCHED_BATCH、SCHED_IDLE
  5. idle_sched_class — 空闲调度类,当没有其他进程可运行时执行
重要约束: 实时进程(rt_sched_class)和Deadline进程(dl_sched_class)会无条件抢占CFS进程。如果系统中存在大量实时进程,CFS进程可能长时间得不到CPU资源。这是设计上的硬实时保证,也是调优时需要关注的风险点。

4.5 调度类接口方法详解

每个调度类必须实现一组标准操作接口,内核通过函数指针表统一调用。下表列出了核心接口方法及其语义:

接口方法 参数 功能描述
enqueue_task rq, p, flags 将进程p加入就绪队列rq,flags控制唤醒/迁移等行为
dequeue_task rq, p, flags 将进程p从就绪队列rq中移除
pick_next_task rq, p, prev 选择下一个要运行的进程,返回选中的task_struct指针
put_prev_task rq, p 当前进程p被切换出去前的回调,更新统计信息
task_tick rq, p, queued 周期性时钟中断触发,检查是否需要抢占或重新调度
set_curr_task rq, p 当进程p被选为当前运行进程时调用,初始化调度上下文
yield_task rq, p 进程主动让出CPU,调度类决定如何重新排队
check_preempt_curr rq, p, flags 检查新唤醒的进程p是否可以抢占当前运行进程
switched_from rq, p 进程从当前调度类切换到其他调度类时的清理操作
switched_to rq, p 进程切换到当前调度类时的初始化操作

4.6 CFS调度类的核心实现路径

以CFS调度类(fair_sched_class)为例,其核心操作路径展示了调度实体与调度类的协作方式:

// 内核源码:kernel/sched/fair.c

// CFS调度类实例定义
const struct sched_class fair_sched_class = {
    .next           = &idle_sched_class,        // 指向下一个优先级更低的调度类
    .enqueue_task   = enqueue_task_fair,
    .dequeue_task   = dequeue_task_fair,
    .pick_next_task = pick_next_task_fair,
    .put_prev_task  = put_prev_task_fair,
    .task_tick      = task_tick_fair,
    .set_curr_task  = set_curr_task_fair,
    .yield_task     = yield_task_fair,
    .check_preempt_curr = check_preempt_curr_fair,
    // ... 其他接口
};

// pick_next_task_fair 核心逻辑(简化)
static struct task_struct *
pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
    struct cfs_rq *cfs_rq = &rq->cfs;
    struct sched_entity *se;
    struct task_struct *p;

    // 从CFS红黑树中选取vruntime最小的调度实体
    se = pick_next_entity(cfs_rq, NULL);
    if (!se)
        return NULL;

    // 将调度实体转换为task_struct
    p = task_of(se);
    return p;
}
设计模式洞察: 调度类采用“策略模式”(Strategy Pattern),sched_class 结构体相当于策略接口,每个调度类提供独立实现。核心调度器通过 sched_class 指针调用对应方法,无需关心具体调度算法细节。这种设计使得添加新的调度策略(如曾经的BFS、MuQSS)变得非常容易。

4.7 调度实体与调度类的协作流程

下图展示了从时钟中断触发到进程切换的完整协作流程:

调度实体与调度类协作流程(时钟中断触发) 1. 时钟中断 scheduler_tick() 2. 调用task_tick 当前进程的sched_class 3. 更新vruntime sched_entity->vruntime 4. 检查是否需要抢占 check_preempt_tick() 5. 设置TIF_NEED_RESCHED 标记需要重新调度 6. pick_next_task → 选择vruntime最小的se 从CFS红黑树中选取,执行上下文切换 关键数据结构 • rq->cfs (CFS就绪队列) • cfs_rq->tasks_timeline (红黑树根节点)
性能关键路径: 从时钟中断到进程切换的整个流程中,pick_next_task_fair 的红黑树查找操作是CFS调度器的核心性能热点。在拥有数千个进程的系统中,红黑树查找的O(log n)复杂度保证了调度延迟的可预测性。