sched discussion
[PATCH v31 0/9] Sleeping Owner Handling for Proxy Execution (v31)
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.h、init/init_task.c、kernel/fork.c、kernel/sched/core.c、kernel/sched/deadline.c、kernel/sched/ext/ext.c、kernel/sched/fair.c、kernel/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/9 | Christian Loehle | sched/deadline: Ignore proxy-exec sched_yield() | bugfix |
| 2/9 | Vasily Gorbik | Don't steal a proxy-exec donor | bugfix |
| 3/9 | John Stultz | Avoid migrating blocked_on tasks | fix |
| 4/9 | Vasily Gorbik | Don't proxy-exec unmatched cookie lock owners | bugfix |
| 5/9 | John Stultz | Switch rq->next_class in proxy_reset_donor() | bugfix |
| 6/9 | John Stultz | Break out core of attach_tasks() into sched.h | refactor |
| 7/9 | John Stultz | Migrate whole chain in proxy_migrate_task() | perf |
| 8/9 | Peter Zijlstra | Add deactivated (sleeping) owner handling | feature |
| 9/9 | Andrea Righi | Distinguish proxy activations from wakeups | feature |
关键实现
新增的 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。