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 性能档位等机制。
目录
- 一、源码目录结构
- 二、调度方法总览
- 三、sched_class 调度类接口
- 四、统一调度框架原理
- 五、各调度策略原理详解
- 六、SMP 多核调度原理
- 七、调频联动(schedutil)
- 八、完整调度时序
- 九、RK3588 平台特有机制
- 十、调试与观测接口
- 十一、总结
- 附录 A:源码文件与关键函数索引
- 延伸阅读:
- 调度算法原理详解.md — CFS/RT/DL 公式与算法流程
- 上下文切换与状态保存详解.md
一、源码目录结构
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 调度分为两层概念:
- 用户可见调度策略(
sched_setscheduler()/sched_attr.policy):进程属于哪种策略。 - 内核调度类(
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_class(idle.c),两者完全不同。
2.0.1 调度类优先级链(选任务顺序)
链接器通过 __sched_class_highest → __sched_class_lowest 将调度类串成链表,从高到低遍历:
1 | stop → deadline → rt → fair → idle |
源码(sched.h):
1 | extern const struct sched_class stop_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 | // core.c: __pick_next_task() |
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 | // sched.h |
各调度类的实现注册:
| 调度类 | 注册变量 | 源文件 |
|---|---|---|
| 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 | struct rq { |
每个任务的 task_struct->sched_class 指向其所属调度类的 vtable。
4.2 调度主流程 __schedule()
关键代码路径(core.c):
1 | static void __sched notrace __schedule(unsigned int sched_mode) |
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 | // core.c |
唤醒路径:
1 | try_to_wake_up() |
跨调度类抢占(core.c):
1 | void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags) |
五、各调度策略原理详解
算法级深入描述(公式、伪代码、流程图、函数对照)见:调度算法原理详解.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 | // fair.c: update_curr() |
- nice 值越小(优先级越高)→ weight 越大 → 同样物理时间 vruntime 增长越慢 → 获得更多 CPU
- nice 0 权重 1024,nice +19 约 15,nice -20 约 88761
红黑树选任务
所有可运行任务按 vruntime 排序存入 红黑树 cfs_rq.tasks_timeline:
1 | [vruntime=100] |
- 左子树 vruntime 最小 → 最”该运行”的任务
pick_next_entity()取 leftmost 节点,同时考虑 buddy 机制
调度周期与时间片
CFS 没有固定时间片,而是按”调度周期”分配:
1 | 调度周期 = sched_latency(默认 6ms × (1+ilog(ncpus))) |
关键 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 | // fair.c: wakeup_preempt_entity() |
这保证了 交互式任务(频繁 sleep/wake)比 CPU 密集型任务 获得更快响应。
place_entity:新任务/唤醒任务的 vruntime 放置
1 | // fair.c: place_entity() |
- 新 fork 的任务:vruntime = min_vruntime + 一个时间片(START_DEBIT),避免立即抢占
- 唤醒的任务:vruntime 适当减小,补偿 sleep 期间”欠”的 CPU 时间
Buddy 机制(缓存局部性优化)
pick_next_entity() 在公平性允许范围内优先选 buddy:
1 | // 优先级:next buddy > last buddy > leftmost |
5.2 RT 实时调度(rt.c)
数据结构
RT 任务按 静态优先级(0–99,数字越大优先级越高)组织:
1 | rt_rq.active[] — 100 个优先级数组,每个数组是一个链表 |
调度规则
- SCHED_FIFO:同优先级 FIFO,运行直到主动阻塞或被更高优先级抢占
- SCHED_RR:同优先级轮转,时间片
sched_rr_timeslice(默认 100ms)用完重新排队
抢占逻辑
1 | // rt.c: check_preempt_curr_rt() |
RT 任务 绝对优先于 CFS 任务:只要 RT 队列非空,CFS 任务无法运行。
RT 带宽控制
防止 RT 任务占满 CPU:
1 | 默认:sched_rt_period_us = 1,000,000(1 秒) |
SMP 负载均衡
RT 任务过载时(一个 CPU 上有多个 RT 任务),通过 RT Push IPI 机制将任务推到其他 CPU 的 RT 队列。
5.3 Deadline 调度(deadline.c)
算法:EDF + CBS
每个任务有三个参数:
- runtime:每个周期内需要的 CPU 时间
- period:周期长度
- deadline:deadline = 当前时间 + period
1 | 任务参数示例:runtime=2ms, period=10ms, 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 线程(内核线程,非用户进程),当没有其他可运行任务时运行:
cpu_startup_entry()→do_idle()循环- 调用
cpuidle_idle_call()进入低功耗(WFI/WFE) - 被 tick/中断唤醒后检查
need_resched,必要时schedule_idle()切到正常任务 - 优先级最低,由
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 | 打开 TTY → sched_autogroup_create_attach() 创建 autogroup |
可通过 /proc/<pid>/autogroup 调整组内 nice;开关 /proc/sys/kernel/sched_autogroup_enabled。
Core Scheduling 原理
1 | task A (cookie=1) ─┐ |
用户接口:prctl(PR_SCHED_CORE_*);选任务逻辑在 core.c 的 CONFIG_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 | RK3588 示例: |
每层域有 SD_* 标志控制行为:
SD_LOAD_BALANCE:允许负载均衡SD_ASYM_CPUCAPACITY:非对称 CPU 容量(A76 vs A55)SD_SHARE_CPUCAPACITY:共享 L2/L3 cache
load_balance 流程
1 | // fair.c: load_balance() |
触发时机:
- 周期性:
scheduler_tick()→trigger_load_balance()(默认每 4ms) - 空闲时:
newidle_balance()— CPU 即将 idle 前主动拉任务 - 主动均衡:misfit 任务(负载超过 CPU 容量)触发
active_balance
Misfit 检测
当任务的 util_avg 超过当前 CPU 的 cpu_capacity 时,标记为 misfit:
1 | 例:util_avg=800 的任务跑在 A55(capacity=512)上 |
6.3 EAS 能耗感知调度(RK3588 关键)
RK3588 为 4×A76 + 4×A55 big.LITTLE,find_energy_efficient_cpu() 在唤醒时选最省电的 CPU:
1 | // fair.c: find_energy_efficient_cpu() |
EAS 启用条件(topology.c):
- 系统有 Energy Model(EM)
- 使用 schedutil governor
- 存在
SD_ASYM_CPUCAPACITY调度域 - EM 复杂度 < 2048(
EM_MAX_COMPLEXITY)
策略:cluster-packing(优先在同一 cluster 内分配)+ 大任务上大核。
6.4 唤醒选 CPU 路径
1 | try_to_wake_up() |
七、调频联动(schedutil)
cpufreq_schedutil.c 将 PELT 利用率映射为 CPU 频率:
1 | util = cpu_util_cfs() + cpu_util_rt() + cpu_util_dl() |
Rockchip 定制:引入 target_load 参数(默认 80%),调整映射曲线:
1 | // cpufreq_schedutil.c |
含义:当 CPU 利用率达到 80% 时就请求接近最高频率,提升响应速度。
可通过 sysfs 调节:/sys/devices/system/cpu/cpufreq/policyX/schedutil/target_load
八、完整调度时序
以普通进程 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 | // rockchip_performance.c |
提供的接口:
| 接口 | 作用 |
|---|---|
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 调度适配要点:
- 非对称容量:A76 capacity ≈ 1024,A55 ≈ 512
- EAS 迁移:
find_energy_efficient_cpu()比较迁移前后能耗 - Misfit 检测:高负载任务在小核上标记 misfit 并迁移到大核
- 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 调度数据流
附录 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 | schedule() / preempt |
Tick 驱动
1 | timer interrupt |
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 源码整理。
正在加载留言…