sched-ext discussion
[PATCH] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work
LLM 分析
rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work
系列基线信息
| 字段 | 值 |
|---|---|
| 标题 | [PATCH] rcu-tasks: Defer IRQ-disabled callback enqueue to irq_work |
| 作者 | Matt Fleming (Cloudflare) |
| 版本 | v1(尚未提交 v2) |
| 规模 | 单 patch,约 +103/-43 行 |
| Message-ID | 20260713101245.3207634-1-matt@readmodwrite.com |
| 来源 | sched-ext 频道 |
| RCU Maintainer | Paul E. McKenney |
明确目的
call_rcu_tasks_generic() 在 IRQ 已禁用的上下文中被调用时,会直接尝试获取 cbs_gbl_lock,从而与调度器 runqueue 锁形成 ABBA 死锁。
具体触发路径:sched_ext 的 task-storage 拆卸 → 持有 rq->lock → 调用 call_rcu_tasks_generic() → 尝试获取 cbs_gbl_lock;而另一条路径(rcu_tasks_one_gp → printk → try_to_wake_up)恰好持有 cbs_gbl_lock 后又请求 rq->lock,形成循环等待。
本 patch 的目标:在 IRQ 禁用场景下,将回调入队延迟到 irq_work 中执行,避免在 IRQ 禁用上下文中拿锁,从根本上消除锁序反转。
遍历代码
1. 新增数据结构
每个 per-CPU 的 rcu_tasks_percpu 新增两个字段:
struct irq_work rtp_irq_bypass_work; // 延迟排空用的 IRQ work
struct llist_head rtp_irq_bypass_list; // 无锁回调暂存链表
初始化时 rtp_irq_bypass_work 绑定为 IRQ_WORK_INIT_HARD(硬 IRQ 上下文执行),rtp_irq_bypass_list 初始化为空链表。
2. call_rcu_tasks_generic() 快速路径变更
原函数入口处检测 irqs_disabled():
bool irqsoff = irqs_disabled();
- IRQ 开启:走原有路径,直接拿锁、入队,逻辑不变。
- IRQ 禁用:将回调挂入
rtp_irq_bypass_list(llist_add,无锁),然后通过irq_work_queue()排空 work,立即返回,不拿任何自旋锁。
3. call_rcu_tasks_iw_drain_irq_bypass() — irq_work 回调
由硬 IRQ 上下文触发,调用 rcu_tasks_drain_irq_bypass(rtpcp)。
4. rcu_tasks_drain_irq_bypass() — 核心排空逻辑
local_irq_save()+rcu_read_lock()— 确保 IRQ 和 RCU 读侧保护。call_rcu_tasks_lock()— 尝试拿 per-CPU cblist 锁,返回是否争抢过。llist_del_all()— 一次性取出 bypass 链表全部节点。- 若无节点,解锁返回。
llist_reverse_order()— 恢复原始入队顺序(FIFO 语义)。- 遍历每个回调,调用
call_rcu_tasks_enqueue_locked()正式入队。 - 若争抢过,调用
rcu_tasks_need_queue_adjust()检查是否需要扩展为 per-CPU 模式。 - 若需唤醒 kthread,调用
rcuwait_wake_up()。
5. call_rcu_tasks_enqueue_locked() — 提取的入队逻辑
将原来 call_rcu_tasks_generic() 中入队、lazy timer、urgent_gp 等逻辑提取为独立函数,供正常路径和排空路径共用。
6. rcu_barrier_tasks_generic() 和 rcu_tasks_need_gpcb() 的补丁
两处都在操作 cblist 前增加 rcu_tasks_drain_irq_bypass(rtpcp) 调用,确保 barrier 和 GP 检测时 bypass 链表中的回调不会被遗漏。
ASCII 流程图
IRQ-disabled 上下文调用
call_rcu_tasks_generic()
|
+----------+----------+
| |
irqs_disabled() irqs_enabled()
| |
llist_add(bypass) 直接拿锁入队
| call_rcu_tasks_
irq_work_queue() enqueue_locked()
| |
[返回,不拿锁] [正常完成]
|
=== 硬 IRQ 上下文 ===
|
call_rcu_tasks_iw_drain_irq_bypass()
|
rcu_tasks_drain_irq_bypass()
|
llist_del_all(bypass)
|
llist_reverse_order()
|
call_rcu_tasks_enqueue_locked()
(正式入队到 per-CPU cblist)
死锁路径对比
=== 修复前 (ABBA 死锁) ===
CPU 0 CPU 1
------ ------
sched_ext_free()
rq->lock <---+
| rcu_tasks_one_gp()
call_rcu_tasks_generic() cbs_gbl_lock <---+
cbs_gbl_lock <---+ |
| printk()
| try_to_wake_up()
| rq->lock <---+
| |
+------------- 循环等待 -----------+
=== 修复后 (IRQ 禁用时绕过) ===
CPU 0 (IRQs off)
------
sched_ext_free()
rq->lock
call_rcu_tasks_generic()
llist_add(bypass) <-- 无锁!
irq_work_queue() <-- 延迟处理
[返回,不拿 cbs_gbl_lock]
概念类比
想象一个银行柜台(cbs_gbl_lock)和保安岗(rq->lock)。顾客 A 在保安岗登记后去柜台排队,顾客 B 在柜台办完事后去保安岗登记——如果两人同时进行,A 等柜台、B 等保安岗,就死锁了。
修复方案:当顾客已经站在保安岗旁(IRQ 禁用 = 安全区域限制),不再让他直接去柜台排队,而是让他把需求投进一个"快速投递箱"(rtp_irq_bypass_list),然后由一个专门的跑腿员(irq_work)在合适时机把投递箱里的需求统一送到柜台。跑腿员只在保安岗空闲时才去柜台,避免了两个人同时占着各自的资源等对方。
Highlight 突出问题
rcu_barrier_tasks_generic()需排空所有队列:Paul McKenney 指出,RCU Tasks 有时存在多个回调队列,barrier 必须排空所有队列。当前 patch 的 barrier 排空逻辑可能不完整,v2 需要确保覆盖所有 per-CPU bypass 链表。- CPU hotplug 场景:CPU 下线时若 bypass 链表还有未排空的回调,谁来处理?Paul 提到正在讨论的 call_rcu()/call_srcu() deferral 系列会解决此问题,v2 应 piggyback 其上。
- 与 BPF 程序的交互:Paul 提到 off-list 讨论中发现了 BPF 程序与 CPU hotplug 之间"有趣的关系",影响
call_rcu_tasks()路径,但细节尚未公开。 - patch 已过时:v1 不再能干净地应用到 Paul 的树,需要提交 v2。
版本演进
| 版本 | 状态 | 关键改动 |
|---|---|---|
| v1 | 已提交,需重做 | 初始实现 IRQ bypass 机制;Paul 反馈 barrier 需排空所有队列;建议等待 call_rcu()/call_srcu() deferral 系列后再做 v2 |
与其他相关 patch 系列的关联
- call_rcu()/call_srcu() deferral 系列(Paul McKenney 团队正在开发):同样采用
irqsoff局部变量检测 + 延迟入队模式,将提供可复用的 helper 函数。Matt 同意 v2 将 piggyback 在此系列之上。 - 该系列还将明确 CPU 下线时 deferred callback 的处理方式。
一句话总结
通过在 IRQ 禁用时将 RCU Tasks 回调暂存到无锁 llist 并延迟到 irq_work 排空,消除了 call_rcu_tasks_generic() 与调度器 runqueue 锁之间的 ABBA 死锁;v2 将等待上游 call_rcu()/call_srcu() deferral 基础设施就绪后重新提交。