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 结构图
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 的关系
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 与调度类的关系
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 内部工作流程
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 影响新任务的初始位置,可调参数影响调度延迟 |