0/5 已展开

LLM 分析

CFS 带宽控制:移除 cpu.max 与 cpu.max.burst 的写入顺序依赖

系列概况

  • 标题:v1 [PATCH 0/2] sched/fair: Reset incompatible burst on quota change → v2 [PATCH v2 0/3] sched/fair: remove quota/burst write-order dependency
  • 作者:Zhe Liu <liuzhe1@kylinos.cn>
  • 版本:v1(2 patches,2026-08-20)→ v2(3 patches,2026-09-04)
  • 规模:v1 共 4 文件 +90/-8;v2 共 5 文件 +59/-13
  • 修改文件
    • kernel/sched/core.c
    • kernel/sched/fair.c
    • tools/testing/selftests/cgroup/test_cpu.c
    • Documentation/admin-guide/cgroup-v2.rst
    • Documentation/scheduler/sched-bwc.rst
  • Message-ID 主线
    • 20260820033218.214259-1..3-liuzhe1@kylinos.cn(v1 三封)
    • aobnZEh1hfIBhswD@localhost.localdomain(Michal Koutný 评审)
    • 20260826030006.171041-1-liuzhe1@kylinos.cn(作者回复,预告 v2 设计)
    • 20260904062013.504236-1..4-liuzhe1@kylinos.cn(v2 四封)
    • ce007c25-8d49-4df4-9508-3af911f90eb7@linux.dev(Tao Cui 测试反馈)
  • 完整性:cover letter + v1/v2 patch + 评审 + 作者回复 + 测试结果均齐全;Fixes: f4183717b370 指向上游引入 burst 的提交,回归定位清晰。

补丁目的

修复 cgroup v1/v2 CPU bandwidth 中 quota 与 burst 的写入顺序耦合。

当前 tg_set_bandwidth() 把 burst 的合法性挂在「当前 quota」上,导致两类失败:

  1. unlimited quota 下先写大 burst,再写有限 quota,因 burst_us > quota_us-EINVAL 拒绝;
  2. quota 与 burst 同时增大、burst 先写也会失败。

v1 方案:在 quota 写入路径里把不兼容 burst 直接清零。
v2 方案:保留用户配置的 burst 值,在 CFS runtime refill 时用 min(burst, quota) 裁剪生效值,采纳 Michal Koutný 与 Sashiko「不让用户配置值丢失」的建议。

旧流程的问题

// kernel/sched/core.c tg_set_bandwidth()
if (quota_us != RUNTIME_INF && (burst_us > quota_us ||
    burst_us + quota_us > max_bw_runtime_us))
    return -EINVAL;

burst 在「写入时」就被强制 ≤ quota。复现路径:

# echo 100000000 > cpu.max.burst
# echo "50000 100000" > cpu.max
sh: write error: Invalid argument

quota 实际没生效,cgroup 仍为 unlimited;用户必须先清零 burst 再重试,运维/Docker/K8s 工具被迫引入写入顺序知识。

新流程

把 burst 的「配置合法性」与「运行时生效上限」拆开:

  • 写入路径:仅校验 burst_us > max_bw_runtime_us,去掉与 quota 的耦合;
  • refill 路径:用 min(burst, quota) 把运行时生效 burst 贴 quota 上限。
// kernel/sched/core.c
- if (quota_us != RUNTIME_INF && (burst_us > quota_us ||
-     burst_us + quota_us > max_bw_runtime_us))
+ if (burst_us > max_bw_runtime_us)
      return -EINVAL;

// kernel/sched/fair.c __refill_cfs_bandwidth_runtime()
- cfs_b->runtime = min(cfs_b->runtime, cfs_b->quota + cfs_b->burst);
+ cfs_b->runtime = min(cfs_b->runtime,
+                      cfs_b->quota + min(cfs_b->burst, cfs_b->quota));

