4. 调度器核心数据结构:task_struct、sched_entity、rq、cfs_rq

Android 内核调度器的每一次决策——从选择下一个运行的线程到计算时间片——都依赖于一组精心设计的内核数据结构。这些数据结构构成了调度器的“骨架”和“血液”,理解它们是进行调度器调优和驱动响应时间控制的基石。本章将深入剖析四个最核心的数据结构:task_struct、sched_entity、rq 和 cfs_rq。

核心观点: 调度器本质上是一个“数据驱动”的状态机。所有调度策略、优先级计算、负载均衡最终都体现为对这些数据结构的读写与维护。调优的本质,就是理解并调整这些结构中的关键参数。

4.1 task_struct:进程的“身份证”与“档案袋”

task_struct 是 Linux 内核中最为庞大的数据结构之一,它描述了一个进程(或线程)的全部信息。对于调度器而言,它关注的是其中与调度相关的字段。

4.1.1 调度相关关键字段

字段名 类型 作用与调优意义
state volatile long 进程状态(TASK_RUNNING, TASK_INTERRUPTIBLE 等)。调度器只选择状态为 TASK_RUNNING 的进程。
prio int 动态优先级。调度器实际使用的优先级,范围 0~139(数值越小优先级越高)。
static_prio int 静态优先级。由 nice 值计算得出(nice -20 ~ 19 对应 100 ~ 139)。
normal_prio int 常规优先级。基于 static_prio 和调度策略计算。
rt_priority unsigned int 实时优先级。用于 SCHED_FIFO / SCHED_RR 策略,范围 0~99。
policy unsigned int 调度策略(SCHED_NORMAL, SCHED_FIFO, SCHED_EXT 等)。
se struct sched_entity CFS 调度实体。这是进程被 CFS 调度器管理时的“视角”。
rt struct sched_rt_entity 实时调度实体。用于实时调度策略。
dl struct sched_dl_entity Deadline 调度实体。用于 SCHED_DEADLINE 策略。
cpus_allowed cpumask_t CPU 亲和性掩码。限制进程可以在哪些 CPU 上运行。
调优视角: 在 Android 中,通过 sched_setattr() 系统调用修改 policy 和 prio 是控制驱动响应时间的核心手段。例如,将关键音频驱动线程设置为 SCHED_FIFO 并赋予高 rt_priority,可以确保其在竞争 CPU 时获得优先响应。

4.1.2 task_struct 与调度器交互的 SVG 结构图

task_struct 调度相关字段结构图 task_struct 调度相关字段结构图 task_struct state / prio / policy se (sched_entity) rt / dl 实体 vruntime load / sum_exec_runtime 说明: 1. task_struct 是调度器操作的基本单元,每个线程对应一个。 2. se/rt/dl 是不同调度类(CFS/RT/Deadline)的“视图”,同一进程可被不同调度类管理。 3. cpus_allowed 字段在 Android 中常用于将关键线程绑定到特定 CPU 核心(如大核)。

4.2 sched_entity:CFS 调度器的“调度单元”

sched_entity 是 CFS(完全公平调度器)的核心抽象。它不直接代表一个进程,而是代表一个可以被 CFS 调度的“实体”。一个进程可以包含一个 sched_entity(普通进程),一个 task group(如容器或 cgroup)也可以包含一个 sched_entity。

4.2.1 关键字段解析

字段 含义 对调优的影响
load 调度实体的负载权重。由进程优先级(nice 值)决定。 权重越高,获得的 CPU 时间比例越大。调整 nice 值直接影响此字段。
vruntime 虚拟运行时间。CFS 选择 vruntime 最小的实体运行。 这是 CFS 调度的“心脏”。通过调整 sched_min_granularity_ns 等参数可以影响 vruntime 的增长速度。
sum_exec_runtime 实体实际消耗的 CPU 总时间(纳秒)。 用于统计和负载计算。在 EAS(能耗感知调度)中用于预测未来负载。
prev_sum_exec_runtime 上次统计时的 sum_exec_runtime 快照。 用于计算一个周期内的 CPU 使用率。
nr_migrations 实体在 CPU 之间迁移的次数。 频繁迁移会导致缓存失效,影响性能。Android 中常通过 cpuset 减少迁移。

4.2.2 CFS 红黑树与 sched_entity 的关系

CFS 红黑树与 sched_entity 关系图 CFS 红黑树与 sched_entity 关系图 根节点 vruntime=100 vruntime=50 vruntime=80 vruntime=200 vruntime=150 vruntime=300 调度规则: CFS 始终选择红黑树中 vruntime 最小的节点(最左侧节点)作为下一个运行的实体。上例中,vruntime=50 的实体将最先被调度。

4.3 rq(运行队列):每个 CPU 的“调度中心”

