0/3 已展开

LLM 分析

CFS 调度器:set_protect_slice() 简化被退回

系列概况

  • 标题: [PATCH] sched/fair: Drop min_vruntime() call from set_protect_slice()
  • 作者: Kayra Cizmeci kayracizmeci@gmail.com
  • 版本: v1(单 patch,无后续版本;作者在 thread 内主动撤回)
  • 规模: 1 file changed, 6 insertions(+), 1 deletion(-)
  • 修改文件: kernel/sched/fair.c
  • 代码统计: set_protect_slice() 函数体内1 处删除、4 行注释新增- Message-ID(首封): 20260810115422.38390-1-kayracizmeci@gmail.com
  • 完整性: 完整 3 封——patch / reviewer 反驳 / 作者主动 drop

补丁目的

set_protect_slice()用来设置 se->vprot,它是 RUN_TO_PARITY 调度特性在 pick 实体时防止该实体被过度抢占的"保护水位"。

作者的原意是:进入该分支时 slice 已经是 min(slice, se->slice),所以 slice < se->slicese->vruntime + calc_delta_fair(slice, se) 这条算出的路径必然是 min_vruntime() 的两个候选里更小的那一个。于是可以直接把 min_vruntime() 包装剥掉,省一次比较。

旧流程的问题

作者在 commit message 里隐含的假设是"se->vruntime + calc_delta_fair(slice, se) 一定在 se->deadline 之前"。Zhan Xusheng 在 review 里把这个假设拆穿了:

  1. place_entity()PLACE_DEADLINE_INITIAL(默认开)+ ENQUEUE_INITIAL 时会把 vslice /= 2,新实体的 se->deadline 只领先 半个 slice。
  2. 进入运行后,update_deadline()vruntime 还没过 deadline 时直接返回、不刷新;put_prev_entity() 把被抢占的实体重新入队时也不会续期 deadline。下一次 pick 时 se->deadline - se->vruntime 只是剩下那点 slice 余量。
  3. 因此只要 cfs_rq_min_slice(cfs_rq) 大于 (se->deadline - se->vruntime),直接算出的结果就 越过 se->deadline。此时只有 min_vruntime()vprot 拉回到 deadline 之前。
  4. 一旦 vprot > deadlineprotect_slice() 只比较 se->vruntimese->vprot,RUN_TO_PARITY 会把实体保护到其本应停止的时间之后,破坏 CFS 公平性。

换句话说,原作者看到的是"两个候选里算出来的更小",但他没看到"算出来的可能已经晚于 deadline"——min_vruntime 真正兜底的是这一层语义,而不是简单的"取小"。

新流程

作者在第 3 封邮件中明确请求 drop,patch 没有被合并。set_protect_slice() 维持原状:

slice = cfs_rq_min_slice(cfs_rq);
slice = min(slice, se->slice);
if (slice != se->slice) {
    vprot = min_vruntime(vprot, se->vruntime + calc_delta_fair(slice, se));
    se->vprot = vprot;
}

Patch 概览

只动 kernel/sched/fair.cset_protect_slice()

- vprot = min_vruntime(vprot, se->vruntime + calc_delta_fair(slice, se));
+ vprot = se->vruntime + calc_delta_fair(slice, se);

并在函数体内追加 4 行注释,断言"算出来的值始终在 se->deadline 之前,因此不需要比较"。

关键实现

  • 删除 min_vruntime() 包装,直接把算式赋给 vprot
  • 注释里写的"< se->slice ⇒ 计算值必在 min_vruntime 之前"这一步假定了 se->deadline 等于完整 slice,但这个前提并不总成立。
  • 测试只覆盖 Zen3 x86 的启动路径,没跑 RUN_TO_PARITY + 自定义 slice 的功能场景。

类比

se->deadline 想成电影票上的"入场截止时间",se->vprot 是"持票人受保护的最后时刻":

  • 简化 patch 的潜台词是"我提前到,肯定赶得上"。
  • 但这张票其实是"首映优惠票"——PLACE_DEADLINE_INITIAL 把时间砍到一半(half slice);而且放映已经开始了一段时间(实体已运行),截止时间比票面上看到的更靠前。
  • 真正稳妥的 min_vruntime 就是"在'你算出的时间'和'票面上的截止时间'之间取 更早 的那个",避免在票失效之后还把观众留在大厅里享受保护。
  new entity enqueue  (PLACE_DEADLINE_INITIAL = on, ENQUEUE_INITIAL = set)
        |
        v   deadline = vruntime + 0.5 * slice    <-- half-slice deadline!
        |
        v   entity runs a while        |
        v   update_deadline() returns early (vruntime < deadline)
        |
        v   preempted -> put_prev_entity() re-enqueues WITHOUT renewing deadline
        |
        v   by next pick: (deadline - vruntime) = small remainder < 0.5 * slice
        |
        v   cfs_rq_min_slice(cfs_rq) < se->slice   --> branch fires
        |
        v   computed = vruntime + calc_delta_fair(slice, se)
        |
        +--- WITHOUT min_vruntime : vprot = computed (PAST deadline)  BUG
        |
        +--- WITH    min_vruntime : vprot = min(computed, deadline)
 keeps vprot at/before deadline
       reviewer call path (Zhan Xusheng)
        |
        v
 place_entity() halves vslice under PLACE_DEADLINE_INITIAL
        |
        v
  update_deadline() no-op while vruntime < deadline
        |
        v
  put_prev_entity() re-enqueues without deadline renewal
        |
        v
  next pick sees (deadline - vruntime) << se->slice
        |
        v
  set_protect_slice() branch fires with cfs_rq_min_slice(cfs_rq) > remainder
        |
        v
  computed > deadline  ==>  min_vruntime() is the safety net, not redundancy

Highlight:风险与注意点

  • PLACE_DEADLINE_INITIAL 默认开启:所有 ENQUEUE_INITIAL 路径上 deadline 都只领先半 slice,作者没有覆盖这一分支。
  • put_prev_entity() 不续期 deadline:被抢占再入队后,剩余时间窗比表面看上去要小得多。
  • 这是语义错而不是性能回退:单纯做 boot test 抓不到;需要 trace 或功能测试覆盖 RUN_TO_PARITY + 自定义 slice 场景。
  • simplification 类 patch 必须双向读:只看 set_protect_slice() 单点、忽略 place_entity() / update_deadline(),极易得出错误的不变量。
  • 若作者未来再提 v2:必须先证明 se->vruntime + calc_delta_fair(slice, se) <= se->deadline 在所有路径上恒成立,否则应保留 min_vruntime() 作为兜底。
  • review 价值点:Zhan 通过读 place_entity() 反推出 half-slice deadline,再结合 put_prev_entity() 的非续期行为,才把原作者"两个候选里取较小"这种纯局部论证驳倒——这是函数级别优化 review 中典型的"跨函数求证"思路。

版本变化

仅 v1,thread 内由作者主动撤回;不存在 v2 或后续 spin。

一句话总结

Kayra 想省掉 set_protect_slice() 里的 min_vruntime(),Zhan 指出 PLACE_DEADLINE_INITIAL + 运行消耗会让 deadline 不等于完整 slice,算出的 vprot 可能越过 deadline、破坏 RUN_TO_PARITY,作者当场认错并请求 drop patch。