背景

kmemleak 的扫描在大内存 debug 内核上一跑就是很久。此前它靠 cond_resched() 防 soft lockup,但 cond_resched() 在 CONFIG_PREEMPTION 内核上是空操作,也从来不是 RCU-tasks 的静止状态。

RCU-tasks 没有读侧加锁原语,grace period 的判定靠任务自己走到静止状态:自愿上下文切换、执行用户态代码、进 idle 或主动上报都算,被抢占不算。kmemleak 线程在扫描里既不做自愿切换也不上报,被挂上 holdout 链表,grace period 被拖着不结束。等待方跟着遭殃:BPF struct_ops map 的释放走 synchronize_rcu_mult() 等这个 grace period,被卡 122 秒触发 hung task 告警。

kmemleak 不上报静止状态的传导链
┌───────────────────────────────────────┐
│ kmemleak 扫描长跑,cond_resched() 是  │
│ 空操作                                │
└───────────────────┬───────────────────┘
                    ▼
┌───────────────────────────────────────┐
│ kmemleak 线程成 holdout,grace period │
│ 拖延                                  │
└───────────────────┬───────────────────┘
                    ▼
┌───────────────────────────────────────┐
│ 等 grace period 的 BPF map 释放被卡   │
│ 122 秒,触发 hung task                │
└───────────────────────────────────────┘

问题

  • kmemleak 扫描路径的 cond_resched() 在 PREEMPTION 内核是空操作
  • cond_resched() 不报 RCU-tasks 静止状态,扫描再久也不算数
  • kmemleak 线程成 holdout,grace period 被拖住不结束
  • 等待方 synchronize_rcu_mult() 被卡 122 秒,触发 hung task 告警

方案

把扫描路径的 7 个 cond_resched() 让出点全部换成 cond_resched_tasks_rcu_qs(),每次让出都顺带向 RCU-tasks 上报一次静止状态,不再以发生调度为前提。

七个让出点统一换上报原语
┌──────────────────────────────────────┐
│ 扫描路径 7 个 cond_resched() 让出点  │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│ 全部换成 cond_resched_tasks_rcu_qs() │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐
│  每轮扫描上报静止状态,调度语义不变  │
└──────────────────────────────────────┘

7 个让出点分布在 scan_large_block()、scan_object()(2 处)、scan_gray_list()、kmemleak_cond_resched()、kmemleak_scan_task_stacks(),以及 kmemleak_scan() 主循环(每 64 个 PFN 让出一次)。其中几处原本就是先放锁再让出、让出完再拿锁的结构,换上的原语同样落在无锁窗口里,安全性与原有调度点一致。

cond_resched_tasks_rcu_qs() 的前半段 rcu_tasks_qs() 清掉当前任务的 holdout 标记,效果等同声明 grace period 不必再等自己;后半段 cond_resched() 保持原语义。soft lockup 防护不受影响,RCU-tasks 的上报补上了。

收益

作者未提供性能数据,从代码逻辑推断的预期收益:

  • kmemleak 扫描不再拖延 RCU-tasks grace period,消除 rcu_tasks stall 告警
  • synchronize_rcu_mult() 的等待方不再被连带卡死,消除 122 秒级 hung task
  • soft lockup 防护保持原样,每轮让出仅多一次标记读取,开销可忽略