0/30 已展开

LLM 分析

sched/steal_governor 系列:Preferred CPU 与基于 steal time 的 vCPU 退避

系列概况

  • 标题: [PATCH v9 00/11] sched, steal_governor: Introduce preferred CPUs and steal-driven vCPU backoff
  • 作者: Shrikanth Hegde (sshegde@linux.ibm.com, IBM)
  • 版本: v9, 共 11 个 patch
  • 规模: 跨 scheduler core、cpumask、sysfs、virt 驱动四大子系统
  • 修改文件: Documentation/scheduler/sched-arch.rst、include/linux/cpumask.h、kernel/cpu.c、kernel/sched/core.c、kernel/sched/fair.c、kernel/sched/sched.h、kernel/sched/debug.c、include/linux/sched.h、drivers/base/cpu.c、drivers/virt/Kconfig、drivers/virt/Makefile、drivers/virt/steal_governor.c(新)、MAINTAINERS、Documentation/driver-api/steal-governor.rst(新)
  • 代码统计: 约 350+ 行新增(不含 docs);核心驱动 steal_governor.c 约 230 行
  • Message-ID: 20260724140732.2683314-1-sshegde@linux.ibm.com
  • 完整性: 完整 11 patch 系列;reviewer 主要为 Yury Norov,另有 Mete Durlu 反馈;已收 v10 改动清单

补丁目的

在虚拟化(paravirt)环境下,提供一个内核级、协作式、可动态伸缩的 vCPU 压缩机制。

  • 当 VM 部署在 pCPU 过载的 hypervisor 上时,steal time 上升意味着频繁 vCPU 抢占。
  • 现有方案(CPU hotplug、isolated cpusets、显式 affinity)要么破坏用户亲和性、要么管理成本高。
  • 本系列引入 scheduler 一等公民 "preferred CPU" 状态,并配套 steal_governor 策略驱动,让 guest 在检测到高 steal time 时主动 fold 到一个较小的 preferred 子集上,从而降低 vCPU 抢占。

旧流程的问题

  • CPU hotplug / isolated cpusets:重型管理操作,需要拓扑重建,破坏 userspace CPU 亲和性。
  • 显式 task affinity:用户难以管理大量 vCPU 的 affinity。
  • 缺乏快速、协作式的内核级反应机制,无法在不影响亲和性的前提下动态伸缩。

新流程

  • Layer A(调度器机制):cpu_preferred_mask 严格 ⊆ cpu_active_mask。wakeup 路径 is_cpu_allowed 检查并通过 select_fallback_rq 选 preferred;tick 路径在当前 CPU 非 preferred 时用 stopper 推任务;load balance 仅在 cpu_preferred_mask 上进行。
  • Layer B(steal_governor 驱动):周期性采样系统 steal time;> high_threshold(默认 5%)preferred 减 1 核;< low_threshold(默认 2%)preferred 加 1 核;至少保留 1 个 preferred;preferred ⊆ active 必须成立。
  • 严格遵守用户 affinity:若用户把任务 exclusive 绑定到 non-preferred CPU,调度器不会破坏。

Patch 概览

  1. 01/11 文档:把概念写进 sched-arch.rst(后续挪到新建 sched-paravirt.rst)。
  2. 02/11 基础设施:cpu_preferred_mask + set_cpu_preferred() + cpu_preferred() + CONFIG_PREFERRED_CPU。
  3. 03/11 sysfs:/sys/devices/system/cpu/preferred 导出 cpumask。
  4. 04/11 wakeup 改造:is_cpu_allowed 调用 task_can_sched_on_preferred()。
  5. 05/11 load balance 改造:sched_balance_rq 仅扫描 preferred;_nohz_idle_balance 和 sched_balance_newidle 早退。
  6. 06/11 push 机制:sched_tick 检测非 preferred 时调度 stopper 迁出当前任务。
  7. 07/11 统计:nr_migrations_cpu_non_preferred 暴露到 proc_sched_show_task。
  8. 08/11 驱动骨架:drivers/virt/steal_governor.c + steal-governor.rst + MAINTAINERS。
  9. 09/11 模块参数:interval_ms、high_threshold、low_threshold 校验。
  10. 10/11 策略循环:compute_preferred_cpus_work() + decrease/increase_preferred_cpus()。
  11. 11/11 Kconfig 启用:CONFIG_STEAL_GOVERNOR 选 CONFIG_PREFERRED_CPU,默认 m。

关键实现

