0/12 已展开

LLM 分析

core-sched:修补 pick_next_task 自递归与重整 balance 路径

系列概况

  • 标题:[PATCH 0/7] sched: core-sched fixes and balancing
  • 作者:Peter Zijlstra (Intel) peterz@infradead.org
  • 版本:作者自述 "vastly expanded",未保留前版号;前版在 lore 链接 [1](2026/06/24)
  • 规模:7 patch + cover letter,外加 4 封评审回复(Tejun ×2、Peter ×1、Aaron ×1)
  • 修改文件:kernel/sched/{core,fair,rt,deadline,idle,stop_task}.c、sched.h
  • 代码统计:净删约 80~120 行;7/7 一项即 -88/+25,整体以删减为主
  • Message-ID20260828101659.812011872@infradead.org
  • 完整性:完整,0/7~7/7 全在,加 3 封 review

补丁目的

起源是 Peter 自己把 core-sched 改坏,随后和 TJ 各自补补丁收拾残局。三件事:

  1. 修掉 pick_next_task() 释锁后被兄弟 CPU 递归进入、踩乱 core_pick/core_cookie 状态造成 NULL deref。
  2. 统一 RQCF_UPDATED 时钟标志更新语义,避免 pick_task() 释锁再锁回后读到陈旧 clock 触发 set_next_task() 错误。
  3. 重整 balance 与 pick_task 职责:删除 sched_class::balance(),把 balance_{rt,dl}() 塞进 pick_task_{rt,dl}();重新允许 newidle 在 core-sched 下执行,但通过 task_on_core() 禁止偷已被选中的任务。

旧流程的问题

  • pick_task() 内部可能因 newidle balance 释锁,期间另一个 sibling 进入 pick_next_task() 把 core-wide 状态踩掉,回来 NULL 解引用。
  • 旧代码用 core_clock_updated 布尔加多处"猜"时钟是否更新过的逻辑;pick_task() 释锁再锁回时 RQCF_UPDATED 被无端清除,sibling 拿到陈旧 clock。
  • core-sched 下 newidle 被关,又出现 SMT0 选中 A、SMT1 没活做 newidle、把 A 迁移又选中的重复挑任务 bug。
  • sched_class::balance() 与 pick_task() 功能重叠;prev_balance() 只对单 rq 调用,core-sched 下漏掉 sibling 平衡机会。

新流程

  • 进入 pick_next_task() 时缓存 core_task_seq 局部变量;只要 pick_task() 后 seq 变了,就视为兄弟借机偷跑,goto restart。
  • 新增 opt_update_rq_clock():仅 RQCF_UPDATED 未置位时才更新;每个 sibling 先调一次,循环出口处给 this_rq 强制重置 RQCF_UPDATED 补偿 sibling lock+unlock 吞 flag 的副作用。
  • 引入 task_on_core(rq, p):若任务已是本核 core_pick,affine_move_task / can_migrate_task / task_is_pushable 都拒绝迁移它,禁止偷取。
  • pick_task_{rt,dl}() 开头 rq_modified_begin() → balance_{rt,dl}() → rq_modified_above() 判定,期间其他类别任务变动则返回 RETRY_TASK。
  • pick_task_fair() 空队列时调 sched_balance_newidle()(不再返回魔法数字),平衡后用 rq_modified_above 判定 RETRY_TASK;解锁只在需要跨域扫描时发生。

Patch 概览

#主题类型
1/7pick_next_task() 自递归 via core_task_seqbugfix
2/7opt_update_rq_clock() 重写时钟更新bugfix + cleanup
3/7core-sched 允许 newidle,task_on_core()bugfix + feature
4/7balance_{rt,dl}() 早退cleanup
5/7重排 pick_task_fair/newidlecleanup + 重开 core-sched balance
6/7unlock 下推到 sched_balance_rq 深处cleanup
7/7删除 sched_class::balance()refactor / cleanup

关键实现

patch 1/7 + 2/7 在 pick_next_task() 的核心循环:

