0/27 已展开

LLM 分析

kthread:将剩余用户迁移到 kthread_create_worker*

系列概况

  • 标题: [PATCH v2 0/5] kthread: convert remaining users to kthread_create_worker
  • 作者: Bradley Morgan brads@mainlining.org
  • 版本: v2(5 patches)
  • 规模: 6 文件改动,+50 / -67
  • 修改文件:
    • drivers/media/pci/ivtv/ivtv-driver.c / .h
    • drivers/net/ethernet/microchip/encx24j600.c
    • drivers/tty/serial/sc16is7xx.c
    • kernel/sched/cpufreq_schedutil.c
    • kernel/kthread.c
  • Message-ID: cover.1788513591.git.brads@mainlining.org
  • 完整性: 系列本体 + maintainer 反馈(Kicinski、Greg KH)+ Sashiko AI bot + kernel test robot 真实编译错误,结构完整。

补丁目的

kthread_worker_fn() 里有一段 FIXME 挂了将近十年:

worker->task = current;

历史上必要的原因是:旧 API kthread_run(kthread_worker_fn, worker, ...) 在 worker 启动前并不会告诉 worker 它的 task_struct 是谁,所以 worker 必须自赋值。新 API kthread_create_worker*() 在 worker 启动之前就把 worker->task 填好,这一行就成了死代码。

本系列把最后 4 个还用旧模式的驱动(ivtv / encx24j600 / sc16is7xx / cpufreq_schedutil)迁过去,然后删除自赋值,保留一个 WARN_ON() 当 OOT 用户的兜底 sanity check。

旧流程的问题

  • 老 API 把 kthread_workertask_struct 拆成两个字段,调用方要自己维护;
  • worker->task = current 自我赋值长期存在,掩盖了 API 的真正责任划分;
  • 关闭路径 kthread_flush_worker(&x) + kthread_stop(task) 容易被忘记一半;
  • 散落的 &priv->kworker 取地址调用遍布多个驱动。

新流程

  • 4 个驱动统一换成 kthread_run_worker() / kthread_create_worker()
  • worker 改成指针 struct kthread_worker *kworker,不再保留 *_task 副本;
  • 关闭路径统一 kthread_flush_worker(p) + kthread_destroy_worker(p)
  • kernel/kthread.c 删除自赋值,WARN_ON(worker->task != current) 作为 OOT 兜底。

Patch 概览

#子系统关键改动
1/5media: ivtv删除 irq_worker_taskkthread_run_worker() 替换
2/5net: encx24j600同上模式
3/5tty: sc16is7xx9 处 &s->kworker 改成 s->kworker
4/5cpufreq: schedutilkthread_create_worker(),调度属性改走 worker->task
5/5kernel/kthread删除自赋值,保留 WARN_ON

关键实现

以 patch 1 为代表的核心 hunk:

/* before */
kthread_init_worker(&itv->irq_worker);
itv->irq_worker_task = kthread_run(kthread_worker_fn,
                                   &itv->irq_worker,
                                   "%s", itv->v4l2_dev.name);
sched_set_fifo(itv->irq_worker_task);

/* after */
itv->irq_worker = kthread_run_worker(0, "%s", itv->v4l2_dev.name);
sched_set_fifo(itv->irq_worker->task);

kthread_create_worker*() 内部就把 worker->task 装好,再把 worker 线程跑进 kthread_worker_fn,调用方不再需要单独保留 task 指针。

patch 5:

/* before */
WARN_ON(worker->task && worker->task != current);
worker->task = current;

/* after */
WARN_ON(worker->task != current);

注释同步改成「所有 worker 都用新 API 创建了,这里 WARN 说明调用方坏了」。

类比

把 worker 想象成公司前台:

  • 旧流程:你(驱动)雇了一个前台(kthread),但没告诉前台他的工号。前台上岗第一件事是把「工号」贴到自己工牌上(worker->task = current)。如果忘了这一步,分派任务的人就找不到他。
  • 新流程:人事部门(kthread_create_worker())在雇前台之前就把工号印到工牌上。前台上岗时只用核对「这工号是我的」(WARN_ON),不需要再贴一次。

