名称

futex-快速的用户空间锁定

遵循规范

此系统调用是特定于Linux的。

另外参见

get_robust_list(2),restart_syscall(2),pthread_mutexattr_getprotocol(3),futex(7),sched(7)

以下内核源文件:

*
文档/pi-futex.txt
*
文档/futex-requeue-pi.txt
*
文档/锁定/rt-mutex.txt
*
文档/锁定/rt-mutex-design.txt
*
文档/robust-futex-ABI.txt

Franke,H.,Russell,R.和Kirwood,M.,2002年。Fuss,Futexes和Furwocks:Linux中的快速用户级锁定(摘自Ottawa Linux Symposium 2002会议录),

Hart,D.,2009年。futex概述和更新,

Hart,D.和Guniguntala,D.,2009年。Requeue-PI:使Glibc Condvars具有PI-Aware(来自2009 Real-Time Linux Workshop的会议记录),

美国Drepper,2011年。Futexes是Tricky,

Futex示例库futex-*。tar.bz2位于

语法

#include <linux/futex.h>
#include <sys/time.h>

int futex(int *uaddr, int futex_op, int val,
          const struct timespec *timeout,   /* or: uint32_t val2 */
          int *uaddr2, int val3);

注意:此系统调用没有glibc包装器。请参阅注释。

FUTEX - Linux手册页

Linux程序员手册 第2部分
更新日期: 2020-06-09

示例

下面的程序演示了在程序中父进程的使用,其中父进程和子进程使用位于共享匿名映射内的一对futex对同步对共享资源的访问:终端。这两个进程各自向终端写入nloop(如果省略则默认为5的命令行参数)消息,并使用同步协议确保其交替写入消息。运行此程序后,我们看到如下输出:

$ ./futex_demo
Parent (18534) 0
Child  (18535) 0
Parent (18534) 1
Child  (18535) 1
Parent (18534) 2
Child  (18535) 2
Parent (18534) 3
Child  (18535) 3
Parent (18534) 4
Child  (18535) 4

Program source

/* futex_demo.c

   Usage: futex_demo [nloops]
                    (Default: 5)

   Demonstrate the use of futexes in a program where parent and child
   use a pair of futexes located inside a shared anonymous mapping to
   synchronize access to a shared resource: the terminal. The two
   processes each write aqnum-loopsaq messages to the terminal and employ
   a synchronization protocol that ensures that they alternate in
   writing messages.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

static int *futex1, *futex2, *iaddr;

static int
futex(int *uaddr, int futex_op, int val,
      const struct timespec *timeout, int *uaddr2, int val3)
{
    return syscall(SYS_futex, uaddr, futex_op, val,
                   timeout, uaddr2, val3);
}

/* Acquire the futex pointed to by aqfutexpaq: wait for its value to
   become 1, and then set the value to 0. */

static void
fwait(int *futexp)
{
    int s;

    /* atomic_compare_exchange_strong(ptr, oldval, newval)
       atomically performs the equivalent of:

           if (*ptr == *oldval)
               *ptr = newval;

       It returns true if the test yielded true and *ptr was updated. */

    while (1) {

        /* Is the futex available? */
        const int one = 1;
        if (atomic_compare_exchange_strong(futexp, &one, 0))
            break;      /* Yes */

        /* Futex is not available; wait */

        s = futex(futexp, FUTEX_WAIT, 0, NULL, NULL, 0);
        if (s == -1 && errno != EAGAIN)
            errExit("futex-FUTEX_WAIT");
    }
}

/* Release the futex pointed to by aqfutexpaq: if the futex currently
   has the value 0, set its value to 1 and the wake any futex waiters,
   so that if the peer is blocked in fpost(), it can proceed. */

static void
fpost(int *futexp)
{
    int s;

    /* atomic_compare_exchange_strong() was described in comments above */

    const int zero = 0;
    if (atomic_compare_exchange_strong(futexp, &zero, 1)) {
        s = futex(futexp, FUTEX_WAKE, 1, NULL, NULL, 0);
        if (s  == -1)
            errExit("futex-FUTEX_WAKE");
    }
}

int
main(int argc, char *argv[])
{
    pid_t childPid;
    int j, nloops;

    setbuf(stdout, NULL);

    nloops = (argc > 1) ? atoi(argv[1]) : 5;

    /* Create a shared anonymous mapping that will hold the futexes.
       Since the futexes are being shared between processes, we
       subsequently use the "shared" futex operations (i.e., not the
       ones suffixed "_PRIVATE") */

    iaddr = mmap(NULL, sizeof(int) * 2, PROT_READ | PROT_WRITE,
                MAP_ANONYMOUS | MAP_SHARED, -1, 0);
    if (iaddr == MAP_FAILED)
        errExit("mmap");

    futex1 = &iaddr[0];
    futex2 = &iaddr[1];

    *futex1 = 0;        /* State: unavailable */
    *futex2 = 1;        /* State: available */

    /* Create a child process that inherits the shared anonymous
       mapping */

    childPid = fork();
    if (childPid == -1)
        errExit("fork");

    if (childPid == 0) {        /* Child */
        for (j = 0; j < nloops; j++) {
            fwait(futex1);
            printf("Child  (%ld) %d\n", (long) getpid(), j);
            fpost(futex2);
        }

        exit(EXIT_SUCCESS);
    }

    /* Parent falls through to here */

    for (j = 0; j < nloops; j++) {
        fwait(futex2);
        printf("Parent (%ld) %d\n", (long) getpid(), j);
        fpost(futex1);
    }

    wait(NULL);

    exit(EXIT_SUCCESS);
}