/* is_cpu_allowed: 优先选 preferred CPU */
static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
{
        if (!(p->flags & PF_KTHREAD)) {
                if (task_can_sched_on_preferred(cpu, p))
                        return false;
                return cpu_active(cpu);
        }
        if (task_can_sched_on_preferred(cpu, p))
                return false;
        return cpu_online(cpu);
}

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);
}
/* stopper: 基于 select_fallback_rq 选 preferred CPU */
static int sched_non_preferred_cpu_push_stop(void *arg)
{
        ...
        if (cpu_preferred(rq->cpu)) {
                rq->push_task_work_done = false;
                put_task_struct(p);
                return 0;
        }
        cpu = select_fallback_rq(rq->cpu, p);
        ...
        if (task_rq(p) == rq && task_on_rq_queued(p) && !is_migration_disabled(p)) {
                struct rq *dest_rq = __migrate_task(rq, &rf, p, cpu);
                if (rq != dest_rq)
                        schedstat_inc(p->stats.nr_migrations_cpu_non_preferred);
                rq = dest_rq;
        }
}
/* steal_governor 策略循环 */
static void compute_preferred_cpus_work(struct work_struct *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;
        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();
        schedule_delayed_work(&sg_ctx.work, sg_ctx.delay);
}
  wakeup                  tick                  load balance
     |                      |                        |
  is_cpu_allowed        sched_tick()           sched_balance_rq()
     |                      |                        |
  task_can_sched_on_   if !cpu_preferred       cpumask_and(sd span,
     preferred()         sched_push_curr_         cpu_preferred_mask)
     |                   non_preferred_cpu
     v                        |
  select_fallback_rq          v
  -> preferred CPU        stopper thread
                          -> __migrate_task

  steal_governor (delayed_work, interval_ms)
     |
     v
  get_system_steal_time() ---> delta_steal
     |                            |
     +--> steal_ratio ------------+
              |
   +----------+-----------+----------+
   |                      |          |
steal > high_th      steal <= low_th   other
   |                      |          |
decrease_preferred   increase_preferred   no-op
   CPUs                  CPUs
   |                      |
   v                      v
set_cpu_preferred   set_cpu_preferred
(cpus, false)       (cpus, true)

类比

自助餐厅:80 张桌子(pCPU),4 个旅行团各想占 60 张(vCPU 总 240 > 80)。走廊太挤(steal 飙升)时,协调员让每团自愿让出几张,于是通行变顺。steal_governor 就是吧台后定期巡视、协调桌位的服务员;push 机制则是把坐错桌的客人请到正确桌,避免已经迁出桌位的人又走回头路。

Highlight:风险与注意点

  1. 命名误导:Yury 指出 series 已从 arch-specific RFC 演变为通用机制,sched-arch.rst 与 arch 无关;建议改名或新建 sched-paravirt.rst。
  2. 代码去重遗留:v8 时 Yury 已指出 get_system_steal_time() 与 s390 hiperdispatch 中的 hd_calculate_steal_percentage() 重复,v9 仍未合并;Shrikanth 将通过 kcpustat_field_total() 统一抽象,并同步修改 fs/proc/uptime.c。
  3. 复杂度退化:单任务与全集都仅落在 non-preferred 集合时,wakeup 路径会变成 O(N^2),目前仅靠"罕见"为论据,缺乏上界估计。
  4. 协作成败:方案前提是所有 VM 都启用;若一台 VM 不启用,可能"吃掉"更多 CPU,需运维侧保证。
  5. NUMA 简化:策略未做 NUMA 切片,依赖用户侧假设 CPU 均匀分布在大节点间。
  6. 版本节奏:v9 在 v8 之后几天内发出,Yury 提醒应给 reviewer 更多时间;Shrikanth 解释是因为即将失去测试机。
  7. 只读模块参数 + 模块化:参数加载后不可改,作者强烈建议 =m;一旦所有 vCPU 都 pinned 到 non-preferred,load balance 完全消失,需要运维侧知晓。

版本变化(v8 -> v9)

v9 本身无大功能新增,明确为 v8 修订重发。后续 v10 计划项已公开:

  • 文档挪到新建 sched-paravirt.rst。
  • _nohz_idle_balance 移除 cpu_preferred 检查,让 nohz.next_balance 自然推进。
  • find_new_ilb 增加 cpu_preferred 选择(基于 Andrea Righi 系列)。
  • 新增 kcpustat_field_total() 公共函数,统一 s390 hiperdispatch 与本驱动的汇总。
  • 增加阈值限制说明、文档 nit。

一句话总结

本系列把 "preferred CPU" 作为 scheduler 一等 cpumask 状态,让 steal_governor 驱动按 steal time 动态收缩/扩展 preferred 集合,并在 wakeup、tick push、load balance 三处主动 fold 工作负载,既不破坏 userspace affinity 又能缓解 paravirt 下的 vCPU 抢占噪声。