sched discussion
[PATCH v11 00/12] sched, steal_governor: Introduce preferred CPUs and steal-driven vCPU backoff
LLM 分析
sched/steal_governor:引入 preferred CPU 与窃取时间驱动的 vCPU 退让
系列概况
- 标题: [PATCH v11 00/12] sched, steal_governor: Introduce preferred CPUs and steal-driven vCPU backoff
- 作者: Shrikanth Hegde sshegde@linux.ibm.com
- 版本: v11(共 12 patch;希望在 7.3-rc 进入 sched/core,目标 7.4)
- 规模: 12 patch,新增 drivers/virt/steal_governor.c、sched-paravirt.rst、steal-governor.rst 等
- 修改文件: kernel/sched/{core,fair,debug}.c、include/linux/{cpumask,kernel_stat,sched,bitmap}.h、kernel/{Kconfig.preempt,cpu.c}、drivers/{base/cpu.c,virt/{Kconfig,Makefile,steal_governor.c}}、arch/s390/kernel/hiperdispatch.c、fs/proc/uptime.c、MAINTAINERS、docs
- 代码统计: 约 +400/-50 行,驱动主体(patch 09–11)200+ 行
- Message-ID: 20260825103855.721013-1-sshegde@linux.ibm.com(cover letter)
- 完整性: 多封 reply 正文被截断在 hunk 引用里,但 12 个 patch 主体完整
补丁目的
针对 paravirt 场景下 vCPU overcommit 引发的「noisy neighbour」问题,提供内核协作式方案:
- 核心:guest 端把 FAIR 任务自动折叠到更小的 preferred vCPU 集合上,降低 pCPU 争用与 vCPU 抢占带来的锁持有者抢占、cache/TLB 抖动等隐性损失
- 约束:严格不破坏用户亲和性,pinned 在非 preferred 上的任务不会被强迁
- 双层:A 层 core scheduler 机制(cpu_preferred_mask),B 层可加载模块 steal_governor(CONFIG_STEAL_GOVERNOR=m)按 steal time 驱动策略
旧流程的问题
- CPU hotplug / isolated cpuset / cpuset:重,要重建 topology,且会破坏 affinity
- 显式 task affinity:用户态难管理
- 没有 fast/cooperative 的内核侧退让机制;vCPU 抢占造成的锁临界区中断、TLB miss 等拖慢 forward progress
新流程
- steal_governor 周期(默认 1s)采样系统级 steal time 与 active CPU 数
- 计算
steal_ratio = delta_steal * 10000 / (delta_ns * num_active_cpus),单位 0.01% steal_ratio > high_threshold(默认 5%)→decrease_preferred_cpus(),按 core 退一steal_ratio <= low_threshold(默认 2%)→increase_preferred_cpus(),按 core 进一- 调度器沿 wakeup/tick/load balance 三条路径自动把任务拉到 preferred CPU,pinned 任务不动
+---------------------------------------------+
| Guest kernel |
| +---------------------------------------+ |
| | Scheduler core | |
| | is_cpu_allowed -> prefer preferred | |
| | sched_tick -> push out | |
| | sched_balance -> span = preferred | |
| +------------------+--------------------+ |
| | cpu_preferred_mask |
| v |
| +---------------------------------------+ |
| | steal_governor (drivers/virt) | |
| | poll steal time every interval_ms | |
| | high -> decrease_preferred_cpus | |
| | low -> increase_preferred_cpus | |
| +---------------------------------------+ |
+---------------------+----------------------+
| steal time signal
v
+----------------+
| Hypervisor |
| shared pCPUs |
+----------------+
Patch 概览
- 01: kcpustat_field_total helper
- 02: Documentation/scheduler/sched-paravirt.rst
- 03: cpu_preferred_mask + CONFIG_PREFERRED_CPU
- 04: /sys/devices/system/cpu/preferred sysfs
- 05: is_cpu_allowed 中尝试 preferred(wakeup 路径)
- 06: load balance span 限定为 preferred
- 07: tick 上 stopper push 任务到 preferred
- 08: nr_migrations_cpu_non_preferred 调试统计
- 09: steal_governor.c 骨架 + docs
- 10: 模块参数 interval_ms / high_threshold / low_threshold
- 11: steal_governor_loop 策略循环
- 12: Kconfig/Makefile 启用 + MAINTAINERS 登记
关键实现
cpumask 引入 preferred
新增 __cpu_preferred_mask 与 cpu_preferred() / set_cpu_preferred()。invariant:preferred ⊆ active ⊆ online ⊆ present ⊆ possible。CONFIG_PREFERRED_CPU=n 时 set_cpu_preferred() 为空操作、cpu_preferred() 退化为 active 状态,零开销回退。
wakeup 走 preferred
static inline bool task_can_sched_on_preferred(int cpu, struct task_struct *p)
{
if (cpu_preferred(cpu)) return false;
if (p->sched_class != &fair_sched_class) return false;
if (!cpumask_test_cpu(task_cpu(p), p->cpus_ptr)) return false;
return cpumask_intersects(p->cpus_ptr, cpu_preferred_mask);
}
仅 FAIR class 生效;显式跳过 affinity 切换中的边界(避免 migration_cpu_stop 中途 abort);arm64 32-bit 用户态等 arch-specific mask 用 task_cpu_possible_mask() 二次校验。仅在 !cpu_preferred 时进入 O(N) 检查,preferred 时零成本。
tick push
sched_tick 检测当前 CPU 非 preferred 时调 sched_push_current_non_preferred_cpu(),通过 stopper 进入 sched_non_preferred_cpu_push_stop() → select_fallback_rq() 选 preferred CPU → __migrate_task()。push_task_work_done 防 stopper 重入;migration_disabled 任务跳过;每个 tick 只搬当前一个任务,保持简单。
load balance
sched_balance_rq span 限定为 cpu_preferred_mask;sched_balance_newidle 对非 preferred this_cpu 早返回。避免 LB 把任务拉到一个马上又被 tick 推走的 CPU,与 push 互不打架。
steal_governor 策略循环
static void steal_governor_loop(struct work_struct *work)
{
u64 curr_steal, delta_steal, delta_ns, steal_ratio;
ktime_t now;
now = ktime_get();
delta_ns = ktime_to_ns(ktime_sub(now, sg_ctx.time));
if (unlikely(delta_ns < NSEC_PER_MSEC)) goto requeue_work;
curr_steal = get_system_steal_time();
delta_steal = curr_steal > sg_ctx.steal ? curr_steal - sg_ctx.steal : 0;
sg_ctx.steal = curr_steal;
sg_ctx.time = now;
delta_ns = max_t(u64, div_u64(delta_ns * get_system_cpus(), 10000), 1);
steal_ratio = div64_u64(delta_steal, delta_ns);
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();
return;
}
requeue_work:
schedule_delayed_work(&sg_ctx.work, sg_ctx.delay);
}
delta_steal * 10000 / (delta_ns * num_active_cpus),通过提前除 10000 缓解溢出- 增删粒度以 core 为单位,匹配 SMT 与 powerVM core-scheduling
decrease_保留至少一个HK_TYPE_KERNEL_NOISEcore 作为兜底preferred_cpus_valid()自检preferred ⊆ active且非空,违反时把 mask 恢复到 active 并停止 requeue
steal_time ^
high_threshold (5%)
|--------------------------------------| decrease by 1 core
low_threshold (2%)
|--------------------------------------| increase by 1 core
[in-between, no-op]
类比
把一个 guest 想象成一大家人合住在一个餐厅里,hypervisor 是服务员,pCPU 是桌子。没有 preferred 时,全家人无序抢桌子,服务员随时抽走某人的饭(vCPU 抢占),锁 / TLB / cache 全部失效。引入 preferred 后,当大家观察到吃饭很赶(steal 高),就主动围到几张稳定的桌子上(preferred 集合变小);到了淡季再散开(preferred 集合回到 active)。pinned 的客人不动,餐厅本身不需要任何改动,这就是 cooperative 折叠的本质。
Highlight:风险与注意点
- ARM64 验证空白:Vincent/Dietmar 多次明示「ARM64 testing is obviously missed」,默认在 ARM64 启用仍需补测
- 合作式前提:必须所有 VM 都启用,单方让 CPU 等于白让,文档要求管理员全 VM 一致
- pinned 任务零 LB:pinned 在非 preferred 上的任务不被迁移,可能形成局部热点,是设计取舍
- overflow 风险:先除 10000 缓解,仍依赖 64-bit 算术
- 调试可观测:新增
nr_migrations_cpu_non_preferred通过proc_sched_show_task暴露,但公开测试数据集中在 PowerPC - 模块参数运行时只读:必须 rmmod/modprobe 才能改值,影响运维节奏
- CPU hotplug 跨界:用户 offlining 致 preferred 为空时驱动恢复并停止 requeue,需运维告警配套
- NUMA 简化:策略循环按 core 增删而不做 NUMA splice,NUMA 不均时收敛速度不均
版本变化
v11(cover letter 自述基于 v8 之后的设计)相对之前的主要收敛:
- 「deliberately kept simple」:删除
available_idle_cpu()中与本机制冲突的额外校验,逻辑统一收敛到is_cpu_allowed() find_new_ilb()不再特殊优化,复用 idle CPU 查找天然命中 preferred- 文档拆为
sched-paravirt.rst(scheduler 侧)与steal-governor.rst(driver 侧)两份 - Kconfig 默认
m,并把推荐原因写在 help 里 - 阈值默认收敛到
high=5% / low=2% / interval=1s,文档强调需按 workload 调优 - 经 Yury Norov 多轮 review,简化 helper 命名与模块参数定义
一句话总结
通过 cpumask 体系新增 cpu_preferred_mask,让 wakeup / tick / load balance 三条路径自动把 FAIR 任务向 preferred 集合折叠,并由可加载模块 steal_governor 周期采样 steal time 驱动 preferred 的增减,在 paravirt 场景下以合作式降低 vCPU 抢占带来的锁 / cache / TLB 损失,且严格不破坏用户亲和性。