rq(runqueue)是每个 CPU 核心独有的调度数据结构。它包含了该 CPU 上所有调度类的运行队列、负载信息以及调度统计。在 SMP(对称多处理)系统中,每个 CPU 都有一个独立的 rq。

4.3.1 rq 的核心组成

// 简化后的 struct rq 定义(kernel/sched/sched.h)
struct rq {
    raw_spinlock_t      lock;           // 保护 rq 的自旋锁
    unsigned int        nr_running;     // 当前 CPU 上可运行的任务数
    unsigned int        nr_switches;    // 上下文切换次数统计
    struct cfs_rq       cfs;            // CFS 调度类的运行队列
    struct rt_rq        rt;             // 实时调度类的运行队列
    struct dl_rq        dl;             // Deadline 调度类的运行队列
    struct task_struct  *curr;          // 当前正在运行的进程
    struct task_struct  *idle;          // 空闲进程(idle 线程)
    unsigned long       nr_uninterruptible; // 不可中断睡眠进程数
    u64                 clock;          // 当前 CPU 的时钟(用于时间计算)
    int                 cpu;            // 所属 CPU 编号
    struct callback_head  *balance_callback; // 负载均衡回调链表
};

4.3.2 rq 与调度类的关系

rq 与调度类关系架构图 rq 与调度类关系架构图 struct rq (CPU 0) nr_running=5, curr=task_A cfs_rq SCHED_NORMAL / SCHED_BATCH rt_rq SCHED_FIFO / SCHED_RR dl_rq SCHED_DEADLINE 调度优先级顺序(从高到低): 1. dl_rq (Deadline) — 最严格的截止时间要求,如视频编解码。 2. rt_rq (Real-Time) — 实时线程,如音频处理、触摸响应。 3. cfs_rq (CFS) — 普通线程,如应用 UI 线程、后台服务。 4. idle — 空闲线程,当所有其他队列为空时运行。

4.4 cfs_rq:CFS 调度类的“微观世界”

cfs_rq 是 CFS 调度器在单个 CPU 上的运行队列实现。它管理着所有属于 CFS 调度类的 sched_entity,并维护着红黑树、负载统计和调度参数。

4.4.1 cfs_rq 关键字段

字段 类型 作用
nr_running unsigned int 当前 cfs_rq 中可运行的调度实体数量。
load struct load_weight 所有可运行实体的总负载权重。用于计算 CPU 占用比例。
min_vruntime u64 红黑树中最小的 vruntime 值。新进程的 vruntime 会基于此值初始化。
tasks_timeline struct rb_root_cached 红黑树的根节点。所有可运行实体按 vruntime 排序。
curr struct sched_entity * 当前正在运行的调度实体(如果当前运行的是 CFS 任务)。
exec_clock u64 该 cfs_rq 上所有实体执行的总时间。
throttled int 是否被限流(CFS 带宽控制)。Android 中用于限制后台 cgroup 的 CPU 使用。

4.4.2 cfs_rq 内部工作流程

cfs_rq 内部工作流程图 cfs_rq 内部工作流程图 1. 选择下一个实体 2. 从红黑树取最左节点 3. 更新 curr 指针 4. 执行上下文切换 5. 更新 vruntime 并回插 循环调度 关键点:步骤 5 中,当前实体运行一段时间后,其 vruntime 会增加,然后被重新插入红黑树。如果它不再是 vruntime 最小的,调度器将在下一次 tick 或主动调度时选择新的最左节点。

4.5 数据结构关系总结

这四个数据结构构成了一个层次分明的调度体系:

  • task_struct 是进程的“本体”,包含所有调度策略和优先级信息。
  • sched_entity 是 CFS 调度器眼中的“进程”,通过红黑树组织。
  • rq 是每个 CPU 的“调度总控”,包含所有调度类的队列。
  • cfs_rq 是 rq 中 CFS 部分的“具体实现”,管理红黑树和负载。
常见误区: 初学者常误以为 sched_entity 和 task_struct 是一对一的关系。实际上,一个 task_group(如 Android 中的 top-app cgroup)也可以拥有自己的 sched_entity,从而实现组调度。在 Android 的 CPU 分组调优中,理解这一点至关重要。

4.5.1 数据结构关系总览表

数据结构 作用域 核心功能 与调优的直接关系
task_struct 每个进程/线程 描述进程所有属性 修改 policy/prio/cpus_allowed 控制调度行为
sched_entity 每个 CFS 任务或组 CFS 调度的基本单元 vruntime 决定调度顺序,load 决定 CPU 份额
rq 每个 CPU 一个 CPU 级别的调度总控 nr_running 反映 CPU 负载,用于负载均衡决策
cfs_rq 每个 CPU 的 CFS 部分 管理 CFS 实体的红黑树 min_vruntime 影响新任务的初始位置,可调参数影响调度延迟