0/10 已展开

LLM 分析

Proxy Execution v31:Sleeping Owner Handling 关键一步

系列概况

  • 标题: [PATCH v31 0/9] Sleeping Owner Handling for Proxy Execution (v31)
  • 作者: John Stultz jstultz@google.com;贡献者含 Christian Loehle、Vasily Gorbik、Peter Zijlstra、Andrea Righi
  • 版本: v31(共 9 个 patch)
  • 规模: 8 个文件,+374 / -43
  • 修改文件: include/linux/sched.hinit/init_task.ckernel/fork.ckernel/sched/core.ckernel/sched/deadline.ckernel/sched/ext/ext.ckernel/sched/fair.ckernel/sched/sched.h
  • Message-ID: 20260724181238.3445275-1-jstultz@google.com
  • 完整性: 9 个 patch 全部到齐;PATCH 8/9 正文在 list_replace_init() 处被截断

补丁目的

Proxy Execution 让阻塞在 mutex 上的 waiter 把自己的调度上下文临时"捐给"锁 owner,避免优先级反转。本系列解决"owner 正在 sleep"这一死角:此时 boost owner 无意义(它根本不在 runqueue 上),于是把 waiter deactivate 并挂到 owner 的 blocked_head 链表,owner 醒来时一次性在同一 rq 上激活这些 waiter,由它们反过来 boost owner。

旧流程的问题

  • owner 睡眠时 waiter 无法 boost,只能空等,唤醒时延不可控。
  • donor 与 rq->curr 解耦后,try_steal_cookie()proxy_reset_donor()find_proxy_task() 等仍按 rq->curr 判断,导致 stale 状态与 core-scheduling cookie 被绕过。
  • yield_task_dl() 语义是强制 DL 实体睡到 replenishment,套到 proxy donor 上过重。
  • proxy 迁移路径上 nr_iowait / nr_uninterruptible 计数容易失衡。

新流程

   [waiter A]   [waiter B]   [waiter C]
        \            |            /
         v           v           v
   +-------------------------------+
   |    owner->blocked_head        |
   |    (owner is sleeping)        |
   +-------------------------------+
                 |
       owner wakeup -> schedule()
                 |
       activate_blocked_waiters(owner)
                 |
   +-------------+-------------+
   v             v             v
A on owner_rq  B on owner_rq  C on owner_rq
                 |
   A/B/C donate sched ctx -> boost owner

Patch 概览

#作者主题性质
1/9Christian Loehlesched/deadline: Ignore proxy-exec sched_yield()bugfix
2/9Vasily GorbikDon't steal a proxy-exec donorbugfix
3/9John StultzAvoid migrating blocked_on tasksfix
4/9Vasily GorbikDon't proxy-exec unmatched cookie lock ownersbugfix
5/9John StultzSwitch rq->next_class in proxy_reset_donor()bugfix
6/9John StultzBreak out core of attach_tasks() into sched.hrefactor
7/9John StultzMigrate whole chain in proxy_migrate_task()perf
8/9Peter ZijlstraAdd deactivated (sleeping) owner handlingfeature
9/9Andrea RighiDistinguish proxy activations from wakeupsfeature

关键实现

新增的 task_struct 字段把等待树具象化:

struct task_struct {
#ifdef CONFIG_SCHED_PROXY_EXEC
    struct list_head blocked_head;            /* tasks blocked on this task */
    struct list_head blocked_node;            /* my entry on someone's blocked_head */
    struct list_head blocked_activation_node; /* walk list, avoids recursion */
    struct task_struct *sleeping_owner;       /* whose blocked_head I sit on */
#endif
};

activate_task() 在 proxy-exec 下把"摘链"和"入队"放在同一把 blocked_lock 下,与 find_proxy_task() 排序:

void activate_task(struct rq *rq, struct task_struct *p, int en_flags)
{
    if (!sched_proxy_exec()) {
        __activate_task(rq, p, en_flags);
        return;
    }
    lockdep_assert_rq_held(rq);
    proxy_remove_from_sleeping_owner(p);
    raw_spin_lock(&p->blocked_lock);
    __activate_task(rq, p, en_flags);
    raw_spin_unlock(&p->blocked_lock);
}