流程图

旧 API 与新 API 的差异:

                   Old API                              New API
              (kthread_run)                    (kthread_create_worker*)
                     |                                       |
    driver -> kthread_run(fn, &worker)      driver -> kthread_create_worker()
                     |                                       |
                     v                                       v
           kthread_worker_fn()                    kthread_worker_fn()
                     |                                       |
   worker->task = current  <-- DEAD CODE     worker->task preset by API
                     |                                       |
                     v                                       v
       scheduler sees worker->task             scheduler sees worker->task

     driver must keep task_struct*            single pointer is enough

生命周期配对:

   init   : kthread_init_worker + kthread_run(fn, &worker)
   cleanup: kthread_flush_worker(&worker) + kthread_stop(task)
                      ||
                      \/
   init   : kthread_run_worker() / kthread_create_worker()
   cleanup: kthread_flush_worker(p) + kthread_destroy_worker(p)

整体 5 patch 作用关系:

  patch 1/5 ivtv      ----+
  patch 2/5 encx24j600 ----+--> migrate 4 drivers off kthread_run(kthread_worker_fn)
  patch 3/5 sc16is7xx ----+
  patch 4/5 schedutil  --+
                          |
                          v
            patch 5/5 kthread.c: drop worker->task self-assign
                     (only safe AFTER all tree users migrated)

Highlight:风险与注意点

  1. 真实编译失败:kernel test robot 在 riscv-allmodconfig + clang 下抓到 drivers/media/pci/ivtv/ivtv-irq.c:1053:22: incompatible pointer types passing 'struct kthread_worker **' to parameter of type 'struct kthread_worker *'; remove &。patch 1 没把 ivtv-irq.c 里残留的 &itv->irq_worker 全部改干净,v3 必须补;loongarch-allmodconfig 也复现同一错误。
  2. UAF 隐患:Sashiko 提示 patch 1 中 kthread_destroy_worker()free_irq() 之前调用,可能与中断处理路径形成 use-after-free;patch 3 的 worker 也早于 devm-managed threaded IRQ 释放。需要确认 IRQ handler 是否还会 kthread_queue_work() 到已释放的 worker。
  3. 维护流程摩擦:Jakub Kicinski / Greg KH 都明确指出系列跨多个子系统,patch 5/6 不该打包让 kthread 维护者直接收。Bradley 计划 v3 把 4 个驱动 patch 拆成独立 series 分别发给 media / net / tty / cpufreq 维护者,等合入后再单独发 patch 5。
  4. OOT deadlock 风险:删掉 worker->task = current 之后,tree 外的旧式调用方会立刻因缺 wake_up 路径而永久死锁——WARN_ON() 只能提示,不能阻止。
  5. pre-existing 缺陷:sc16is7xx 多 port 共用 poll_work 的 cancel 行为、encx24j600 priv->tx_skb 未在 TX timeout / remove 路径清理、SPI 字节序未转换——这些都跟本系列无关,但 review 过程中被一起暴露出来。

版本变化

  • v1 → v2(来自 cover letter):
    • 保留 WARN_ON() 作为 sanity check(呼应 Frederic / Peter 的反馈);
    • WARN_ON 加注释解释为什么留它;
    • 修正 commit subject 与 changelog 措辞。

一句话总结

5 patch 系列把最后 4 个 kthread_run(kthread_worker_fn) 用户迁到 kthread_create_worker*(),顺势删掉 kthread_worker_fn() 里挂了 10 年的 worker->task = current 自赋值并保留 WARN_ON 兜底,但 v2 已被 maintainer 要求拆 series 分头发,并被 test robot 抓到 patch 1 在 ivtv-irq.c 遗漏一处 &itv->irq_worker 的编译错误,v3 必须修。