kernel/sched 调度机制与原理详解

kernel/sched 调度机制与原理详解

源码路径rk3588/kernel-6.1/kernel/sched/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE)
文档目录linuxDoc/kernel/sched/

该目录实现 Linux 6.1 的 通用多策略调度框架。所有策略通过 struct sched_class 虚函数表接入统一入口 __schedule(),在 RK3588 上额外叠加 big.LITTLE、EAS、Rockchip 性能档位等机制。


目录


一、源码目录结构

1.1 编译单元(Makefile)

为平衡编译时间,源码拆成 4 个 .o 文件:

编译单元 包含内容 规模
core.o 调度核心:__schedule、唤醒、迁移、上下文切换 ~11,293 行
fair.o CFS 公平调度、负载均衡、EAS ~12,520 行
build_policy.o idle / rt / deadline / pelt / cputime ~8,000 行
build_utility.o topology / psi / cpufreq / wait / debug 等 ~8,000 行

1.2 主要源文件

文件 功能
core.c 调度主入口、任务唤醒/阻塞、上下文切换、迁移
fair.c CFS 完全公平调度、负载均衡、EAS
rt.c 实时调度 SCHED_FIFO / SCHED_RR
deadline.c Deadline 调度 SCHED_DEADLINE(EDF + CBS)
idle.c per-CPU idle 线程
stop_task.c 最高优先级 stop 任务
pelt.c PELT 负载跟踪
topology.c 调度域拓扑、EAS 初始化
cpufreq_schedutil.c schedutil CPU 调频 governor
sched.h 调度器内部类型与 inline 方法
core_sched.c SMT 核心调度(cookie 机制)
psi.c Pressure Stall Information
clock.c 调度器时钟 sched_clock()
loadavg.c /proc/loadavg 计算
wait.c / completion.c 等待队列、完成量

二、调度方法总览

Linux 调度分为两层概念:

  1. 用户可见调度策略sched_setscheduler() / sched_attr.policy):进程属于哪种策略。
  2. 内核调度类sched_class):策略在 kernel/sched 中的实现入口,按优先级链式选取。

2.0 用户策略 ↔ 调度类对照表

策略常量 数值 调度类 源文件 调度算法 典型用途
SCHED_OTHER / SCHED_NORMAL 0 fair (CFS) fair.c vruntime 红黑树 + 权重 普通用户进程(默认)
SCHED_FIFO 1 rt rt.c 100 级优先级 FIFO 队列 软实时,同优先级跑到底
SCHED_RR 2 rt rt.c 同优先级时间片轮转 软实时,同优先级公平轮转
SCHED_BATCH 3 fair (CFS) fair.c CFS + batch 提示 批处理,减少唤醒抢占
SCHED_IDLE 5 fair (CFS) fair.c CFS 极低权重 低优先级后台任务
SCHED_DEADLINE 6 deadline (dl) deadline.c EDF + CBS 硬实时周期任务
(无用户策略) stop stop_task.c 每 CPU 唯一 stop 任务 stop_machine 等内核关键路径
(无用户策略) idle idle.c 最低优先级 idle 线程 CPU 空闲时进入 cpuidle

注意区分SCHED_IDLE 策略任务仍在 CFS(fair.c)中调度;idle 线程是每 CPU 的内核线程,使用 idle_sched_classidle.c),两者完全不同。

2.0.1 调度类优先级链(选任务顺序)

链接器通过 __sched_class_highest__sched_class_lowest 将调度类串成链表,从高到低遍历:

1
stop → deadline → rt → fair → idle

源码(sched.h):

1
2
3
4
5
extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;

抢占规则:高调度类任务存在时,低调度类任务无法获得 CPU。例如 RT 队列非空时 CFS 任务不会运行;CFS/RT/DL 都空时才运行 idle 线程。

2.1 五大调度类(按优先级从高到低)

调度类 源文件 策略 用途
stop stop_task.c 内核 stop 任务 CPU 热插拔、迁移等,绝对最高优先级
deadline (dl) deadline.c SCHED_DEADLINE 硬实时,EDF + CBS 带宽控制
rt rt.c SCHED_FIFO / SCHED_RR 软实时,静态优先级 0–99
fair (CFS) fair.c SCHED_NORMAL / SCHED_BATCH / SCHED_IDLE 普通进程,完全公平调度
idle idle.c per-CPU idle 线程 CPU 空闲时进入 cpuidle 省电

