sched discussion
[PATCH] sched/core: Call wq_worker_tick() for the execution context
LLM 分析
sched/core:在执行上下文上调用 wq_worker_tick()
系列概况
- 标题:sched/core: Call wq_worker_tick() for the execution context
- 作者:Hui Su sh_def@163.com
- 版本:单封 PATCH(message-id 后缀 "-2" 暗示这是 1/2 系列中的第二封;本 thread 只展示这一封核心补丁)
- 规模:1 个补丁,1 个文件,+5/-4
- 修改文件:
kernel/sched/core.c - 代码统计:9 行 diff(5 行新增 / 4 行删除)
- Message-ID:20260902150208.1209922-2-sh_def@163.com
- 完整性:完整可用,包含 commit message、Fixes tag、base-commit、stats;Tejun Heo 在回复中给出
Acked-by并询问 Peter Zijlstra 路由方式
补丁目的
修复 workqueue 在 proxy execution 场景下的 CPU 时间记账错误。
引入 proxy execution 后,rq->donor 表示"调度上下文"(决定调度行为归属哪个任务),rq->curr 表示"执行上下文"(实际正在 CPU 上跑的 task_struct)。wq_worker_tick() 需要做两件事:记录 kworker 实际占用的 CPU 时间,并判断是否需要标记 WORKER_CPU_INTENSIVE 让它退出 worker pool。如果传 rq->donor,会误把 donor 当成 worker 处理,导致两类问题:
- 真正在跑的 kworker 没被记账,
WORKER_CPU_INTENSIVE检测延迟,pool 并发度不收敛; - donor 是 worker 但实际运行的是别的 task 时,把"并未执行"的 worker 错算成在跑。
修复办法是:wq_worker_tick() 必须传入 rq->curr(执行上下文),而调度器内部其他记账仍保留 rq->donor(调度上下文)。
旧流程的问题
补丁前 sched_tick() 调用 wq_worker_tick(donor),把调度上下文当成 worker 处理:
- kworker 在为普通 donor 执行时,跳过对 kworker 自身的 CPU 记账;
- donor 是 worker、但实际是另一个 task 在跑时,会对一个"并未运行"的 worker 误报 CPU 占用。
这会延迟 workqueue 对长任务的驱逐(WORKER_CPU_INTENSIVE)以及 worker pool 的并发收缩,间接推迟依赖该 worker 的内核工作与用户态操作。
新流程
把 wq_worker_tick() 改为传入 rq->curr,即真正在 CPU 上跑的执行上下文:
- 调度器自己的记账(PSI、perf event、调度统计等)继续用
rq->donor; - workqueue 钩子只关心"实际占 CPU 的那个 task"是谁,因此改用
rq->curr。
Patch 概览
- kernel/sched/core.c:
sched_tick()中新增curr = rq->curr;,调用wq_worker_tick()时改用curr而非donor;注释从 "accounting goes to the donor task" 改为 "scheduler accounting goes to the donor task",明确 donor 仅用于调度侧。
关键实现
void sched_tick(void)
{
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
struct rq_flags rf;
unsigned long hw_pressure;
u64 resched_latency;
/* scheduler accounting goes to the donor task */
struct task_struct *curr, *donor;
sched_clock_tick();
rq_lock(rq, &rf);
donor = rq->donor; /* 调度上下文:谁"代表"谁 */
psi_account_irqtime(rq, donor, NULL);
perf_event_task_tick();
/* workqueue 记账:谁真正在 CPU 上跑 */
curr = rq->curr; /* 执行上下文 */
if (!scx_switched_all()) {
rq->idle_balance = idle_cpu(cpu);
/* ... */
}
/* ... */
if (curr->flags & PF_WQ_WORKER)
wq_worker_tick(curr); /* 关键修复点 */
}
核心 hunk(仅说明结构,不翻译代码行):
@@ -5770,8 +5770,8 @@
- /* accounting goes to the donor task */
- struct task_struct *donor;
+ /* scheduler accounting goes to the donor task */
+ struct task_struct *curr, *donor;
@@ -5782,6 +5782,7 @@
+ curr = rq->curr;
@@ -5807,8 +5808,8 @@
- if (donor->flags & PF_WQ_WORKER)
- wq_worker_tick(donor);
+ if (curr->flags & PF_WQ_WORKER)
+ wq_worker_tick(curr);
修复 commit 引用:af0c8b2bf67b("sched: Split scheduler and execution contexts")拆分 donor/curr 时,wq_worker_tick() 一处忘了同步切换,所以打了 Fixes: 标签。
类比
把 rq->donor 想成"出资人 / 老板",rq->curr 想成"实际干活的工人"。wq_worker_tick() 关心的是工人今天到底搬了几块砖、是不是已经累得该换班——所以必须看工人(curr)而不是老板(donor)。调度器自己(PSI、cfs 统计)则关心公司整体的人力调配,所以仍用老板(donor)。这次补丁就是:把"老板出勤表"和"工人打卡表"两张表分开,别再混着填。
路由决策(Tejun 询问 Peter)
┌────────────────────────────┐
│ Hui Su: PATCH (bugfix) │
│ Fixes af0c8b2bf67b │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Tejun Heo: Acked-by │
│ "route via sched or wq?" │
└─────────────┬──────────────┘
│
┌─────────────┴───────────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ via sched │ │ via workqueue │
│ (Peter) │ │ (Tejun) │
└──────────────┘ └──────────────────┘
proxy execution 下 donor 与 curr 的关系
┌────────────────┐ ┌────────────────────┐
│ donor (调度) │ │ curr (执行) │
│ 谁代表谁调度 │ ──────► │ 真正占用 CPU 的 task │
└────────────────┘ proxy └────────────────────┘
│ │
│ 仅用于调度侧记账 │ 用于 workqueue 钩子
▼ ▼
psi_account_irqtime() wq_worker_tick()
perf_event_task_tick() WORKER_CPU_INTENSIVE 检测
旧流程 vs 新流程(sched_tick 内的 workqueue 钩子)
旧流程 (bug):
┌──────────────────────────────────────────────┐
│ if (donor->flags & PF_WQ_WORKER) │
│ wq_worker_tick(donor); ← 看错对象 │
└──────────────────────────────────────────────┘
新流程 (fix):
┌──────────────────────────────────────────────┐
│ curr = rq->curr; │
│ if (curr->flags & PF_WQ_WORKER) │
│ wq_worker_tick(curr); ← 真正在跑的 task │
└──────────────────────────────────────────────┘
Highlight:风险与注意点
Fixes:标签指向af0c8b2bf67b,说明这是一个回归(regression),需要在 stable 树上回溯;社区应尽快确认受影响版本范围。wq_worker_tick()在 tick 中调用,开销很小,但传错对象会让worker->sleeping/WORKER_CPU_INTENSIVE状态长期不刷新,可能表现为"长时间运行的 kworker 没有被踢出 pool",需要 workqueue 维护者确认是否有用户态可见症状。- 本补丁只改了
sched_tick()中的判断,但其他 workqueue 钩子(wq_worker_waking_up、wq_worker_sleeping、wq_worker_last_task等)也需要确认是否用了正确的 donor/curr;本补丁只覆盖 tick 路径,其他路径是否一致是后续 review 重点。 - Tejun Heo 在
Acked-by后问 Peter Zijlstra "通过 sched 还是 wq tree 收",说明补丁横跨两个子系统,可能还需 workqueue 维护者额外 ack,路径未定会延迟合入。 - 建议补充 selftest/workqueue 用例覆盖 proxy execution 场景,避免再次回归。
版本变化
本 thread 只展示单封 PATCH,无 v1/v2 演进历史。
一句话总结
修复 proxy execution 拆分后遗留的回归:wq_worker_tick() 应当看 rq->curr(执行上下文)而不是 rq->donor(调度上下文),否则 kworker 的 CPU 记账与 WORKER_CPU_INTENSIVE 检测会被错位延迟。