0/18 已展开

LLM 分析

CPU Scheduler:cpu_preferred_mask 与 steal_governor 动态 vCPU backoff

系列概况

  • 标题:[PATCH v8 00/11] sched, steal_governor: Introduce cpu_preferred_mask and steal-driven vCPU backoff
  • 作者:Shrikanth Hegde sshegde@linux.ibm.com
  • 版本:v8,11 个 patch(1 篇 cover letter + 11 篇正文)
  • 规模:净增约 390 行(不含文档/sysfs/Kconfig);新增子目录 drivers/virt/steal_governor/
  • 修改文件:Documentation/scheduler/sched-arch.rst、Documentation/ABI/testing/sysfs-devices-system-cpu、Documentation/driver-api/steal-governor.rst、include/linux/cpumask.h、kernel/Kconfig.preempt、kernel/cpu.c、kernel/sched/{core,fair,debug,sched.h}.c、include/linux/sched.h、drivers/base/cpu.c、drivers/virt/Kconfig、drivers/virt/Makefile、drivers/virt/steal_governor/{Kconfig,Makefile,core.c,core.h}、MAINTAINERS
  • Message-ID:20260720172250.2257582-1-sshegde@linux.ibm.com
  • 完整性:本帖仅含 v8 首发 + 6 篇 Yury/Shrikanth 互动的回复;v8 之后未见新版本。

补丁目的

在半虚拟化环境中,多个 VM 同时高负载会触发 hypervisor 抢占 vCPU;持有锁或中断关闭区间的 vCPU 一旦被抢占,前向进度会被破坏。本系列在 guest 内核里新增"Preferred CPU"概念:通过 cpu_preferred_mask 把可用 vCPU 收成一个动态子集,让 scheduler 在 wakeup/tick/load balance 三个时机主动折叠负载;策略则由独立的 steal_governor 驱动按 steal time 周期性地逐核收缩或扩张,实现不破坏用户 affinity 的协作式降载。

旧流程的问题

  • CPU hotplug / isolated cpuset 改动大、需要重建 topology、破坏 affinity。
  • 显式 task affinity 难维护,无法跟随负载自适应。
  • 缺乏内核侧快速、可逆的 backoff,所有压力卸载由 hypervisor 抢占解决,代价高。

新流程

  1. 注册 cpu_preferred_maskPREFERRED_CPU 开启时存在,关闭时别名到 cpu_active_mask),保证 preferred ⊆ active ⊆ online ⊆ present ⊆ possible
  2. select_fallback_rqis_cpu_allowed 在非 preferred CPU 上优先把任务搬到 preferred 集合。
  3. tick 路径上若当前 CPU 非 preferred,由 stopper 主动把运行任务迁走(仅 FAIR class)。
  4. load balance 把 sched_domain span 限制在 preferred mask 内,避免向 non-preferred 倒灌。
  5. steal_governor 周期计算 steal_ratio,按高/低阈值加或减一颗 core 的 preferred 状态。

Patch 概览

  • 01/11 docs:Documentation/scheduler/sched-arch.rst 增加 Preferred CPU 章节
  • 02/11 cpumask:__cpu_preferred_maskset_cpu_preferredcpu_preferred()PREFERRED_CPU Kconfig
  • 03/11 sysfs:/sys/devices/system/cpu/preferred 只读文件
  • 04/11 core:is_cpu_allowed() 增加 task_can_sched_on_preferred() 短路
  • 05/11 fair:sched_balance_rq_nohz_idle_balancesched_balance_newidle 限定 preferred 集
  • 06/11 core:tick 路径 sched_push_current_non_preferred_cpu() + stopper work
  • 07/11 debug:新增 nr_migrations_cpu_non_preferred 计数与 proc 输出
  • 08/11 virt:驱动骨架、模块出入口、restore_preferred_to_active()
  • 09/11 virt:module_param_cb interval_mshigh_thresholdlow_threshold
  • 10/11 virt:delayed work 主循环、decrease/increase_preferred_cpus() 等辅助
  • 11/11 Kconfig/Makefile:注册 STEAL_GOVERNOR,select PREFERRED_CPU