优先级遍历逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// core.c: __pick_next_task()
static inline struct task_struct *
__pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
// 快速路径:若全是 CFS 任务,直接 pick_next_task_fair
if (likely(!sched_class_above(prev->sched_class, &fair_sched_class) &&
rq->nr_running == rq->cfs.h_nr_running)) {
p = pick_next_task_fair(rq, prev, rf);
// ...
}
// 通用路径:从高到低遍历所有调度类
for_each_class(class) {
p = class->pick_next_task(rq);
if (p) return p;
}
}

2.2 辅助/联动机制

机制 文件 作用
PELT 负载跟踪 pelt.c 指数衰减估算 CPU 利用率
拓扑与负载均衡 topology.c + fair.c 构建 sched_domain,跨 CPU 迁移任务
EAS 能耗感知调度 fair.c + topology.c big.LITTLE 上按能耗选 CPU
schedutil 调频 cpufreq_schedutil.c 根据 PELT 利用率调节 CPU 频率
uclamp 利用率钳制 core.c + fair.c 限制任务最低/最高 CPU 利用率
PSI 压力监控 psi.c CPU/内存/IO 压力 stall 信息
CFS 带宽控制 fair.c cgroup CPU 配额 throttle
RT 带宽控制 rt.c RT 任务最多占用 95% CPU
核心调度 SMT core_sched.c 同物理核超线程任务的 cookie 协同
Rockchip 性能档位 rockchip_performance.c 低/中/高性能模式,影响 uclamp 与 RT 选核

三、sched_class 调度类接口

各调度策略实现 struct sched_class 虚函数表,由 __pick_next_task() 按优先级遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// sched.h
struct sched_class {
/* 将任务 @p 加入运行队列 @rq;@flags 为 ENQUEUE_* */
void (*enqueue_task)(struct rq *rq, struct task_struct *p, int flags);
/* 将任务 @p 从运行队列 @rq 移除;@flags 为 DEQUEUE_* */
void (*dequeue_task)(struct rq *rq, struct task_struct *p, int flags);
/* 主动让出 CPU(如 sched_yield) */
void (*yield_task)(struct rq *rq);

/* 判断唤醒/入队的 @p 是否应抢占 rq->curr */
void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);

/* 在本调度类中选取 @rq 上优先级最高的可运行任务 */
struct task_struct *(*pick_next_task)(struct rq *rq);

/* rq->curr 即将停止运行时的统计更新(上下文切换前) */
void (*put_prev_task)(struct rq *rq, struct task_struct *p);
/* 准备 @p 作为 rq->curr 运行(选中后、context_switch 前) */
void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first);

#ifdef CONFIG_SMP
/* 周期性或空闲时负载均衡 */
int (*balance)(struct rq *rq, struct task_struct *prev, struct rq_flags *rf);
/* 为任务 @p 选择最合适的 CPU(唤醒、fork 或迁移时) */
int (*select_task_rq)(struct task_struct *p, int task_cpu, int flags);
/* 轻量级选任务,仅用于核心调度 CONFIG_SCHED_CORE */
struct task_struct *(*pick_task)(struct rq *rq);
/* 通知调度类:@p 迁移后运行队列已变为 @new_cpu */
void (*migrate_task_rq)(struct task_struct *p, int new_cpu);
#endif

/* 运行任务的定时器 tick 处理 */
void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
/* fork 时初始化调度状态 */
void (*task_fork)(struct task_struct *p);
/* 进程退出时清理调度状态 */
void (*task_dead)(struct task_struct *p);

/* 任务切换调度类或优先级时的钩子 */
void (*switched_from)(struct rq *this_rq, struct task_struct *task);
void (*switched_to)(struct rq *this_rq, struct task_struct *task);
void (*prio_changed)(struct rq *this_rq, struct task_struct *task, int oldprio);

/* 更新 rq->curr 的运行时间统计(vruntime、dl runtime 等) */
void (*update_curr)(struct rq *rq);
};

