sched discussion
[PATCH v6 00/23] sched: Introduce cpu_preferred_mask and steal-driven vCPU backoff
LLM 分析
Linux 调度器:cpu_preferred_mask 与 steal-driven vCPU backoff
系列概况
- 标题:[PATCH v6 00/23] sched: Introduce cpu_preferred_mask and steal-driven vCPU backoff
- 作者:Shrikanth Hegde (IBM)
- 版本:v6;base commit tip/sched/core
b2463ebf2674 - 规模:23 个 patch,18 个文件,+682/-5;新建 5 个文件
- 修改文件:
Documentation/scheduler/sched-arch.rst、Documentation/driver-api/{index.rst,steal-monitor.rst}、Documentation/ABI/testing/sysfs-devices-system-cpu、drivers/base/cpu.c、drivers/virt/Makefile、drivers/virt/steal_monitor/{Makefile,sm_core.c,sm_core.h,defaults.c}、include/linux/{cpumask.h,sched.h}、kernel/Kconfig.preempt、kernel/cpu.c、kernel/sched/{core.c,fair.c,debug.c,sched.h} - Message-ID:20260701141654.500125-1-sshegde@linux.ibm.com
- 完整性:62 封邮件(cover + 23 patch + 38封评审回复)
补丁目的
虚拟化过载场景下 vCPU 总数大于 pCPU 总数。当所有 VM 都高负载时,hypervisor 跨 VM 抢占 vCPU(vCPU preemption),比 VM 内 task切换昂贵得多。本系列:
- 新增
cpu_preferred_mask,表达当前可放心使用的 vCPU 子集(恒为cpu_active_mask子集); - 新增
steal_monitor驱动周期采样 steal time,按阈值动态调整 preferred 集合; - 在 wakeup、load balance、tick push 等路径让任务优先落在 preferred CPU,并主动把 non-preferred CPU 上的任务推走。
旧流程的问题
- Linux 无跨 VM 协作:steal time 升高时各 VM 仍尽量用满 vCPU,反而放大 pCPU 竞争。
- 既有隔离手段(CPU hotplug、isolcpus、cpuset partition)会永久破坏用户 affinity,无法动态恢复。
is_cpu_allowed/sched_balance_rq/sched_can_stop_tick不知道哪些 vCPU 此时不该用,把任务拉进 non-preferred CPU 后下一个 tick 又被推走,形成无效迁移。
新流程
steal_monitor每interval_ms(默认 1000ms)调用get_system_steal_time()与ktime_get();- 计算
steal_ratio = (delta_steal*100*100)/(delta_ns * num_cpus); - 若
steal_ratio > high_threshold(默认 500,5%)->decrease_preferred_cpus(); - 若
steal_ratio <= low_threshold(默认 200,2%)->increase_preferred_cpus(); - Scheduler 路径只把任务放到 preferred CPU;non-preferred CPU 上 tick 强制开,直到 stopper 推走任务。
+----------------------------------------------+
| steal_monitor delayed_work (interval_ms) |
+----------------------------------------------+
|
sample steal via kcpustat_cpu
v
ratio>high --> decrease_preferred_cpus()
ratio<=low --> increase_preferred_cpus()
v
+-----------------------------+
| cpu_preferred_mask |
| (subset of cpu_active_mask) |
+-----------------------------+
| | |
v v v
is_cpu_ load_balance sched_tick
allowed preferred push stopper
关键实现
cpumask 基础设施(patch 03)
#ifdef CONFIG_PREFERRED_CPU
extern struct cpumask __cpu_preferred_mask;
#else
#define __cpu_preferred_mask __cpu_active_mask
#endif
static __always_inline bool cpu_preferred(unsigned int cpu)
{
return cpumask_test_cpu(cpu, cpu_preferred_mask);
}
不变量 preferred ⊆ active 由 sched_cpu_activate / sched_cpu_deactivate 钩子维持。
is_cpu_allowed 偏向 preferred(patch 05)
if (!(p->flags & PF_KTHREAD)) {
if (task_can_sched_on_preferred(cpu, p))
return false; /* 倾向 preferred */
return cpu_active(cpu);
}
task_can_sched_on_preferred 判断 !cpu_preferred(cpu) 且任务 cpumask 与 preferred mask 有交集。仅 FAIR class 生效。
tick push(patch 09)
void sched_tick(void)
{
if (!cpu_preferred(cpu))
sched_push_current_non_preferred_cpu(rq);
}
void sched_push_current_non_preferred_cpu(struct rq *rq)
{
...
get_task_struct(push_task);
rq->push_task_work_done = 1;
stop_one_cpu_nowait(rq->cpu, sched_non_preferred_cpu_push_stop,
push_task, this_cpu_ptr(&npc_push_task_work));
}
stopper 中调用 select_fallback_rq 选 preferred CPU 并 __migrate_task。push_task_work_done 防 stopper 重入。
steal_monitor 周期工作(patch 16/20)
u64 delta_steal = (curr_steal > prev_steal) ? curr_steal - prev_steal : 0;
u64 delta_ns = max_t(u64, ktime_to_ns(now - prev_time), 1);
/* 先乘 num_cpus 再除 100*100,规避 u64溢出 */
delta_ns = div_u64(delta_ns * get_num_cpus_steal_ratio(), 100 * 100);
if (!delta_ns) return;
u64 steal_ratio = div64_u64(delta_steal, delta_ns);
if (steal_ratio > high_threshold)
decrease_preferred_cpus(&sm_core_ctx);
if (steal_ratio <= low_threshold)
increase_preferred_cpus(&sm_core_ctx);
方向控制(patch 21)要求两次连续同向(DECREASE/INCREASE)才真正动作,避免抖动。
默认增减策略(patch 18/23)
保留第一个 housekeeping core 作为必保集合;其余按 SMT core 整体增减:
全部 housekeeping 时直接取最后一个 CPU(快速路径),否则回退遍历。480 vCPU 实测从约 6us 降至约 3us。
Patch概览
| # | 主题 | 类型 |
|---|---|---|
| 01 | Documentation:cpu_preferred_mask 概念 | docs |
| 02 | Kconfig:引入 PREFERRED_CPU | feature |
| 03 | cpumask:cpu_preferred_mask 与 set/get | infra |
| 04 | sysfs:/sys/devices/system/cpu/preferred | feature |
| 05 | is_cpu_allowed 倾向 preferred | feature |
| 06 | load balance 仅在 preferred 间 | feature |
| 07 | nohz/newidle balance 跳过 non-preferred | feature |
| 08 | non-preferred CPU 保持 tick | feature |
| 09 | stopper 把 non-preferred CPU 任务推走 | feature |
| 10 | 新增 nr_migrations_cpu_non_preferred | debug |
| 11-23 | steal_monitor 骨架/参数/周期工作/默认实现/方向控制/自检/优化 | driver |
类比
把 VM 想象成写字楼里各家公司,pCPU 是大楼电梯:
- 早高峰电梯不够,员工被反复赶出电梯换班(vCPU preemption);
steal_monitor相当于公司行政通知:今天让一半员工居家(preferred = active 子集);set_cpu_preferred(false)是给员工发"今天别来"工牌;- scheduler 的 tick push 是保安:看到员工还在电梯口就请他回家;
- 只有所有公司都配合,整栋楼才不堵——这正是 cooperative scheme 的本质。
Highlight:风险与注意点
- patch 粒度过细:Yury 明确指出 steal_monitor 拆 12 个 patch 不利于整体 review,建议 v7 合并到 2~3 个。
- per-core vs per-cpu:当前按 SMT core 管理,Yury 关心复杂度收益比。
- first-set nohz_full:
decrease_preferred_cpus对nohz_full=设在最前一组 CPU 场景不友好;Yury提议两遍扫描(先在 nohz_full 范围找 preferred候选,再回退到整 mask)。 interval_ms=0风险:仅靠WARN_ON+ 回退 1000ms,文档未充分强调;Yury 担心触发 schedule_delayed_work(... 0) 锁死。WARN_ON噪音:patch 16 每次 tick 触发可能刷屏,Yury 倾向WARN_ON_ONCE。- Kconfig 耦合:
PREFERRED_CPU=y隐含编译 steal_monitor;Yury 主张引入独立VIRT_STEAL_MONITOR让外置 monitor 也能复用 PREFERRED_CPU 基础设施。 - u64 溢出:v6 已把
delta_ns * num_cpus放进div_u64,后续若 num_cpus 继续增长需审视delta_steal溢出。 - 方向控制保守:连续两次同向才动作,可能导致 preferred 集合恢复过慢。
- NUMA 未考虑:默认实现不做 NUMA 亲和,跨节点增减 core 可能影响尾延迟。
- MAINTAINERS 缺失:作者主动询问 driver 应新增 entry 还是归入 SCHEDULER。
版本变化
v5 -> v6(本次重发关键改动)
- 移除首个 patch(已被 Peter 合入 sched/core);
cpu_preferred_mask改为EXPORT_SYMBOL_GPL(Peter);set_cpu_preferred在CONFIG_PREFERRED_CPU=n下成 NOP(Peter/Yury);- 移除
select_fallback_rq的 preferred 缓存优化(存在 mask 变化后 affinity 被旧值覆写的竞争); - 移除独立 wakeup patch(无性能退化,
is_cpu_allowed已覆盖); - 处理 CPU hotplug 时
cpu_active_mask访问并发(sashiko); - 处理
delta_ns * num_cpus的 u64 溢出(sashiko); - 修复
nohz_full=设在最后一组 CPU 时decrease_preferred_cpus行为(sashiko); - 新增"全部 housekeeping"快速路径(patch 23);
- 文档修正 nits(Randy)。
一句话总结
本系列用 steal_monitor 驱动周期采样 steal time、动态调整 cpu_preferred_mask,并让 scheduler 在 wakeup、load balance、tick push 等路径把任务收敛到 preferred vCPU 上,从而缓解 overcommit 场景下的 vCPU preemption 开销。