0/4 已展开

LLM 分析

scx_pair: 从废弃回调迁移到 sched_switch Tracepoint

系列基线信息

字段
标题[PATCH sched_ext/for-7.3] tools/sched_ext: scx_pair: Convert to sched_switch TP
作者Cheng-Yang Chou
版本单 patch,无版本号
规模1 patch
首封 Message-ID20260719142917.34238-1-yphbchou0911@gmail.com
来源sched-ext
状态已合入 sched_ext/for-7.3(Tejun Heo 确认 apply)

明确目的

ops.cpu_acquire/release() 回调已在 commit a3f5d4822253 中废弃;加载 scx_pair 会触发废弃警告。

本 patch 将 pair_cpu_acquire / pair_cpu_release 两个回调替换为 tp_btf/sched_switch tracepoint 程序,通过边沿检测(edge-detect)复现相同的 CPU 抢占/回收语义,消除废弃警告并使 scx_pair 与新 API 对齐。

遍历代码

1. 新增辅助函数

  • pair_task_is_highpri(p):用 p->prio < MAX_RT_PRIO 判断任务是否高于 SCX(rt/dl 范围)。选 prio 而非 policy,因为 rt_mutex_setprio() 做 PI 提升时只改 prio 不改 policy——用 policy 会双向误判。
  • pair_cpu_acquire_locked() / pair_cpu_release_locked():把原来散在回调里的 lock+mask 操作提取成带 _locked 后缀的辅助函数,收拢锁内逻辑,减少 tracepoint 路径的代码量。

2. sched_switch tracepoint 主体

SEC("tp_btf/sched_switch")
int BPF_PROG(pair_sched_switch, bool preempt,
             struct task_struct *prev, struct task_struct *next,
             unsigned int prev_state)

核心判断逻辑:

  • release 条件next 是高优先级任务 + prev 是非高优先级任务(即 SCX 任务)+ 当前 CPU 的 preempted_mask 位尚未置位(首次抢占)。→ 标记 preempted_mask、清 active_mask、置 draining、kick pair CPU 带 SCX_KICK_WAIT
  • acquire 条件next 不是高优先级任务(CPU 回到 SCX/空闲)+ 当前 CPU 的 preempted_mask 位已置位。→ 清 preempted_mask、kick pair CPU(如果 pair 没也被抢占)。
  • idle→高优先级直跳不算 release:CPU 上没有 SCX 任务在跑,无需 drain;kick SCX_KICK_WAIT 会让 pair CPU 无意义地等 rt burst。
  • 无状态变化则提前返回:tracepoint 在每次上下文切换都触发,常见情况(无 transition)在取锁前就过滤掉了——per-CPU preempted_mask 位只由本 CPU 写,无锁读取精确安全。

3. 删除旧回调

scx_pair_ops 结构体中移除 .cpu_acquire.cpu_release,彻底消掉废弃接口的使用。

ASCII 流程图

         sched_switch tracepoint (every ctx switch)
                    |
         read preempted_mask[cpu] ( unlocked, per-CPU )
                    |
          [next highpri?]
           /          \
         YES           NO
          |             |
   [prev non-highpri?]  [preempted bit set?]
    /       \            /          \
  YES       NO         YES          NO
   |         |          |            |
  RELEASE   skip     ACQUIRE       skip
   |                  |
   mark preempted     clear preempted
   clear active       kick pair (if pair
   kick pair            not also preempted)
   (SCX_KICK_WAIT)

  idle --> highpri direct jump:
  NOT a release (no SCX task was running)
  Time  CPU0 (SCX task T0)     CPU1 (pair of CPU0)
   t0   T0 running             T1 running (paired)
   t1   RT task R0 preempts T0  T1 stalls (draining)
         --> RELEASE: preempted=1
         --> kick CPU1 WAIT
   t2   R0 finishes             T1 can't dispatch
         --> next=idle/SCX
         --> ACQUIRE: preempted=0
         --> kick CPU1 PREEMPT
   t3   T0 or new SCX task      T1 re-dispatches

概念类比

想象一对舞伴(paired CPUs)必须同步跳舞。当舞台监督(RT scheduler)突然拉走其中一个舞伴去表演独舞,另一个舞伴得停下等(release + WAIT kick)。等独舞结束、舞伴回到舞台(acquire),通知搭档可以继续。

旧方式是舞台管理员主动喊"你的搭档被拉走了"和"搭档回来了"(回调)。新方式是舞伴自己看节目单每次换人记录(tracepoint),边沿检测搭档是否被拉走或回来了——不再依赖管理员通知,自己判断更精确,而且不会在搭档只是短暂旁观(idle→RT 直跳)时误报"被拉走"。

Highlight 突出问题

  1. sched_setscheduler() 的盲区:对一个正在运行的任务改变调度类不会触发上下文切换,所以 tracepoint 只能在下一次切换时才观察到变化。在这期间 stale active_mask 位会让 pair CPU 在 try_dispatch() 中等待,但该等待被下一次切换自然截止。这与旧 switch_class() 回调存在同样的盲区——不是新引入的问题。

  2. switch-all 模式的隐含前提pair_task_is_highpri() 只区分 rt/dl 与非 rt/dl,无法区分 fair 和 SCX——因为在 switch-all 模式下不存在 fair 任务。如果 scx_pair 以后在 partial 模式下运行,fair 任务也会抢占 SCX,但此函数不会检测到,逻辑会出错。

  3. tracepoint 开销sched_switch 在每次上下文切换都触发,虽然无状态变化的常见路径在取锁前就退出,但在高频切换场景下仍有额外 BPF 程序执行开销——需关注大规模部署时的性能影响。

  4. PI 提升误判风险:如果未来内核的 rt_mutex_setprio() 行为变化(例如也开始修改 policy),p->prio vs p->policy 的选择可能需要重新审视。

版本演进

本系列为单 patch v1,无多版本迭代。Tejun Heo 直接 apply 到 sched_ext/for-7.3

与其他相关 patch 系列的关联

  • commit a3f5d4822253(sched_ext: Allow scx_bpf_reenqueue_local() to be called from anywhere):废弃了 ops.cpu_acquire/release() 回调,是本 patch 的前置动机。
  • 其他 sched_ext 示例调度器(scx_qmap 等)也可能需要类似的迁移,可跟进观察。

一句话总结

scx_pair 从废弃的 cpu_acquire/release 回调迁移到 sched_switch tracepoint 边沿检测,用 p->prio 识别高优先级抢占,消除废弃警告,保持与旧回调相同的语义和盲区。