sched discussion
[PATCH v10 00/12] sched, steal_governor: Introduce preferred CPUs and steal-driven vCPU backoff
LLM 分析
sched, steal_governor:基于 steal time 的 preferred CPU 与 vCPU 动态回缩
系列概况
- 标题:[PATCH v10 00/12] sched, steal_governor: Introduce preferred CPUs and steal-driven vCPU backoff
- 作者:Shrikanth Hegde sshegde@linux.ibm.com(IBM)
- 版本:v10(此前已迭代 v1–v9;本版基于 next-20260812,base commit f2c2ba7219e5)
- 规模:12 个 patch,覆盖核心调度器、cpumask 基础设施、sysfs ABI、文档,以及一个全新的 drivers/virt/steal_governor.c 模块
- 修改文件:kernel/sched/{core,fair,debug}.c、kernel/sched/sched.h、kernel/cpu.c、kernel/Kconfig.preempt、include/linux/{cpumask,kernel_stat,sched}.h、drivers/base/cpu.c、fs/proc/uptime.c、arch/s390/kernel/hiperdispatch.c、drivers/virt/{Kconfig,Makefile,steal_governor.c}、Documentation/scheduler/sched-paravirt.rst、Documentation/driver-api/steal-governor.rst、Documentation/ABI/testing/sysfs-devices-system-cpu、MAINTAINERS
- 代码统计:约 +390 行(drivers/virt/steal_governor.c ~290 行 + 调度器核心 ~100 行 + 文档/ABI/header)
- Message-ID:20260812054033.95658-1-sshegde@linux.ibm.com
- 完整性:完整线程;包含 cover letter、12 个 patch,以及 Yury Norov、Mete Durlu、Ionut Nechita、Dietmar Eggemann 多轮 review
补丁目的
针对 paravirt 重载场景(PowerVM SPLPAR、KVM、Xen 等)下多 VM 同时高负载导致的 pCPU 竞争与 vCPU 抢占问题,传统手段(CPU hotplug、isolated cpuset、显式 affinity)成本高且会破坏 userspace affinity 契约。
本系列的目标:
- 在调度器侧引入一个新的 CPU 状态 preferred(cpu_preferred_mask ⊆ cpu_active_mask),区分"可安全使用"与"仍在 active 但建议折叠"的 CPU。
- 在 wakeup、tick push、load balance 三处利用该 hint,让任务优先落在 preferred CPU 上,并主动把任务从非 preferred CPU 上推走。
- 在 virt 子系统新增可选模块 steal_governor(CONFIG_STEAL_GOVERNOR=m),按 steal time 高低阈值动态放大或缩小 preferred mask。
强约束:严格不破坏用户 affinity;pin 在 non-preferred 的任务不会被强制迁移;driver 是 policy 层、scheduler 是 mechanism 层,二者解耦。
旧流程的问题
- vCPU 抢占代价高:hypervisor 在 pCPU 紧张时抢占一个 vCPU,可能打断锁持有者或关中断区,整体 forward progress 退化。
- CPU hotplug / isolated cpuset 太重:要 topology rebuild,破坏 userspace affinity;显式 affinity 又难管理。
- 没有一个对用户透明、动态、可在 kernel 内完成的"voluntary folding"机制。
新流程
steal time samples
|
v
+------------------------------+
| steal_governor_loop | delayed work, default1s
| ratio > high / <= low |
+------------------------------+
| |
ratio>high| |ratio<=low
v v
+----------------------+ +-----------------------+
| decrease_preferred_ | | increase_preferred_ |
| cpus() | | cpus() |
+----------------------+ +-----------------------+
| |
v v
set_cpu_preferred(cpu, false) set_cpu_preferred(cpu, true)
|
v
cpu_preferred_mask updated
|
+--------------------+--------------------+
| | |
v v v is_cpu_allowed sched_tick push sched_balance_rq span
(wakeup path) (stopper thread) (span &= pref mask)
三条路径都用 cpu_preferred(cpu) 做 hint,并保留 select_fallback_rq() 兜底,保证 affinity 契约不被破坏。
Patch 概览
| # | patch | 关键改动 |
|---|---|---|
| 01 | sched/cputime: kcpustat_field_total helper | 抽取 cpumask 内 cpustat 求和 helper;改 s390 hiperdispatch、fs/proc/uptime |
| 02 | sched/docs: sched-paravirt.rst | 新增 paravirt 子系统文档,描述 preferred 概念与设计契约 |
| 03 | cpumask: Introduce cpu_preferred_mask | CONFIG_PREFERRED_CPU + __cpu_preferred_mask + cpu_preferred()/set_cpu_preferred(),CPU activate/deactivate 时同步 preferred |
| 04 | sysfs: /sys/devices/system/cpu/preferred | sysfs 只读 cpulist,供用户/irqbalance 使用 |
| 05 | sched/core: is_cpu_allowed 用 preferred | 任务 wakeup 时优先选 preferred;O(N) 主路径,O(N²) 仅对 pin 在纯 non-preferred 的罕见任务 |
| 06 | sched/fair: LB 仅在 preferred 间 | sched_balance_rq span 改为 sched_domain ∩ cpu_preferred_mask;sched_balance_newidle 用 cpu_preferred() bail |
| 07 | sched/core: tick 推走 non-preferred 上的任务 | stopper thread + select_fallback_rq;rq->push_task_work_done 防重入 |
| 08 | sched/debug: non-preferred 迁移统计 | 新增 nr_migrations_cpu_non_preferred 计数 |
| 09 | virt: Introduce steal_governor | 新建 drivers/virt/steal_governor.c + 文档 + MAINTAINERS 条目 |
| 10 | virt/steal_governor: 三个 module 参数 | interval_ms / high_threshold / low_threshold,带范围校验 |
| 11 | virt/steal_governor: policy loop | get_system_steal_time、steal_ratio、decrease/increase_preferred_cpus、preferred_cpus_valid 自检 |
| 12 | virt/steal_governor: Kconfig / Makefile | CONFIG_STEAL_GOVERNOR tristate default m,select PREFERRED_CPU |
关键实现
1. preferred CPU 基础设施(patch 03)
#ifdef CONFIG_PREFERRED_CPU
extern struct cpumask __cpu_preferred_mask;
#else
#define __cpu_preferred_mask __cpu_active_mask
#endif
#define cpu_preferred_mask ((const struct cpumask *)&__cpu_preferred_mask)
static __always_inline bool cpu_preferred(unsigned int cpu)
{
return cpumask_test_cpu(cpu, cpu_preferred_mask);
}
不变量:preferred ⊆ active ⊆ online ⊆ present ⊆ possible。CONFIG_PREFERRED_CPU=n 时 preferred 退化为 active,set_cpu_preferred 是 nop;driver 不加载时完全无开销。
2. wakeup 路径(patch 05)
static inline bool task_can_sched_on_preferred(int cpu, struct task_struct *p)
{
if (cpu_preferred(cpu))
return false;
/* Only FAIR tasks honor preferred CPU state */
if (unlikely(p->sched_class != &fair_sched_class))
return false;
return cpumask_intersects(p->cpus_ptr, cpu_preferred_mask);
}
被 is_cpu_allowed() 在用户任务和 unbound kthread 上调用:只要任务还有 preferred CPU 可用,就把当前 non-preferred 标为不可用,让 select_fallback_rq() 选 preferred。
3. tick push(patch 07)
sched_tick() 在 !cpu_preferred(cpu) 时调用 sched_push_current_non_preferred_cpu(rq);后者用 stop_one_cpu_nowait 起 stopper thread,调 __migrate_task() 把任务搬到 select_fallback_rq 选出的 preferred。rq->push_task_work_done 防止重入;首次 rq->curr 检测时 task_can_sched_on_preferred() 仍允许跳过 pin 在纯 non-preferred 的任务。
4. load balance(patch 06)
cpumask_and(cpus, sched_domain_span(sd), cpu_preferred_mask);
...
/* NEWIDLE: bail if this CPU is non-preferred */
if (!cpu_preferred(this_cpu))
return;
让 LB 与 tick push 朝同一方向,避免 LB 把任务拉回 non-preferred 又被 tick 推回去。
5. steal_governor policy loop(patch 11)
delta_steal = curr_steal - sg_ctx.steal; /* monotonic */
delta_ns = ktime_to_ns(ktime_sub(now, sg_ctx.time));
delta_ns = max_t(u64, div_u64(delta_ns * num_possible_cpus(), 10000), 1);
steal_ratio = div64_u64(delta_steal, delta_ns); /* unit: 0.01% */
if (steal_ratio > sg_ctx.high_threshold)
decrease_preferred_cpus();
else if (steal_ratio <= sg_ctx.low_threshold)
increase_preferred_cpus();
if (!preferred_cpus_valid())
restore_preferred_to_active();
decrease/increase 按 core(topology_sibling_cpumask)粒度操作,保证 SMT 兄弟线程同步;decrease 还保护第一个 housekeeping core 永远为 preferred。preferred_cpus_valid() 自检发现 preferred 空或不再是 active 子集时,自动 restore 并停 work。
类比
把这一套机制想成 酒店宴会厅的桌号管理:
-
客人(vCPU 任务)来到宴会厅想坐下。
-
服务员(scheduler)手里有两份名单:
active是"今天营业的桌子"(满厅),preferred是"现在还能上菜的桌子"(其中一部分)。 -
经理(steal_governor)盯着厨房出菜速度(steal time):
- 出菜变慢(steal 超过 high_threshold)→ 经理把最远那张桌从 preferred 名单里划掉(policy 缩),并让 waiter 把还坐在那张桌的客人礼貌地请到 preferred 桌(tick push)。
- 出菜恢复(steal 低于 low_threshold)→ 经理把刚才那张桌重新加回 preferred,客人自然回归。
桌号调整是"软"的:如果某位客人是 VIP 写明要坐那张桌(affinity),服务员不会硬拉,他继续在那张桌吃饭,宴会照常进行——只是他那桌暂时不出新菜。这正对应 preferred ⊆ active 和"不破坏 affinity 契约"的设计。
再具体一点:preferred mask 就是"现在可以坐的桌子清单"。wakeup 是"问一句还坐得下",tick push 是"巡场把跑错桌的客人请回",load balance 是"不要再把新客人往那张桌引"。三者同时动作,桌号才能稳定收敛。
Highlight:风险与注意点
- x86/KVM/Xen 上默认阈值被稀释。QEMU
-smp 4,maxcpus=32会让num_possible_cpus()远大于num_online_cpus(),steal_ratio分母放大 8×,默认 5%/2% 实际成 40%/16%,driver 进入"什么都不做"区间。Ionut 建议 init 时按 possible/online 比打 warning,或自动按 online 缩放,v11 已规划。 - Xen dom0 加载会拖垮整台 host。dom0 的 blkback/netback 服务所有 guest,shrink dom0 preferred 等于让 host 自我降速,建议加
xen_initial_domain()拒绝加载或 loud warning,并在文档声明"driver 只面向 guest"。 - 32-bit 任务在 ARM64 上可能误判(Dietmar 指出)。
cpus_ptr可能与 64-bit-only preferred 重叠,is_cpu_allowed()返回 false;同时 64-bit CPU 又被task_allowed_on_cpu()拒掉,所有 CPU 全拒,select_fallback_rq 陷入死循环。需要在 intersection 检查里叠加task_cpu_possible_mask()。 - core 粒度在 KVM/Xen 退化为单 vCPU。
topology_sibling_cpumask()在这些平台等于自身,每轮只动一颗 vCPU,policy 收敛变慢。需要在文档说明 core 步进依赖 guest 拓扑反映 host 调度粒度。 - module 参数 init 后只读。阈值不匹配现场 VM 配置时只能 rmmod/modprobe 重载;CONFIG_STEAL_GOVERNOR=y 时失去灵活性,所以推荐 m。
- cooperative 假设:若只有部分 VM 启用,禁用 driver 的 VM 会"白占"更多 pCPU;doc 要求 all VMs opt-in。
- CPU hotplug 边界:preferred mask 可能因 offline CPU 变空;
preferred_cpus_valid()自检发现后自动 restore 并停 work。 - schbench 性能数据退化:PowerVM 上 hackbench 高负载提升 10–44%,但 schbench 部分配置几乎不变甚至略退化;Yury 要求 v11 补充解读。
- s390 计划另写 governor(Mete):后续 s390 可能引入自己的 governor 模块,希望未来有 cpuidle 式框架挂多 driver;Shrikanth 暂保持单文件形态,留待后续演化。
版本变化(v9 → v10)
- 新增
kcpustat_field_total()helper(Yury 建议)。 - 设计检查改为无条件执行,避免把 driver 约束塞进核心 hotplug 路径。
- 去掉 idle balance 中的
cpu_preferred检查,让nohz.next_balance自然更新。 - 推迟
find_new_ilb()修改(普通场景用不到)。 - 把 scheduler 文档迁到独立
sched-paravirt.rst(Yury)。 - 文档补充"默认值不一定适合所有配置"的限制(Yury)。
task_can_sched_on_preferred从 sched.h 移到 core.c(Mete)。- 补全多处 Suggested-by tag;统一 polish changelog。
- 顺手修复
hd_calculate_steal_percentage中遗留未使用的cpu局部变量。
一句话总结
通过在 scheduler 引入 cpu_preferred_mask 提示,并在 virt/ 下新增 steal_governor 模块按 steal time 阈值动态折叠,本系列让 paravirt guest 在高竞争时主动收敛到 preferred CPU 子集以缓解 vCPU 抢占,同时严格保留用户 affinity 契约。