Linux sys_futex futex_wake与hashbucket锁定
Linux sys_futex futex_wake与hashbucket锁定futex(2) 系统调用是 Linux 实现高效用户态同步的核心机制。与所有经典同步原语不同的是futex 在无竞争时完全在用户态通过原子操作完成仅在需要等待或唤醒时进入内核。sys_futex 的入口是 do_futexclong do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,u32 __user *uaddr2, u32 val2, u32 val3){int ret -ENOSYS;switch (op) {case FUTEX_WAIT:ret futex_wait(uaddr, flags, val, timeout, val3);break;case FUTEX_WAKE:ret futex_wake(uaddr, flags, val, val3);break;case FUTEX_REQUEUE:ret futex_requeue(uaddr, flags, uaddr2, val, val2, val3, 0);break;case FUTEX_CMP_REQUEUE:ret futex_requeue(uaddr, flags, uaddr2, val, val2, val3, 1);break;case FUTEX_WAIT_BITSET:ret futex_wait(uaddr, flags, val, timeout, val3);break;case FUTEX_WAKE_BITSET:ret futex_wake(uaddr, flags, val, val3);break;case FUTEX_LOCK_PI:ret futex_lock_pi(uaddr, flags, timeout, 0);break;case FUTEX_UNLOCK_PI:ret futex_unlock_pi(uaddr, flags);break;...}return ret;}do_futex 根据 op 分派到不同的处理函数。对于 FUTEX_WAKE核心路径是 futex_wakecstatic int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset){struct futex_hash_bucket *hb;struct futex_q *this, *next;union futex_key key FUTEX_KEY_INIT;int ret 0;if (!bitset)return -EINVAL;ret get_futex_key(uaddr, flags, key, FUTEX_READ);if (unlikely(ret ! 0))goto out;hb hash_futex(key);spin_lock(hb-lock);plist_for_each_entry_safe(this, next, hb-chain, list) {if (match_futex(this-key, key)) {if (this-pi_state || this-rt_waiter) {ret -EINVAL;break;}if (!(this-bitset bitset))continue;wake_futex(this);if (ret nr_wake)break;}}spin_unlock(hb-lock);out:return ret;}get_futex_key 是第一个关键操作。它通过 get_user_pages_fast 锁定用户态的页防止页面被换出导致物理地址变化然后根据该页所在的位置常规映射或匿名映射构造一个独特的 futex_keycint get_futex_key(u32 __user *uaddr, unsigned int flags, union futex_key *key,enum futex_access rw){unsigned long address (unsigned long)uaddr;struct mm_struct *mm current-mm;struct page *page, *tail;struct address_space *mapping;int err, ro 0;if (unlikely((address % sizeof(u32)) ! 0))return -EINVAL;address PAGE_MASK;err get_user_pages_fast(address, 1, rw FUTEX_WRITE, page);if (err 0)return err;...}key 的构成决定了 futex 的关联方式。对于基于物理页框的共享 futexMAP_SHAREDkey 使用 mapping index对于私有映射key 使用 mm address。这使得 fork 之后的父子进程通过 COW 页面触发不同的 key避免交叉唤醒。hash_futex 将 key 哈希到 futex_hash_bucketcstatic struct futex_hash_bucket *hash_futex(union futex_key *key){u32 hash jhash2((u32 *)key, offsetof(typeof(*key), both.offset) / 4,key-both.offset);return futex_queues[hash (futex_hashsize - 1)];}futex_queues 是一个 hash bucket 数组每个桶包含一个 plist优先级排序链表和一个 spinlock。plist 按优先级排序确保优先级继承机制的 futex 操作中高优先级等待者被优先唤醒。wake_futex 执行实际的唤醒操作cstatic void wake_futex(struct futex_q *q){struct task_struct *p q-task;get_task_struct(p);plist_del(q-list, q-hb-chain);WRITE_ONCE(q-lock_ptr, q-hb-lock);...wake_up_state(p, TASK_NORMAL);put_task_struct(p);}wake_futex 从 hash bucket 链表中删除该 futex_q然后调用 wake_up_state 将等待者的状态从 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 切换为 TASK_RUNNING并将其加入运行队列。hb-lock 是保护同一个 hash bucket 内所有 futex_q 的 spinlock。在 futex_wait 路径中等待者在调用 futex_wait_queue_me 时会将 futex_q 插入到 hb-chain并使用 set_current_state 设置 TASK_INTERRUPTIBLE随后检查用户态的 futex 值是否发生变化。这种 double-check 机制是 futex 的核心wake 和 wait 基于同一个 hb-lock 保证原子性避免唤醒信号丢失。全局的 futex hash 表大小在启动时根据物理内存调整默认散列到 256 个桶。