错误说明

EACCES
没有对futex字的存储器的读取访问。
EAGAIN
(FUTEX_WAITFUTEX_WAIT_BITSET,FUTEX_WAIT_REQUEUE_PI)uaddr指向的值不等于调用时的期望值val。
注意:在Linux上,符号名EAGAIN和EWOULDBLOCK(它们都出现在内核futex代码的不同部分)具有相同的值。
EAGAIN
(FUTEX_CMP_REQUEUE,FUTEX_CMP_REQUEUE_PI)uaddr指向的值不等于期望值val3。
EAGAIN
(FUTEX_LOCK_PIFUTEX_TRYLOCK_PI,FUTEX_CMP_REQUEUE_PI)uaddr的futex所有者线程ID(对于FUTEX_CMP_REQUEUE_PI:uaddr2)即将退出,但尚未处理内部状态清除。再试一次。
EDEADLK
(FUTEX_LOCK_PIFUTEX_TRYLOCK_PI,FUTEX_CMP_REQUEUE_PI)uaddr处的futex词已被调用方锁定。
EDEADLK
(FUTEX_CMP_REQUEUE_PI)在uaddr2处为PI futex的侍应词重新排队等待服务员时,内核检测到死锁。
EFAULT
必需的指针参数(即uaddr,uaddr2或超时)未指向有效的用户空间地址。
EINTR
FUTEX_WAIT或FUTEX_WAIT_BITSET操作被信号中断(请参见信号(7))。在Linux 2.6.22之前的内核中,也可能会由于虚假唤醒而返回此错误。从Linux 2.6.22开始,这种情况不再发生。
EINVAL
futex_op中的操作是采用超时的操作之一,但是提供的超时参数无效(tv_sec小于零,或tv_nsec不小于1,000,000,000)。
EINVAL
futex_op中指定的操作使用指针uaddr和uaddr2中的一个或两个,但是其中之一未指向有效对象,即地址不是四字节对齐的。
EINVAL
(FUTEX_WAIT_BITSET,FUTEX_WAKE_BITSET)val3中提供的位掩码为零。
EINVAL
(FUTEX_CMP_REQUEUE_PI)uaddr等于uaddr2(即,尝试重新排队到同一futex)。
EINVAL
(FUTEX_FD)val中提供的信号编号无效。
EINVAL
(FUTEX_WAKEFUTEX_WAKE_OPFUTEX_WAKE_BITSETFUTEX_REQUEUE,FUTEX_CMP_REQUEUE)内核检测到uaddr处的用户空间状态与内核状态之间存在不一致-即,它检测到了在uaddr上的FUTEX_LOCK_PI中等待的服务员。
EINVAL
(FUTEX_LOCK_PIFUTEX_TRYLOCK_PI,FUTEX_UNLOCK_PI)内核检测到uaddr处的用户空间状态与内核状态之间存在不一致。这表明状态损坏或内核在uaddr上找到了正在通过FUTEX_WAIT或FUTEX_WAIT_BITSET等待的服务程序。
EINVAL
(FUTEX_CMP_REQUEUE_PI)内核检测到uaddr2上的用户空间状态与内核状态之间存在不一致。也就是说,内核检测到一个等待者,该等待者通过uaddr2上的FUTEX_WAIT或FUTEX_WAIT_BITSET等待。
EINVAL
(FUTEX_CMP_REQUEUE_PI)内核检测到uaddr处的用户空间状态与内核状态之间存在不一致;也就是说,内核检测到一个等待者,该等待者通过uaddr上的FUTEX_WAIT或FUTEX_WAIT_BITESET等待。
EINVAL
(FUTEX_CMP_REQUEUE_PI)内核检测到uaddr处的用户空间状态与内核状态之间存在不一致;也就是说,内核检测到一个通过FUTEX_LOCK_PI(而不是FUTEX_WAIT_REQUEUE_PI)在uaddr上等待的服务程序。
EINVAL
(FUTEX_CMP_REQUEUE_PI)尝试将服务员重新排队至与匹配该服务员的FUTEX_WAIT_REQUEUE_PI调用所指定的功能不同的futex。
EINVAL
(FUTEX_CMP_REQUEUE_PI)val参数不是1。
EINVAL
无效的论点。
ENFILE
(FUTEX_FD)已达到系统范围内打开文件总数的限制。
ENOMEM
(FUTEX_LOCK_PIFUTEX_TRYLOCK_PI,FUTEX_CMP_REQUEUE_PI)内核无法分配内存来保存状态信息。
ENOSYS
futex_op中指定的操作无效。
ENOSYS
在futex_op中指定了FUTEX_CLOCK_REALTIME选项,但是附带的操作既不是FUTEX_WAITFUTEX_WAIT_BITSET,也不是FUTEX_WAIT_REQUEUE_PI。
ENOSYS
(FUTEX_LOCK_PIFUTEX_TRYLOCK_PIFUTEX_UNLOCK_PIFUTEX_CMP_REQUEUE_PI,FUTEX_WAIT_REQUEUE_PI)运行时检查确定该操作不可用。并非在所有体系结构上都实现PI-futex操作,某些CPU变体不支持PI-futex操作。
EPERM
(FUTEX_LOCK_PIFUTEX_TRYLOCK_PI,FUTEX_CMP_REQUEUE_PI)不允许调用方将自己附加到uaddr上的futex(对于FUTEX_CMP_REQUEUE_PI:uaddr2上的futex)。 (这可能是由于用户空间中的状态损坏引起的。)
EPERM
(FUTEX_UNLOCK_PI)调用者不拥有futex单词表示的锁。
ESRCH
(FUTEX_LOCK_PIFUTEX_TRYLOCK_PI,FUTEX_CMP_REQUEUE_PI)uaddr处的futex字中的线程ID不存在。
ESRCH
(FUTEX_CMP_REQUEUE_PI)uaddr2处的futex字中的线程ID不存在。
ETIMEDOUT
futex_op中的操作使用了timeout中指定的超时,并且该超时在操作完成之前到期。

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/

