futex_waitv2
Wait for futex_wake operation on a vector of futexes
Usage
#include <linux/futex.h> /* Definition of FUTEX* constants */ #include <sys/syscall.h> /* Definition of SYS_* constants */ #include <unistd.h> #include <time.h>
long syscall(unsigned int n; SYS_futex_waitv, struct futex_waitv waiters[n], unsigned int n, unsigned int flags, const struct timespec *_Nullable timeout, clockid_t clockid);
#include <linux/futex.h>
struct futex_waitv {
u64 val; /* Expected value at uaddr */
u64 uaddr; /* User address to wait on */
u32 flags; /* Flags for this waiter */
u32 __reserved; /* Align to u64 */ };
Library
Standard C library (libc, -lc)
Overview
Implements the FUTEX_WAIT_MULTIPLE operation, analogous to a synchronous atomic parallel FUTEX_WAIT(2const) or FUTEX_WAIT_PRIVATE on up to FUTEX_WAITV_MAX futex words. For an overview of futexes, see futex(7); for a description of the general interface, see futex(2); for general minutiae of futex waiting, see the page above.
This operation tests that the values at the futex words waiters[].uaddr[0] still contain respective expected values waiters[].val, and if so, sleeps waiting for a FUTEX_WAKE(2const) operation on any of the futex words, and returns the index of a waiter whose futex was woken.
If the thread starts to sleep, it is considered a waiter on all given futex words. If any of the futex values do not match their respective waiters[].val, the call fails immediately with the error EAGAIN.
If timeout is NULL, the call blocks indefinitely. Otherwise, *timeout specifies a deadline measured against clock clockid. This interval will be rounded up to the system clock granularity, and is guaranteed not to expire early.
The following clocks are supported:
CLOCK_MONOTONIC
CLOCK_REALTIME
Futex words to monitor are given by struct futex_waitv, whose fields are analogous to FUTEX_WAIT(2const) parameters, except .__reserved must be 0 and .flags must contain exactly one size flag, ORed with some other flags.
FUTEX2_SIZE_U32-
.val and .uaddr[] are 32-bit unsigned integers.
FUTEX2_SIZE_U8
FUTEX2_SIZE_U16
FUTEX2_SIZE_U64-
These are defined, but not supported (EINVAL).
FUTEX2_NUMA-
The futex word is followed by another word of the same size (.uaddr points to uintN_t[2] rather than uintN_t. The word is given by .uaddr[1]), which can be either
FUTEX_NO_NODE(all bits set) or a NUMA node number.
-
If the NUMA word is
FUTEX_NO_NODE, the node number of the processor the syscall executes on is written to it. (Except that in an EINVAL or EFAULT condition, this happens to all waiters whose .flags haveFUTEX2_NUMAset.)
-
Futexes are placed on the NUMA node given by the NUMA word. Futexes without this flag are placed on a random node.
FUTEX2_PRIVATE-
By default, the futex is shared (like FUTEX_WAIT(2const)), and can be accessed by multiple processes; this flag waits on a private futex word, where all users must use the same virtual memory map (like
FUTEX_WAIT_PRIVATE; this most often means they are part of the same process). Private futexes are faster than shared ones.
Programs should assign to .uaddr by casting a pointer to uintptr_t.
Return value
Returns an index to an arbitrary entry in waiters corresponding to some woken-up futex. This implies no information about other waiters.
On error, -1 is returned, and errno is set to indicate the error.
Errors
- EFAULT
-
waiters points outside the accessible address space.
- EFAULT
-
timeout is not NULL and points outside the accessible address space.
- EFAULT
-
Any waiters[].uaddr field points outside the accessible address space.
- EINVAL
-
Any waiters[].uaddr field does not point to a valid object—that is, the address is not aligned appropriately for the specified
FUTEX2_SIZE_*.
- EINVAL
-
flags was not 0.
- EINVAL
-
n was not in the range [
1,FUTEX_WAITV_MAX].
- EINVAL
-
timeout was not NULL and clockid was not a valid clock.
- EINVAL
-
*timeout is denormal (before epoch or tv_nsec not in the range [
0,999'999'999]).
- EINVAL
-
Any waiters[].flags field contains an unknown flag.
- EINVAL
-
Any waiters[].flags field does not contain exactly one size flag, or it contains an unsupported one.
- EINVAL
-
Any waiters[].__reserved field is not 0.
- EINVAL
-
Any waiters[].value field has more bits set than permitted than the size flags.
- EINVAL
-
FUTEX2_NUMAwas set in waiters[].flags, and the NUMA word (which is the same size as the futex word) is too small to contain the highest possible index of a NUMA domain (for example,FUTEX2_SIZE_U8and there are at least 255 possible NUMA domains).
- EINVAL
-
FUTEX2_NUMAwas set in waiters[].flags, and the NUMA word is larger than the maximum possible NUMA node and notFUTEX_NO_NODE.
- ETIMEDOUT
-
The timeout elapsed before any futex was woken.
- EAGAIN or EWOULDBLOCK
-
The value in .uaddr[0] was not equal to the expected value .val at the time of the call.
- EINTR
-
The operation was interrupted by a signal (see signal(7)).
Examples
The program below executes a linear-time operation on 10 threads, displaying the results in real time, waiting at most 1 second for each new result. The first 3 threads operate on the same data (complete in the same time). ! indicates the futex that woke up each futex_waitv().
$ ./futex_waitv 153 153 153 237 100 245 177 127 215 61 122! 200! 254! 306 306! 306! 354! 430! 474! 490! futex_waitv: my_futex_waitv: Connection timed out
#include <err.h>
#include <errno.h>
#include <linux/futex.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdcountof.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
static inline long
my_futex_wake_private(_Atomic uint32_t *uaddr, uint32_t val)
{
return syscall(SYS_futex, uaddr, FUTEX_WAKE_PRIVATE, val);
}
static inline long
my_futex_waitv(unsigned int n;
struct futex_waitv waiters[n], unsigned int n,
unsigned int flags, const struct timespec *timeout,
clockid_t clockid)
{
return syscall(SYS_futex_waitv, waiters, n, flags, timeout, clockid);
}
void *
worker(void *arg)
{
_Atomic uint32_t *futex = arg;
usleep(*futex * 10000);
*futex *= 2;
my_futex_wake_private(futex, 1);
return NULL;
}
int
main(void)
{
_Atomic uint32_t futexes[10];
uint8_t init[countof(futexes)];
struct futex_waitv waiters[countof(futexes)] = {};
if (getentropy(init, sizeof(init)))
err(EXIT_FAILURE, "getentropy");
init[0] = init[1] = init[2];
for (int i = 0; i < countof(futexes); ++i) {
printf("%w8u\t", init[i]);
atomic_init(&futexes[i], init[i]);
pthread_create(&(pthread_t){}, NULL, worker, &futexes[i]);
}
putchar('\n');
for (int i = 0; i < countof(futexes); ++i) {
waiters[i].val = futexes[i];
waiters[i].uaddr = (uintptr_t) &futexes[i];
waiters[i].flags = FUTEX2_SIZE_U32 | FUTEX2_PRIVATE;
}
for (;;) {
int woke;
struct timespec timeout;
clock_gettime(CLOCK_MONOTONIC, &timeout);
timeout.tv_sec += 1;
woke = my_futex_waitv(waiters, countof(futexes), 0, &timeout, CLOCK_MONOTONIC);
if (woke == -1 && (errno != EAGAIN && errno != EWOULDBLOCK))
err(EXIT_FAILURE, "my_futex_waitv");
for (int i = 0; i < countof(futexes); ++i) {
if (futexes[i] != waiters[i].val)
printf("%w32u%s", futexes[i], i == woke ? "!" : "");
putchar('\t');
}
putchar('\n');
for (int i = 0; i < countof(futexes); ++i)
waiters[i].val = futexes[i];
}
}