0/16 已展开

LLM 分析

kthread:移除 worker->task 自赋值 FIXME

系列概况

  • 标题:[RESEND PATCH 0/5] kthread: remove worker->task self-assignment
  • 作者:Bradley Morgan brads@mainlining.org
  • 版本:v1(RESEND,把首次拆成 5 个独立 thread 的 patch 重新合成一个系列)
  • 规模:5 个 patch + 后续 11 封 maintainer 回复
  • 修改文件:6 个 — drivers/media/pci/ivtv/ivtv-driver.c、drivers/media/pci/ivtv/ivtv-driver.h、drivers/net/ethernet/microchip/encx24j600.c、drivers/tty/serial/sc16is7xx.c、kernel/sched/cpufreq_schedutil.c、kernel/kthread.c
  • 代码统计:21 insertions(+), 63 deletions(-)
  • Message-ID:20260829161150.16301-1-include@grrlz.net
  • 完整性:cover letter 与 5 个 patch 齐全,编号一致;RESEND 后无 v2

补丁目的

kthread_worker_fn() 里挂着一段长达十年的 FIXME:worker 启动时把 worker->task = current,用来兜底那些用 kthread_run(kthread_worker_fn, ...) 老 API 的驱动。新 API kthread_create_worker*() 在 worker 启动前就把 task 设好了,于是这段自赋值变成死代码。

系列先把 4 个还在用老 API 的驱动(ivtv、encx24j600、sc16is7xx、cpufreq_schedutil)迁到新 API,再删除 FIXME。声明无功能变更。

旧流程的问题

驱动自己 embed struct kthread_worker,再调 kthread_init_worker + kthread_run(kthread_worker_fn, ...),并另外保存一个 struct task_struct *xxx_task 给后续 sched_set_fifokthread_stop 用。

  driver side                     kernel kthread_worker_fn
  -----------                     -------------------------
  itv->irq_worker (embed)         /* FIXME: ten-year-old note */
  itv->irq_worker_task = NULL     WARN_ON(worker->task && worker->task != current);
  kthread_init_worker(...)        worker->task = current;          // fall-back
  itv->irq_worker_task =
      kthread_run(kthread_worker_fn, &itv->irq_worker, "%s", ...)
  sched_set_fifo(itv->irq_worker_task)
  ...
  kthread_stop(itv->irq_worker_task)
  kthread_flush_worker(&itv->irq_worker)

每个驱动都重复「embed worker + 单独保存 task」两件事;自赋值 + WARN_ON 变成一段「文档式兜底」,没人真的删。

新流程

驱动只持有 struct kthread_worker *worker,由 kthread_run_worker() / kthread_create_worker() 一并创建并启动;task 句柄从 worker->task 读。

  driver side                     kernel kthread_create_worker
  -----------                     -----------------------------
  itv->irq_worker = NULL
  itv->irq_worker =
      kthread_run_worker(0, "%s", ...)   worker->task = <task>;   // set before start
  sched_set_fifo(itv->irq_worker->task)
  ...
  kthread_flush_worker(itv->irq_worker)
  kthread_destroy_worker(itv->irq_worker)  // internally stops task

驱动里不再需要单独的 *_task 指针。

Patch 概览

#文件转换目标
1/5drivers/media/pci/ivtv/ivtv-driver.{c,h}kthread_init_worker + kthread_run(kthread_worker_fn) -> kthread_run_worker
2/5drivers/net/ethernet/microchip/encx24j600.c同上
3/5drivers/tty/serial/sc16is7xx.c同上
4/5kernel/sched/cpufreq_schedutil.ckthread_init_worker + kthread_create(kthread_worker_fn) -> kthread_create_worker(先建不跑)
5/5kernel/kthread.c删除 kthread_worker_fn() 中 7 行 self-assignment + FIXME

关键实现

每条 driver 转换 patch 的核心都是把「embed worker + 独立 task 指针」换成「worker 指针」:

/* before */
struct ivtv {
    struct kthread_worker irq_worker;      /* kthread worker for PIO/YUV/VBI actions */
    struct task_struct    *irq_worker_task; /* task for irq_worker */
};

kthread_init_worker(&itv->irq_worker);
itv->irq_worker_task = kthread_run(kthread_worker_fn,
                                   &itv->irq_worker,
                                   "%s", itv->v4l2_dev.name);
if (IS_ERR(itv->irq_worker_task)) { ... }
sched_set_fifo(itv->irq_worker_task);

