0/62 已展开

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.rstDocumentation/driver-api/{index.rst,steal-monitor.rst}Documentation/ABI/testing/sysfs-devices-system-cpudrivers/base/cpu.cdrivers/virt/Makefiledrivers/virt/steal_monitor/{Makefile,sm_core.c,sm_core.h,defaults.c}include/linux/{cpumask.h,sched.h}kernel/Kconfig.preemptkernel/cpu.ckernel/sched/{core.c,fair.c,debug.c,sched.h}
  • Message-ID20260701141654.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 又被推走,形成无效迁移。

新流程

  1. steal_monitorinterval_ms(默认 1000ms)调用 get_system_steal_time()ktime_get()
  2. 计算 steal_ratio = (delta_steal*100*100)/(delta_ns * num_cpus)
  3. steal_ratio > high_threshold(默认 500,5%)-> decrease_preferred_cpus()
  4. steal_ratio <= low_threshold(默认 200,2%)-> increase_preferred_cpus()
  5. 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 ⊆ activesched_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_taskpush_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概览

#主题类型
01Documentation:cpu_preferred_mask 概念docs
02Kconfig:引入 PREFERRED_CPUfeature
03cpumask:cpu_preferred_mask 与 set/getinfra
04sysfs:/sys/devices/system/cpu/preferredfeature
05is_cpu_allowed 倾向 preferredfeature
06load balance 仅在 preferred 间feature
07nohz/newidle balance 跳过 non-preferredfeature
08non-preferred CPU 保持 tickfeature
09stopper 把 non-preferred CPU 任务推走feature
10新增 nr_migrations_cpu_non_preferreddebug
11-23steal_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_fulldecrease_preferred_cpusnohz_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_preferredCONFIG_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 开销。