0/2 已展开

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-ID20260902150208.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 处理,导致两类问题:

  1. 真正在跑的 kworker 没被记账,WORKER_CPU_INTENSIVE 检测延迟,pool 并发度不收敛;
  2. 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.csched_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_upwq_worker_sleepingwq_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 检测会被错位延迟。