版本

Futexes首先在Linux 2.6.0的稳定内核发行版中可用。

最初的futex支持在Linux 2.5.7中进行了合并,但是语义与上述内容有所不同。 Linux 2.5.40中引入了具有此页面描述的语义的四参数系统调用。在Linux 2.5.70中添加了第五个参数,在Linux 2.6.7中添加了第六个参数。

返回值

在发生错误的情况下(并假设通过syscall(2)调用了futex()),所有操作均返回-1并设置errno以指示错误原因。

成功返回的值取决于操作,如以下列表所述:

FUTEX_WAIT
如果呼叫者被唤醒,则返回0。请注意,唤醒也可能是由不相关的代码中常见的futex使用模式引起的,而这些模式以前曾经使用过futex字的存储位置(例如,在某些情况下,典型的基于futex的Pthreads互斥体实现可能会导致这种情况)。因此,调用者应始终保守地假设返回值0意味着虚假唤醒,并使用futex字的值(即用户空间同步方案)来决定是否继续阻止。
FUTEX_WAKE
返回被唤醒的服务员的数量。
FUTEX_FD
返回与futex关联的新文件描述符。
FUTEX_REQUEUE
返回被唤醒的服务员的数量。
FUTEX_CMP_REQUEUE
返回被uaddr2上的futex单词唤醒或重新排队到futex的服务员总数。如果此值大于val,则不同之处在于uaddr2上的futex单词重新排队到futex的服务员数量。
FUTEX_WAKE_OP
返回被唤醒的服务员总数。这是uaddr和uaddr2上的futex单词的两个futex上唤醒的服务员的总和。
FUTEX_WAIT_BITSET
如果呼叫者被唤醒,则返回0。请参阅FUTEX_WAIT,以了解如何在实践中正确解释这一点。
FUTEX_WAKE_BITSET
返回被唤醒的服务员的数量。
FUTEX_LOCK_PI
如果futex成功锁定,则返回0。
FUTEX_TRYLOCK_PI
如果futex成功锁定,则返回0。
FUTEX_UNLOCK_PI
如果futex成功解锁,则返回0。
FUTEX_CMP_REQUEUE_PI
返回在uaddr2上的futex单词被唤醒或重新排队到futex的服务员总数。如果该值大于val,则区别在于uaddr2上的futex单词重新排队到futex的服务员数量。
FUTEX_WAIT_REQUEUE_PI
如果成功将调用者重新排队到uaddr2的futex单词,则返回0。

说明

futex()系统调用提供了一种等待直到特定条件变为真的方法。它通常在共享内存同步的上下文中用作阻止结构。使用futex时,大多数同步操作在用户空间中执行。用户空间程序仅在程序必须阻塞更长的时间直到条件变为真时才使用futex()系统调用。其他futex()操作可用于唤醒等待特定条件的任何进程或线程。

