0/7 已展开

LLM 分析

sched/proxy:推迟 donor commit 直到 proxy 解析完成

系列概况

  • 标题:[PATCH RFC] sched/proxy: Defer donor commit until proxy resolution
  • 作者:Xukai Wang <kingxukai@zohomail.com>
  • 版本:v1 (RFC,单 patch)
  • 规模:1 patch;kernel/sched/core.c 共 70 行变动
  • 修改文件kernel/sched/core.c
  • 代码统计:1 file changed, 40 insertions(+), 30 deletions(-)
  • Message-ID20260707-sched-proxy-v1-0-5928bf6dedf0@zohomail.com(封面),patch 体 …-v1-1-…
  • 完整性:cover letter + patch 体 + 4 封回复(K Prateek Nayak / Xukai Wang 各两轮),讨论尚未收敛

补丁目的

重新划分 pick_next_task()__schedule() 在 proxy execution 场景下的职责边界。

旧模型下 pick_next_task() 选完任务立即通过 put_prev_set_next_task() 把 class 当前任务切换到所选任务;
启用 proxy execution 后所选任务可能就是被阻塞的 donor,__schedule() 还会再调 find_proxy_task() 解代理链。
若解析失败走 pick_again,此前那次投机性 class 切换就成了无效副作用,需要回滚。

新模型让 pick_next_task() 只返回候选任务,class commit 由 __schedule()find_proxy_task() 成功之后执行,避免把最终不运行的 blocked donor 先 set_next 再撤掉。

旧流程的问题

pick_next_task() 把"挑选"和"提交"耦合在一起。当 proxy 链解析失败时:

  1. class current state 已被投机性切换到一个最终不会运行的 blocked donor。
  2. __schedule()pick_again 时必须把这个错误切换撤销。
  3. proxy_deactivate() / proxy_migrate_task() 等路径中,下游代码假设被处理的 task 就是 rq->donor,必须先 proxy_resched_idle() 切到 idle 上下文再 block_task(),以免 donor->on_rq=0 后别处 wake 走它。

作者给出的实测数据:一次运行 find_call=75find_null=43,null 率 57.33%,其中绝大部分来自迁移/失活路径——也就是说大多数 find_proxy_task() 返回 NULL 的场景最终选择被放弃,而投机性 set_next 已经发生过。

新流程

pick_next_task() 不再调用 put_prev_set_next_task(),只返回候选;__schedule() 拿到候选后:

struct task_struct *donor = next;
donor->blocked_donor = NULL;
if (unlikely(donor->is_blocked))
    next = find_proxy_task(rq, donor, &rf);

put_prev_set_next_task(rq, prev_donor, donor);
rq_set_donor(rq, donor);

只在 find_proxy_task() 成功后才执行真正的 class commit。
proxy 候选不再一定是已提交的 rq->donorproxy_deactivate() 据此区分两种情形:仍是 rq->donor 才走 proxy_resched_idle(),否则直接 block_task()
proxy_migrate_task() 仍把已提交 donor 切到 idle 再放锁,保持迁移路径上 rq->donor 与 class 当前状态一致。

+------------------+        +-----------------------+
| pick_next_task() |        |      __schedule()     |
|  returns p only  | -----> |  donor = next         |
|  NO class commit |        |  find_proxy_task()    |
+------------------+        +-----------+-----------+
                                        |
                                        v
                            +-----------+-----------+
                            | donor==NULL?          |
                            |   yes -> pick_again   |
                            |   no  -> commit donor |
                            +-----------+-----------+
                                        |
                                        v
                       +--------------------------------+
                       | put_prev_set_next_task(prev,donor)|
                       | rq_set_donor(rq, donor)        |
                       +--------------------------------+
                                        |
                                        v
                       +--------------------------------+
                       | switch to actual next (owner) |
                       +--------------------------------+

Patch 概览

  1. __pick_next_task()pick_next_task() 内删除 put_prev_set_next_task() 调用;out_set_next 标签改为 out_return_next
  2. proxy_deactivate() 加分支 if (donor == rq->donor) proxy_resched_idle(rq);,未提交候选直接走 block_task()
  3. proxy_migrate_task() 注释更新为"放弃当前 pick,把已提交 donor 切到 idle 再放锁"。
  4. __schedule() 引入 donor = next 局部变量,把 put_prev_set_next_task() + rq_set_donor() 移到 find_proxy_task() 之后。

关键实现

  • pick_next_task() 现在只做"挑候选",不触碰 class state。
  • __schedule() 成为唯一做 class commit 的地方,紧贴 rq_set_donor()
  • 候选未提交时省去一次 proxy_resched_idle(),因为它仍是 queued 任务,on_rq 仍为 1,可直接 block_task()
  • 迁移路径仍调 proxy_resched_idle(),避免放锁后其他 CPU 通过 rq->donor/cfs_rq->curr 引用一个已迁移走的 task。

类比

pick_next_task() 想成餐厅的"前台领位":

  • 旧流程:领位直接把客人领到桌前、登记为当前顾客,然后才发现客人其实在等别人的位子(mutex owner 还没好),于是又把客人请走、请下家。桌位状态被错乱地翻动过一遍。
  • 新流程:领位先口头确认候选("可能是这位"),等后厨确认 proxy chain 真实可达(mutex owner 真能跑)后再正式落座登记;如果链断了就直接换候选,桌位状态从没经历过那次错误的翻动。

rq->donor 就像桌位登记表;旧流程中它会被错误地翻到 blocked donor 那一页,新流程只在最终决定后翻页。

Highlight:风险与注意点

  1. rq->donor 与候选 task 脱钩pick_next_task() 选出的 task 不再同步到 rq->donor,任何依赖二者相等的下游路径都需要 review(如 CFS 的 cfs_rq->currh_curr 更新时机)。
  2. find_proxy_task() 内部对 rq->donor 的隐式依赖:Prateek 提到请 John Idias 复查是否有尚未合并的 proxy 子系列依赖此不变量。
  3. 锁内 pick 收敛反论:Prateek 指出只要 rq_lock 持锁、pick 总会收敛到同一 donor,过渡态不构成性能问题;作者需要 benchmark 或 warn 数据证明延迟 commit 的真实收益。
  4. class 切换时机后移vruntime/update_curr 等记账顺序会改变,CFS/DL 的公平性回归测试需要重跑。
  5. proxy chain 迁移主导 null 路径:作者实测 null_migrate=2131,提示真正能压平 null 的杠杆是减少链上迁移,而非 commit 推迟;补丁可能只解决 warn 触发而非吞吐。

一句话总结

pick_next_task() 从"边选边 commit"改为"只挑不 commit",让 __schedule()find_proxy_task() 解析成功后做唯一的 class commit,避免把最终不运行的 blocked donor 投机性切换到 class current state。