sched discussion
[PATCH v4] sched/core: Preserve wake flags across queued wakeups
LLM 分析
sched/core:跨队列唤醒保留 wake flags
系列概况
- 标题:
[PATCH v4] sched/core: Preserve wake flags across queued wakeups - 作者:Shubhang Kaushik (Ampere) sh@gentwo.org
- 版本:v4(独立单 patch,基于 origin/master v7.2-rc4 / 248951ddc14d)
- 规模:
include/linux/sched.h与kernel/sched/core.c共 2 个文件,+12 / -18 - Message-ID:
20260722-b4-sched-ttwu-wake-flags-v4-1-8b151b1692cd@gentwo.org - 完整性:单封 patch mail,diff 完整、签名完整、changelog 与前置 v1/v2/v3 链接齐备
补丁目的
调度器里 wakeup 路径分为"直接唤醒"和"延迟的队列唤醒"(通过 ttwu_queue_wakelist() 挂到目标 CPU 的待处理列表上)。当目标 CPU 之后在 sched_ttwu_pending() 里排空 wakelist 时,需要把当时传入的 wake_flags 还原给 ttwu_do_activate() 使用。
老实现只存了"是否跨 CPU 迁移"这一位(WF_MIGRATED),把 WF_RQ_SELECTED 等其它语义位丢掉了。select_task_rq() 在调度器选好目标 runqueue 后会置上 WF_RQ_SELECTED,它被 ttwu_do_activate() 用来触发 ENQUEUE_RQ_SELECTED、也被 wakeup_preempt_fair() 用来决定抢占阈值。一旦被排入 wakelist,这些信息就丢失了,可能导致 ENQUEUE 路径行为退化、抢占判断变保守或过激进。
因此本 patch 引入新的 sched_remote_wakeup_flags 字段(u8),把对队列唤醒仍然有意义的 WF_TTWU | WF_SYNC | WF_MIGRATED | WF_RQ_SELECTED 跨过队列边界完整保留下来。
旧流程的问题
Direct wakeup path (correct):
waker ---> ttwu() ---> ttwu_do_activate(wake_flags)
|
+---> wakeup_preempt_fair(wake_flags)
wake_flags carries WF_RQ_SELECTED, consumed downstream correctly.
Queued wakeup path (info lost):
waker ---> ttwu() ---> ttwu_queue_wakelist()
|
+-- saves only the single bit WF_MIGRATED
target CPU ---> sched_ttwu_pending()
|
+---> ttwu_do_activate(WF_MIGRATED or 0)
+---> wakeup_preempt_fair(0)
WF_RQ_SELECTED is dropped at the wakelist boundary.
老字段 sched_remote_wakeup:1 与其它"scheduler bits"共用一个字时,依赖"严格 current 串行化"假设。v3/v4 改用独立 u8 后的注释说明:wakelist 入队不再被 p->on_cpu 串行化,跨 CPU 看到 p->on_cpu 的 acquire 与 finish_task() 中的 release 配对,flags 必须在入队前发布,所以这个 bit 不能继续留在共享 bitfield 字里。
新流程
Direct wakeup path (unchanged):
ttwu() ---> ttwu_do_activate(wake_flags)
|
+---> wakeup_preempt_fair(wake_flags)
Queued wakeup path (flags preserved):
ttwu() ---> ttwu_queue_wakelist()
|
+-- p->sched_remote_wakeup_flags = wake_flags & WF_TTWU_QUEUE_MASK
target CPU ---> sched_ttwu_pending()
|
+---> ttwu_do_activate(rq, p,
| p->sched_remote_wakeup_flags, &rf)
+---> wakeup_preempt_fair(p->sched_remote_wakeup_flags)
新的 WF_TTWU_QUEUE_MASK 明确划出哪些位需要跨队列保留:WF_TTWU(TTWU 上下文)、WF_SYNC(同步唤醒)、WF_MIGRATED(迁移标记)、WF_RQ_SELECTED(已选 runqueue)。WF_CURRENT_CPU 只是 CPU 选择提示,离开选择阶段就无意义,所以不进入 mask。
关键实现
/* include/linux/sched.h - 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) before ttwu_queue_wakelist() pairs
* with smp_store_release(&p->on_cpu) in finish_task(). The flags are
* published before the task is added to the wakelist.
*/
u8 sched_remote_wakeup_flags;
/* kernel/sched/core.c */
#define WF_TTWU_QUEUE_MASK \
(WF_TTWU | WF_SYNC | WF_MIGRATED | WF_RQ_SELECTED)
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);
/* ... */
p->sched_remote_wakeup_flags = wake_flags & WF_TTWU_QUEUE_MASK;
}
void sched_ttwu_pending(void *arg)
{
/* ... */
ttwu_do_activate(rq, p, p->sched_remote_wakeup_flags, &rf);
}
两个关键点:
- 字段从 1 bit 改成
u8,并移到 scheduler bitfield 之外,避免与"必须由调度器锁串行化"的位共字——这与 release/acquire 配对语义匹配。 WF_TTWU_QUEUE_MASK让"哪些位需要保留"成为显式契约,未来再添 wake flag 时需要主动决定要不要进 mask,避免再次悄悄丢信息。
类比
把 ttwu_queue_wakelist() 想成快递柜:waker 把包裹(被唤醒的任务)丢进柜子,给收件人(目标 CPU)一张取件码。原先柜子只允许贴一个"是否跨城"标签,柜员 sched_ttwu_pending() 凭这个标签决定是否加急,但包裹里的其它属性("已选好派送路线"、"VIP 件"、"加急同步")都丢了。新的设计是给柜子加一张小卡片 sched_remote_wakeup_flags(u8),明确登记"派送需要的全部属性",由柜员照单全收,恢复原本应该享受的服务等级(抢占阈值、ENQUEUE_RQ_SELECTED 等)。
Highlight:风险与注意点
WF_RQ_SELECTED漏传在大多数情况下只是"行为略不优化",但它会进入wakeup_preempt_fair()的抢占阈值计算,可能让高优先级 wakee 错过应该立刻抢占的窗口——典型的"功能退化"型 bug,没有 crash 但公平/响应性受影响。- v3 改用
READ_ONCE()/WRITE_ONCE()、v4 又退回去的取舍需要后续 review 跟进:wakelist 入队时p->on_cpu已经由 release/acquire 串行化,作者因此认定普通读写在跨 CPU 视角下可见,但内核 memory model 组对此是否买账值得确认。 WF_TTWU_QUEUE_MASK是一次性白名单,将来增加新WF_*flag 时容易遗漏更新 mask;建议在enum定义处加BUILD_BUG_ON提醒。- 该字段是
u8,未来若wake_flags位数超过 8 还要回头扩展;当前 wake flags 数量远小于 8,暂无压力。
版本变化
- v1 → v2:把
sched_remote_wakeup_flags移到 scheduler bitfield 之外成为独立u8;删除sched_ttwu_pending()里多余的清零;新增WF_TTWU_QUEUE_MASK。 - v2 → v3:改用
READ_ONCE()/WRITE_ONCE();changelog 围绕WF_RQ_SELECTED抢占行为重新立锚;保留 task_struct 跨 CPU 顺序注释。 - v3 → v4:去掉
READ_ONCE()/WRITE_ONCE();移除sched_ttwu_pending()中多余的wake_flags局部;修正 task_struct 字段顺序注释。
一句话总结
把"队列唤醒"路径里被吞掉的 wake flags 重新装回一个独立的 u8 字段并显式白名单化,从而恢复 WF_RQ_SELECTED 等位在 ttwu_do_activate() 与 wakeup_preempt_fair() 中的语义,避免 wakelist 边界上的功能退化。