0/1 已展开

LLM 分析

cpufreq schedutil:迁移到 kthread_create_worker 统一 API

系列概况

  • 标题: [PATCH 4/5] cpufreq: schedutil: convert to kthread_create_worker
  • 作者: Bradley Morgan brads@mainlining.org
  • 版本: v1(系列第 4/5 帖,整系列 5 patch)
  • 规模: 1 文件,14 行新增 / 19 行删除
  • 修改文件: kernel/sched/cpufreq_schedutil.c
  • 代码统计: +14 / -19
  • Message-ID: 20260829160001.6245-1-include@grrlz.net
  • 完整性: 仅含本 patch + diffstat,diff 完整可读;系列其他 4 个 patch 未在本帖给出

补丁目的

sugov(schedutil governor)原本在 struct sugov_policy就地嵌入一个 kthread_worker,再单独 kthread_create() 一个内核线程来跑 kthread_worker_fn。本 patch 把这两步合并成新的 kthread_create_worker(),让 worker 自己管理 task 指针;同时删除多余的 task_struct *thread 字段。

这不是 RFC,也不是修复具体 bug,而是清理型重构 —— 属于上游"移除 kthread_worker_fn()worker->task 自赋值"系列工作的一环,schedutil 作为 kernel/sched/ 下受影响最大的使用者必须跟进重写。

旧流程的问题

旧 API 等于把"工作线程"拆成三个手动步骤:

  1. kthread_init_worker(&sg_policy->worker) 在结构体里原地初始化 worker;
  2. kthread_create(kthread_worker_fn, &sg_policy->worker, ...) 单独起一个 task_struct
  3. kthread_worker_fn 内部,把当前 task 写回 worker->task(自赋值副作用)。

这套用法既冗余(多管一个 thread 指针)、又依赖 kthread_worker_fn 内部那行隐藏的 worker->task = current;。一旦上游把这行自赋值拿掉,所有手动嵌入 worker 的驱动都要重写,schedutil 首当其冲。

新流程

kthread_create_worker() 一次性创建 worker + 配套 task,并把 task 直接挂在 worker->task 上;调用者只需要:

sg_policy->worker = kthread_create_worker(0, "sugov:%d",
                                          cpumask_first(policy->related_cpus));

清理路径配套换成 kthread_destroy_worker(),替代原先 kthread_flush_worker() + kthread_stop() 两步走。

Patch 概览

整系列 5 patch,本帖是第 4 个,专门负责 schedutil 这一个文件。

关键实现

struct sugov_policy 结构调整:

struct sugov_policy {
    /* 旧:值类型 worker + 单独 thread 指针 */
    /* struct kthread_worker worker; */
    /* struct task_struct   *thread; */

    /* 新:指针类型 worker,task 嵌在 worker 里 */
    struct kthread_worker *worker;
    /* ... 其它字段保持不变 ... */
};

sugov_kthread_create() 关键替换:

/* 旧
 * kthread_init_worker(&sg_policy->worker);
 * thread = kthread_create(kthread_worker_fn,
 *                         &sg_policy->worker,
 *                         "sugov:%d", cpumask_first(...));
 * sched_setattr_nocheck(thread, &attr);
 */
sg_policy->worker = kthread_create_worker(0, "sugov:%d",
        cpumask_first(policy->related_cpus));
ret = sched_setattr_nocheck(sg_policy->worker->task, &attr);

sugov_kthread_stop() 关键替换:

/* 旧
 * kthread_flush_worker(&sg_policy->worker);
 * kthread_stop(sg_policy->thread);
 */
kthread_flush_worker(sg_policy->worker);
kthread_destroy_worker(sg_policy->worker);

散落在文件里的 sg_policy->thread 引用(set_cpus_allowed_ptrkthread_bind_maskwake_up_processsugov_irq_work 排队等位置)全部改写成 sg_policy->worker->tasksg_policy->worker

类比

旧 API 像自己开夫妻店:要自己租铺面(嵌入的 worker 字段)、雇一个售货员(kthread_create)、再让售货员自己登记上岗(worker->task = current 的隐式自赋值)。新 API 像加盟连锁:挂个招牌,kthread_create_worker() 一次把铺面 + 售货员都配齐,售货员信息直接写在 worker 这张工牌上(worker->task)。离职时也只需要给连锁总部打一个电话(kthread_destroy_worker)。少管一个 thread 指针,就少一处"售货员走了但铺面没退"的风险。

Highlight:风险与注意点

  • 过渡期一致性:整系列是上游"移除 kthread_worker_fn() 自赋值"清理的一部分,需要确认上游补丁先合入或同步落地,否则本 patch 等于在 worker API 还不稳定时抢跑,cherry-pick 时容易断链。
  • 错误路径收尾:旧代码在 sched_setattr_nocheck 失败时调用 kthread_stop(thread);新代码必须改用 kthread_destroy_worker(),并且要注意 flush 在 destroy 之前是否仍必要。
  • 亲和性时机set_cpus_allowed_ptrkthread_bind_mask 改到 worker->task 上,需要确认 kthread_create_worker() 返回的 worker 已经持有可用的 task;否则亲和性调用可能命中尚未初始化的指针。
  • SCHED_DEADLINE 路径sched_setattr_nocheck(sg_policy->worker->task, &attr) 把 schedutil 工作线程绑定到 SCHED_DEADLINE,是 sugov 实时性保证的关键,必须在 cpufreq governor 切换、S3 唤醒、CPU 热插拔等场景下重新覆盖测试。
  • 用户可见 API 未变:governor 对外交付(policy 属性、fast_switch_enabled 等)保持不变,影响面理论上局限在 cpufreq 内部,仍建议 cpufreq + thermal + rt-tests 全套跑一遍。

版本变化

只有 v1,本帖就是系列第 4/5 patch。系列其余 patch 标题信息未在本帖给出,只能从 cover letter 描述推断是同一清理工作的多个驱动使用者迁移。

一句话总结

schedutil 把嵌入式的 kthread_worker 改成指针式 kthread_create_worker(),省掉独立 thread 字段,让 worker 生命周期统一由 worker API 管理。

+----------------------------+ +----------------------------+
| Old pattern (two-step)     |         | New pattern (one-shot)     |
+----------------------------+         +----------------------------+
| sugov_policy { |         | sugov_policy {             |
|   kthread_worker worker;  |         |   kthread_worker *worker; -|--+
|   task_struct *thread;   |         | } |  |
| }                          |         +----------------------------+  |
|      |                     |         | kthread_create_worker(0,..)|<-+
|      v |         |   -> worker + task in one  |
| kthread_init_worker(&w)    |         |      |                     |
| kthread_create(worker_fn,..)|        |      v                     |
|      |                     |         | worker->task is set inside |
|      v                     |         | the helper (no self-assign)|
| worker->task = current |         |      |                     |
| (implicit self-assignment) |         |      v                     |
|      |                     |         | set_cpus_allowed_ptr(     |
|      v                     |         |   worker->task, ...)       |
| set_cpus_allowed_ptr(thread)| | kthread_bind_mask( |
| kthread_bind_mask(thread)  |         |   worker->task, ...)       |
| wake_up_process(thread)    |         | wake_up_process(worker->t.)|
+----------------------------+         +----------------------------+
| Cleanup: |         | Cleanup:                   |
|  kthread_flush_worker(&w)  |         |  kthread_flush_worker(w)   |
|  kthread_stop(thread)      |         |  kthread_destroy_worker(w) |
+----------------------------+         +----------------------------+