0/14 已展开

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

新流程

  1. steal_governor 周期(默认 1s)采样系统级 steal time 与 active CPU 数
  2. 计算 steal_ratio = delta_steal * 10000 / (delta_ns * num_active_cpus),单位 0.01%
  3. steal_ratio > high_threshold(默认 5%)→ decrease_preferred_cpus(),按 core 退一
  4. steal_ratio <= low_threshold(默认 2%)→ increase_preferred_cpus(),按 core 进一
  5. 调度器沿 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_maskcpu_preferred() / set_cpu_preferred()。invariant:preferred ⊆ active ⊆ online ⊆ present ⊆ possibleCONFIG_PREFERRED_CPU=nset_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_masksched_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_NOISE core 作为兜底
  • 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:风险与注意点

  1. ARM64 验证空白:Vincent/Dietmar 多次明示「ARM64 testing is obviously missed」,默认在 ARM64 启用仍需补测
  2. 合作式前提:必须所有 VM 都启用,单方让 CPU 等于白让,文档要求管理员全 VM 一致
  3. pinned 任务零 LB:pinned 在非 preferred 上的任务不被迁移,可能形成局部热点,是设计取舍
  4. overflow 风险:先除 10000 缓解,仍依赖 64-bit 算术
  5. 调试可观测:新增 nr_migrations_cpu_non_preferred 通过 proc_sched_show_task 暴露,但公开测试数据集中在 PowerPC
  6. 模块参数运行时只读:必须 rmmod/modprobe 才能改值,影响运维节奏
  7. CPU hotplug 跨界:用户 offlining 致 preferred 为空时驱动恢复并停止 requeue,需运维告警配套
  8. 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 损失,且严格不破坏用户亲和性。