futex是一个32位值-以下简称为futex字-其地址提供给futex()系统调用。 (Futexe在所有平台(包括64位系统)上的大小均为32位。)所有futex操作均受该值控制。为了在进程之间共享futex,将futex放置在使用(例如)mmap(2)或shmat(2)创建的共享内存区域中。 (因此,futex字在不同的进程中可能具有不同的虚拟地址,但是这些地址都指向物理内存中的相同位置。)在多线程程序中,将futex字放在所有线程共享的全局变量中就足够了。 。

当执行请求阻塞线程的futex操作时,仅当futex字具有调用线程提供的值(作为futex()调用的参数之一)作为futex字的期望值时,内核才会阻塞。 futex字的值的加载,该值与期望值的比较以及实际阻塞将自动发生,并且将相对于其他线程在同一futex字上执行的并发操作进行完全排序。因此,futex字用于将用户空间中的同步与内核阻止的实现联系起来。类似于可能更改共享内存的原子比较和交换操作,通过futex进行阻塞是原子比较和块操作。

futex的一种用途是实现锁。锁的状态(即已获取或未获取)可以表示为共享内存中的原子访问标志。在无竞争的情况下,线程可以使用原子指令访问或修改锁定状态,例如,使用原子比较和交换指令将其从未获取状态原子地更改为未获取状态。 (此类指令完全在用户模式下执行,内核不维护有关锁定状态的信息。)另一方面,线程可能无法获取锁定,因为它已被另一个线程获取。然后,它可以将锁的标志作为futex字传递,并将表示获取的状态的值作为期望值传递给futex()等待操作。当且仅当仍然获得锁定时(即,futex字中的值仍与" acquired state"相匹配),此futex()操作才会阻塞。释放锁定时,线程必须首先将锁定状态重置为未获取状态,然后执行futex操作,该操作唤醒在用作futex字的锁定标志上阻塞的线程(可以进一步优化以避免不必要的唤醒)。有关如何使用futex的更多详细信息,请参见futex(7)。

除了基本的等待和唤醒futex功能之外,还有其他旨在支持更复杂用例的futex操作。

注意,使用futex不需要显式的初始化或销毁。内核仅在对特定的futex字执行如下所述的诸如FUTEX_WAIT之类的操作时才维护futex(即内核内部实现工件)。

Arguments

uaddr参数指向futex单词。在所有平台上,futex是必须在四字节边界上对齐的四字节整数。在futex上执行的操作在futex_op参数中指定; val是一个值,其含义和目的取决于futex_op。

其余参数(超时,uaddr2和val3)仅对于以下所述的某些futex操作是必需的。如果不需要这些参数之一,则将其忽略。

对于几个阻塞操作,timeout参数是指向timespec结构的指针,该结构指定操作的超时。但是,尽管上面显示了原型,但对于某些操作,此参数的最低有效四个字节被用作整数,其含义由操作确定。对于这些操作,内核首先将超时值转换为unsigned long,然后转换为uint32_t,在此页面的其余部分中,以这种方式解释时,此参数称为val2。

在需要的地方,uaddr2参数是指向该操作使用的第二个futex字的指针。

最终整数参数val3的解释取决于运算。

Futex operations

futex_op参数由两部分组成:一个命令,该命令指定要执行的操作,与零个或多个修改操作行为的选项进行按位或运算。 futex_op中可能包含的选项如下:

FUTEX_PRIVATE_FLAG(since Linux 2.6.22)
该选项位可用于所有futex操作。它告诉内核futex是进程专用的,并且不与另一个进程共享(即仅用于同一进程的线程之间的同步)。这使内核可以进行一些其他性能优化。
为了方便起见,使用后缀_PRIVATE定义一组常量,这些常量与下面列出的所有操作等效,但是将FUTEX_PRIVATE_FLAG ORed设置为常量值。因此,有FUTEX_WAIT_PRIVATE,FUTEX_WAKE_PRIVATE等。
FUTEX_CLOCK_REALTIME(since Linux 2.6.28)
该选项位只能与FUTEX_WAIT_BITSET,FUTEX_WAIT_REQUEUE_PI和(自Linux 4.5起)FUTEX_WAIT操作一起使用。
如果设置了此选项,则内核将根据CLOCK_REALTIME时钟来测量超时。
如果未设置此选项,则内核将根据CLOCK_MONOTONIC时钟来测量超时。

futex_op中指定的操作是以下之一:

