0/7 已展开

LLM 分析

sched_ext:把 scx 仓库里的 tools 头文件同步回内核树

系列概况

  • 标题:[PATCHSET sched_ext/for-7.3-fixes] sched_ext: Sync tools headers from the scx repo
  • 作者:Tejun Heo tj@kernel.org
  • 版本:无版本号(单轮 patchset),目标分支 sched_ext/for-7.3-fixes,基于 5f01293930d1
  • 规模:2 个 patch,5 个文件改动,+501/-32 行
  • 修改文件
    • tools/sched_ext/include/scx/common.bpf.h (+140/-)
    • tools/sched_ext/include/scx/compat.bpf.h (+68/-)
    • tools/sched_ext/include/scx/compat.h (+97,新文件)
    • tools/sched_ext/include/scx/enum_defs.autogen.h (+5/-1)
    • tools/sched_ext/include/scx/enums_abi.autogen.h (+223,新文件)
  • Message-ID20260818205711.3175265-1-tj@kernel.org(cover),...-2-(1/2),...-3-(2/2)
  • 完整性:完整;含完整 diff、git 分支指针、最终被 maintainer 标记为 Applied

补丁目的

sched_ext 的 BPF 用户态工具头在 tools/sched_ext/include/scx/ 下与 scx 仓库各自维护了一份。

时间一长,两边已经漂移:scx 仓库里积累了若干 fix 与改进,但内核树拷贝没跟上。

本系列把这些修复反向同步回内核树,让 make -C tools/sched_ext 编译出的 BPF skeleton 直接吃到最新逻辑。

它一并解决四类问题:

  1. 64 位 scx enum 在老 BTF(无 BTF_KIND_ENUM64)上读取截断;
  2. v6.18+ 不开 CONFIG_PREEMPT_RCU 时,is_migration_disabled() 对当前任务 under-report;
  3. scx_bpf_dsq_peek() 的版本门控以及 Gavin/Changwoo 提出的 scx_bpf_reenqueue_local_from_anywhere()
  4. 兼容老内核用的 __COMPAT_scx_bpf_cpu_curr() 被恢复;同时文档化 rq clock helper 在 idle CPU 上的 stale-read 行为。

旧流程的问题

is_migration_disabled() 只有单一公式:

if (p->migration_disabled == 1)
    return bpf_get_current_task_btf() != p;
else
    return p->migration_disabled;

这公式的前提是「BPF trampoline prolog 一定 migrate_disable()」。

但 8e4f0b1ebcf2 之后,prolog 只在 CONFIG_PREEMPT_RCU 下才 migrate_disable()

所以 v6.18+、不开 PREEMPT_RCU 的内核上跑这条老逻辑,对 p == current 的 task 会返回 false —— 即错误地认为 current 没被禁迁移,下游可能去别的 CPU 上 dispatch 它,调度器就 crash。

__COMPAT_read_enum() 在老 BTF 上读 64-bit enum 会被截断;__COMPAT_scx_bpf_cpu_curr() 之前没保留,旧调度器无法在 pre-v6.18 内核上跑。

新流程

新流程加入运行时探测 + 多分支快速判定:

                   is_migration_disabled(p)
                            |
        p->migration_disabled == 1 ?
                  /                   \
                no                     yes
                 |                      |
       return migration_disabled     CONFIG_PREEMPT_RCU ?
                                    /                \
                                  yes                no
                                   |                  |
                          current == p ?     LINUX_KERNEL_VERSION >= 6.18 ?
                              /      \            /              \
                          return    return    return            return
                          false      true     true        __scx_prolog_disables
                                                       _migration ? cmp: return true

__scx_prolog_disables_migration 默认 true,由 scx_lib_init_probe(一个挂在 bpf_scx_reg 上的 fentry)写入。失败时按保守值(pre-v6.18 prolog 行为)兜底。

Patch 概览

Patch 1/2 — Sync autogen enum headers

  • enum_defs.autogen.h:补 HAVE_SCX_DSP_NONE/LOCAL/PREV/RETRY,删 HAVE_SCX_RQ_IN_BALANCE
  • 新增 enums_abi.autogen.h:以 __attribute__((unused)) 表形式存放编译期已知的 scx 枚举值,作为老 BTF 上读 64-bit enum 的回查表。

