sched discussion
[PATCH] futex: Fix might_sleep() warning in futex_pivot_pending()
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.h、include/linux/wait_bit.h、kernel/futex/core.c、kernel/sched/wait.c、kernel/sched/wait_bit.c - 代码统计:55 行新增,4 行删除
- Message-ID:
<20260820074927.GH1246887@noisy.programming.kicks-ass.net> - 完整性:完整,已被
tip-bot2推送到tip:locking/urgent(commitd8aa5dd97944),含Fixes/Reported-by/Signed-off-by,关联 syzbot bug350a93852ac854927f45,并Fixescommit8e7ff730dd96
补丁目的
私有 futex 哈希表在 resize 时会调用 futex_pivot_pending() 等待旧桶上的等待者迁移完成。早期 commit 8e7ff730dd96 用 wait_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 概览
补丁同时改造两条路径:
- 核心调用点:
kernel/futex/core.c中wait_var_event()被替换为手写路径。 - 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() 告警。