FUTEX_WAIT(since Linux 2.6.0)
此操作测试地址uaddr指向的futex字上的值仍然包含期望值val,如果是,则休眠,等待对futex字执行FUTEX_WAKE操作。 futex字的值的负载是原子存储器访问(即,使用相应体系结构的原子机器指令)。该负载,与期望值的比较以及开始睡眠是相对于同一futex单词上的其他futex操作以原子方式完全排序的。如果线程开始休眠,则将其视为该futex单词的服务生。如果futex值与val不匹配,则调用立即失败,并显示错误EAGAIN。
与期望值进行比较的目的是防止丢失唤醒。如果在调用线程决定基于先前的值进行阻塞之后另一个线程更改了futex单词的值,并且如果另一个线程在值更改之后且在此FUTEX_WAIT操作之前执行了FUTEX_WAKE操作(或类似的唤醒),则调用线程将观察值的变化,并且不会开始休眠。
如果超时不为NULL,则指向的结构将指定等待超时。 (此间隔将四舍五入为系统时钟的粒度,并保证不会提前过期。)默认情况下,超时是根据CLOCK_MONOTONIC时钟测量的,但是,从Linux 4.5开始,可以通过在以下位置指定FUTEX_CLOCK_REALTIME来选择CLOCK_REALTIME时钟。 futex_op。如果超时为NULL,则调用将无限期阻塞。
注意:对于FUTEX_WAIT,超时被解释为相对值。这与其他futex操作不同,后者将超时解释为绝对值。要获得绝对超时的等效FUTEX_WAIT,请使用val3指定为FUTEX_BITSET_MATCH_ANY的FUTEX_WAIT_BITSET。
参数uaddr2和val3被忽略。
FUTEX_WAKE(since Linux 2.6.0)
此操作最多唤醒正在地址uaddr上的futex字等待的val侍者(例如FUTEX_WAIT内部)。最常见的是,val被指定为1(唤醒一个侍者)或INT_MAX(唤醒所有侍者)。无法保证唤醒哪些侍者(例如,不能保证优先级较高的侍者优先于调度优先级较高的侍者被唤醒)。
参数timeout,uaddr2和val3被忽略。
FUTEX_FD(from Linux 2.6.0 up to and including Linux 2.6.25)
此操作将创建与uaddr上的futex关联的文件描述符。使用后,调用者必须关闭返回的文件描述符。当另一个进程或线程对futex字执行FUTEX_WAKE时,文件描述符指示可通过select(2),poll(2)和epoll(7)读取
文件描述符可用于获取异步通知:如果val不为零,那么,当另一个进程或线程执行FUTEX_WAKE时,调用者将收到在val中传递的信号号。
参数超时,uaddr2和val3被忽略。
因为它本质上很老套,所以FUTEX_FD已从Linux 2.6.26开始删除。
FUTEX_REQUEUE(since Linux 2.6.0)
该操作执行与FUTEX_CMP_REQUEUE相同的任务(见下文),不同之处在于不使用val3中的值进行检查。 (参数val3被忽略。)
FUTEX_CMP_REQUEUE(since Linux 2.6.7)
此操作首先检查位置uaddr是否仍包含值val3。如果不是,则操作失败,并显示错误EAGAIN。否则,该操作将唤醒最多在uaddr的futex上等待的val服务员。如果有多个val等待者,则其余的等待者将从uaddr处的源futex的等待队列中删除,并添加至uaddr2处的目标futex的等待队列中。 val2参数指定在uaddr2处重新排队到futex的侍者数量的上限。
来自uaddr的负载是原子内存访问(即使用相应体系结构的原子机器指令)。此负载,与val3的比较以及任何等待者的重新排队都相对于同一futex字上的其他操作以原子方式完全排序。
为val指定的典型值为0或1。(指定INT_MAX无效,因为它会使FUTEX_CMP_REQUEUE操作等效于FUTEX_WAKE。)通过val2指定的极限值通常为1或INT_MAX。 (将参数指定为0无效,因为它会使FUTEX_CMP_REQUEUE操作等效于FUTEX_WAIT。)
添加了FUTEX_CMP_REQUEUE操作,以替代早期的FUTEX_REQUEUE。区别在于,可以使用uaddr处的值检查来确保仅在某些条件下才发生重新排队,这可以避免在某些使用情况下出现争用条件。
FUTEX_REQUEUE和FUTEX_CMP_REQUEUE都可以用来避免在所有唤醒的服务员都需要获取另一个futex的情况下使用FUTEX_WAKE时可能发生的"雷电群"唤醒。考虑以下场景,其中多个等待线程正在B上等待,该等待队列是使用futex实现的:
lock(A)
while (!check_value(V)) {
    unlock(A);
    block_on(B);
    lock(A);
};
unlock(A);
如果唤醒线程使用FUTEX_WAKE,则所有在B上等待的侍者都将被唤醒,并且他们都将尝试获取锁A。但是,以这种方式唤醒所有线程将毫无意义,因为除其中一个线程之外的所有线程都会立即再次锁定A。相比之下,重新排队操作仅唤醒一个服务生并移动其他服务生以锁定A,并且当唤醒的服务生解锁A时,下一个服务生可以继续进行。
FUTEX_WAKE_OP(since Linux 2.6.14)
添加此操作是为了支持某些用户空间用例,其中必须同时处理多个futex。最著名的示例是pthread_cond_signal(3)的实现,它需要对两个futex进行操作,一个用于实现互斥锁,一个在用于与条件变量关联的等待队列的实现中使用。 FUTEX_WAKE_OP允许在不导致高争用和上下文切换率的情况下实现此类情况。
FUTEX_WAKE_OP操作等效于在两个提供的futex字中的任何一个上以原子方式相对于其他futex操作完全执行以下代码:
int oldval = *(int *) uaddr2;
*(int *) uaddr2 = oldval op oparg;
futex(uaddr, FUTEX_WAKE, val, 0, 0, 0);
if (oldval cmp cmparg)
    futex(uaddr2, FUTEX_WAKE, val2, 0, 0, 0);