各调度类的实现注册:

调度类 注册变量 源文件
stop stop_sched_class stop_task.c
deadline dl_sched_class deadline.c
rt rt_sched_class rt.c
fair fair_sched_class fair.c
idle idle_sched_class idle.c

四、统一调度框架原理

4.1 核心数据结构

每个 CPU 一个运行队列 struct rq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct rq {
raw_spinlock_t __lock; // 运行队列锁
task_struct *curr; // 当前运行任务
unsigned int nr_running; // 可运行任务数

cfs_rq cfs; // CFS 公平调度队列
rt_rq rt; // 实时调度队列
dl_rq dl; // Deadline 调度队列

u64 clock; // 调度器时钟
u64 clock_pelt; // PELT 专用时钟
sched_domain *sd; // 调度域(负载均衡范围)
root_domain *rd; // 根域(SMP 全局状态)
};

每个任务的 task_struct->sched_class 指向其所属调度类的 vtable。

4.2 调度主流程 __schedule()

触发调度__schedulerq_lock 加锁update_rq_clock 更新时钟prev 是否阻塞?deactivate_task 移出 rq继续pick_next_task 选下一个任务prev != next?context_switch 上下文切换解锁返回switch_to 汇编切换寄存器/栈

关键代码路径(core.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void __sched notrace __schedule(unsigned int sched_mode)
{
rq = cpu_rq(smp_processor_id());
prev = rq->curr;

rq_lock(rq, &rf);
update_rq_clock(rq);

// 非抢占路径:任务主动阻塞
if (!(sched_mode & SM_MASK_PREEMPT) && prev_state)
deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);

next = pick_next_task(rq, prev, &rf);

if (likely(prev != next)) {
RCU_INIT_POINTER(rq->curr, next);
rq = context_switch(rq, prev, next, &rf); // MM 切换 + switch_to
}
}

4.3 调度触发时机

触发方式 机制 代码路径
主动阻塞 mutex、waitqueue、sleep schedule()__schedule(SM_NONE)
tick 抢占 定时器中断 scheduler_tick()entity_tick()resched_curr()
唤醒抢占 高优先级任务被唤醒 try_to_wake_up()check_preempt_curr()
主动让出 sched_yield() yield_task_fair()resched_curr()
内核抢占 中断/系统调用返回 检查 TIF_NEED_RESCHED 标志

4.4 任务生命周期:入队/出队

1
2
3
4
5
6
7
8
9
10
11
12
// core.c
void activate_task(struct rq *rq, struct task_struct *p, int flags)
{
enqueue_task(rq, p, flags); // 调用 sched_class->enqueue_task
p->on_rq = TASK_ON_RQ_QUEUED;
}

void deactivate_task(struct rq *rq, struct task_struct *p, int flags)
{
p->on_rq = (flags & DEQUEUE_SLEEP) ? 0 : TASK_ON_RQ_MIGRATING;
dequeue_task(rq, p, flags); // 调用 sched_class->dequeue_task
}

唤醒路径:

1
2
3
4
try_to_wake_up()
→ select_task_rq() 选 CPU
→ activate_task() 入队
→ check_preempt_curr() 判断是否抢占

跨调度类抢占(core.c):

1
2
3
4
5
6
7
void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags)
{
if (p->sched_class == rq->curr->sched_class)
rq->curr->sched_class->check_preempt_curr(rq, p, flags);
else if (sched_class_above(p->sched_class, rq->curr->sched_class))
resched_curr(rq); // 高优先级调度类直接抢占
}

五、各调度策略原理详解

算法级深入描述(公式、伪代码、流程图、函数对照)见:调度算法原理详解.md

