Linux Futex2, futex_waitv & Kernel Wait Queue Studio
Model high-performance Linux concurrency. Trace userspace atomic compare-and-swap fast paths, simulate kernel wait queue transitions under contention, evaluate Linux 5.16+ futex_waitv() multi-wait vectors, and inspect Priority Inheritance (PI) deadlock defenses.
1. Futex Word State & Concurrency Architecture
2. Thread Execution States & Kernel Wait Queue Table
3. Production Linux 5.16+ futex_waitv() C Blueprint
#define _GNU_SOURCE
#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdint.h>
// Linux 5.16+ futex_waitv vector structure
struct futex_waitv {
uint64_t val; /* Expected value at *uaddr */
uint64_t uaddr; /* Pointer to userspace 32-bit futex */
uint32_t flags; /* FUTEX_32 | FUTEX_CLOCK_MONOTONIC */
uint32_t __reserved;
};
int wait_for_any_futex(struct futex_waitv *waiters, unsigned int count) {
/* Atomically sleeps until ANY futex in the vector changes/wakes */
int ret = syscall(SYS_futex_waitv, waiters, count, 0, NULL, 0);
return ret; /* Returns index of triggered futex (0 to count-1) */
}
⚠️ 5 Fatal Traps in Linux Futex Programming
1. The Missed Wakeup Race Condition
If a thread tests a mutex condition, decides to sleep, but another thread unlocks and calls FUTEX_WAKE before the first thread enters the kernel, the wake signal is lost forever unless the kernel checks the expected value. The kernel MUST verify *uaddr == expected_val atomically under the bucket spinlock.
2. Mixing 32-bit and 64-bit Word Pointers
Classic futexes strictly require a 32-bit integer (uint32_t). Passing a 64-bit integer pointer on little-endian architectures can cause silent memory corruption or spurious wakeups if the upper 32 bits change unexpectedly.
3. Thundering Herd via FUTEX_WAKE Broadcasts
Waking all waiting threads via futex(uaddr, FUTEX_WAKE, INT_MAX) causes all sleeping threads to wake up simultaneously. Only one thread wins the mutex CAS, while all other N-1 threads burn CPU context switching only to sleep again. Use FUTEX_REQUEUE to move waiters directly to the target mutex queue.
4. Forking Without Handling Robust Futex Cleanup
If a process holding a shared futex in anonymous shared memory crashes or is killed by SIGKILL, other processes waiting on that futex are deadlocked forever. Multi-process architectures must register robust lists using set_robust_list() so the kernel marks deadlocks with FUTEX_OWNER_DIED upon process death.
5. Overlooking EINTR Signal Interruptions
A sleeping FUTEX_WAIT call can return -1 with errno = EINTR whenever a signal (such as SIGCHLD or SIGALRM) is delivered to the thread. Naive wrappers that assume return code 0 indicates lock acquisition will enter critical sections without actually holding the lock!