0/3 已展开

LLM 分析

sched/core:跨排队 wakeup 保留 wake flags

系列概况

  • 标题: [PATCH v3] sched/core: Preserve wake flags across queued wakeups
  • 作者: Shubhang Kaushik (Ampere) sh@gentwo.org
  • 版本: v3(前置 v2、v1)
  • 规模: 3 封邮件(本 thread)
  • 修改文件: include/linux/sched.hkernel/sched/core.c
  • 代码统计: +18 / -18(2 文件)
  • Message-ID: 20260721-b4-sched-ttwu-wake-flags-v3-1-07b92122bf28@gentwo.org
  • 完整性: 完整 changelog、v1/v2/v3 链接、Sign-off、base-commit v7.2-rc4 (248951ddc14d)、change-id 齐全;测试覆盖 perf bench / hackbench / SPECjBB

补丁目的

try_to_wake_up() 在跨 CPU 时把 wakeup 排到目标 CPU 的 llistttwu_queue_wakelist()),但目前只保存 "wakee 是否迁移" 这一个 bit。目标 CPU 排空 wakelist 调用 sched_ttwu_pending() 时只能传 WF_MIGRATED 或 0,丢掉 WF_RQ_SELECTED 等在 select_task_rq() 之后仍然重要的 flag。这会让 queued wakeup 路径在 ENQUEUE_RQ_SELECTEDwakeup_preempt_fair() 抢占阈值计算上与 direct wakeup 行为不一致。

本 patch 把这些 flag 完整保存下来,让两条路径对齐。

旧流程的问题

direct  : select_task_rq() -> WF_RQ_SELECTED
          ttwu_do_activate(WF_RQ_SELECTED)
          wakeup_preempt_fair(WF_RQ_SELECTED)

queued  : select_task_rq() -> WF_RQ_SELECTED
          ttwu_queue_wakelist() -> save only WF_MIGRATED (1 bit)
          sched_ttwu_pending() -> ttwu_do_activate(WF_MIGRATED or 0)
                                  [LOST] WF_RQ_SELECTED and others

target CPU 看到的 wake flags 不完整,ENQUEUE 路径和抢占判断走错分支。

新流程

select_task_rq()        -> set WF_RQ_SELECTED
ttwu_queue_wakelist()   -> WRITE_ONCE(p->sched_remote_wakeup_flags,
                                       wake_flags & WF_TTWU_QUEUE_MASK)
sched_ttwu_pending()    -> wake_flags = READ_ONCE(p->sched_remote_wakeup_flags)
                           ttwu_do_activate(rq, p, wake_flags, &rf)

WF_TTWU_QUEUE_MASK = WF_TTWU | WF_SYNC | WF_MIGRATED | WF_RQ_SELECTEDWF_CURRENT_CPU 只是 CPU 选择提示,跨队列无意义,刻意丢弃。

Patch 概览

  • include/linux/sched.h:删除 sched_remote_wakeup:1,新增独立 u8 sched_remote_wakeup_flags + cross-CPU ordering 注释
  • kernel/sched/core.cttwu_queue_wakelist()WF_TTWU_QUEUE_MASK 写入新字段;sched_ttwu_pending()READ_ONCE 读出再传入 ttwu_do_activate()

关键实现

/* include/linux/sched.h */
struct task_struct {
    ...
    /*
     * Must not share the scheduler bitfield word because wakelist
     * queueing is not serialized by p->on_cpu.
     *
     * smp_load_acquire(&p->on_cpu) in ttwu_queue_wakelist() pairs
     * with smp_mb__after_spinlock() in schedule(), making current's
     * stores visible before the target CPU uses these flags from
     * sched_ttwu_pending().
     */
    u8 sched_remote_wakeup_flags;
};
/* kernel/sched/core.c */
#define WF_TTWU_QUEUE_MASK (WF_TTWU | WF_SYNC | WF_MIGRATED | \
                            WF_RQ_SELECTED)

void sched_ttwu_pending(void *arg)
{
    ...
    llist_for_each_entry_safe(p, t, llist, wake_entry.llist) {
        int wake_flags;
        ...
        wake_flags = READ_ONCE(p->sched_remote_wakeup_flags);
        ttwu_do_activate(rq, p, wake_flags, &rf);
    }
}

static void __ttwu_queue_wakelist(struct task_struct *p, int cpu,
                                  int wake_flags)
{
    struct rq *rq = cpu_rq(cpu);
    WRITE_ONCE(rq->ttwu_pending, 1);
    WRITE_ONCE(p->sched_remote_wakeup_flags,
               wake_flags & WF_TTWU_QUEUE_MASK);
    ...
}

字段从 scheduler word 拆出的根本原因是 wakelist 队列不再由 p->on_cpu 串行化,必须用独立字段 + READ_ONCE / WRITE_ONCE 保证跨 CPU 可见性。

类比

ttwu_queue_wakelist() 想象成快递公司把包裹扔进目的站点的"待上架寄存柜":旧版柜子上只贴了一张"是否跨城转运"标签,目的站点柜员(sched_ttwu_pending)开柜后只知道转运状态,不知道"客户已选好货架号"(WF_RQ_SELECTED)和"加急件"(WF_SYNC),只能盲投默认货架。补丁相当于把 1 bit 标签换成完整托运单,柜员看到全部指令。

另一类比是餐厅排队:旧版只告诉取餐员"外卖改堂食(迁移)",新版把"加急、已选座、换桌"等全部写在取餐牌上,厨师按完整指令出餐。

Highlight:风险与注意点

  • READ_ONCE/WRITE_ONCE 是否必要:K Prateek Nayak 质疑,llist 添加已有 release 语义,接收方看到对象时 sched_remote_wakeup_flags 一定已稳定;其它 llist 上的 csd 字段就没用这些 barrier。
  • 注释措辞误导smp_load_acquire(&p->on_cpu) in ttwu_queue_wakelist() 应当是 before;且真正配对的是 finish_task() 里的 smp_store_release()(目的 CPU context switch 后、开中断前),而不是 schedule() 里的 smp_mb__after_spinlock()
  • 字段独立化的代价:从 scheduler bitfield word 拆出后,必须靠 atomic/barrier 风格访问,不能再依赖 scheduler lock 隐式同步。
  • 跨拓扑验证缺失:仅在 80 CPU Ampere Altra(单 socket 假设)上验证,跨 die / 跨 socket 仍需观察。
  • v4 预期:注释措辞会改,READ_ONCE/WRITE_ONCE 可能被回退。

版本变化

  • v1 → v2
    • sched_remote_wakeup_flags 独立成 u8,拆出 scheduler bitfield
    • 删除 sched_ttwu_pending() 中多余 reset
    • 引入 WF_TTWU_QUEUE_MASK 集中定义
    • 强化 direct vs queued 一致性的 changelog
  • v2 → v3
    • 增加 READ_ONCE / WRITE_ONCE
    • WF_RQ_SELECTED 抢占行为差异作为 changelog 主线
    • 保留 cross-CPU ordering 注释(但内容仍可商榷)

一句话总结

把跨 CPU 排队 wakeup 保存的 wake flag 从 1 bit 升级为 8 bit 完整 mask,让 sched_ttwu_pending() 看到与直接 wakeup 一致的 flags,修复 ENQUEUE_RQ_SELECTEDwakeup_preempt_fair() 路径偏差。