0/2 已展开

LLM 分析

futex:修复 futex_pivot_pending()might_sleep() 告警

系列概况

  • 标题[PATCH] futex: Fix might_sleep() warning in futex_pivot_pending()
  • 作者:Peter Zijlstra(Intel)<peterz@infradead.org>
  • 版本:单封 PATCH(v1)
  • 规模:5 个文件,+55 / -4- 修改文件include/linux/wait.hinclude/linux/wait_bit.hkernel/futex/core.ckernel/sched/wait.ckernel/sched/wait_bit.c
  • 代码统计:55 行新增,4 行删除
  • Message-ID<20260820074927.GH1246887@noisy.programming.kicks-ass.net>
  • 完整性:完整,已被 tip-bot2 推送到 tip:locking/urgent(commit d8aa5dd97944),含 Fixes/Reported-by/Signed-off-by,关联 syzbot bug 350a93852ac854927f45,并 Fixes commit 8e7ff730dd96

补丁目的

私有 futex 哈希表在 resize 时会调用 futex_pivot_pending() 等待旧桶上的等待者迁移完成。早期 commit 8e7ff730dd96wait_var_event(mm, futex_pivot_pending(mm)) 做轮询等待;该接口虽然是为“可被 might_sleep() 警告的嵌套睡眠循环”准备的简化版本,但缺少 wait-bit 支持。当再次进入 futex_hash_allocate()(例如 prctl(FUTEX_HASH 或 clone 路径)时,等待上下文本身就在一个可睡眠区域里,wait_var_event() 内层循环里的 might_sleep() 触发 syzkaller 报告的告警。补丁把 wait_var_event() 替换成手写 add/sleep/wake 路径,并补上 wait-bit 唤醒函数,把嵌套等待变成显式且有内存屏障的正确同步。

旧流程的问题

旧流程在 kernel/futex/core.c::futex_hash_allocate()(custom 分支)只调用一次:

again:
    wait_var_event(mm, futex_pivot_pending(mm));

wait_var_event() 内部用 wait_woken() + woken_wake_function(),而后者最终走 default_wake_function()try_to_wake_up(),会在 atomic上下文外检查 WARN_ONCE(...) 等。结合 might_sleep() 的嵌套检查,syzbot 在 futex_pivot_pending() 路径里看到了告警。

新流程

新流程手工拼出 add-waitqueue、自旋重检查、remove-waitqueue 三段,并把唤醒端的 key 解析独立成 __var_wake_key(),新增 woken_wake_bit_function() 做 wait-bit 唤醒。同时加 smp_mb() 配对 wait_woken() 里的 smp_store_mb(),保证不会观察到 !futex_pivot_pending() && !waitqueue_active() 的窗口。

Patch 概览

补丁同时改造两条路径:

  1. 核心调用点kernel/futex/core.cwait_var_event() 被替换为手写路径。
  2. wait 子系统
    • include/linux/wait.h 暴露 woken_wake_bit_function
    • include/linux/wait_bit.h 暴露 __var_wake_key
    • kernel/sched/wait.c 新增 woken_wake_bit_function,含 MB + WQ_FLAG_WOKEN
    • kernel/sched/wait_bit.c 把 key 解析拆出 __var_wake_key,保留原 var_wake_function

关键实现

futex_hash_allocate() 新写法(精简):

struct wait_bit_queue_entry __wbq_entry;
struct wait_queue_head *__wq_head;

__wq_head = __var_waitqueue(mm);
init_wait_var_entry(&__wbq_entry, mm, 0);
__wbq_entry.wq_entry.func = woken_wake_bit_function;
add_wait_queue(__wq_head, &__wbq_entry.wq_entry);

/*
 * add_wait_queue() futex_ref_put()
 * MB (this)            MB (implied)
 * futex_pivot_pending() wake_up_var()
 * waitqueue_active()
 *
 * Notably, it must not be possible to see
 * !futex_pivot_pending() && !waitqueue_active().
 */
smp_mb();

while (!futex_pivot_pending(mm) &&
 wait_woken(&__wbq_entry.wq_entry, TASK_UNINTERRUPTIBLE,
                  MAX_SCHEDULE_TIMEOUT))
    /* empty */;

remove_wait_queue(__wq_head, &__wbq_entry.wq_entry);

woken_wake_bit_function

int woken_wake_bit_function(struct wait_queue_entry *wq_entry, unsigned mode,
                            int sync, void *arg)
{
    struct wait_bit_key *key = __var_wake_key(wq_entry, arg);
    if (!key)
        return 0;
    /* Pairs with the smp_store_mb() in wait_woken(). */
    smp_mb(); /* C */
    wq_entry->flags |= WQ_FLAG_WOKEN;
    return default_wake_function(wq_entry, mode, sync, key);
}

__var_wake_key() 把原 var_wake_function() 的 key 校验部分抽出来,命中则返回 key,未命中(不同 flags/bit_nr)返回 NULL 表示“不属于我,不唤醒”。

类比

mm 当作一栋楼,futex_pivot_pending() 是电梯口的“旧楼是否清空”的指示灯。wait_var_event() 像让保安每隔几分钟下楼瞄一眼指示灯,但保安自己也是人,会让楼里值班的另一位同事被 might_sleep() 抱怨“你怎么又在打盹”。补丁相当于给保安配了专用对讲机和醒目的“值班中”挂牌(WQ_FLAG_WOKEN + smp_mb()),并且对讲机的频段只对特定房间(key.flags/bit_nr)有效 —— 不同房间呼叫不会误唤醒这位保安,从而消除了“在楼里睡觉还要被楼里其他值班的检查睡眠”的悖论。

Highlight:风险与注意点

  • 内存屏障成对:add_wait_queue() 之后必须 smp_mb() 才能避免 futex_pivot_pending()wake_up_var() 之间出现 !pending && !active 的短暂窗口;woken_wake_bit_function 中的 smp_mb() 必须与 wait_woken() 里的 smp_store_mb() 配对。
  • __var_wake_key() 返回 NULL 的语义是“不是我负责的 entry”,不能误改成 BUG_ON;否则多 entry 同 head 的场景会自爆。
  • init_wait_var_entry(&__wbq_entry, mm, 0)bit_nr=0 一定要和 wait_queue_head__var_waitqueue(mm) 输出对得上,否则 key 永远匹配不上 → 永远睡死。
  • TASK_UNINTERRUPTIBLE 是有意为之:futex private hash resize 不希望被信号打断;调用方需要保证自己不在必须响应信号的上下文里。
+-------------------+ smp_mb() +------------------------+
| futex_hash_alloc  | ----------------------> | waitqueue active check |
|  add_wait_queue   |                         |  futex_pivot_pending() |
+---------+---------+                         +-----------+------------+
          |                                               |
          |  while (!pending && wait_woken(...))         |
          v v
   +--------------+        wake_up_var()        +-------------------+
 |  scheduler   | <--------------------------- | refcount dropper |
   |  sleeps |                              |  (futex_ref_put)  |
   +------+-------+                              +-------------------+
          |
          | smp_mb() in woken_wake_bit_function
          v
   +--------------+
   | WQ_FLAG_WOKEN|
   +--------------+

版本变化

仅 v1,无版本演进;后续由 tip-bot2 自动推送到 tip:locking/urgent 分支(commit d8aa5dd97944)。

一句话总结

为私有 futex 哈希 resize 中的等待循环补上 wait-bit 唤醒函数和内存屏障,避免在已可睡眠的路径里再次触发 might_sleep() 告警。