sched discussion
[PATCH RFC] sched/proxy: Defer donor commit until proxy resolution
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-ID:
20260707-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 链解析失败时:
- class current state 已被投机性切换到一个最终不会运行的 blocked donor。
__schedule()走pick_again时必须把这个错误切换撤销。- 在
proxy_deactivate()/proxy_migrate_task()等路径中,下游代码假设被处理的 task 就是rq->donor,必须先proxy_resched_idle()切到 idle 上下文再block_task(),以免donor->on_rq=0后别处 wake 走它。
作者给出的实测数据:一次运行 find_call=75,find_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->donor,proxy_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 概览
__pick_next_task()与pick_next_task()内删除put_prev_set_next_task()调用;out_set_next标签改为out_return_next。proxy_deactivate()加分支if (donor == rq->donor) proxy_resched_idle(rq);,未提交候选直接走block_task()。proxy_migrate_task()注释更新为"放弃当前 pick,把已提交 donor 切到 idle 再放锁"。__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:风险与注意点
rq->donor与候选 task 脱钩:pick_next_task()选出的 task 不再同步到rq->donor,任何依赖二者相等的下游路径都需要 review(如 CFS 的cfs_rq->curr、h_curr更新时机)。find_proxy_task()内部对rq->donor的隐式依赖:Prateek 提到请 John Idias 复查是否有尚未合并的 proxy 子系列依赖此不变量。- 锁内 pick 收敛反论:Prateek 指出只要
rq_lock持锁、pick 总会收敛到同一 donor,过渡态不构成性能问题;作者需要 benchmark 或 warn 数据证明延迟 commit 的真实收益。 - class 切换时机后移:
vruntime/update_curr等记账顺序会改变,CFS/DL 的公平性回归测试需要重跑。 - 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。