sched discussion
[RESEND PATCH 0/5] kthread: remove worker->task self-assignment
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_fifo、kthread_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/5 | drivers/media/pci/ivtv/ivtv-driver.{c,h} | kthread_init_worker + kthread_run(kthread_worker_fn) -> kthread_run_worker |
| 2/5 | drivers/net/ethernet/microchip/encx24j600.c | 同上 |
| 3/5 | drivers/tty/serial/sc16is7xx.c | 同上 |
| 4/5 | kernel/sched/cpufreq_schedutil.c | kthread_init_worker + kthread_create(kthread_worker_fn) -> kthread_create_worker(先建不跑) |
| 5/5 | kernel/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。