In other words, FUTEX_WAKE_OP

does the following:

*
将futex单词的原始值保存在uaddr2处,并执行修改uaddr2处的futex值的操作;这是原子的读-修改-写内存访问(即使用相应体系结构的原子机器指令)
*
在uaddr上最多唤醒val上的val服务员等待futex单词;和
*
根据uaddr2上futex单词的原始值的测试结果,在uaddr2上为futex单词唤醒futex上最多val2的服务员。
要执行的操作和比较被编码在参数val3的位中。如图所示,编码为:
+---+---+-----------+-----------+
|op |cmp|   oparg   |  cmparg   |
+---+---+-----------+-----------+
  4   4       12          12    <== # of bits
用代码表示,编码为:
#define FUTEX_OP(op, oparg, cmp, cmparg) \
                (((op & 0xf) << 28) | \
                ((cmp & 0xf) << 24) | \
                ((oparg & 0xfff) << 12) | \
                (cmparg & 0xfff))
在上面,op和cmp分别是下面列出的代码之一。 oparg和cmparg组件是文字数字值,除非另有说明。
op组件具有以下值之一:
FUTEX_OP_SET        0  /* uaddr2 = oparg; */
FUTEX_OP_ADD        1  /* uaddr2 += oparg; */
FUTEX_OP_OR         2  /* uaddr2 |= oparg; */
FUTEX_OP_ANDN       3  /* uaddr2 &= tioparg; */
FUTEX_OP_XOR        4  /* uaddr2 ha= oparg; */
此外,将以下值按位进行"或"运算(1
FUTEX_OP_ARG_SHIFT  8  /* Use (1 << oparg) as operand */
cmp字段是以下之一:
FUTEX_OP_CMP_EQ     0  /* if (oldval == cmparg) wake */
FUTEX_OP_CMP_NE     1  /* if (oldval != cmparg) wake */
FUTEX_OP_CMP_LT     2  /* if (oldval < cmparg) wake */
FUTEX_OP_CMP_LE     3  /* if (oldval <= cmparg) wake */
FUTEX_OP_CMP_GT     4  /* if (oldval > cmparg) wake */
FUTEX_OP_CMP_GE     5  /* if (oldval >= cmparg) wake */
FUTEX_WAKE_OP的返回值是在futex uaddr上唤醒的侍者数与在futex uaddr2上唤醒的侍者数之和。
FUTEX_WAIT_BITSET(since Linux 2.6.25)
此操作类似于FUTEX_WAIT,不同之处在于val3用于为内核提供32位位掩码。该位掩码(其中必须至少设置一位)以等待者的内核内部状态存储。有关更多详细信息,请参见FUTEX_WAKE_BITSET的描述。
如果超时不为NULL,则它指向的结构为等待操作指定绝对超时。如果超时为NULL,则该操作可以无限期地阻塞。
uaddr2参数将被忽略。
FUTEX_WAKE_BITSET(since Linux 2.6.25)
此操作与FUTEX_WAKE相同,除了val3参数用于为内核提供32位位掩码。此位掩码(其中必须至少设置一位)用于选择应唤醒哪些侍者。选择是通过"唤醒"位掩码(即val3中的值)与存储在服务员内核内部状态中的位掩码(即"等待"位掩码)的按位与完成的使用FUTEX_WAIT_BITSET设置)。所有AND结果为非零的服务员都被唤醒;其余的服务员则保持睡眠状态。
FUTEX_WAIT_BITSET和FUTEX_WAKE_BITSET的作用是允许在同一futex上被阻止的多个等待者之间进行选择性唤醒。但是,请注意,根据使用情况,在futex上使用此位掩码多路复用功能可能比简单地使用多个futex效率低,因为使用位掩码多路复用要求内核检查futex上的所有服务员,包括那些对唤醒不感兴趣的对象(即,他们的"等待"位掩码中没有设置相关位)。
常量FUTEX_BITSET_MATCH_ANY对应于位掩码中设置的所有32位,可用作FUTEX_WAIT_BITSET和FUTEX_WAKE_BITSET的val3参数。除了对超时参数的处理不同之外,FUTEX_WAIT操作等效于val3指定为FUTEX_BITSET_MATCH_ANY的FUTEX_WAIT_BITSET;也就是说,允许任何唤醒者唤醒。 FUTEX_WAKE操作等效于val3指定为FUTEX_BITSET_MATCH_ANY的FUTEX_WAKE_BITSET;也就是说,叫醒所有服务员。
uaddr2和timeout参数将被忽略。

