sched discussion
[PATCH RFC v2] sched/proxy: Defer donor commit until proxy resolution
LLM 分析
sched/proxy:延迟 donor 提交直到代理解析
系列概况
- 标题:
[PATCH RFC v2] sched/proxy: Defer donor commit until proxy resolution - 作者:Xukai Wang
<kingxukai@zohomail.com> - 版本:v2(RFC)
- 规模:1 个 patch
- 修改文件:
kernel/sched/core.c - 代码统计:+49 / -41
- base-commit:
19b7bdc3a1550ab2550427c33395bec7caeaf72d - change-id:
20260707-sched-proxy-4404b6e97ad9 - Message-ID:
- cover
20260713-sched-proxy-v2-0-729170082633@zohomail.com - patch
20260713-sched-proxy-v2-1-729170082633@zohomail.com
- cover
- 完整性:6 封邮件 = 1 cover + 1 patch + 4 回复(作者复述、John Stultz 提问、作者再次引用、kernel test robot 编译告警)
补丁目的
proxy execution 启用后,pick_next_task() 既选任务又通过 put_prev_set_next_task() 把候选 commit 到调度类的 current 状态。但选出的可能是 blocked donor,真正可以运行的 owner 要由 find_proxy_task() 解析代理链才能得到。若 find_proxy_task() 返回 NULL,__schedule() 走 pick_again,可此时已经发生过一次投机性的 set_next(),需要再撤销。
本 RFC 把“选”与“commit”解耦:pick_next_task() 只返回候选;put_prev_set_next_task() 与 rq_set_donor() 推迟到 __schedule() 中 find_proxy_task() 解析成功之后。
旧流程的问题
pick_next_task() 兼任“选 + commit”,与 __schedule() 中“proxy 解析后才知道真正运行哪个任务”相互交织。当候选是 blocked donor 时,find_proxy_task() 失败走 pick_again,但调度类的 current 状态已经被投机性切换,必须在重试前撤销,造成一次多余的 put_prev/set_next。
新流程
pick_next_task() 退化为纯选择器。__schedule() 拿到候选后先解析 proxy 链;解析成功且不是退回 idle 才执行真正的 commit。失败路径不再留有“先 commit 再撤销”的痕迹,重试只需重新选。
关键实现
__pick_next_task()删除put_prev_set_next_task(rq, rq->donor, p)调用。pick_next_task()出口out_set_next改名out_return_next,去掉末尾的 commit 段。proxy_resched_idle()内新增zap_balance_callbacks(rq),因为该函数做了一次 idle commit,回调必须在这里清掉。proxy_deactivate()增加if (donor == rq->donor)守卫:只有当传入的 donor 仍是已提交 donor 时才proxy_resched_idle();否则只作为 queued 候选,可直接block_task()。proxy_migrate_task()注释改写:迁移的是 proxy 链中某个任务,候选不一定已 commit;丢锁前依旧把已 commit 的 donor 切到 idle,保持 rq 与 class 状态定义明确。find_proxy_task()增加出参*donor,把调用方持有的候选交回__schedule()。__schedule()中donor = next;只在find_proxy_task()返回非 NULL 且非 idle 后执行:
外层 NULL/idle 路径不再无条件put_prev_set_next_task(rq, rq->donor, donor); rq_set_donor(rq, donor);zap_balance_callbacks()。
static void __sched notrace __schedule(int sched_mode)
{
...
donor = next; /* only a candidate, no commit yet */
donor->blocked_donor = NULL;
if (unlikely(donor->is_blocked)) {
next = find_proxy_task(rq, donor, &rf);
if (!next) /* candidate stays uncommitted */
goto pick_again;
if (next == rq->idle) /* fall back to idle, no commit */
goto pick_again;
}
/* commit only after proxy chain is resolved */
put_prev_set_next_task(rq, rq->donor, donor);
rq_set_donor(rq, donor);
...
}
Patch 概览
v1 -> v2 关键改动:
- 把
put_prev_set_next_task()/rq_set_donor()从pick_next_task()内部迁到__schedule()中 proxy/non-proxy 分支之后。 - 把
zap_balance_callbacks()从外层 NULL/idle 路径收回proxy_resched_idle(),紧邻 idle commit。 proxy_deactivate()/proxy_migrate_task()注释与条件更新,反映“候选 != 已提交 donor”。find_proxy_task()增加*donor出参以解耦选择与解析。
旧流程 vs 新流程
OLD FLOW (commit happens inside pick_next_task)
=================================================
|
v
+------------------+
| pick_next_task() |
| - select task |
| - set_next() | <-- commit before proxy resolution
+--------+---------+
|
v
+------------------+
| find_proxy_task()|
+--------+---------+
|
NULL? --+--> go back to pick_again
| but class current is already on
| a blocked donor (must undo set_next)
v
retry
NEW FLOW (commit moved into __schedule after resolution)
========================================================
|
v
+------------------+
| pick_next_task() |
| - select only | <-- no commit, return candidate
+--------+---------+
|
v
+------------------+
| find_proxy_task()|
+--------+---------+
|
NULL? --+--> pick_again (candidate not committed,
| nothing to undo)
v
not NULL and not idle?
|
v
+------------------+
| put_prev_set_next|
| rq_set_donor() | <-- commit only on success
+------------------+
类比
把调度器想成餐厅的点单流程:
- 旧流程:服务员一边报菜名一边把菜直接端上桌(pick + set_next 一体)。如果临时发现后厨这道菜还没做(proxy 解析失败),服务员得先把端上来的菜撤回去,再去换别的菜——既浪费动作,又让“已上桌”这个状态短暂错乱。
- 新流程:服务员先口头确认“这位客人点 X”(只选不 commit),确认 X 已经做好(proxy 解析成功)才端上桌。如果中途发现 X 还没好,直接换菜即可,不必先撤。
代理(proxy)则是“朋友托我先点,他随后就到”:调度器先用 donor 占位,确认 owner 真的可以上桌再端。
Highlight:风险与注意点
- John Stultz 要求下一版 commit message 顶部回答“为什么
set_next_task(donor)有问题?”——是单纯优化pick_again时的重复put_prev/set_next,还是另有收益?影响是否可被 benchmark 测量? - kernel test robot 在 alpha-defconfig(GCC 16.1.0,W=1)下报
kernel/sched/core.c:5103:13: warning: 'zap_balance_callbacks' defined but not used:本 patch 把外层zap_balance_callbacks()调用全部收进proxy_resched_idle(),但函数定义未变,alpha 配置路径上看不到任何调用方,需要加__maybe_unused或留至少一个外部调用点。 - 本 patch 与更上游 proxy execution RFC 系列耦合:
find_proxy_task()新增的*donor出参、proxy 链解析语义需要 John Stultz 确认上游没有反向依赖(即是否依赖 rq->donor 在解析前就被更新)。 - 选择与 commit 解耦后,任何在
pick_next_task()中“信赖已 commit 状态”的旁路代码(如 hrtick、fair load tracking)必须确认不会提前看到未经 commit 的候选。 proxy_resched_idle()现在内含zap_balance_callbacks();调用顺序必须保证“idle commit -> zap -> 返回”,否则外层 lockdep 或回调链表可能产生双重 zap 风险。
版本变化
v1 -> v2:
put_prev_set_next_task()/rq_set_donor()推迟到 proxy/non-proxy 分支之后再统一提交。zap_balance_callbacks()从外层 NULL/idle 路径收回,统一放进proxy_resched_idle()。proxy_deactivate()/proxy_migrate_task()注释由“必须先proxy_resched_idle”改为“仅当仍是已提交 donor 时才需要”。find_proxy_task()出参donor上提到__schedule()持有。
一句话总结
把 pick_next_task() 改成“只选不交”,把 put_prev_set_next_task() 与 rq_set_donor() 推迟到 __schedule() 中 find_proxy_task() 解析成功之后执行,从而消除 proxy 解析失败时“投机提交再撤销”的浪费与状态混乱。