Linux 什么是“FUTEX_REQUEUE”bug?

Linux 什么是“FUTEX_REQUEUE”bug?,linux,synchronization,Linux,Synchronization,我将Linux手册页指定为操作系统课程中的必读内容,以警告学生在设计同步原语时不要自满 futex()系统调用是Linux提供的API,允许用户级线程同步原语在必要时休眠和唤醒。手册页描述了可使用futex()系统调用调用的5种不同操作。这两个基本操作是FUTEX\u WAIT(当线程试图获取同步对象并且有人已经持有它时,它会将自己置于睡眠状态)和FUTEX\u WAKE(当线程释放同步对象时,它会用它唤醒所有等待的线程) 接下来的三个操作是乐趣的开始。手册页的描述如下所示: FUTEX_FD

我将Linux手册页指定为操作系统课程中的必读内容,以警告学生在设计同步原语时不要自满

futex()
系统调用是Linux提供的API,允许用户级线程同步原语在必要时休眠和唤醒。手册页描述了可使用
futex()
系统调用调用的5种不同操作。这两个基本操作是
FUTEX\u WAIT
(当线程试图获取同步对象并且有人已经持有它时,它会将自己置于睡眠状态)和
FUTEX\u WAKE
(当线程释放同步对象时,它会用它唤醒所有等待的线程)

接下来的三个操作是乐趣的开始。手册页的描述如下所示:

FUTEX_FD (present up to and including Linux 2.6.25)
       [...]
       Because it was inherently racy, FUTEX_FD has been removed
       from Linux 2.6.26 onward.
这篇文章描述了比赛状态(这是一个潜在的错过唤醒)。但还有更多:

FUTEX_REQUEUE (since Linux 2.5.70)
       This operation was introduced in order to avoid a
       "thundering herd" effect when FUTEX_WAKE is used and all
       processes woken up need to acquire another futex. [...]

FUTEX_CMP_REQUEUE (since Linux 2.6.7)
       There was a race in the intended use of FUTEX_REQUEUE, so
       FUTEX_CMP_REQUEUE was introduced. [...]

FUTEX_REQUEUE的比赛是什么?Ulrich的论文甚至没有提到它(论文描述了一个函数
futex_-requeue()
,它是使用
futex_-CMP_-requeue
实现的,而不是
futex_-requeue
操作来实现的)。

看起来竞争条件是由于glibc中实现了互斥锁,以及它们与futex的差异造成的
FUTEX\u CMP\u REQUEUE
似乎需要支持更复杂的glibc互斥体:

它们要复杂得多,因为它们支持更多的特性,例如死锁测试和递归锁定。因此,它们有一个内部锁来保护额外的状态。这个额外的锁意味着由于可能的竞争,他们不能使用FUTEX_REQUEUE多路复用功能


来源:

旧的重新请求操作采用两个地址addr1和addr2,首先它在addr1上解析等待者,然后将它们停回addr2

新的requeue操作在验证
*addr1==用户提供的\u val
后执行所有这些操作

找出可能的竞争条件,考虑以下两个线程:

wait(cv, mutex);
  lock(&cv.lock);
  cv.mutex_ref = &mutex;
  unlock(&mutex);
  let futexval = ++cv.futex;
  unlock(&cv.lock);
  FUTEX_WAIT(&cv.futex, futexval);   // --- (1)
系统调用(1)和(2)都是在没有锁的情况下执行的,但要求它们与互斥锁的总顺序相同,这样用户就不会丢失信号

因此,为了在实际的
唤醒
之后检测
等待
操作重新排序,在锁中获取的
futexval
被传递到内核(2)

类似地,我们将
futexval
传递给位于(1)的
FUTEX\u WAIT
调用。futex中明确规定了该设计:

执行请求阻止线程的futex操作时, 仅当futex字具有 提供的调用线程(作为 futex()调用)作为futex单词的预期值。这个 加载futex单词的值,比较该值 使用预期值,将发生实际阻塞 原子的,并且将完全按照并发 由同一futex字上的其他线程执行的操作。 因此,futex字用于连接中的同步 用内核实现用户空间的阻塞。 类似于原子比较和交换操作 可能会改变共享内存,通过futex阻塞是一种 原子比较和块操作

IMHO,在锁外调用(2)的原因主要是性能。持锁时呼叫wake,将导致服务员醒来后无法获得锁的“赶快等待”情况


还值得一提的是,上述答案基于pthread实现的历史版本。最新版本的
pthread\u cond
删除了
REQUEUE
的用法。(查看此项了解详细信息)。

这是答案的开始,但可能的竞争是什么?那份文件没有说。是的,这是答案的一部分。。。今天早上我能挖到的。我想这是值得张贴的,以防有人想插手。
lock(&mutex);
broadcast(cv);
  lock(&cv.lock);
  let futexval = cv.futex;
  unlock(&cv.lock);
  FUTEX_CMP_REQUEUE(&cv.futex,   // --- (2)
    1 /*wake*/,
    ALL /*queue*/,
    &cv.mutex_ref.lock,
    futexval);