5.1 CFS 完全公平调度(fair.c

CFS 是 SCHED_NORMAL(普通进程)和 SCHED_BATCH(批处理)的实现,占日常调度主体。

核心思想:虚拟运行时间 vruntime

每个任务维护一个 vruntime(虚拟运行时间)。CPU 时间按权重折算后累加到 vruntime:

1
vruntime += delta_exec × (NICE_0_LOAD / task_weight)
1
2
3
4
5
6
7
8
// fair.c: update_curr()
static void update_curr(struct cfs_rq *cfs_rq)
{
delta_exec = now - curr->exec_start;
curr->exec_start = now;
curr->vruntime += calc_delta_fair(delta_exec, curr); // 按权重折算
update_min_vruntime(cfs_rq);
}
  • nice 值越小(优先级越高)→ weight 越大 → 同样物理时间 vruntime 增长越慢 → 获得更多 CPU
  • nice 0 权重 1024,nice +19 约 15,nice -20 约 88761

红黑树选任务

所有可运行任务按 vruntime 排序存入 红黑树 cfs_rq.tasks_timeline

1
2
3
       [vruntime=100]
/ \
[vruntime=50] [vruntime=150]
  • 左子树 vruntime 最小 → 最”该运行”的任务
  • pick_next_entity() 取 leftmost 节点,同时考虑 buddy 机制

调度周期与时间片

CFS 没有固定时间片,而是按”调度周期”分配:

1
2
3
4
5
调度周期 = sched_latency(默认 6ms × (1+ilog(ncpus)))
每个任务时间片 = 周期 × (task_weight / 总weight)

若 nr_running > sched_nr_latency:
周期 = nr_running × sched_min_granularity // 防止时间片过小

关键 sysctl 参数:

参数 默认值 含义
sched_latency_ns 6ms × (1+ilog(ncpus)) 目标抢占延迟
sched_min_granularity_ns 0.75ms × (1+ilog(ncpus)) 最小抢占粒度
sched_wakeup_granularity_ns 1ms 唤醒抢占粒度

唤醒抢占粒度

新唤醒的任务是否立即抢占当前任务,取决于 vruntime 差距是否超过 wakeup_granularity

1
2
3
4
5
6
7
8
9
// fair.c: wakeup_preempt_entity()
static int wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
{
s64 vdiff = curr->vruntime - se->vruntime;
if (vdiff <= 0) return -1; // 当前任务更该运行
gran = wakeup_gran(se);
if (vdiff > gran) return 1; // 唤醒任务明显更该运行 → 抢占
return 0; // 差距不够 → 不抢占
}

这保证了 交互式任务(频繁 sleep/wake)比 CPU 密集型任务 获得更快响应。

place_entity:新任务/唤醒任务的 vruntime 放置

1
2
3
4
5
6
7
8
9
10
11
12
// fair.c: place_entity()
static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
vruntime = cfs_rq->min_vruntime;
if (initial && sched_feat(START_DEBIT))
vruntime += sched_vslice(cfs_rq, se); // 新任务:预扣一个时间片
if (!initial) {
// 唤醒任务:vruntime 减去 sleep 补偿(最多一个 latency)
vruntime -= thresh;
}
se->vruntime = max_vruntime(se->vruntime, vruntime);
}
  • 新 fork 的任务:vruntime = min_vruntime + 一个时间片(START_DEBIT),避免立即抢占
  • 唤醒的任务:vruntime 适当减小,补偿 sleep 期间”欠”的 CPU 时间

Buddy 机制(缓存局部性优化)

pick_next_entity() 在公平性允许范围内优先选 buddy:

1
2
3
4
5
6
7
// 优先级:next buddy > last buddy > leftmost
if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)
se = cfs_rq->next; // 刚被唤醒的任务(缓存热)
else if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
se = cfs_rq->last; // 刚被抢占的任务(缓存热)
else
se = left; // 标准:vruntime 最小