seq = ++rq->core->core_task_seq;     /* 缓存 seq */

for_each_cpu_wrap(i, smt_mask, cpu) {
    struct rq_flags rf_i = *rf;
    rq_i = cpu_rq(i);

    opt_update_rq_clock(rq_i);

    p = pick_task(rq_i, &rf_i);
    if (unlikely(seq != rq->core->core_task_seq ||
         WARN_ON_ONCE(p == RETRY_TASK)))
        goto restart;                 /* 兄弟借机重入就重来 */
    rq_i->core_pick = p;
}

/* sibling 若 LOCK+UNLOCK 吞了 RQCF_UPDATED, 这里重置 */
rq->clock_update_flags |= RQCF_UPDATED;

pick_task_fair() 入口统一改用 rq_modified_above 报告 RETRY_TASK:

if (unlikely(!cfs_rq->h_nr_queued)) {
    rq_modified_begin(rq, &fair_sched_class);
    sched_balance_newidle(rq, rf);
    if (rq_modified_above(rq, &fair_sched_class))
        return RETRY_TASK;
    if (!cfs_rq->h_nr_queued)
        return NULL;
}

类比

把一对 SMT 兄弟核想成合住一间卧室的双胞胎:core-sched 就是妈妈要求两个孩子行动同步。旧流程里,妈妈去隔壁接电话(drop rq lock),回来发现孩子已经自己挑好衣服出门(sibling 抢跑 pick_next_task),按旧名单解引用 -> NULL deref。新流程里妈妈记下出门顺序号 core_task_seq,回来时发现顺序号变了就知道被人抢先,重新分配(goto restart)。task_on_core() 就像把已被点名的孩子的鞋藏起来,不让别人穿走。sched_class::balance() 原本是另请的保姆,结果她和妈妈抢活还漏看一个孩子,索性把保姆解雇,让妈妈一个人干完所有事。

Highlight:风险与注意点

  • 2/7 RQCF_UPDATED 时机:Tejun 指出 sibling rq 不一定因 schedule/tick 清 flag,opt_update_rq_clock 可能用陈旧 clock;Peter 已认账,下版应在 unlock 时清 flag 而非 lock 后清。
  • 1/7 no-sync fast-path cookie 失效:Aaron 提的补丁显示,即便 pick_task 没返回 RETRY_TASK,若兄弟在释锁期建了 core-wide cookie,本 CPU 也需 goto restart,否则会选中已被别人 cookie 化的任务。
  • 6/7 收益数据缺失:commit log 自标 "XXX needs numbers",sched_balance_rq unlock 下推究竟减少多少 lock 抖动需要 micro-benchmark。
  • 7/7 diffstat 与正文不一致:Tejun 抓到 diffstat 写 fair.c 但 patch body 无 fair.c 改动,rebase 时需保持一致。
  • core-sched 重开 newidle balance(5/7):即便 task_on_core() 阻止偷取,仍需复测 Aaron/Prateek test case 验证不同 SMT 拓扑。

版本变化

作者明确本版 "vastly expanded",未严格保留版本号。前版(2026/06/24)只有 fix,本版新增:

  • 5/7 重排 pick_task_fair/newidle,重开 core-sched newidle balance
  • 6/7 unlock 下推,上版未涉及
  • 7/7 删除 sched_class::balance(),上版未涉及
+-------------------+
| pick_next_task()  |
+---------+---------+
          |
   seq = ++core_task_seq
          |
+---------v---------+
| for_each sibling: |
|  opt_update_clock |
|  pick_task(&rf_i) |
+---------+---------+
          |
   seq mismatch? --yes--> goto restart
          | no
+---------v---------+
| restore RQCF_UPDATED|
+---------+---------+
          |
          v
    set_next_task

一句话总结

Peter 用 7 patch 系列同时收拾 core-sched 的 NULL deref、RQCF_UPDATED 陈旧 clock、newidle 偷取与 sched_class::balance() 冗余四件事,但 RQCF_UPDATED unlock 时机与 no-sync cookie 失效两点还要在 v2 修补。