关键实现

/* cpumask:preferred ⊆ active */
#ifdef CONFIG_PREFERRED_CPU
extern struct cpumask __cpu_preferred_mask;
#else
#define __cpu_preferred_mask __cpu_active_mask
#endif
#define cpu_preferred_mask ((const struct cpumask *)&__cpu_preferred_mask)

/* tick 路径推任务 */
void sched_tick(void)
{
    ...
    if (!cpu_preferred(cpu))
        sched_push_current_non_preferred_cpu(rq);
}

/* steal_governor 主循环 */
static void compute_preferred_cpus_work(struct work_struct *work)
{
    ...
    delta_steal = curr_steal - sg_core_ctx.steal;
    delta_ns    = max_t(u64,
                        div_u64(delta_ns *
                                get_num_cpus_steal_ratio(), 100 * 100), 1);
    steal_ratio = div64_u64(delta_steal, delta_ns);

    if (steal_ratio > sg_core_ctx.high_threshold)
        decrease_preferred_cpus();
    if (steal_ratio <= sg_core_ctx.low_threshold)
        increase_preferred_cpus();
    ...
    schedule_delayed_work(&sg_core_ctx.work,
                          msecs_to_jiffies(sg_core_ctx.interval_ms));
}
            +------------------------------+
            | userspace affinity (cpuset)  |
            +--------------+---------------+
                           v
       +---------------------------------------+
       |           cpu_preferred_mask          |
       |  (subset of cpu_active_mask)          |
       +---+---------------+---------+---------+
           v               v         v
       wakeup path     tick push   load balance
       (is_cpu_allowed)(stopper)   (span limited)
                           |
                           v
                 +---------------------+
                 |   steal_governor    |
                 |  periodic work      |
                 |  collect steal time |
                 |  compute ratio      |
                 |  high -> -1 core    |
                 |  low  -> +1 core    |
                 +---------------------+

类比

把写字楼里的几间共享会议室当作 pCPU,每家公司(VM)领到的门卡(vCPU)就是它们能用的会议室。当走廊变得拥挤(steal time 上升),物业(steal_governor)通知大家"暂时把几张门卡交还前台",只留关键业务使用;走廊一空,再把门卡发回。整过程租户不可换公司(affinity 不动),但每间会议室利用率自动收敛。

Highlight:风险与注意点

  • Yury 指出 get_system_steal_time()hd_calculate_steal_percentage() 重复,建议抽到 kernel_stat.h 提供 kcpustat_steal_time() 或更通用的 kcpustat_field_total()
  • Yury 建议 init 时把毫秒换算成 jiffies 存入 struct,避免每次调度都做 msecs_to_jiffies;维护者已认可将在 v9 加上 delay 字段。
  • Yury 还质疑 struct steal_governor 提到独立 core.h 是否必要,提示可收敛。
  • restore_preferred_to_active() 后驱动只是"不再 requeue work",进程不会被显著提示,存在"quiet failure"风险。
  • 协作前提是所有 VM 都加载模块;懒 VM 仍按 active mask 占用,会拉低整体收益,需 ops 通过 sysfs 核对。
  • 仅 FAIR class 受影响;kthread 在 pin-only non-preferred 时会被保留,需关注边界用例。
  • 下推/上推粒度按"core/siblings"为单位,与 powerVM 等 core-scheduling hypervisor 对齐,但与非 core 调度器混跑时要验证一跳性。

版本变化

本帖仅 v8。Cover letter 中提到系列自早期 arch-specific RFC 经多轮重设计形成;社区 review(尤其是 Yury Norov 的反复审查)显著推动了 driver 形态从 scheduler 内嵌策略改为可加载模块。

一句话总结

把 cpu_preferred_mask 作为 scheduler 与策略解耦的协作接口,把"按 steal time 收/扩 vCPU 子集"放进独立的 steal_governor 模块,让 guest 在不破坏 affinity 的前提下主动减少 vCPU preemption。