Linux sys_futex futex_wake与hashbucket锁定

📅 2026/7/21 4:44:51 👁️ 阅读次数 📝 编程学习
Linux sys_futex futex_wake与hashbucket锁定

Linux sys_futex futex_wake与hashbucket锁定

futex(2) 系统调用是 Linux 实现高效用户态同步的核心机制。与所有经典同步原语不同的是,futex 在无竞争时完全在用户态通过原子操作完成,仅在需要等待或唤醒时进入内核。sys_futex 的入口是 do_futex:

```c
long 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_wake:

```c
static 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_key:

```c
int 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 的关联方式。对于基于物理页框的共享 futex(MAP_SHARED),key 使用 mapping + index;对于私有映射,key 使用 mm + address。这使得 fork 之后的父子进程通过 COW 页面触发不同的 key,避免交叉唤醒。

hash_futex 将 key 哈希到 futex_hash_bucket:

```c
static 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 执行实际的唤醒操作:

```c
static 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 个桶。