/* after */
struct ivtv {
    struct kthread_worker *irq_worker;
};

itv->irq_worker = kthread_run_worker(0, "%s", itv->v4l2_dev.name);
if (IS_ERR(itv->irq_worker)) { ... }
sched_set_fifo(itv->irq_worker->task);

退出路径由 kthread_stop(task) + kthread_flush_worker(&worker) 改为 kthread_destroy_worker(worker) + kthread_flush_worker(worker)kthread_destroy_worker 内部会 stop task,驱动不必再单独 stop。

cpufreq_schedutil 因为需要先把 worker 建好再设 SCHED_DEADLINE 属性并绑核,所以用 kthread_create_worker()(不自动 wake),随后 set_cpus_allowed_ptr + kthread_bind_mask + wake_up_process(worker->task)

sg_policy->worker = kthread_create_worker(0, "sugov:%d",
                                          cpumask_first(policy->related_cpus));
if (IS_ERR(sg_policy->worker)) { ... return PTR_ERR(...); }
ret = sched_setattr_nocheck(sg_policy->worker->task, &attr);
set_cpus_allowed_ptr(sg_policy->worker->task, policy->related_cpus);
kthread_bind_mask(sg_policy->worker->task, policy->related_cpus);
wake_up_process(sg_policy->worker->task);

最后第 5 个 patch 删除:

/* FIXME: Update the check and remove the assignment when all kthread
 * worker users are created using kthread_create_worker*() functions.
 */
WARN_ON(worker->task && worker->task != current);
worker->task = current;

类比

像「搬家时丢掉一张旧门牌」:以前每户人家(驱动)都把房子(worker)和信箱钥匙(task)分开登记,老物业为了让钥匙一定能对上房号,在住户进门时再确认一遍(FIXME 自赋值)。新物业在分房当天就把房号写进钥匙卡,那次进门确认变成多余动作。系列就是让 4 户老住户搬到新物业,再让老物业关掉那扇确认门。

另一个角度:公司从「工牌自己填」改成「入职当天 HR 就填好」,于是门口保安不再需要让你自己再写一遍工牌。

    old property office              new property office
    ------------------              ------------------
    [ tenant fills key card ]        [ HR fills key card on move-in day ]
            |                                 |
    [ guard double-checks door ]       [ guard no longer needed ]
            |                                 |
    4 old tenants still on old API     all tenants now on new API

Highlight:风险与注意点

  • 核心争议是「死代码」 vs 「防御性 paranoia」:Frederic Weisbecker 与 Peter Zijlstra 都倾向保留 WARN_ON(worker->task != current) 当兜底检测;Bradley Morgan 认为「应该用不到」可彻底删除。这是本 thread 的主要分歧。
  • 退出路径等价性:迁到 kthread_destroy_worker 后需要保证它内部 stop 顺序与原来 kthread_stop(task) + kthread_flush_worker(&worker) 等价;sc16is7xx 在 shutdown 路径同时有 kthread_cancel_delayed_work_sync,destroy 时机不能错。
  • schedutil 的 SCHED_DEADLINE:迁到 kthread_create_worker 之后 task 是 worker->task,绑定 cpu 与设置 deadline 必须在 wake 之前完成,否则 worker 起来后又改绑可能产生迁移抖动。
  • 遗漏风险:本次只清理了社区常见的 4 个驱动;其他子系统(staging/sound/net 等)若有遗漏用法,编译器不会报警,需要在 -next 上做一次 grep -r 'kthread_init_worker' drivers/ 全局复查。
  • 分类判定:属于纯 cleanup,不涉及 fix/regression/crash,不属于 RFC;归类为 other,叠加 is_important = false

版本变化

仅有 v1(RESEND),没有 v1 到 v2 的迭代可比较。RESEND 仅把首发时拆成 5 个独立 thread 的 patch 重新合成一个系列,patch 内容与首版一致。

与其他 patch 系列的关联

与历史上 kthread 健壮性改造(kthread_destroy_worker 的引入、worker flush 时机收紧)属于同一根线;本系列是「兑现 FIXME」那一段。建议合入前在 -next 上对未触及子系统做一次兜底 grep。

一句话总结

把 4 个驱动从老 kthread_init_worker + kthread_run(kthread_worker_fn) 迁到 kthread_run_worker/_create_worker,随后删掉 kthread_worker_fn 里挂了十年的 worker->task = current 自赋值 FIXME。