do_activate_blocked_waiter() 做 block_task() 的逆运算,并在迁移 CPU 之前修正 nr_iowait(v31 修复点):

if (p->in_iowait) {
    delayacct_blkio_end(p);
    atomic_dec(&task_rq(p)->nr_iowait);
}
proxy_set_task_cpu(p, target_cpu);
rq_lock_irqsave(target_rq, &rf);
if (p->sched_contributes_to_load)
    target_rq->nr_uninterruptible--;
activate_task(target_rq, p, en_flags);
resched_curr(target_rq);
rq_unlock_irqrestore(target_rq, &rf);

activate_blocked_waiters() 先把整个 blocked_head list_replace_init() 下来到本地 bal_head,用 blocked_activation_node 串成待处理队列,从而"非递归地做递归",也保证只处理有限条目(处理期间新挂进来的由后续 wake 事件负责)。

patch 9/9 加 ENQUEUE_PROXY,让 sched_ext 区分捐助激活与真 wakeup:

int en_flags = ENQUEUE_WAKEUP | ENQUEUE_NOCLOCK | ENQUEUE_PROXY;
/* ext.c */
if (core_enq_flags & ENQUEUE_PROXY)
    enq_flags &= ~(ENQUEUE_PROXY | SCX_ENQ_WAKEUP);

patch 7/9 则沿 p->blocked_donor 遍历整条链,借用 se.group_node 串进 migrate_list,一次 __attach_tasks() 完成整链迁移。

类比

owner 像银行柜员,waiter 像取号顾客。柜员离岗(sleeping)时顾客叫不到号,只能在柜台上贴便签(blocked_head)。柜员回来的一刻,那摞便签整叠揭下(list_replace_init)展开成队列,所有人一起重新取号,柜员因为被一群人盯着而获得优先服务(boost)。

DL 的修复则像:不能因为柜员挥了下手就当成"下班打卡",那会把他锁进休息室直到下一个班次(replenishment)——这与 FIFO/RR 的"软让位"完全不是一回事。

Highlight:风险与注意点

  • cookie 绕过:4/9 修好了 find_proxy_task() 把不同 cookie 的 owner 拉上 CPU 的路径;后续任何"中途换 owner"逻辑都必须保留该检查。
  • 激活与唤醒并发 race:K Prateek 报的 waiter 挂上 sleeping owner 后可能被卡住,靠 blocked_lock 序列化,需要长期回归验证。
  • nr_iowait 归属:即使数量平衡了,proxy-migrated donor 的 iowait 可能记到 owner CPU 的 rq 上,是可观测差异,作者自己列为待办。
  • se.group_node 复用:只有在任务已 deactivated 且持 rq_lock 时安全,任何时序变化都可能踩到负载均衡路径。
  • 树状唤醒:中间节点被唤醒时需级联处理下层,blocked_activation_node 是这套非递归栈的核心,也是最容易读错的部分。
  • sched_ext 集成ENQUEUE_PROXY 必须在进入 BPF 前被抹掉,否则 BPF 调度器会把捐助激活误当普通 wakeup。

版本变化

v30 → v31:

  • 纳入 Christian 的 yield_task_dl() proxy 早返回补丁
  • 修复 K Prateek 发现的 sleeping owner 唤醒与 waiter 入队并发 race
  • 按 Maria Yu / Tengfei Fan 的复现器调整 do_activate_blocked_waiter(),修正 nr_iowait 失衡
  • 纳入 Andrea Righi 的两行 patch(ENQUEUE_PROXY),为 sched_ext + proxy-exec 并行落地铺路

遗留项:lock-owner-count 优化、性能 regression 复评、chain migration 对 RT/DL 负载不变量的验证、nr_iowait 归属差异。

与其他系列的关联

依赖并延续 Proxy Exec 前四阶段(prep / single-rq / donor 迁移 / 优化迁移);与 Andrea Righi 的 sched_ext 系列相互独立但需两行胶水;下游还有 Suleiman 的 pi-futex、proxy-rwsems、Binder PI 等工作。

一句话总结

v31 用四个新的 task_struct 链表字段加 ENQUEUE_PROXY,把"锁 owner 在睡觉"这一 proxy-exec 死角补上,同时捎带修掉 DL yield、cookie 不匹配、donor 被偷、计数失衡等一串 bug。