Priority-inheritance futexes

Linux支持优先级继承(PI)futex,以处理普通futex锁可能遇到的优先级反转问题。优先级倒置是在阻止高优先级任务等待获取低优先级任务持有的锁,而中优先级任务连续从CPU抢占低优先级任务时发生的问题。因此,低优先级任务在释放锁定方面没有任何进展,而高优先级任务仍然被阻止。

优先级继承是一种处理优先级反转问题的机制。通过这种机制,当高优先级任务被低优先级任务持有的锁阻塞时,低优先级任务的优先级会暂时提高到高优先级任务的优先级,因此不会被高优先级任务抢占任何中级任务,因此可以朝着释放锁的方向发展。为了有效,优先级继承必须是可传递的,这意味着,如果高优先级任务在低优先级任务所持有的锁上阻塞,而低优先级任务本身又被另一个中优先级任务所持有的锁阻塞(依此类推,对于链)任意长度),那么这两个任务(或更笼统地说,是锁链中的所有任务)的优先级都提高到与高优先级任务相同。

从用户空间的角度来看,构成futex PI意识的是用户空间和内核之间关于futex字的值的策略协议(如下所述),并结合使用以下所述的PI-futex操作。 (与上述其他futex操作不同,PI-futex操作是为实现非常特定的IPC机制而设计的。)

下述PI-futex操作与其他futex操作的不同之处在于,它们对使用futex字的值施加了策略:

*
如果未获得锁定,则futex字的值应为0。
*
如果获得了锁,则futex字的值应为所属线程的线程ID(TID;参见gettid(2))。
*
如果拥有该锁,并且有争用该锁的线程,则应在futex字的值中设置FUTEX_WAITERS位;否则,将其设置为0。换句话说,该值为:
FUTEX_WAITERS ||工业贸易署
(请注意,对于没有所有者且未设置FUTEX_WAITERS的PI futex单词来说是无效的。)

使用此策略后,用户空间应用程序可以使用在用户模式下执行的原子指令来获取未获得的锁或释放锁(例如,x86体系结构上的比较和交换操作,例如cmpxchg)。获取锁定仅包括使用比较和交换将前卫单词的值自动设置为调用方的TID(如果其先前值为0)。释放锁定需要使用比较并交换将前卫单词的值设置为0(如果该值是0)。先前的值是预期的TID。

如果已经获取了futex(即具有非零值),则服务员必须使用FUTEX_LOCK_PI操作来获取锁。如果其他线程正在等待锁定,则将futex值中的FUTEX_WAITERS位置1;否则,将其设置为0。在这种情况下,锁所有者必须使用FUTEX_UNLOCK_PI操作来释放锁。

在调用者被迫进入内核的情况下(即执行futex()调用需要),然后他们直接处理所谓的RT-mutex,这是一种内核锁定机制,可实现所需的优先级继承语义。在获取RT-mutex之后,在调用线程返回用户空间之前,将相应更新futex值。

重要的是要注意,内核将在返回用户空间之前更新futex字的值。 (这防止了futex字的值最终以无效状态结束的可能性,例如拥有一个所有者但该值为0,或者具有侍者但未设置FUTEX_WAITERS位。)

如果一个futex在内核中具有关联的RT-mutex(即,有阻塞的侍者)并且futex / RT-mutex的所有者意外死亡,则内核会清理RT-mutex并将其移交给下一个侍者。这进而需要相应地更新用户空间值。为了表明这是必需的,内核将futex字中的FUTEX_OWNER_DIED位与新所有者的线程ID一起设置。用户空间可以通过FUTEX_OWNER_DIED位的存在来检测这种情况,然后负责清理死者所拥有的过时状态。

通过在futex_op中指定下面列出的值之一来操作PI futex。请注意,PI futex操作必须用作配对操作,并且要满足一些其他要求:

*
FUTEX_LOCK_PI和FUTEX_TRYLOCK_PI与FUTEX_UNLOCK_PI配对。 FUTEX_UNLOCK_PI必须仅在由值策略定义的调用线程拥有的futex上调用,否则将产生错误EPERM。
*
FUTEX_WAIT_REQUEUE_PI与FUTEX_CMP_REQUEUE_PI对。必须从非PI futex到不同的PI futex(或错误EINVAL结果)执行此操作。另外,val(要唤醒的侍者数)必须为1(否则将返回错误EINVAL结果)。

PI futex操作如下:

