0/16 已展开

LLM 分析

sched/dynamic:把 PREEMPT_DYNAMIC 收敛到只剩 FULL/LAZY

系列概况

  • 标题:[PATCH v2 0/6] sched: dynamic: Simplify PREEMPT_DYNAMIC
  • 作者:Mark Rutland mark.rutland@arm.com
  • 版本:v2,相对 v1 rebase 到 v7.2-rc4,吸收 Shrikanth、Jinjie 的 Reviewed-by,并新增 accessors 清理 patch
  • 规模:6 个 patch
  • 修改文件:arch/Kconfig、arch/arm64/Kconfig、arch/arm64/include/asm/preempt.h、arch/loongarch/Kconfig、arch/powerpc/Kconfig、arch/riscv/Kconfig、arch/s390/Kconfig、arch/s390/include/asm/preempt.h、arch/x86/Kconfig、arch/x86/include/asm/preempt.h、include/asm-generic/preempt.h、include/linux/irq-entry-common.h、include/linux/kernel.h、include/linux/preempt.h、include/linux/sched.h、kernel/Kconfig.preempt、kernel/entry/common.c、kernel/sched/core.c(共 18 个)
  • 代码统计:+23 / -374
  • Message-ID(首封)20260803191731.3244294-1-mark.rutland@arm.com
  • 完整性:完整线程——6 个 patch + Shrikanth/Mete/Jinjie 的 build 测试与 review 回复 + Mark 的回应 + tip-bot2 6 封合并通知 + Jinjie 关于 kernel-parameters.txt 文档补齐的最后一封

补丁目的

v7.0 起 commit 7dadeaa6e851("sched: Further restrict the preemption modes")让任何支持 ARCH_HAS_PREEMPT_LAZY 的架构都无法选择 NONE/VOLUNTARY 模型,PREEMPT_DYNAMIC 实际上只剩 FULL/LAZY 两个选项。但 Kconfig、enum、动态 wrapper、HAVE_PREEMPT_DYNAMIC_{CALL,KEY}、架构 stub 全部还在,按运行时"事实上的现状"被白白维护着。

系列目标:把这种"事实"提升为 Kconfig/代码层面的正式约束——让 PREEMPT_DYNAMIC 依赖 ARCH_HAS_PREEMPT_LAZY,并按顺序逐个把"NONE/VOLUNTARY 路径 + 多余动态切换机制"砍干净。

旧流程的问题

  • sched_dynamic_mode() 还在认 "none"/"voluntary",但对应的 preempt_dynamic_{none,voluntary} 值被注释成不可选;初始化分支里也看不到这两条路径。
  • 每条 preemption 函数都同时存在 CALL 和 KEY 两套动态化实现:DEFINE_STATIC_CALL(...) 包 trampoline,或 dynamic_*_xxx() 套一层 static_branch_unlikely
  • cond_resched()might_resched() 在 PREEMPT_DYNAMIC 下永远是关的;preempt_schedule()preempt_schedule_notrace()irqentry_exit_cond_resched() 永远是开的。enable/disable 都成了空操作。
  • HAVE_PREEMPT_DYNAMIC_{CALL,KEY}强依赖把 arm64/loongarch/powerpc/riscv/s390 锁到 PREEMPT_DYNAMIC_KEY,把 x86 锁到 CALL 路径。
  • preempt_model_none/voluntary 在 PREEMPT_DYNAMIC=y 时仍是函数体(多一次跳转和 EXPORT_SYMBOL),而 NONE/VOLUNTARY 模型又根本不会进来。

新流程

  • kernel/Kconfig.preempt:PREEMPTION 加 depends on ARCH_HAS_PREEMPT_LAZY;PREEMPT_DYNAMIC 不再 depends on HAVE_PREEMPT_DYNAMIC,也不再 select JUMP_LABEL,默认 y。
  • kernel/sched/core.c:删除 "none"/"voluntary" 字符串解析、对应 enum、PREEMPT_MODEL_ACCESSOR(none/voluntary)、初始化分支里相应的 (unselectable) 注释。
  • 架构 preempt.h__preempt_schedule{,_notrace}() 直接展开成 preempt_schedule_thunk 或普通函数调用;删 dynamic wrapper。
  • kernel/entry/common.craw_irqentry_exit_cond_resched() 改回 irqentry_exit_cond_resched(),删 static_call/key 包装。
  • arch/Kconfig:删除 HAVE_PREEMPT_DYNAMICHAVE_PREEMPT_DYNAMIC_CALLHAVE_PREEMPT_DYNAMIC_KEY 三个 Kconfig;6 个架构的 Kconfig 删除对应 select
  • include/linux/preempt.h:把 preempt_model_none/voluntary 由 out-of-line 实现改为 inline IS_ENABLED

整体把动态切换收窄到一个静态 key 上(FULL/LAZY 切换点见下面"关键实现")。

Patch 概览

#主题关键改动
1/6Make PREEMPT_DYNAMIC depend on ARCH_HAS_PREEMPT_LAZYKconfig 加依赖,core.c 删 none/voluntary 解析
2/6Simplify {cond,might}_resched()两者永远关,删 dynamic wrapper
3/6Simplify preempt_schedule{,_notrace}()两者永远开,删 trampoline 与 dynamic wrapper
4/6Simplify irqentry_exit_cond_resched()永远开,函数名恢复单一实现
5/6Remove HAVE_PREEMPT_DYNAMIC_{CALL,KEY}删除三个 Kconfig 与所有架构 select
6/6Simplify preempt model accessorsaccessor 改 inline,删多余 enum

关键实现

5/6 完成之后,运行时只剩一个静态 key 切换点:

/* kernel/sched/core.c */
static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_lazy);