5.2 RT 实时调度(rt.c

数据结构

RT 任务按 静态优先级(0–99,数字越大优先级越高)组织:

1
2
rt_rq.active[]  —  100 个优先级数组,每个数组是一个链表
rt_rq.bitmap — 位图快速找到最高优先级非空数组

调度规则

  1. SCHED_FIFO:同优先级 FIFO,运行直到主动阻塞或被更高优先级抢占
  2. SCHED_RR:同优先级轮转,时间片 sched_rr_timeslice(默认 100ms)用完重新排队

抢占逻辑

1
2
3
4
5
6
// rt.c: check_preempt_curr_rt()
static void check_preempt_curr_rt(struct rq *rq, struct task_struct *p, int flags)
{
if (p->prio < rq->curr->prio) // 数字越小优先级越高
resched_curr(rq);
}

RT 任务 绝对优先于 CFS 任务:只要 RT 队列非空,CFS 任务无法运行。

RT 带宽控制

防止 RT 任务占满 CPU:

1
2
3
4
默认:sched_rt_period_us = 1,000,000(1 秒)
sched_rt_runtime_us = 950,000(950 毫秒)

RT 任务在一个周期内最多运行 950ms,剩余 50ms 留给 CFS

SMP 负载均衡

RT 任务过载时(一个 CPU 上有多个 RT 任务),通过 RT Push IPI 机制将任务推到其他 CPU 的 RT 队列。


5.3 Deadline 调度(deadline.c

算法:EDF + CBS

每个任务有三个参数:

  • runtime:每个周期内需要的 CPU 时间
  • period:周期长度
  • deadline:deadline = 当前时间 + period
1
2
任务参数示例:runtime=2ms, period=10ms, deadline=10ms
→ 每 10ms 内必须完成 2ms 的计算,deadline 为 10ms

数据结构

1
dl_rq.root  —  红黑树,按 deadline 排序(最早 deadline 优先)

CBS(Constant Bandwidth Server)带宽控制

  • 任务在一个 period 内 runtime 用完 → throttle(暂停运行)
  • 下一个 period 开始 → replenish(补充 runtime)
  • 超出 bandwidth 的任务被限速,不影响其他 deadline 任务

抢占规则

Deadline 任务的优先级 动态计算:deadline 越早,优先级越高(MAX_DL_PRIO - 1 - deadline)。


5.4 Idle 调度(idle.c

每个 CPU 有一个 idle 线程(内核线程,非用户进程),当没有其他可运行任务时运行:

  1. cpu_startup_entry()do_idle() 循环
  2. 调用 cpuidle_idle_call() 进入低功耗(WFI/WFE)
  3. 被 tick/中断唤醒后检查 need_resched,必要时 schedule_idle() 切到正常任务
  4. 优先级最低,由 idle_sched_class 选中

5.5 Stop 调度(stop_task.c

绝对最高优先级,供 stop_machine() 等内核机制使用:

  • 每个 rq 至多一个 stop 任务(rq->stop
  • 不迁移、不让出、不被抢占
  • pick_next_task_stop() 直接返回 rq->stop

5.6 辅助调度机制(非独立 sched_class)

机制 文件 原理
Autogroup autogroup.c 按 TTY 会话自动创建 task_group,同终端前台/后台 CFS 带宽隔离
Core Scheduling core_sched.c + core.c SMT sibling 上仅相同 core_cookie 任务可并行,防侧信道
CFS Bandwidth fair.c cgroup CPU quota,cfs_bandwidth throttle
RT Bandwidth rt.c 每周期 RT 最多跑 950ms(默认),防 RT 饿死 CFS
DL Bandwidth deadline.c root_domain 上 DL 总带宽准入控制
uclamp core.c + fair.c 限制任务 util 上下界,影响 EAS/选核/调频
Housekeeping isolation.c 隔离 CPU 仅跑内核 housekeeping 任务

Autogroup 原理

1
2
3
打开 TTY → sched_autogroup_create_attach() 创建 autogroup
fork → sched_autogroup_fork() 继承父 autogroup
每个 autogroup → 一个 task_group → CFS shares(nice 映射权重)

可通过 /proc/<pid>/autogroup 调整组内 nice;开关 /proc/sys/kernel/sched_autogroup_enabled

Core Scheduling 原理

1
2
3
task A (cookie=1)  ─┐
task B (cookie=1) ─┼─ 可同时在 SMT sibling 上运行
task C (cookie=2) ─┘─ 与 cookie=1 互斥,后者 forced idle

用户接口:prctl(PR_SCHED_CORE_*);选任务逻辑在 core.cCONFIG_SCHED_CORE 分支。


六、SMP 多核调度原理

6.1 PELT 负载跟踪(pelt.c

Per-Entity Load Tracking 用指数衰减几何级数估算历史负载:

1
load_avg ≈ load × y^n    (y^32 ≈ 0.5,约 32ms 半衰期)

每个 sched_entity 和每个 rq 维护三个平均值:

字段 含义 用途
load_avg 加权可运行负载 CFS 权重计算
runnable_avg 可运行任务数 负载均衡决策
util_avg CPU 利用率 schedutil 调频、EAS 能耗估算

PELT 在 update_load_avg() 中更新,触发点:enqueue、dequeue、tick、migration。

6.2 调度域与负载均衡

sched_domain 层次结构

topology.c 根据 CPU 拓扑构建多层调度域:

1
2
3
4
RK3588 示例:
DIE 域(8 CPUs,跨 cluster)
└── MC 域(4 CPUs,同 cluster 共享 L3)
└── CPU 域(1 CPU)

每层域有 SD_* 标志控制行为:

  • SD_LOAD_BALANCE:允许负载均衡
  • SD_ASYM_CPUCAPACITY:非对称 CPU 容量(A76 vs A55)
  • SD_SHARE_CPUCAPACITY:共享 L2/L3 cache

load_balance 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// fair.c: load_balance()
static int load_balance(int this_cpu, struct rq *this_rq,
struct sched_domain *sd, ...)
{
// 1. 判断是否需要均衡
if (!should_we_balance(&env)) goto out_balanced;

// 2. 找最忙的调度组
group = find_busiest_group(&env);

// 3. 找最忙的运行队列
busiest = find_busiest_queue(&env, group);

// 4. 从 busy 队列拉取任务到本地
cur_ld_moved = detach_tasks(&env); // 从 busiest 摘下
attach_tasks(&env); // 挂到 this_rq
}

触发时机:

  • 周期性scheduler_tick()trigger_load_balance()(默认每 4ms)
  • 空闲时newidle_balance() — CPU 即将 idle 前主动拉任务
  • 主动均衡:misfit 任务(负载超过 CPU 容量)触发 active_balance

Misfit 检测

当任务的 util_avg 超过当前 CPU 的 cpu_capacity 时,标记为 misfit

1
2
例:util_avg=800 的任务跑在 A55(capacity=512)上
→ misfit → 主动迁移到 A76(capacity=1024)

6.3 EAS 能耗感知调度(RK3588 关键)

RK3588 为 4×A76 + 4×A55 big.LITTLE,find_energy_efficient_cpu() 在唤醒时选最省电的 CPU:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// fair.c: find_energy_efficient_cpu()
static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
{
// 1. 检查 EAS 是否可用(Energy Model + schedutil + 非 overutilized)
pd = rcu_dereference(rd->pd);
if (!pd || rd->overutilized) goto unlock;

// 2. 遍历每个 performance domain(A76 域、A55 域)
for (; pd; pd = pd->next) {
// 3. 在每个域内找 spare capacity 最大的 CPU
// 4. 用 Energy Model 计算迁移前后的能耗差
base_energy = compute_energy(..., prev_cpu);
cur_delta = compute_energy(..., target) - base_energy;
// 5. 选能耗增量最小的 CPU
}
}

EAS 启用条件(topology.c):

  1. 系统有 Energy Model(EM)
  2. 使用 schedutil governor
  3. 存在 SD_ASYM_CPUCAPACITY 调度域
  4. EM 复杂度 < 2048(EM_MAX_COMPLEXITY

策略:cluster-packing(优先在同一 cluster 内分配)+ 大任务上大核

6.4 唤醒选 CPU 路径

1
2
3
4
5
6
try_to_wake_up()
└─ select_task_rq_fair()
├─ sync wake / affine 唤醒 → 原 CPU
├─ EAS 启用 → find_energy_efficient_cpu()
├─ 节能模式 → find_idlest_cpu()
└─ 默认 → find_idlest_group() + find_idlest_cpu()

七、调频联动(schedutil)

cpufreq_schedutil.c 将 PELT 利用率映射为 CPU 频率:

1
2
util = cpu_util_cfs() + cpu_util_rt() + cpu_util_dl()
freq = map_util_freq(util, max_freq, max_capacity)

Rockchip 定制:引入 target_load 参数(默认 80%),调整映射曲线:

1
2
3
4
5
6
// cpufreq_schedutil.c
#ifdef CONFIG_ARCH_ROCKCHIP
util = 100 * util / sg_policy->tunables->target_load;
#else
util = map_util_perf(util);
#endif

含义:当 CPU 利用率达到 80% 时就请求接近最高频率,提升响应速度。

可通过 sysfs 调节:/sys/devices/system/cpu/cpufreq/policyX/schedutil/target_load


八、完整调度时序

以普通进程 read() 阻塞与唤醒为例:

CPU 硬件__schedulefair.c (CFS)try_to_wake_up用户进程CPU 硬件__schedulefair.c (CFS)try_to_wake_up用户进程磁盘 IO 完成,中断唤醒中断返回 / preempt_enableread() 阻塞,schedule()deactivate_task (出队)pick_next_task_fairpick_next_entity (红黑树 leftmost)context_switch → idle 线程中断/工作队列select_task_rq_fair (EAS 选 CPU)enqueue_task_fair (入队)check_preempt_wakeup (是否抢占?)resched_curr (设置 TIF_NEED_RESCHED)__schedule (抢占)pick_next_task_faircontext_switch → 唤醒的进程继续执行 read() 返回

九、RK3588 平台特有机制

9.1 Rockchip 性能档位

源码:drivers/soc/rockchip/rockchip_performance.c
头文件:include/soc/rockchip/rockchip_performance.h
调度器侧通过 sched.h 引入,提供三档性能模式:

档位 名称 uclamp_min_rt RT 选核策略 场景
0 LOW 0 RT 倾向小核 (A55) 省电
1 NORMAL 1024 (满) 默认 平衡
2 HIGH 1024 (满) RT 倾向大核 (A76) 高性能

切换方式:module_param level=N 或对应 sysfs。

初始化时按 arch_scale_cpu_capacity() 划分大/小核 mask:

1
2
3
4
5
6
7
// rockchip_performance.c
for_each_possible_cpu(cpu) {
if (arch_scale_cpu_capacity(cpu) > cpub_min_cap)
cpumask_set_cpu(cpu, cpub_mask); // A76 大核
else
cpumask_set_cpu(cpu, cpul_mask); // A55 小核
}

提供的接口:

接口 作用
rockchip_perf_get_level() 获取当前性能档位
rockchip_perf_get_cpul_mask() 获取小核 cpumask
rockchip_perf_get_cpub_mask() 获取大核 cpumask
rockchip_perf_select_rt_cpu() RT 任务选 CPU
rockchip_perf_misfit_rt() 判断 RT 是否跑在错误核心
rockchip_perf_uclamp_sync_util_min_rt_default() 同步 uclamp 默认值

9.2 schedutil target_load

Rockchip 在 schedutil governor 中新增 target_load(默认 80),使 CPU 在 80% 利用率时即接近最高频,提升交互响应。

9.3 EAS + big.LITTLE

RK3588 调度适配要点:

  1. 非对称容量:A76 capacity ≈ 1024,A55 ≈ 512
  2. EAS 迁移find_energy_efficient_cpu() 比较迁移前后能耗
  3. Misfit 检测:高负载任务在小核上标记 misfit 并迁移到大核
  4. uclamp:限制任务最低/最高利用率,避免小核跑重负载

十、调试与观测接口

接口 内容
/proc/sched_debug 各 CPU rq、CFS 树、负载详情
/proc/schedstat 调度统计(迁移次数、唤醒延迟等)
trace/events/sched/ ftrace 调度事件
/sys/kernel/debug/sched/ debugfs 调度特性开关
/sys/kernel/sched_energy_aware EAS 开关
/proc/sys/kernel/sched_* 调度 sysctl 参数
module rockchip_performance level=N Rockchip 性能档位

常用 sysctl:

路径 含义
/proc/sys/kernel/sched_latency_ns CFS 调度周期
/proc/sys/kernel/sched_min_granularity_ns CFS 最小粒度
/proc/sys/kernel/sched_rt_runtime_us RT 带宽上限
/proc/sys/kernel/sched_energy_aware EAS 开关

十一、总结

调度原理核心要点

层次 原理 关键数据结构
框架层 sched_class vtable + per-CPU rq struct sched_class, struct rq
选任务 按调度类优先级遍历,类内按策略选 红黑树(CFS/DL)、优先级数组(RT)
公平性 vruntime 虚拟时间 + 权重 sched_entity.vruntime
抢占 唤醒粒度 + tick 检查 + 调度类优先级 wakeup_granularity, TIF_NEED_RESCHED
负载感知 PELT 指数衰减平均 sched_avg.util_avg
多核均衡 sched_domain 层次 + load_balance sched_domain, root_domain
能耗优化 EAS + Energy Model + schedutil perf_domain, em_perf_domain
带宽控制 CFS cgroup quota + RT/DL bandwidth cfs_bandwidth, rt_bandwidth, dl_bw

RK3588 调度数据流

任务生命周期调度决策调频联动平台定制任务创建 forkenqueue_task_fair更新 PELT 负载check_preempt_curr需要调度?__schedulepick_next_task_fairEAS 启用?find_energy_efficient_cpuload_balancesugov_update_singleschedutil 选频cpufreq 驱动rockchip_performance leveluclamp_min_rttarget_load=80

附录 A:源码文件与关键函数索引

源码中搜索 可定位全部中文注释。

A.1 按文件

文件 调度方法/职责 关键函数
core.c 统一框架 __schedule, try_to_wake_up, pick_next_task, context_switch, check_preempt_curr
fair.c CFS (NORMAL/BATCH/IDLE) enqueue_entity, pick_next_entity, check_preempt_wakeup, load_balance, select_task_rq_fair, find_energy_efficient_cpu
rt.c RT (FIFO/RR) enqueue_task_rt, pick_next_task_rt, check_preempt_curr_rt, _pick_next_task_rt
deadline.c DL (EDF+CBS) enqueue_task_dl, pick_next_task_dl, update_curr_dl, dl_task_offline_migration
idle.c idle 线程 do_idle, cpu_idle_poll, pick_next_task_idle
stop_task.c stop 任务 pick_next_task_stop
pelt.c 负载跟踪 ___update_load_sum, ___update_load_avg
topology.c 调度域 init_sched_domains, build_sched_domains
cpufreq_schedutil.c 调频 sugov_update_single, sugov_should_update_freq
autogroup.c 终端分组 sched_autogroup_create_attach, sched_autogroup_fork
core_sched.c SMT cookie sched_core_alloc_cookie, sched_core_free
cpupri.c RT 迁移 cpupri_find, cpupri_set
cpudeadline.c DL 迁移 cpudl_find, cpudl_set
sched.h 内部类型 struct sched_class, struct rq, struct cfs_rq

A.2 核心调用链

阻塞 → 唤醒 → 抢占 → 切换

1
2
3
4
5
6
7
8
9
10
11
12
13
schedule() / preempt
└─ __schedule() [core.c:6569]
├─ deactivate_task() 睡眠出队
├─ pick_next_task() [core.c:6039]
│ └─ __pick_next_task() [core.c:5957]
│ ├─ 快速路径: pick_next_task_fair()
│ └─ 慢路径: for_each_class → pick_next_task
└─ context_switch() MM + switch_to

try_to_wake_up() [core.c]
├─ select_task_rq() 各 class->select_task_rq
├─ activate_task() → enqueue_task()
└─ check_preempt_curr() 可能 resched_curr()

Tick 驱动

1
2
3
4
5
6
7
timer interrupt
└─ scheduler_tick() [core.c]
├─ curr->sched_class->task_tick()
│ ├─ entity_tick() [fair.c] CFS 时间片检查
│ ├─ task_tick_rt() [rt.c] RR 时间片
│ └─ task_tick_dl() [deadline.c]
└─ trigger_load_balance() [fair.c] 周期性负载均衡

A.3 编译单元(Makefile 拆分)

.o 文件 包含源文件
core.o core.c, clock.c, completion.c, cpuacct.c, cputime.c, loadavg.c, stats.c
fair.o fair.c
build_policy.o idle.c, rt.c, deadline.c, pelt.c, stop_task.c
build_utility.o topology.c, psi.c, cpufreq*.c, wait.c, debug.c, autogroup.c

文档基于 RK3588 / Linux 6.1 内核 kernel/sched 源码整理。

文章互动

阅读 --

留言

0 条留言

正在加载留言…