Patch 概览

  • v1 PATCH 1/2:新增 tg_burst_on_quota_change(),在 cpu_quota_write_s64cpu_max_write 调用处把不兼容 burst 清零;新增 selftest test_cpucg_max_burst_reset
  • v1 PATCH 2/2:同步 cgroup-v2.rstsched-bwc.rst 文档。
  • v2 PATCH 1/3:删除 tg_set_bandwidth() 中 burst 对 quota 的耦合校验,改在 __refill_cfs_bandwidth_runtime()min(burst, quota)
  • v2 PATCH 2/3:新增 test_cpucg_max_burst selftest,覆盖 burst-first 与 quota-first 两种顺序,断言 cpu.max.burst 读出的「配置值」在 quota 变化前后不变。
  • v2 PATCH 3/3:文档改述为「配置值保留 + 运行时裁剪」,并明确「写入顺序无关」。

关键实现

v1引入一个 quota 写入时的临时 helper:

static u64 tg_burst_on_quota_change(u64 quota_us, u64 burst_us) {
    if (quota_us == RUNTIME_INF || quota_us > max_bw_runtime_us)
        return burst_us;
    if (burst_us > quota_us ||
        burst_us > max_bw_runtime_us - quota_us)
        return 0;
    return burst_us;
}

v2 把它彻底删掉,换成「写入只卡总上限、refill 时裁剪」的两层模型——这样 cpu.max.burst 始终展示用户写入值,但实际生效值由 CFS 在每次 refill 时重新计算。

类比

把它想成银行账户的「月额度(quota)」与「透支缓冲(burst)」:v1 像「调月额度时直接把透支缓冲清零」,用户体感是数字被悄悄改写;v2 改成「保留你填写的透支数字,但实际可用透支上限不会超过新额度」,用户看到自己填的数字不变,系统按新额度自动封顶——既不丢信息也不超发。

ASCII 流程图

        用户向 cpu.max / cpu.max.burst 写入
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
   burst 先写                quota 先写
  (quota 当时 unlimited)      (任意 burst)
        │                       │
        ▼                       ▼ v1: tg_set_bandwidth 拒绝 / burst→0
   v2: tg_set_bandwidth 接受,burst 留存
        │                       │
        └───────────┬───────────┘
                    ▼
      __refill_cfs_bandwidth_runtime()
                    │
   cfs_b->runtime = min(runtime,
                        quota + min(burst, quota))
                    │
                    ▼
        实际可用 burst ≤ 当前 quota

Highlight:风险与注意点

  • 配置值 ≠ 生效值cpu.max.burst 读出仍是用户原值,但实际生效值被 min(burst, quota) 封顶。运维/K8s 在写入后立即检查可能误判,文档需明确这一语义差。
  • stable 回溯风险:v2 patch 1/3 带 Cc: stable,运行时语义变化涉及长期维护分支;依赖高峰 burst 的数据库、批处理等场景需要重点回归。
  • 旧校验契约被打破f4183717b370 引入 burst 时要求 burst ≤ quota,v2 改为「可超过、运行时裁剪」;依赖 EINVAL 做合法性自检的旧脚本会受影响。
  • v1 selftest 弃用:v1 的 test_cpucg_max_burst_reset 假设 burst 会被重置为 0,v2 已不适用,迁移时需删除或改写,避免回归误报。
  • Tao Cui 测试反馈截断:第 10 封邮件正文仅报告在 next-20260903 跑了 selftest,但结论部分被截断,需关注后续邮件确认全部通过。

版本变化

v1 → v2 关键改动:

  1. 策略反转:从「写入时把 burst 重置为 0」改为「写入保留 + refill 裁剪」,由 Michal 评审触发;
  2. 去掉 helpertg_burst_on_quota_change() 删除,校验分散到 tg_set_bandwidth__refill_cfs_bandwidth_runtime
  3. 新增顺序无关 selftesttest_cpucg_max_burst 同时验证 burst-first 与 quota-first;
  4. 文档改述:从「incompatible burst 被清零」改为「configured burst 保留、运行时封顶」;
  5. 拆 3 patch:v1 把代码与文档合成单 patch,v2 把 selftest 独立成 PATCH 2/3;
  6. 新增 Cc: stable:v2 把修复标记为可回溯到稳定分支。

一句话总结

把 CFS bandwidth 中 burst 的合法性校验从「写入时」搬到「refill 时」,让 quota/burst 的任意写入顺序都生效且不丢用户配置。