Patch 2/2 — Sync common and compat headers

  • 新增 __scx_prolog_disables_migrationscx_lib_init_probe
  • 重写 is_migration_disabled():CONFIG_PREEMPT_RCU 快速路径、>=6.18 快速路径、pre-v6.18 慢路径。
  • 恢复 __COMPAT_scx_bpf_cpu_curr(),先尝试 scx_bpf_cpu_curr ksym,否则退回 scx_bpf_cpu_rq
  • 文档化 scx_clock_task/scx_clock_pelt 在 NO_HZ_IDLE idle CPU 上的 stale-read。

关键实现

is_migration_disabled() 的 C 形态:

if (p->migration_disabled == 1) {
    if (CONFIG_PREEMPT_RCU)
        return bpf_get_current_task_btf() != p;
    if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(6, 18, 0))
        return true;
    return __scx_prolog_disables_migration
               ? bpf_get_current_task_btf() != p
               : true;
}
return p->migration_disabled;

新 probe 通过 fentry 钩进 bpf_scx_reg

SEC("fentry/bpf_scx_reg") __weak
int scx_lib_init_probe(void *ctx)
{
    if (bpf_core_field_exists(...)) {
        const struct task_struct *p = bpf_get_current_task_btf();
        unsigned int md = p->migration_disabled;
        if (md > 1)
            bpf_printk("...probe result unreliable");
        __scx_prolog_disables_migration = md > 0;
    }
    return 0;
}

类比

is_migration_disabled(p) 想成「问快递员:你手头的包裹是不是被锁在自家车上了?」。

旧版本只问一句「包裹上有没有锁?」,但 BPF trampoline 在你出发前自动给 current 也加了把锁,结果你对所有 current 都以为没锁,把包裹送去了别处 —— 收件人找不到。

新版本先看清三条规则:PREEMPT_RCU 默认会加锁、6.18 之后默认不加锁、介于两者之间还要看仓库当年到底有没有 cherry-pick prolog 改动;同时有一个「上岗第一天的小测试员」—— scx_lib_init_probe —— 在调度器装载那一刻亲自跑一遍 trampoline,把结果记下来给后面用。规则不清晰就用「默认假设 prolog 加了锁」(保守高估,最多本地派送,不会跨 CPU),避免炸调度器。

Highlight:风险与注意点

  1. 默认值的语义翻转:sashiko-bot 与 Changwoo 都指出 __scx_prolog_disables_migration 默认 true,但在慢路径里,true 会走 bpf_get_current_task_btf() != p,对 p == current 返回 false(under-report)。注释说「under-report 会炸调度器」,但代码路径恰恰相反。Tejun 解释 true 是「对齐 stock pre-v6.18 行为」,但下游 cherry-pick 后 probe 失败就会爆。需明确:默认 false 才让 fallback 真的返回 true
  2. sleepable kfunc 不适用:注释明确 SEC("fentry.s/...")BPF_STRUCT_OPS_SLEEPABLESEC("syscall") 不走 prolog 改写路径,误用会在 p == current 处假阴性。
  3. rq clock stale-readscx_clock_task/pelt 文档化了 idle CPU 上的 stale 行为,调用方不能在远端 idle CPU 上做时间差估算,否则窗口接近 0。
  4. __COMPAT_scx_bpf_cpu_curr 顺序:先 bpf_ksym_exists(scx_bpf_cpu_curr),回退到 scx_bpf_cpu_rq,意味着调度器要能在 v6.18 之前/之后都跑。

版本变化

  • 这是同步性 patchset,没有 vN→vN+1 的演进;改动来源是 scx 仓库的累积差异。
  • 评审回复指出 1/2 系列的 enum_defs.autogen.h 同步本身没有 review 反馈记录(无人直接评论 patch 1)。

一句话总结

把 scx 仓库里关于 64-bit enum 回查、v6.18+ prolog 探测、scx_bpf_cpu_curr 兼容、rq clock stale-read 文档化的修复反向同步进 tools/sched_ext/include/scx/,但默认值的语义与 fallback 行为仍需在后续版本里对齐注释。