static void __sched_dynamic_update(int mode)
{
    switch (mode) {
    case preempt_dynamic_full:
        preempt_dynamic_key_disable(preempt_lazy);
        pr_info("Dynamic Preempt: full\n");
        break;
    case preempt_dynamic_lazy:
        preempt_dynamic_key_enable(preempt_lazy);
        pr_info("Dynamic Preempt: lazy\n");
        break;
    }
}

架构侧变成直接调用或单条 asm跳转:

/* arch/x86/include/asm/preempt.h */
#define __preempt_schedule() \
        asm volatile("call preempt_schedule_thunk" : ASM_CALL_CONSTRAINT)
#define __preempt_schedule_notrace() \
        asm volatile("call preempt_schedule_notrace_thunk" : ASM_CALL_CONSTRAINT)

/* arch/arm64, arch/s390, asm-generic/preempt.h */
#define __preempt_schedule()        preempt_schedule()
#define __preempt_schedule_notrace() preempt_schedule_notrace()
                BEFORE AFTER

 PREEMPT_DYNAMIC mode parser PREEMPT_DYNAMIC mode parser
     "none" -> preempt_dynamic_none [DEAD]   "full" -> preempt_dynamic_full
     "voluntary"  -> preempt_dynamic_voluntary[DEAD]"lazy"-> preempt_dynamic_lazy

   Runtime switch mechanism Runtime switch mechanism
     HAVE_PREEMPT_DYNAMIC_CALL (static_call)       sk_dynamic_preempt_lazy (static_key)
       OR                                              only
     HAVE_PREEMPT_DYNAMIC_KEY  (static_key)

   Dynamic wrappers (5 functions)                  Dynamic wrappers
     cond_resched               dynamic_*           cond_resched             direct
     might_resched              dynamic_*           might_resched            direct
     preempt_schedule           dynamic_*           preempt_schedule         direct
     preempt_schedule_notrace   dynamic_*           preempt_schedule_notrace direct
     irqentry_exit_cond_resched dynamic_*           irqentry_exit_cond_resched direct

   Architectures forced to select Architectures
     {arm64,loongarch,powerpc,riscv,s390}: KEY      Just need ARCH_HAS_PREEMPT_LAZY
     {x86} : CALL

   Lines: 18 files, +23 / -374

类比

把这套机制想成餐厅。

菜单上印了五种菜(NONE / VOLUNTARY / FULL / LAZY / RT),厨房里实际只做 FULL 和 LAZY;但每张桌子还摆着"点 NONE/VOLUNTARY 之后请等厨师拒绝"的小纸条——dynamic wrapper 与 Kconfig 分支。

这个系列相当于:

  1. 把菜单上不再供应的两道菜划掉(删 NONE/VOLUNTARY 解析、删 enum 值)。
  2. 撤掉桌面上那些"点了也别做"的小纸条(删 dynamic_*包装与 static_call/key 路径)。
  3. 服务员只需记住"今天炒锅是猛火还是文火"——也就是 dynamic_preempt_lazy 这一个开关。

食客吃到的菜没变,但厨房动作、菜单和服务员都轻了一截。

Highlight:风险与注意点

  • 合入时机偏晚:作者主动指出 v7.2-rc4 才 rebase,tip:sched/core 基于 v7.2-rc1,rc 末班车合入风险大;已用 tip-bot2 推送,证实最终走了 v7.3 周期。
  • 行为默认值改变:删 HAVE_PREEMPT_DYNAMIC_KEY 后 arm64/loongarch/powerpc/riscv/s390 默认也会开 PREEMPT_DYNAMIC。作者已论证运行时只剩 sk_dynamic_preempt_lazy,但下游发行版与打 custom patch 的厂商要重新看默认配置。
  • 文档遗漏:Jinjie 在 9 月 3 日指出 Documentation/admin-guide/kernel-parameters.txtpreempt= 参数仍描述了 "none"/"voluntary" 选项,需要补一封 patch。
  • 评审容易误读:6/6 中保留的 (unselectable) 注释块让人以为模式仍可选;Jinjie 提出异议后 Mark 同意移除,体现后续版本注释需更干净。
  • PowerPC 死代码冲突:v1 之前已与 a2c02aa0c6ca(powerpc 删死代码)冲突;未来再清理时要确认没遗漏的 static_key 路径。
  • 保守边界preempt_model_voluntary() 当前没有用户,作者为 accessor 命名一致性保留 inline 实现;若后续确无调用方,可继续删。

版本变化

  • v1 → v2(作者列点):rebase 到 v7.2-rc4 以避开 powerpc 死代码冲突;应用 Shrikanth、Jinjie 的 Reviewed-by;修正 commit message 错字;新增 patch 6/6("Simplify preempt model accessors"),回应 Shrikanth 的清理建议。
  • v2 期间评审反馈 →计划 v3
    • Jinjie(9 月 1 日)对 6/6:建议把 (unselectable) 注释一并删除;Mark 同意。
    • Jinjie(9 月 3 日):要求同步更新 Documentation/admin-guide/kernel-parameters.txt,会触发一封文档补丁或合入下个 spin。
  • 最终落地:tip-bot2 在 9 月 2 日连续推送 6 封合并通知(commit 9650ce11…、5b9a28ee…、aa4178f6…、b9d267b9…、88e0b3bb…、d3d16750…),全部进入 sched/core 分支。

一句话总结

把 PREEMPT_DYNAMIC 里关于 NONE/VOLUNTARY 的死代码、Kconfig 选项、动态 wrapper 一次性砍掉,让运行时只剩 dynamic_preempt_lazy 一个静态 key,行为不变、6 个 patch 净瘦身 374 行。