土法炼钢兴趣小组的算法知识备份

【操作系统百科】futex

文章导航

分类入口
os
标签入口
#futex#pthread-mutex#priority-inheritance#robust-futex#futex2

目录

pthread_mutex_lock 在没有竞争时不需要进内核——一条 CAS 就够。竞争时才用 futex(Fast Userspace muTEX)进入内核等待。

一、先看图

flowchart TD
    LOCK[pthread_mutex_lock] --> CAS{CAS 成功?}
    CAS -- 是 --> HOLD[持有锁<br/>零 syscall]
    CAS -- 否 --> FUTEX[futex(FUTEX_WAIT)<br/>进入内核等待]
    FUTEX --> HASH[futex hash table<br/>按地址查找]
    HASH --> SLEEP[加入等待队列<br/>schedule]

    UNLOCK[pthread_mutex_unlock] --> CAS2{有等待者?}
    CAS2 -- 否 --> DONE[直接返回]
    CAS2 -- 是 --> WAKE[futex(FUTEX_WAKE)<br/>唤醒一个]

    classDef fast fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    classDef slow fill:#f0883e22,stroke:#f0883e,color:#adbac7;
    class HOLD,DONE fast
    class FUTEX,HASH,SLEEP,WAKE slow
    class LOCK,UNLOCK,CAS,CAS2 fast

二、核心 API

// 用户态变量
int futex_word = 0;

// 等待:如果 *uaddr == val 则睡眠
futex(uaddr, FUTEX_WAIT, val, timeout, NULL, 0);

// 唤醒:唤醒最多 n 个等待者
futex(uaddr, FUTEX_WAKE, n, NULL, NULL, 0);

关键:内核不管锁语义——只做”比较并等待”和”唤醒”。锁协议完全在用户态实现。

三、内核实现

3.1 Hash Table

// kernel/futex/core.c
static struct futex_hash_bucket futex_queues[1 << FUTEX_HASHBITS];

用户地址 → hash → bucket → 等待队列。

3.2 等待路径

  1. futex_wait() → hash 查找 bucket → 锁 bucket
  2. 检查 *uaddr == val(防止 lost wakeup)
  3. 加入等待队列 → schedule()

3.3 唤醒路径

  1. futex_wake() → hash 查找 bucket
  2. 唤醒队列中 n 个等待者

四、PI futex(优先级继承)

pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);

低优先级线程持锁 → 高优先级线程等待 → 低优先级线程临时提升到高优先级。

内核用 rt_mutex 实现 PI futex → 复用优先级继承链。

五、Robust futex

进程持锁时崩溃 → 锁永远不会释放 → 其他线程永远等待。

pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);

内核维护 robust list → 进程退出时自动标记锁为 FUTEX_OWNER_DIED → 下一个 waiter 收到 EOWNERDEAD → 可以修复状态。

六、FUTEX_REQUEUE

场景:pthread_cond_broadcast() 唤醒所有等待者 → 它们全部竞争 mutex → 惊群。

FUTEX_CMP_REQUEUE:原子地把 condvar 等待队列的线程 转移 到 mutex 等待队列 → 只唤醒一个。

七、futex2(5.16+)

futex_waitv:同时等待多个 futex 地址。

struct futex_waitv waitv[] = {
    { .uaddr = addr1, .val = expected1, .flags = FUTEX_32 },
    { .uaddr = addr2, .val = expected2, .flags = FUTEX_32 },
};
futex_waitv(waitv, 2, 0, timeout);

用途:Wine/Proton 模拟 Windows WaitForMultipleObjects。

八、安全问题

8.1 CVE-2021-3347

PI futex 的 requeue 路径存在 use-after-free → 内核提权。

根因:futex 状态机复杂 + 用户态可构造任意地址。

8.2 futex 是攻击面

九、观察

# 查看 futex 等待
cat /proc/<pid>/syscall | grep futex
strace -e futex -p <pid>

# 统计
bpftrace -e 'tracepoint:syscalls:sys_enter_futex { @op[arg1 & 0x7f] = count(); }'
# op: 0=WAIT, 1=WAKE, 6=CMP_REQUEUE, 9=WAIT_BITSET

十、小结


参考文献

工具


上一篇percpu_refcount 下一篇优先级反转

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-01 · os

【操作系统百科】优先级反转与继承

火星探路者号因优先级反转重启——这是实时系统最经典的故事。本文讲优先级反转现象、PIP 优先级继承协议、PCP 优先级天花板、rt_mutex、PREEMPT_RT 的 spinlock 转换、DEADLINE 任务反转。

2026-04-18 · os

【操作系统百科】SCHED_FIFO/RR 与 PREEMPT_RT

Linux 里的实时:SCHED_FIFO/RR 提供优先级调度,但原版内核仍有不可抢占点。PREEMPT_RT 补丁集 20 年后(6.12)合入主线,把几乎所有 spinlock 变 rt_mutex、IRQ 变线程。本文讲 RT 调度语义、RT-throttling、cyclictest 基线、优先级继承、以及 RT 部署的陷阱。

2026-04-17 · os

【操作系统百科】线程模型:1:1、N:1、M:N 与虚拟线程

同样一个 'concurrent' 的需求,OS 级 1:1 线程、用户态 N:1 协程、调度器混合 M:N、运行时虚拟线程各给出了不同答案。本文梳理 LinuxThreads → NPTL 的演进、Solaris LWP 的失败、Go GPM / Java Loom / Erlang BEAM / async-await 的选择,以及 M:N 模型的阿克琉斯之踵:阻塞 syscall。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。


By .