FUTEX_LOCK_PI(since Linux 2.6.18)
在尝试通过原子用户模式指令获取锁失败后使用了此操作,因为futex字具有非零值,具体来说,因为它包含锁所有者的(特定于PID命名空间的)TID。
The operation checks the value of the futex word at the address uaddr.

If the value is 0, then the kernel tries to atomically set
the futex value to the caller's TID.
If the futex word's value is nonzero,
the kernel atomically sets the
FUTEX_WAITERS

bit, which signals the futex owner that it cannot unlock the futex in
user space atomically by setting the futex value to 0.

After that, the kernel:

1.
尝试查找与所有者TID关联的线程。
2.
代表所有者创建或重用内核状态。 (如果这是第一个服务员,则该futex没有内核状态,因此通过锁定RT-mutex创建内核状态,并且将futex所有者设为RT-mutex的所有者。如果存在现有的服务员,则现有状态被重用。)
3.
将服务员附加到futex(即,服务员已排队在RT-mutex服务员列表上)。
如果存在多个侍者,则侍者的入队按降序排列。 (有关优先级排序的信息,请参见sched(7)中有关SCHED_DEADLINE,SCHED_FIFO和SCHED_RR调度策略的讨论。)所有者继承了服务员的CPU带宽(如果服务员是根据SCHED_DEADLINE策略进行调度的)。 (如果服务员是根据SCHED_RR或SCHED_FIFO策略安排的)。在嵌套锁定的情况下,此继承遵循锁定链并执行死锁检测。
timeout参数为锁定尝试提供超时。如果超时不为NULL,则指向的结构将指定绝对超时,以CLOCK_REALTIME时钟为准。如果超时为NULL,则操作将无限期阻塞。
uaddr2,val和val3参数将被忽略。
FUTEX_TRYLOCK_PI(since Linux 2.6.18)
此操作尝试获取uaddr的锁。当用户空间原子获取由于futex字不为0而失败时,将调用该方法。
由于内核访问的状态信息多于用户空间,因此如果futex字(即,使用空间可访问的状态信息)包含陈旧状态(FUTEX_WAITERS和/或FUTEX_OWNER_DIED)。当futex的所有者死亡时,可能会发生这种情况。用户空间无法以无竞争的方式处理这种情况,但是内核可以解决此问题并获得成功。
uaddr2val,timeout和val3参数将被忽略。
FUTEX_UNLOCK_PI(since Linux 2.6.18)
该操作将唤醒在uaddr参数提供的futex地址上的FUTEX_LOCK_PI中等待的最高优先级服务员。
当uaddr上的用户空间值不能从(所有者的)TID原子地更改为0时,将调用此方法。
uaddr2val,timeout和val3参数将被忽略。
FUTEX_CMP_REQUEUE_PI(since Linux 2.6.31)
此操作是FUTEX_CMP_REQUEUE的PI感知变体。它将通过uaddr上的FUTEX_WAIT_REQUEUE_PI阻塞的服务员从非PI源futex(uaddr)重新排队到PI目标futex(uaddr2)。
与FUTEX_CMP_REQUEUE一样,此操作最多唤醒在uaddr上的futex上等待的val等待程序。但是,对于FUTEX_CMP_REQUEUE_PI,val必须为1(因为主要要点是避免打雷群)。其余的侍者在uaddr处从源futex的等待队列中删除,并在uaddr2处添加到目标futex的等待队列中。
val2和val3参数的作用与FUTEX_CMP_REQUEUE相同。
FUTEX_WAIT_REQUEUE_PI(since Linux 2.6.31)
在uaddr上等待非PI futex,并有可能重新排队(通过另一个任务中的FUTEX_CMP_REQUEUE_PI操作)到uaddr2上的PI futex。 uaddr上的等待操作与FUTEX_WAIT相同。
可以在另一个任务中通过FUTEX_WAKE操作将等待者从对uaddr的等待中删除,而无需在uaddr2上重新排队。在这种情况下,FUTEX_WAIT_REQUEUE_PI操作失败,并显示错误EAGAIN。
如果超时不为NULL,则它指向的结构为等待操作指定绝对超时。如果超时为NULL,则该操作可以无限期地阻塞。
val3参数将被忽略。
添加了FUTEX_WAIT_REQUEUE_PI和FUTEX_CMP_REQUEUE_PI以支持一个相当特定的用例:支持优先级继承的POSIX线程条件变量。想法是这些操作应始终配对,以确保用户空间和内核保持同步。因此,在FUTEX_WAIT_REQUEUE_PI操作中,用户空间应用程序会预先指定在FUTEX_CMP_REQUEUE_PI操作中发生的重新排队的目标。

备注

Glibc不为该系统调用提供包装器;使用syscall(2)调用它。

多个高级编程抽象是通过futex实现的,包括POSIX信号量和各种POSIX线程同步机制(互斥体,条件变量,读写锁和屏障)。

日期:2019-08-20 17:58:46 来源:oir作者:oir