sched discussion
[PATCH v7 00/12] sched, steal_monitor: Introduce cpu_preferred_mask and steal-driven vCPU backoff
LLM 分析
sched/steal_monitor:cpu_preferred_mask 与 steal 驱动的 vCPU 退避
系列概况
- 标题:[PATCH v7 00/12] sched, steal_monitor: Introduce cpu_preferred_mask and steal-driven vCPU backoff
- 作者:Shrikanth Hegde sshegde@linux.ibm.com,审阅者 Yury Norov,文档审阅 Randy Dunlap
- 版本:v7(12 个补丁,base =
tip/sched/core04998aa54848 "sched/eevdf: Delayed dequeue task can't preempt") - 规模:21 个文件,+737 / -5;新增 drivers/virt/steal_monitor/{sm_core.c, defaults.c, sm_core.h, Kconfig, Makefile} 与 Documentation/driver-api/steal-monitor.rst
- Message-ID(首封):
20260709215648.1246821-1-sshegde@linux.ibm.com - 完整性:37封,含 13 个 patch 与 24 封回复/审阅意见,覆盖 v7 整套 12 个 patch 的全部讨论
补丁目的
在虚拟化超分配场景下,guest 内核只能看到 steal time 这一被动信号,但旧调度器没有任何机制能据此把负载折叠到更小的 vCPU 集合上。本系列引入 cpu_preferred_mask(设计上 preferred ⊆ active ⊆ online ⊆ present ⊆ possible),并配套 drivers/virt/steal_monitor 周期性采样 steal 比例:跨过 high_threshold 就收缩一个核心的 preferred CPU,落到 low_threshold 就再扩张一个核心;调度器侧 wakeup、tick、load balance 全部把任务优先放到 preferred CPU 上,从而降低 vCPU preemption 与锁持有者抢占开销。
旧流程的问题
- guest 只能"被动接受" hypervisor 的调度,被抢占 vCPU 时只能挨打,无法主动让出。
- 调度器只认 active_mask,load balance 仍会把任务拉进正在被宿主抢的 vCPU,下一个 tick 又被赶走,徒增迁移。
- 想解决只能靠 CPU hotplug 或 cpuset 隔离,这两条都会破坏用户亲和性(cpuset 还破坏全局调度视图),不适合做动态微调。
- irqbalance 等用户态工具无法感知"现在哪几颗 vCPU 还能放心用"。
新流程
+-----------------------+
| steal_monitor module |
| load -> delayed_work |
+----------+------------+
| every interval_ms
+----------v------------+
| compute steal_ratio |
| = delta_steal / |
| (delta_ns * npcpu |
| / 10000) |
+----------+------------+
|
+----------v------------+ consecutive hi -> DECREASE
| prev_dir==DECREASE ? | -- ratio > high_thr --> decrease_preferred_cpus()
| prev_dir==INCREASE ? | -- ratio <= low_thr --> increase_preferred_cpus()
+----------+------------+
|
+----------v------------+
| set_cpu_preferred() | (per SMT sibling group)
+----------+------------+
|
+----------v------------+
| sched wakeup / LB / |
| tick stopper use |
| cpu_preferred_mask |
+-----------------------+
decrease_preferred_cpus() 总是把"受保护 housekeeping 核心"之外最末尾的核心及其 SMT sibling 一起标 non-preferred,保证至少一个 housekeeping core 始终在 preferred 内。
Patch 概览
- 1/12 Documentation:把 preferred CPU 概念写进
Documentation/scheduler/sched-arch.rst。 - 2/12 cpumask:定义
__cpu_preferred_mask、set_cpu_preferred()、cpu_preferred();!CONFIG_PREFERRED_CPU下退化为空操作 +cpu_active()。 - 3/12 sysfs:
/sys/devices/system/cpu/preferred只读 cpulist。 - 4/12
is_cpu_allowed:新增task_can_sched_on_preferred(),让 wakeup/select_fallback_rq 倾向 preferred。 - 5/12
sched/fair:load balance 仅在 preferred CPU 之间进行,IDLE/NEWIDLE 早退。 - 6/12
sched_tick:在 non-preferred CPU 上用 stopper 把当前任务迁出(新增push_task_work_done防重入)。 - 7/12 schedstat:新增
nr_migrations_cpu_non_preferred,便于验证。 - 8/12 drivers/virt/steal_monitor:新建模块、Kconfig、Makefile、
sm_core.c入口、MAINTAINERS。 - 9/12 模块参数:
interval_ms、low_threshold、high_threshold,module_param_cb在加载时校验。 - 10/12
defaults.c:实现get_system_steal_time/decrease/increase_preferred_cpus/get_num_cpus_steal_ratio。 - 11/12 周期 work:
compute_preferred_cpus_work,连续两次越界才动作,方向记忆防 ping-pong,结束时WARN_ON_ONCE检查不变量。 - 12/12 nohz_full:把 non-preferred nohz_full CPU 的 tick 保留到任务出清(作者宣布 v8 删除)。
关键实现
/* kernel/sched/sched.h */
static inline bool task_can_sched_on_preferred(int cpu, struct task_struct *p)
{
if (cpu_preferred(cpu))
return false;
if (unlikely(p->sched_class != &fair_sched_class))
return false;
return cpumask_intersects(p->cpus_ptr, cpu_preferred_mask);
}
/* kernel/sched/core.c */
void sched_push_current_non_preferred_cpu(struct rq *rq)
{
struct task_struct *push_task = rq->curr;
if (!task_can_sched_on_preferred(rq->cpu, push_task))
return;
if (rq->push_task_work_done)
return;
get_task_struct(push_task);
scoped_guard(rq_lock, rq)
rq->push_task_work_done = true;
stop_one_cpu_nowait(rq->cpu, sched_non_preferred_cpu_push_stop,
push_task, this_cpu_ptr(&npc_push_task_work));
}
/* drivers/virt/steal_monitor/sm_core.c */
static void compute_preferred_cpus_work(struct work_struct *work)
{
u64 curr_steal = get_system_steal_time();
u64 delta_steal = curr_steal > sm_core_ctx.prev_steal ?
curr_steal - sm_core_ctx.prev_steal : 0;
sm_core_ctx.prev_steal = curr_steal;
u64 delta_ns = ktime_to_ns(ktime_sub(ktime_get(),
sm_core_ctx.prev_time));
/* avoid overflow: divide denominator first */
delta_ns = div_u64(delta_ns * get_num_cpus_steal_ratio(), 100 * 100);
u64 steal_ratio = div64_u64(delta_steal, delta_ns);
if (sm_core_ctx.prev_direction == SM_DIR_DECREASE &&
steal_ratio > sm_core_ctx.high_threshold)
decrease_preferred_cpus(&sm_core_ctx);
if (sm_core_ctx.prev_direction == SM_DIR_INCREASE &&
steal_ratio <= sm_core_ctx.low_threshold)
increase_preferred_cpus(&sm_core_ctx);
/* ... direction + requeue ... */
}
设计不变量通过 cpumask 自身结构 + set_cpu_preferred(cpu, true) 仅在 sched_cpu_activate 时调用来保证;!CONFIG_PREFERRED_CPU 编译路径上 set_cpu_preferred 是 do { } while (0)、cpu_preferred() 是 cpu_active()。
类比
想象 guest是一家共享办公室,整栋楼(possible)有 720 个工位,今天开放的(active)有 720 个。宿主超分配就像走廊突然塞满人(pCPU 抢占),空调不够用了:steal time 是"走廊热度计"。前台(steal_monitor)一旦发现温度超过 high_threshold,就把靠走廊尽头那几排的灯关掉、让大家集中到靠窗的几排(preferred),等人流过了再把灯一排排重新打开。这样既没裁员(没碰 affinity),又没有占着茅坑(没人在被抢占的 vCPU 上空跑)。
Highlight:风险与注意点
- Invariant 恢复:Yury 指出
WARN_ON_ONCE只是打印,破坏后必须主动rmmod或恢复 cpu_preferred_mask;目前只 printk 风险较大,VM 可能带着空 preferred mask 撑到深夜。 - Kconfig 依赖:
CONFIG_PREFERRED_CPU当前是裸bool,Yury 要求加depends on SMP && PARAVIRT,否则可能造成STEAL_MONITOR=y && PREFERRED_CPU=n的坏组合。 - steal 用 possible 而非 active:CPU 下线瞬间要靠
curr > prev钳位保护,否则会注入伪下降。 - 跨 VM 协作:单 VM 开启会让自己"吃亏",需要部署文档明确全员开启。
- 非 FAIR class:RT/DL 不走 preferred 路径,文档/sysfs 需写清。
- Patch 12 删除:作者确认 v8 移除 nohz_full tick 守卫,pinned 任务与
TICK_DEP_BIT_SCHED清理难处理。 - 文档:Randy 触发
docutils警告,需在.. SPDX-License-Identifier: GPL-2.0后加空行。 - 模块参数只读:阈值/间隔只能
rmmod后改,部署脚本需注意。 - Config 位置:Yury 建议把 STEAL_MONITOR Kconfig 移到 series 末尾,避免 bisect 中段坏掉。
- decrease/increase 状态返回:Yury 建议返回状态并记忆在
struct steal_monitor,作者认为 stateless 已足够;v8 待定。 - sysfs gating:Yury 主张
/sys/devices/system/cpu/preferred用CONFIG_PREFERRED_CPUifdef 包住,作者已认可在 v8 加上。
版本变化
v6 → v7(已合并):
- steal_monitor 拆成 4–5 块;新增
CONFIG_STEAL_MONITORselectPREFERRED_CPU。 - 模块参数改
module_param_cb,加载期校验,运行期只读。 is_cpu_allowed简化,删冗余注释。nohz_fulltick 守卫拆成独立 patch 12;两个 load balance 补丁合并。- steal 计算由
active改possible;移除__weak。 scoped_guard去多余空格;sched_push_current_non_preferred_cpu去 class 检查。- 修复
push_task_work_done与 early-return 的 requeue;WARN_ON换WARN_ON_ONCE。 - MAINTAINERS 增加条目。
预计 v8:
- 文档:补
steal-monitor.rst空行。 - Kconfig:
PREFERRED_CPU加depends on SMP && PARAVIRT,STEAL_MONITOR移至系列末尾。 - 不变量破坏时主动恢复/卸载驱动。
- sysfs
preferred文件用#ifdef CONFIG_PREFERRED_CPU包起来。 - 删除 patch 12。
- 清理
increase/decrease_preferred_cpus的 unusedctx形参。
一句话总结
v7 已把 cpu_preferred_mask 的 cpumask/sysfs/sched 接口与 drivers/virt/steal_monitor 周期调参机制完整打通;下一轮 v8 重点是修复 invariant 破坏时的恢复策略、Kconfig 依赖与 patch 12 的去留。