Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
futex_numa_mpol
futex_priv_hash
futex_requeue
futex_requeue_pi
futex_requeue_pi_mismatched_ops
futex_requeue_pi_signal_restart
futex_wait
futex_wait_private_mapped_file
futex_wait_timeout
futex_wait_uninitialized_heap
futex_wait_wouldblock
futex_waitv
futex_numa
robust_list
@@ -0,0 +1,35 @@
# SPDX-License-Identifier: GPL-2.0
PKG_CONFIG ?= pkg-config
LIBNUMA_TEST = $(shell sh -c "$(PKG_CONFIG) numa --atleast-version 2.0.18 > /dev/null 2>&1 && echo SUFFICIENT || echo NO")
INCLUDES := -I../include -I../../ $(KHDR_INCLUDES)
CFLAGS := $(CFLAGS) -g -O2 -Wall -pthread -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64 $(INCLUDES) $(KHDR_INCLUDES) -DLIBNUMA_VER_$(LIBNUMA_TEST)=1
LDLIBS := -lpthread -lrt
ifeq ($(LIBNUMA_TEST),SUFFICIENT)
LDLIBS += -lnuma
endif
LOCAL_HDRS := \
../include/futextest.h \
../include/atomic.h
TEST_GEN_PROGS := \
futex_wait_timeout \
futex_wait_wouldblock \
futex_requeue_pi \
futex_requeue_pi_signal_restart \
futex_requeue_pi_mismatched_ops \
futex_wait_uninitialized_heap \
futex_wait_private_mapped_file \
futex_wait \
futex_requeue \
futex_priv_hash \
futex_numa_mpol \
futex_waitv \
futex_numa \
robust_list
TEST_PROGS := run.sh
top_srcdir = ../../../../..
DEFAULT_INSTALL_HDR_PATH := 1
include ../../lib.mk
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: GPL-2.0
#include <pthread.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include "futextest.h"
#include "futex2test.h"
typedef u_int32_t u32;
typedef int32_t s32;
typedef u_int64_t u64;
static unsigned int fflags = (FUTEX2_SIZE_U32 | FUTEX2_PRIVATE);
static int fnode = FUTEX_NO_NODE;
/* fairly stupid test-and-set lock with a waiter flag */
#define N_LOCK 0x0000001
#define N_WAITERS 0x0001000
struct futex_numa_32 {
union {
u64 full;
struct {
u32 val;
u32 node;
};
};
};
void futex_numa_32_lock(struct futex_numa_32 *lock)
{
for (;;) {
struct futex_numa_32 new, old = {
.full = __atomic_load_n(&lock->full, __ATOMIC_RELAXED),
};
for (;;) {
new = old;
if (old.val == 0) {
/* no waiter, no lock -> first lock, set no-node */
new.node = fnode;
}
if (old.val & N_LOCK) {
/* contention, set waiter */
new.val |= N_WAITERS;
}
new.val |= N_LOCK;
/* nothing changed, ready to block */
if (old.full == new.full)
break;
/*
* Use u64 cmpxchg to set the futex value and node in a
* consistent manner.
*/
if (__atomic_compare_exchange_n(&lock->full,
&old.full, new.full,
/* .weak */ false,
__ATOMIC_ACQUIRE,
__ATOMIC_RELAXED)) {
/* if we just set N_LOCK, we own it */
if (!(old.val & N_LOCK))
return;
/* go block */
break;
}
}
futex2_wait(lock, new.val, fflags, NULL, 0);
}
}
void futex_numa_32_unlock(struct futex_numa_32 *lock)
{
u32 val = __atomic_sub_fetch(&lock->val, N_LOCK, __ATOMIC_RELEASE);
assert((s32)val >= 0);
if (val & N_WAITERS) {
int woken = futex2_wake(lock, 1, fflags);
assert(val == N_WAITERS);
if (!woken) {
__atomic_compare_exchange_n(&lock->val, &val, 0U,
false, __ATOMIC_RELAXED,
__ATOMIC_RELAXED);
}
}
}
static long nanos = 50000;
struct thread_args {
pthread_t tid;
volatile int * done;
struct futex_numa_32 *lock;
int val;
int *val1, *val2;
int node;
};
static void *threadfn(void *_arg)
{
struct thread_args *args = _arg;
struct timespec ts = {
.tv_nsec = nanos,
};
int node;
while (!*args->done) {
futex_numa_32_lock(args->lock);
args->val++;
assert(*args->val1 == *args->val2);
(*args->val1)++;
nanosleep(&ts, NULL);
(*args->val2)++;
node = args->lock->node;
futex_numa_32_unlock(args->lock);
if (node != args->node) {
args->node = node;
printf("node: %d\n", node);
}
nanosleep(&ts, NULL);
}
return NULL;
}
static void *contendfn(void *_arg)
{
struct thread_args *args = _arg;
while (!*args->done) {
/*
* futex2_wait() will take hb-lock, verify *var == val and
* queue/abort. By knowingly setting val 'wrong' this will
* abort and thereby generate hb-lock contention.
*/
futex2_wait(&args->lock->val, ~0U, fflags, NULL, 0);
args->val++;
}
return NULL;
}
static volatile int done = 0;
static struct futex_numa_32 lock = { .val = 0, };
static int val1, val2;
int main(int argc, char *argv[])
{
struct thread_args *tas[512], *cas[512];
int c, t, threads = 2, contenders = 0;
int sleeps = 10;
int total = 0;
while ((c = getopt(argc, argv, "c:t:s:n:N::")) != -1) {
switch (c) {
case 'c':
contenders = atoi(optarg);
break;
case 't':
threads = atoi(optarg);
break;
case 's':
sleeps = atoi(optarg);
break;
case 'n':
nanos = atoi(optarg);
break;
case 'N':
fflags |= FUTEX2_NUMA;
if (optarg)
fnode = atoi(optarg);
break;
default:
exit(1);
break;
}
}
for (t = 0; t < contenders; t++) {
struct thread_args *args = calloc(1, sizeof(*args));
if (!args) {
perror("thread_args");
exit(-1);
}
args->done = &done;
args->lock = &lock;
args->val1 = &val1;
args->val2 = &val2;
args->node = -1;
if (pthread_create(&args->tid, NULL, contendfn, args)) {
perror("pthread_create");
exit(-1);
}
cas[t] = args;
}
for (t = 0; t < threads; t++) {
struct thread_args *args = calloc(1, sizeof(*args));
if (!args) {
perror("thread_args");
exit(-1);
}
args->done = &done;
args->lock = &lock;
args->val1 = &val1;
args->val2 = &val2;
args->node = -1;
if (pthread_create(&args->tid, NULL, threadfn, args)) {
perror("pthread_create");
exit(-1);
}
tas[t] = args;
}
sleep(sleeps);
done = true;
for (t = 0; t < threads; t++) {
struct thread_args *args = tas[t];
pthread_join(args->tid, NULL);
total += args->val;
// printf("tval: %d\n", args->val);
}
printf("total: %d\n", total);
if (contenders) {
total = 0;
for (t = 0; t < contenders; t++) {
struct thread_args *args = cas[t];
pthread_join(args->tid, NULL);
total += args->val;
// printf("tval: %d\n", args->val);
}
printf("contenders: %d\n", total);
}
return 0;
}
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef LIBNUMA_VER_SUFFICIENT
#include <numa.h>
#include <numaif.h>
#endif
#include <linux/futex.h>
#include <sys/mman.h>
#include "futextest.h"
#include "futex2test.h"
#include "kselftest_harness.h"
#define MAX_THREADS 64
static pthread_barrier_t barrier_main;
static pthread_t threads[MAX_THREADS];
struct thread_args {
void *futex_ptr;
unsigned int flags;
int result;
};
static struct thread_args thread_args[MAX_THREADS];
#ifndef FUTEX_NO_NODE
#define FUTEX_NO_NODE (-1)
#endif
#ifndef FUTEX2_MPOL
#define FUTEX2_MPOL 0x08
#endif
static void *thread_lock_fn(void *arg)
{
struct thread_args *args = arg;
int ret;
pthread_barrier_wait(&barrier_main);
ret = futex2_wait(args->futex_ptr, 0, args->flags, NULL, 0);
args->result = ret;
return NULL;
}
static void create_max_threads(void *futex_ptr)
{
int i, ret;
for (i = 0; i < MAX_THREADS; i++) {
thread_args[i].futex_ptr = futex_ptr;
thread_args[i].flags = FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA;
thread_args[i].result = 0;
ret = pthread_create(&threads[i], NULL, thread_lock_fn, &thread_args[i]);
if (ret)
ksft_exit_fail_msg("pthread_create failed\n");
}
}
static void join_max_threads(void)
{
int i, ret;
for (i = 0; i < MAX_THREADS; i++) {
ret = pthread_join(threads[i], NULL);
if (ret)
ksft_exit_fail_msg("pthread_join failed for thread %d\n", i);
}
}
static void __test_futex(void *futex_ptr, int err_value, unsigned int futex_flags)
{
int to_wake, ret, i, need_exit = 0;
pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
create_max_threads(futex_ptr);
pthread_barrier_wait(&barrier_main);
to_wake = MAX_THREADS;
do {
ret = futex2_wake(futex_ptr, to_wake, futex_flags);
if (err_value) {
if (ret >= 0)
ksft_exit_fail_msg("futex2_wake(%d, 0x%x) should fail, but didn't\n",
to_wake, futex_flags);
if (errno != err_value)
ksft_exit_fail_msg("futex2_wake(%d, 0x%x) expected error was %d, but returned %d (%s)\n",
to_wake, futex_flags, err_value, errno, strerror(errno));
break;
}
if (ret < 0) {
ksft_exit_fail_msg("Failed futex2_wake(%d, 0x%x): %m\n",
to_wake, futex_flags);
}
if (!ret)
usleep(50);
to_wake -= ret;
} while (to_wake);
join_max_threads();
for (i = 0; i < MAX_THREADS; i++) {
if (err_value && thread_args[i].result != -1) {
ksft_print_msg("Thread %d should fail but succeeded (%d)\n",
i, thread_args[i].result);
need_exit = 1;
}
if (!err_value && thread_args[i].result != 0) {
ksft_print_msg("Thread %d failed (%d)\n", i, thread_args[i].result);
need_exit = 1;
}
}
if (need_exit)
ksft_exit_fail_msg("Aborting due to earlier errors.\n");
}
static void test_futex(void *futex_ptr, int err_value)
{
__test_futex(futex_ptr, err_value, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA);
}
TEST(futex_numa_mpol)
{
struct futex32_numa *futex_numa;
void *futex_ptr;
int mem_size;
mem_size = sysconf(_SC_PAGE_SIZE);
futex_ptr = mmap(NULL, mem_size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if (futex_ptr == MAP_FAILED)
ksft_exit_fail_msg("mmap() for %d bytes failed\n", mem_size);
/* Create an invalid memory region for the "Memory out of range" test */
mprotect(futex_ptr + mem_size, mem_size, PROT_NONE);
futex_numa = futex_ptr;
ksft_print_msg("Regular test\n");
futex_numa->futex = 0;
futex_numa->numa = FUTEX_NO_NODE;
test_futex(futex_ptr, 0);
if (futex_numa->numa == FUTEX_NO_NODE)
ksft_exit_fail_msg("NUMA node is left uninitialized\n");
/* FUTEX2_NUMA futex must be 8-byte aligned */
ksft_print_msg("Mis-aligned futex\n");
test_futex(futex_ptr + mem_size - 4, EINVAL);
ksft_print_msg("Memory out of range\n");
test_futex(futex_ptr + mem_size, EFAULT);
futex_numa->numa = FUTEX_NO_NODE;
mprotect(futex_ptr, mem_size, PROT_READ);
ksft_print_msg("Memory, RO\n");
test_futex(futex_ptr, EFAULT);
mprotect(futex_ptr, mem_size, PROT_NONE);
ksft_print_msg("Memory, no access\n");
test_futex(futex_ptr, EFAULT);
mprotect(futex_ptr, mem_size, PROT_READ | PROT_WRITE);
ksft_print_msg("Memory back to RW\n");
test_futex(futex_ptr, 0);
ksft_test_result_pass("futex2 memory boundary tests passed\n");
/* MPOL test. Does not work as expected */
#ifdef LIBNUMA_VER_SUFFICIENT
for (int i = 0; i < 4; i++) {
unsigned long nodemask;
int ret;
nodemask = 1 << i;
ret = mbind(futex_ptr, mem_size, MPOL_BIND, &nodemask,
sizeof(nodemask) * 8, 0);
if (ret == 0) {
ret = numa_set_mempolicy_home_node(futex_ptr, mem_size, i, 0);
if (ret != 0)
ksft_exit_fail_msg("Failed to set home node: %m, %d\n", errno);
ksft_print_msg("Node %d test\n", i);
futex_numa->futex = 0;
futex_numa->numa = FUTEX_NO_NODE;
ret = futex2_wake(futex_ptr, 0, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA | FUTEX2_MPOL);
if (ret < 0)
ksft_test_result_fail("Failed to wake 0 with MPOL: %m\n");
if (futex_numa->numa != i) {
ksft_exit_fail_msg("Returned NUMA node is %d expected %d\n",
futex_numa->numa, i);
}
}
}
ksft_test_result_pass("futex2 MPOL hints test passed\n");
#else
ksft_test_result_skip("futex2 MPOL hints test requires libnuma 2.0.18+\n");
#endif
munmap(futex_ptr, mem_size * 2);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,270 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/prctl.h>
#include <sys/prctl.h>
#include "kselftest_harness.h"
#define MAX_THREADS 64
static pthread_barrier_t barrier_main;
static pthread_mutex_t global_lock;
static pthread_t threads[MAX_THREADS];
static int counter;
#ifndef PR_FUTEX_HASH
#define PR_FUTEX_HASH 78
# define PR_FUTEX_HASH_SET_SLOTS 1
# define PR_FUTEX_HASH_GET_SLOTS 2
#endif
static int futex_hash_slots_set(unsigned int slots)
{
return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0);
}
static int futex_hash_slots_get(void)
{
return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);
}
static void futex_hash_slots_set_verify(int slots)
{
int ret;
ret = futex_hash_slots_set(slots);
if (ret != 0) {
ksft_test_result_fail("Failed to set slots to %d: %m\n", slots);
ksft_finished();
}
ret = futex_hash_slots_get();
if (ret != slots) {
ksft_test_result_fail("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %m\n",
slots, ret);
ksft_finished();
}
ksft_test_result_pass("SET and GET slots %d passed\n", slots);
}
static void futex_hash_slots_set_must_fail(int slots)
{
int ret;
ret = futex_hash_slots_set(slots);
ksft_test_result(ret < 0, "futex_hash_slots_set(%d)\n",
slots);
}
static void *thread_return_fn(void *arg)
{
return NULL;
}
static void *thread_lock_fn(void *arg)
{
pthread_barrier_wait(&barrier_main);
pthread_mutex_lock(&global_lock);
counter++;
usleep(20);
pthread_mutex_unlock(&global_lock);
return NULL;
}
static void create_max_threads(void *(*thread_fn)(void *))
{
int i, ret;
for (i = 0; i < MAX_THREADS; i++) {
ret = pthread_create(&threads[i], NULL, thread_fn, NULL);
if (ret)
ksft_exit_fail_msg("pthread_create failed: %m\n");
}
}
static void join_max_threads(void)
{
int i, ret;
for (i = 0; i < MAX_THREADS; i++) {
ret = pthread_join(threads[i], NULL);
if (ret)
ksft_exit_fail_msg("pthread_join failed for thread %d\n", i);
}
}
#define SEC_IN_NSEC 1000000000
#define MSEC_IN_NSEC 1000000
static void futex_dummy_op(void)
{
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
struct timespec timeout;
int ret;
pthread_mutex_lock(&lock);
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_nsec += 100 * MSEC_IN_NSEC;
if (timeout.tv_nsec >= SEC_IN_NSEC) {
timeout.tv_nsec -= SEC_IN_NSEC;
timeout.tv_sec++;
}
ret = pthread_mutex_timedlock(&lock, &timeout);
if (ret == 0)
ksft_exit_fail_msg("Successfully locked an already locked mutex.\n");
if (ret != ETIMEDOUT)
ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret);
}
static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n";
static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n";
TEST(priv_hash)
{
int futex_slots1, futex_slotsn, online_cpus;
pthread_mutexattr_t mutex_attr_pi;
int ret, retry = 20;
ret = pthread_mutexattr_init(&mutex_attr_pi);
ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi);
if (ret != 0) {
ksft_exit_fail_msg("Failed to initialize pthread mutex.\n");
}
/* First thread, expect to be 0, not yet initialized */
ret = futex_hash_slots_get();
if (ret != 0)
ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret);
ksft_test_result_pass("Basic get slots and immutable status.\n");
ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
if (ret != 0)
ksft_exit_fail_msg("pthread_create() failed: %d, %m\n", ret);
ret = pthread_join(threads[0], NULL);
if (ret != 0)
ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret);
/* First thread, has to initialize private hash */
futex_slots1 = futex_hash_slots_get();
if (futex_slots1 <= 0) {
ksft_print_msg("Current hash buckets: %d\n", futex_slots1);
ksft_exit_fail_msg("%s", test_msg_auto_create);
}
ksft_test_result_pass("%s", test_msg_auto_create);
online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
if (ret != 0)
ksft_exit_fail_msg("pthread_barrier_init failed: %m.\n");
ret = pthread_mutex_lock(&global_lock);
if (ret != 0)
ksft_exit_fail_msg("pthread_mutex_lock failed: %m.\n");
counter = 0;
create_max_threads(thread_lock_fn);
pthread_barrier_wait(&barrier_main);
/*
* The current default size of hash buckets is 16. The auto increase
* works only if more than 16 CPUs are available.
*/
ksft_print_msg("Online CPUs: %d\n", online_cpus);
if (online_cpus > 16) {
retry_getslots:
futex_slotsn = futex_hash_slots_get();
if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
retry--;
/*
* Auto scaling on thread creation can be slightly delayed
* because it waits for a RCU grace period twice. The new
* private hash is assigned upon the first futex operation
* after grace period.
* To cover all this for testing purposes the function
* below will acquire a lock and acquire it again with a
* 100ms timeout which must timeout. This ensures we
* sleep for 100ms and issue a futex operation.
*/
if (retry > 0) {
futex_dummy_op();
goto retry_getslots;
}
ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n",
futex_slots1, futex_slotsn);
ksft_exit_fail_msg("%s", test_msg_auto_inc);
}
ksft_test_result_pass("%s", test_msg_auto_inc);
} else {
ksft_test_result_skip("%s", test_msg_auto_inc);
}
ret = pthread_mutex_unlock(&global_lock);
/* Once the user changes it, it has to be what is set */
futex_hash_slots_set_verify(2);
futex_hash_slots_set_verify(4);
futex_hash_slots_set_verify(8);
futex_hash_slots_set_verify(32);
futex_hash_slots_set_verify(16);
ret = futex_hash_slots_set(15);
ksft_test_result(ret < 0, "Use 15 slots\n");
futex_hash_slots_set_verify(2);
join_max_threads();
ksft_test_result(counter == MAX_THREADS, "Created and waited for %d of %d threads\n",
counter, MAX_THREADS);
counter = 0;
/* Once the user set something, auto resize must be disabled */
ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
create_max_threads(thread_lock_fn);
join_max_threads();
ret = futex_hash_slots_get();
ksft_test_result(ret == 2, "No more auto-resize after manual setting, got %d\n",
ret);
futex_hash_slots_set_must_fail(1 << 29);
futex_hash_slots_set_verify(4);
/*
* Once the global hash has been requested, then this requested can not
* be undone.
*/
ret = futex_hash_slots_set(0);
ksft_test_result(ret == 0, "Global hash request\n");
if (ret != 0)
return;
futex_hash_slots_set_must_fail(4);
futex_hash_slots_set_must_fail(8);
futex_hash_slots_set_must_fail(8);
futex_hash_slots_set_must_fail(0);
futex_hash_slots_set_must_fail(6);
ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
if (ret != 0)
ksft_exit_fail_msg("pthread_barrier_init failed: %m\n");
create_max_threads(thread_lock_fn);
join_max_threads();
ret = futex_hash_slots_get();
ksft_test_result(ret == 0, "Continue to use global hash\n");
}
TEST_HARNESS_MAIN
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright Collabora Ltd., 2021
*
* futex cmp requeue test by André Almeida <andrealmeid@collabora.com>
*/
#include <pthread.h>
#include <limits.h>
#include "futextest.h"
#include "kselftest_harness.h"
#define timeout_ns 30000000
#define WAKE_WAIT_US 10000
volatile futex_t *f1;
void *waiterfn(void *arg)
{
struct timespec to;
to.tv_sec = 0;
to.tv_nsec = timeout_ns;
if (futex_wait(f1, *f1, &to, 0))
printf("waiter failed errno %d\n", errno);
return NULL;
}
TEST(requeue_single)
{
volatile futex_t _f1 = 0;
volatile futex_t f2 = 0;
pthread_t waiter[10];
f1 = &_f1;
/*
* Requeue a waiter from f1 to f2, and wake f2.
*/
ASSERT_EQ(0, pthread_create(&waiter[0], NULL, waiterfn, NULL));
usleep(WAKE_WAIT_US);
EXPECT_EQ(1, futex_cmp_requeue(f1, 0, &f2, 0, 1, 0));
EXPECT_EQ(1, futex_wake(&f2, 1, 0));
}
TEST(requeue_multiple)
{
volatile futex_t _f1 = 0;
volatile futex_t f2 = 0;
pthread_t waiter[10];
int i;
f1 = &_f1;
/*
* Create 10 waiters at f1. At futex_requeue, wake 3 and requeue 7.
* At futex_wake, wake INT_MAX (should be exactly 7).
*/
for (i = 0; i < 10; i++)
ASSERT_EQ(0, pthread_create(&waiter[i], NULL, waiterfn, NULL));
usleep(WAKE_WAIT_US);
EXPECT_EQ(10, futex_cmp_requeue(f1, 0, &f2, 3, 7, 0));
EXPECT_EQ(7, futex_wake(&f2, INT_MAX, 0));
}
TEST_HARNESS_MAIN
@@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
*
* Copyright © International Business Machines Corp., 2006-2008
*
* DESCRIPTION
* This test excercises the futex syscall op codes needed for requeuing
* priority inheritance aware POSIX condition variables and mutexes.
*
* AUTHORS
* Sripathi Kodi <sripathik@in.ibm.com>
* Darren Hart <dvhart@linux.intel.com>
*
* HISTORY
* 2008-Jan-13: Initial version by Sripathi Kodi <sripathik@in.ibm.com>
* 2009-Nov-6: futex test adaptation by Darren Hart <dvhart@linux.intel.com>
*
*****************************************************************************/
#define _GNU_SOURCE
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include "atomic.h"
#include "futextest.h"
#include "kselftest_harness.h"
#define MAX_WAKE_ITERS 1000
#define THREAD_MAX 10
#define SIGNAL_PERIOD_US 100
atomic_t waiters_blocked = ATOMIC_INITIALIZER;
atomic_t waiters_woken = ATOMIC_INITIALIZER;
futex_t f1 = FUTEX_INITIALIZER;
futex_t f2 = FUTEX_INITIALIZER;
futex_t wake_complete = FUTEX_INITIALIZER;
struct thread_arg {
long id;
struct timespec *timeout;
int lock;
int ret;
};
#define THREAD_ARG_INITIALIZER { 0, NULL, 0, 0 }
FIXTURE(args)
{
};
FIXTURE_SETUP(args)
{
};
FIXTURE_TEARDOWN(args)
{
};
FIXTURE_VARIANT(args)
{
long timeout_ns;
bool broadcast;
bool owner;
bool locked;
};
/*
* For a given timeout value, this macro creates a test input with all the
* possible combinations of valid arguments
*/
#define FIXTURE_VARIANT_ADD_TIMEOUT(timeout) \
\
FIXTURE_VARIANT_ADD(args, t_##timeout) \
{ \
.timeout_ns = timeout, \
}; \
\
FIXTURE_VARIANT_ADD(args, t_##timeout##_broadcast) \
{ \
.timeout_ns = timeout, \
.broadcast = true, \
}; \
\
FIXTURE_VARIANT_ADD(args, t_##timeout##_broadcast_locked) \
{ \
.timeout_ns = timeout, \
.broadcast = true, \
.locked = true, \
}; \
\
FIXTURE_VARIANT_ADD(args, t_##timeout##_broadcast_owner) \
{ \
.timeout_ns = timeout, \
.broadcast = true, \
.owner = true, \
}; \
\
FIXTURE_VARIANT_ADD(args, t_##timeout##_locked) \
{ \
.timeout_ns = timeout, \
.locked = true, \
}; \
\
FIXTURE_VARIANT_ADD(args, t_##timeout##_owner) \
{ \
.timeout_ns = timeout, \
.owner = true, \
}; \
FIXTURE_VARIANT_ADD_TIMEOUT(0);
FIXTURE_VARIANT_ADD_TIMEOUT(5000);
FIXTURE_VARIANT_ADD_TIMEOUT(500000);
FIXTURE_VARIANT_ADD_TIMEOUT(2000000000);
int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg,
int policy, int prio)
{
int ret;
struct sched_param schedp;
pthread_attr_t attr;
pthread_attr_init(&attr);
memset(&schedp, 0, sizeof(schedp));
ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
if (ret) {
ksft_exit_fail_msg("pthread_attr_setinheritsched\n");
return -1;
}
ret = pthread_attr_setschedpolicy(&attr, policy);
if (ret) {
ksft_exit_fail_msg("pthread_attr_setschedpolicy\n");
return -1;
}
schedp.sched_priority = prio;
ret = pthread_attr_setschedparam(&attr, &schedp);
if (ret) {
ksft_exit_fail_msg("pthread_attr_setschedparam\n");
return -1;
}
ret = pthread_create(pth, &attr, func, arg);
if (ret) {
ksft_exit_fail_msg("pthread_create\n");
return -1;
}
return 0;
}
void *waiterfn(void *arg)
{
struct thread_arg *args = (struct thread_arg *)arg;
futex_t old_val;
ksft_print_dbg_msg("Waiter %ld: running\n", args->id);
/* Each thread sleeps for a different amount of time
* This is to avoid races, because we don't lock the
* external mutex here */
usleep(1000 * (long)args->id);
old_val = f1;
atomic_inc(&waiters_blocked);
ksft_print_dbg_msg("Calling futex_wait_requeue_pi: %p (%u) -> %p\n",
&f1, f1, &f2);
args->ret = futex_wait_requeue_pi(&f1, old_val, &f2, args->timeout,
FUTEX_PRIVATE_FLAG);
ksft_print_dbg_msg("waiter %ld woke with %d %s\n", args->id, args->ret,
args->ret < 0 ? strerror(errno) : "");
atomic_inc(&waiters_woken);
if (args->ret < 0) {
if (args->timeout && errno == ETIMEDOUT)
args->ret = 0;
else {
ksft_exit_fail_msg("futex_wait_requeue_pi\n");
}
futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
}
futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
ksft_print_dbg_msg("Waiter %ld: exiting with %d\n", args->id, args->ret);
pthread_exit((void *)&args->ret);
}
void *broadcast_wakerfn(void *arg)
{
struct thread_arg *args = (struct thread_arg *)arg;
int nr_requeue = INT_MAX;
int task_count = 0;
futex_t old_val;
int nr_wake = 1;
int i = 0;
ksft_print_dbg_msg("Waker: waiting for waiters to block\n");
while (waiters_blocked.val < THREAD_MAX)
usleep(1000);
usleep(1000);
ksft_print_dbg_msg("Waker: Calling broadcast\n");
if (args->lock) {
ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n", f2, &f2);
futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
}
continue_requeue:
old_val = f1;
args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, nr_wake, nr_requeue,
FUTEX_PRIVATE_FLAG);
if (args->ret < 0) {
ksft_exit_fail_msg("FUTEX_CMP_REQUEUE_PI failed\n");
} else if (++i < MAX_WAKE_ITERS) {
task_count += args->ret;
if (task_count < THREAD_MAX - waiters_woken.val)
goto continue_requeue;
} else {
ksft_exit_fail_msg("max broadcast iterations (%d) reached with %d/%d tasks woken or requeued\n",
MAX_WAKE_ITERS, task_count, THREAD_MAX);
}
futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG);
if (args->lock)
futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
if (args->ret > 0)
args->ret = task_count;
ksft_print_dbg_msg("Waker: exiting with %d\n", args->ret);
pthread_exit((void *)&args->ret);
}
void *signal_wakerfn(void *arg)
{
struct thread_arg *args = (struct thread_arg *)arg;
unsigned int old_val;
int nr_requeue = 0;
int task_count = 0;
int nr_wake = 1;
int i = 0;
ksft_print_dbg_msg("Waker: waiting for waiters to block\n");
while (waiters_blocked.val < THREAD_MAX)
usleep(1000);
usleep(1000);
while (task_count < THREAD_MAX && waiters_woken.val < THREAD_MAX) {
ksft_print_dbg_msg("task_count: %d, waiters_woken: %d\n",
task_count, waiters_woken.val);
if (args->lock) {
ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n",
f2, &f2);
futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
}
ksft_print_dbg_msg("Waker: Calling signal\n");
/* cond_signal */
old_val = f1;
args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2,
nr_wake, nr_requeue,
FUTEX_PRIVATE_FLAG);
if (args->ret < 0)
args->ret = -errno;
ksft_print_dbg_msg("futex: %x\n", f2);
if (args->lock) {
ksft_print_dbg_msg("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p\n",
f2, &f2);
futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
}
ksft_print_dbg_msg("futex: %x\n", f2);
if (args->ret < 0)
ksft_exit_fail_msg("FUTEX_CMP_REQUEUE_PI failed\n");
task_count += args->ret;
usleep(SIGNAL_PERIOD_US);
i++;
/* we have to loop at least THREAD_MAX times */
if (i > MAX_WAKE_ITERS + THREAD_MAX) {
ksft_exit_fail_msg("max signaling iterations (%d) reached, giving up on pending waiters.\n",
MAX_WAKE_ITERS + THREAD_MAX);
}
}
futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG);
if (args->ret >= 0)
args->ret = task_count;
ksft_print_dbg_msg("Waker: exiting with %d\n", args->ret);
ksft_print_dbg_msg("Waker: waiters_woken: %d\n", waiters_woken.val);
pthread_exit((void *)&args->ret);
}
void *third_party_blocker(void *arg)
{
struct thread_arg *args = (struct thread_arg *)arg;
int ret2 = 0;
args->ret = futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
if (args->ret)
goto out;
args->ret = futex_wait(&wake_complete, wake_complete, NULL,
FUTEX_PRIVATE_FLAG);
ret2 = futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
out:
if (args->ret || ret2)
ksft_exit_fail_msg("third_party_blocker() futex error");
pthread_exit((void *)&args->ret);
}
TEST_F(args, futex_requeue_pi)
{
struct thread_arg blocker_arg = THREAD_ARG_INITIALIZER;
struct thread_arg waker_arg = THREAD_ARG_INITIALIZER;
pthread_t waiter[THREAD_MAX], waker, blocker;
void *(*wakerfn)(void *) = signal_wakerfn;
bool third_party_owner = variant->owner;
long timeout_ns = variant->timeout_ns;
bool broadcast = variant->broadcast;
struct thread_arg args[THREAD_MAX];
struct timespec ts, *tsp = NULL;
bool lock = variant->locked;
int *waiter_ret, i, ret = 0;
ksft_print_msg(
"\tArguments: broadcast=%d locked=%d owner=%d timeout=%ldns\n",
broadcast, lock, third_party_owner, timeout_ns);
if (timeout_ns) {
time_t secs;
ksft_print_dbg_msg("timeout_ns = %ld\n", timeout_ns);
ret = clock_gettime(CLOCK_MONOTONIC, &ts);
secs = (ts.tv_nsec + timeout_ns) / 1000000000;
ts.tv_nsec = ((int64_t)ts.tv_nsec + timeout_ns) % 1000000000;
ts.tv_sec += secs;
ksft_print_dbg_msg("ts.tv_sec = %ld\n", ts.tv_sec);
ksft_print_dbg_msg("ts.tv_nsec = %ld\n", ts.tv_nsec);
tsp = &ts;
}
if (broadcast)
wakerfn = broadcast_wakerfn;
if (third_party_owner) {
if (create_rt_thread(&blocker, third_party_blocker,
(void *)&blocker_arg, SCHED_FIFO, 1)) {
ksft_exit_fail_msg("Creating third party blocker thread failed\n");
}
}
atomic_set(&waiters_woken, 0);
for (i = 0; i < THREAD_MAX; i++) {
args[i].id = i;
args[i].timeout = tsp;
ksft_print_dbg_msg("Starting thread %d\n", i);
if (create_rt_thread(&waiter[i], waiterfn, (void *)&args[i],
SCHED_FIFO, 1)) {
ksft_exit_fail_msg("Creating waiting thread failed\n");
}
}
waker_arg.lock = lock;
if (create_rt_thread(&waker, wakerfn, (void *)&waker_arg,
SCHED_FIFO, 1)) {
ksft_exit_fail_msg("Creating waker thread failed\n");
}
/* Wait for threads to finish */
/* Store the first error or failure encountered in waiter_ret */
waiter_ret = &args[0].ret;
for (i = 0; i < THREAD_MAX; i++)
pthread_join(waiter[i],
*waiter_ret ? NULL : (void **)&waiter_ret);
if (third_party_owner)
pthread_join(blocker, NULL);
pthread_join(waker, NULL);
if (!ret) {
if (*waiter_ret)
ret = *waiter_ret;
else if (waker_arg.ret < 0)
ret = waker_arg.ret;
else if (blocker_arg.ret)
ret = blocker_arg.ret;
}
if (ret)
ksft_test_result_fail("fail");
}
TEST_HARNESS_MAIN
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
*
* Copyright © International Business Machines Corp., 2009
*
* DESCRIPTION
* 1. Block a thread using FUTEX_WAIT
* 2. Attempt to use FUTEX_CMP_REQUEUE_PI on the futex from 1.
* 3. The kernel must detect the mismatch and return -EINVAL.
*
* AUTHOR
* Darren Hart <dvhart@linux.intel.com>
*
* HISTORY
* 2009-Nov-9: Initial version by Darren Hart <dvhart@linux.intel.com>
*
*****************************************************************************/
#include <errno.h>
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "futextest.h"
#include "kselftest_harness.h"
futex_t f1 = FUTEX_INITIALIZER;
futex_t f2 = FUTEX_INITIALIZER;
int child_ret = 0;
void *blocking_child(void *arg)
{
child_ret = futex_wait(&f1, f1, NULL, FUTEX_PRIVATE_FLAG);
if (child_ret < 0) {
child_ret = -errno;
ksft_exit_fail_msg("futex_wait\n");
}
return (void *)&child_ret;
}
TEST(requeue_pi_mismatched_ops)
{
pthread_t child;
int ret;
if (pthread_create(&child, NULL, blocking_child, NULL))
ksft_exit_fail_msg("pthread_create\n");
/* Allow the child to block in the kernel. */
sleep(1);
/*
* The kernel should detect the waiter did not setup the
* q->requeue_pi_key and return -EINVAL. If it does not,
* it likely gave the lock to the child, which is now hung
* in the kernel.
*/
ret = futex_cmp_requeue_pi(&f1, f1, &f2, 1, 0, FUTEX_PRIVATE_FLAG);
if (ret < 0) {
if (errno == EINVAL) {
/*
* The kernel correctly detected the mismatched
* requeue_pi target and aborted. Wake the child with
* FUTEX_WAKE.
*/
ret = futex_wake(&f1, 1, FUTEX_PRIVATE_FLAG);
if (ret == 1)
ret = 0;
else if (ret < 0)
ksft_exit_fail_msg("futex_wake\n");
else
ksft_exit_fail_msg("futex_wake did not wake the child\n");
} else {
ksft_exit_fail_msg("futex_cmp_requeue_pi\n");
}
} else if (ret > 0) {
ksft_test_result_fail("futex_cmp_requeue_pi failed to detect the mismatch\n");
} else {
ksft_exit_fail_msg("futex_cmp_requeue_pi found no waiters\n");
}
pthread_join(child, NULL);
if (!ret && !child_ret)
ksft_test_result_pass("futex_requeue_pi_mismatched_ops passed\n");
else
ksft_test_result_pass("futex_requeue_pi_mismatched_ops failed\n");
}
TEST_HARNESS_MAIN
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
*
* Copyright © International Business Machines Corp., 2006-2008
*
* DESCRIPTION
* This test exercises the futex_wait_requeue_pi() signal handling both
* before and after the requeue. The first should be restarted by the
* kernel. The latter should return EWOULDBLOCK to the waiter.
*
* AUTHORS
* Darren Hart <dvhart@linux.intel.com>
*
* HISTORY
* 2008-May-5: Initial version by Darren Hart <dvhart@linux.intel.com>
*
*****************************************************************************/
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "atomic.h"
#include "futextest.h"
#include "kselftest_harness.h"
#define DELAY_US 100
futex_t f1 = FUTEX_INITIALIZER;
futex_t f2 = FUTEX_INITIALIZER;
atomic_t requeued = ATOMIC_INITIALIZER;
int waiter_ret = 0;
int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg,
int policy, int prio)
{
struct sched_param schedp;
pthread_attr_t attr;
int ret;
pthread_attr_init(&attr);
memset(&schedp, 0, sizeof(schedp));
ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
if (ret)
ksft_exit_fail_msg("pthread_attr_setinheritsched\n");
ret = pthread_attr_setschedpolicy(&attr, policy);
if (ret)
ksft_exit_fail_msg("pthread_attr_setschedpolicy\n");
schedp.sched_priority = prio;
ret = pthread_attr_setschedparam(&attr, &schedp);
if (ret)
ksft_exit_fail_msg("pthread_attr_setschedparam\n");
ret = pthread_create(pth, &attr, func, arg);
if (ret)
ksft_exit_fail_msg("pthread_create\n");
return 0;
}
void handle_signal(int signo)
{
ksft_print_dbg_msg("signal received %s requeue\n",
requeued.val ? "after" : "prior to");
}
void *waiterfn(void *arg)
{
unsigned int old_val;
int res;
ksft_print_dbg_msg("Waiter running\n");
ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on f2=%x @ %p\n", f2, &f2);
old_val = f1;
res = futex_wait_requeue_pi(&f1, old_val, &(f2), NULL,
FUTEX_PRIVATE_FLAG);
if (!requeued.val || errno != EWOULDBLOCK) {
ksft_test_result_fail("unexpected return from futex_wait_requeue_pi: %d (%s)\n",
res, strerror(errno));
ksft_print_dbg_msg("w2:futex: %x\n", f2);
if (!res)
futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
}
pthread_exit(NULL);
}
TEST(futex_requeue_pi_signal_restart)
{
unsigned int old_val;
struct sigaction sa;
pthread_t waiter;
int res;
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGUSR1, &sa, NULL))
ksft_exit_fail_msg("sigaction\n");
ksft_print_dbg_msg("m1:f2: %x\n", f2);
ksft_print_dbg_msg("Creating waiter\n");
res = create_rt_thread(&waiter, waiterfn, NULL, SCHED_FIFO, 1);
if (res)
ksft_exit_fail_msg("Creating waiting thread failed");
ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on f2=%x @ %p\n", f2, &f2);
ksft_print_dbg_msg("m2:f2: %x\n", f2);
futex_lock_pi(&f2, 0, 0, FUTEX_PRIVATE_FLAG);
ksft_print_dbg_msg("m3:f2: %x\n", f2);
while (1) {
/*
* signal the waiter before requeue, waiter should automatically
* restart futex_wait_requeue_pi() in the kernel. Wait for the
* waiter to block on f1 again.
*/
ksft_print_dbg_msg("Issuing SIGUSR1 to waiter\n");
pthread_kill(waiter, SIGUSR1);
usleep(DELAY_US);
ksft_print_dbg_msg("Requeueing waiter via FUTEX_CMP_REQUEUE_PI\n");
old_val = f1;
res = futex_cmp_requeue_pi(&f1, old_val, &(f2), 1, 0,
FUTEX_PRIVATE_FLAG);
/*
* If res is non-zero, we either requeued the waiter or hit an
* error, break out and handle it. If it is zero, then the
* signal may have hit before the waiter was blocked on f1.
* Try again.
*/
if (res > 0) {
atomic_set(&requeued, 1);
break;
} else if (res < 0) {
ksft_exit_fail_msg("FUTEX_CMP_REQUEUE_PI failed\n");
}
}
ksft_print_dbg_msg("m4:f2: %x\n", f2);
/*
* Signal the waiter after requeue, waiter should return from
* futex_wait_requeue_pi() with EWOULDBLOCK. Join the thread here so the
* futex_unlock_pi() can't happen before the signal wakeup is detected
* in the kernel.
*/
ksft_print_dbg_msg("Issuing SIGUSR1 to waiter\n");
pthread_kill(waiter, SIGUSR1);
ksft_print_dbg_msg("Waiting for waiter to return\n");
pthread_join(waiter, NULL);
ksft_print_dbg_msg("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p\n", f2, &f2);
futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
ksft_print_dbg_msg("m5:f2: %x\n", f2);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright Collabora Ltd., 2021
*
* futex cmp requeue test by André Almeida <andrealmeid@collabora.com>
*/
#include <pthread.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <fcntl.h>
#include "futextest.h"
#include "kselftest_harness.h"
#define timeout_ns 30000000
#define WAKE_WAIT_US 10000
#define SHM_PATH "futex_shm_file"
void *futex;
static void *waiterfn(void *arg)
{
struct timespec to;
unsigned int flags = 0;
if (arg)
flags = *((unsigned int *) arg);
to.tv_sec = 0;
to.tv_nsec = timeout_ns;
if (futex_wait(futex, 0, &to, flags))
printf("waiter failed errno %d\n", errno);
return NULL;
}
TEST(private_futex)
{
unsigned int flags = FUTEX_PRIVATE_FLAG;
u_int32_t f_private = 0;
pthread_t waiter;
int res;
futex = &f_private;
/* Testing a private futex */
ksft_print_dbg_msg("Calling private futex_wait on futex: %p\n", futex);
if (pthread_create(&waiter, NULL, waiterfn, (void *) &flags))
ksft_exit_fail_msg("pthread_create failed\n");
usleep(WAKE_WAIT_US);
ksft_print_dbg_msg("Calling private futex_wake on futex: %p\n", futex);
res = futex_wake(futex, 1, FUTEX_PRIVATE_FLAG);
if (res != 1) {
ksft_test_result_fail("futex_wake private returned: %d %s\n",
errno, strerror(errno));
} else {
ksft_test_result_pass("futex_wake private succeeds\n");
}
}
TEST(anon_page)
{
u_int32_t *shared_data;
pthread_t waiter;
int res, shm_id;
/* Testing an anon page shared memory */
shm_id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);
if (shm_id < 0) {
if (errno == ENOSYS)
ksft_exit_skip("shmget syscall not supported\n");
perror("shmget");
exit(1);
}
shared_data = shmat(shm_id, NULL, 0);
*shared_data = 0;
futex = shared_data;
ksft_print_dbg_msg("Calling shared (page anon) futex_wait on futex: %p\n", futex);
if (pthread_create(&waiter, NULL, waiterfn, NULL))
ksft_exit_fail_msg("pthread_create failed\n");
usleep(WAKE_WAIT_US);
ksft_print_dbg_msg("Calling shared (page anon) futex_wake on futex: %p\n", futex);
res = futex_wake(futex, 1, 0);
if (res != 1) {
ksft_test_result_fail("futex_wake shared (page anon) returned: %d %s\n",
errno, strerror(errno));
} else {
ksft_test_result_pass("futex_wake shared (page anon) succeeds\n");
}
shmdt(shared_data);
}
TEST(file_backed)
{
u_int32_t f_private = 0;
pthread_t waiter;
int res, fd;
void *shm;
/* Testing a file backed shared memory */
fd = open(SHM_PATH, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
ksft_exit_fail_msg("open\n");
if (ftruncate(fd, sizeof(f_private)))
ksft_exit_fail_msg("ftruncate\n");
shm = mmap(NULL, sizeof(f_private), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shm == MAP_FAILED)
ksft_exit_fail_msg("mmap\n");
memcpy(shm, &f_private, sizeof(f_private));
futex = shm;
ksft_print_dbg_msg("Calling shared (file backed) futex_wait on futex: %p\n", futex);
if (pthread_create(&waiter, NULL, waiterfn, NULL))
ksft_exit_fail_msg("pthread_create failed\n");
usleep(WAKE_WAIT_US);
ksft_print_dbg_msg("Calling shared (file backed) futex_wake on futex: %p\n", futex);
res = futex_wake(shm, 1, 0);
if (res != 1) {
ksft_test_result_fail("futex_wake shared (file backed) returned: %d %s\n",
errno, strerror(errno));
} else {
ksft_test_result_pass("futex_wake shared (file backed) succeeds\n");
}
munmap(shm, sizeof(f_private));
remove(SHM_PATH);
close(fd);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
*
* Copyright FUJITSU LIMITED 2010
* Copyright KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
*
* DESCRIPTION
* Internally, Futex has two handling mode, anon and file. The private file
* mapping is special. At first it behave as file, but after write anything
* it behave as anon. This test is intent to test such case.
*
* AUTHOR
* KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
*
* HISTORY
* 2010-Jan-6: Initial version by KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
*
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <syscall.h>
#include <unistd.h>
#include <errno.h>
#include <linux/futex.h>
#include <pthread.h>
#include <libgen.h>
#include <signal.h>
#include "futextest.h"
#include "kselftest_harness.h"
#define PAGE_SZ 4096
char pad[PAGE_SZ] = {1};
futex_t val = 1;
char pad2[PAGE_SZ] = {1};
#define WAKE_WAIT_US 3000000
struct timespec wait_timeout = { .tv_sec = 5, .tv_nsec = 0};
void *thr_futex_wait(void *arg)
{
int ret;
ksft_print_dbg_msg("futex wait\n");
ret = futex_wait(&val, 1, &wait_timeout, 0);
if (ret && errno != EWOULDBLOCK && errno != ETIMEDOUT)
ksft_exit_fail_msg("futex error.\n");
if (ret && errno == ETIMEDOUT)
ksft_exit_fail_msg("waiter timedout\n");
ksft_print_dbg_msg("futex_wait: ret = %d, errno = %d\n", ret, errno);
return NULL;
}
TEST(wait_private_mapped_file)
{
pthread_t thr;
int res;
res = pthread_create(&thr, NULL, thr_futex_wait, NULL);
if (res < 0)
ksft_exit_fail_msg("pthread_create error\n");
ksft_print_dbg_msg("wait a while\n");
usleep(WAKE_WAIT_US);
val = 2;
res = futex_wake(&val, 1, 0);
ksft_print_dbg_msg("futex_wake %d\n", res);
if (res != 1)
ksft_exit_fail_msg("FUTEX_WAKE didn't find the waiting thread.\n");
ksft_print_dbg_msg("join\n");
pthread_join(thr, NULL);
ksft_test_result_pass("wait_private_mapped_file");
}
TEST_HARNESS_MAIN
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
*
* Copyright © International Business Machines Corp., 2009
*
* DESCRIPTION
* Block on a futex and wait for timeout.
*
* AUTHOR
* Darren Hart <dvhart@linux.intel.com>
*
* HISTORY
* 2009-Nov-6: Initial version by Darren Hart <dvhart@linux.intel.com>
* 2021-Apr-26: More test cases by André Almeida <andrealmeid@collabora.com>
*
*****************************************************************************/
#include <pthread.h>
#include "futextest.h"
#include "futex2test.h"
#include "kselftest_harness.h"
static long timeout_ns = 100000; /* 100us default timeout */
static futex_t futex_pi;
static pthread_barrier_t barrier;
/*
* Get a PI lock and hold it forever, so the main thread lock_pi will block
* and we can test the timeout
*/
void *get_pi_lock(void *arg)
{
int ret;
volatile futex_t lock = 0;
ret = futex_lock_pi(&futex_pi, NULL, 0, 0);
if (ret != 0)
ksft_exit_fail_msg("futex_lock_pi failed\n");
pthread_barrier_wait(&barrier);
/* Blocks forever */
ret = futex_wait(&lock, 0, NULL, 0);
ksft_exit_fail_msg("futex_wait failed\n");
return NULL;
}
/*
* Check if the function returned the expected error
*/
static void test_timeout(int res, char *test_name, int err)
{
if (!res || errno != err) {
ksft_test_result_fail("%s returned %d\n", test_name,
res < 0 ? errno : res);
} else {
ksft_test_result_pass("%s succeeds\n", test_name);
}
}
/*
* Calculate absolute timeout and correct overflow
*/
static int futex_get_abs_timeout(clockid_t clockid, struct timespec *to,
long timeout_ns)
{
if (clock_gettime(clockid, to))
ksft_exit_fail_msg("clock_gettime failed\n");
to->tv_nsec += timeout_ns;
if (to->tv_nsec >= 1000000000) {
to->tv_sec++;
to->tv_nsec -= 1000000000;
}
return 0;
}
TEST(wait_bitset)
{
futex_t f1 = FUTEX_INITIALIZER;
struct timespec to;
int res;
/* initialize relative timeout */
to.tv_sec = 0;
to.tv_nsec = timeout_ns;
res = futex_wait(&f1, f1, &to, 0);
test_timeout(res, "futex_wait relative", ETIMEDOUT);
/* FUTEX_WAIT_BITSET with CLOCK_REALTIME */
if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
ksft_test_result_error("get_time error");
res = futex_wait_bitset(&f1, f1, &to, 1, FUTEX_CLOCK_REALTIME);
test_timeout(res, "futex_wait_bitset realtime", ETIMEDOUT);
/* FUTEX_WAIT_BITSET with CLOCK_MONOTONIC */
if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns))
ksft_test_result_error("get_time error");
res = futex_wait_bitset(&f1, f1, &to, 1, 0);
test_timeout(res, "futex_wait_bitset monotonic", ETIMEDOUT);
}
TEST(requeue_pi)
{
futex_t f1 = FUTEX_INITIALIZER;
struct timespec to;
int res;
/* FUTEX_WAIT_REQUEUE_PI with CLOCK_REALTIME */
if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
ksft_test_result_error("get_time error");
res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, FUTEX_CLOCK_REALTIME);
test_timeout(res, "futex_wait_requeue_pi realtime", ETIMEDOUT);
/* FUTEX_WAIT_REQUEUE_PI with CLOCK_MONOTONIC */
if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns))
ksft_test_result_error("get_time error");
res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, 0);
test_timeout(res, "futex_wait_requeue_pi monotonic", ETIMEDOUT);
}
TEST(lock_pi)
{
struct timespec to;
pthread_t thread;
int res;
/* Create a thread that will lock forever so any waiter will timeout */
pthread_barrier_init(&barrier, NULL, 2);
pthread_create(&thread, NULL, get_pi_lock, NULL);
/* Wait until the other thread calls futex_lock_pi() */
pthread_barrier_wait(&barrier);
pthread_barrier_destroy(&barrier);
/*
* FUTEX_LOCK_PI with CLOCK_REALTIME
* Due to historical reasons, FUTEX_LOCK_PI supports only realtime
* clock, but requires the caller to not set CLOCK_REALTIME flag.
*
* If you call FUTEX_LOCK_PI with a monotonic clock, it'll be
* interpreted as a realtime clock, and (unless you mess your machine's
* time or your time machine) the monotonic clock value is always
* smaller than realtime and the syscall will timeout immediately.
*/
if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
ksft_test_result_error("get_time error");
res = futex_lock_pi(&futex_pi, &to, 0, 0);
test_timeout(res, "futex_lock_pi realtime", ETIMEDOUT);
/* Test operations that don't support FUTEX_CLOCK_REALTIME */
res = futex_lock_pi(&futex_pi, NULL, 0, FUTEX_CLOCK_REALTIME);
test_timeout(res, "futex_lock_pi invalid timeout flag", ENOSYS);
}
TEST(waitv)
{
futex_t f1 = FUTEX_INITIALIZER;
struct futex_waitv waitv = {
.uaddr = (uintptr_t)&f1,
.val = f1,
.flags = FUTEX_32,
.__reserved = 0,
};
struct timespec to;
int res;
/* futex_waitv with CLOCK_MONOTONIC */
if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns))
ksft_test_result_error("get_time error");
res = futex_waitv(&waitv, 1, 0, &to, CLOCK_MONOTONIC);
test_timeout(res, "futex_waitv monotonic", ETIMEDOUT);
/* futex_waitv with CLOCK_REALTIME */
if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
ksft_test_result_error("get_time error");
res = futex_waitv(&waitv, 1, 0, &to, CLOCK_REALTIME);
test_timeout(res, "futex_waitv realtime", ETIMEDOUT);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
*
* Copyright FUJITSU LIMITED 2010
* Copyright KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
*
* DESCRIPTION
* Wait on uninitialized heap. It shold be zero and FUTEX_WAIT should
* return immediately. This test is intent to test zero page handling in
* futex.
*
* AUTHOR
* KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
*
* HISTORY
* 2010-Jan-6: Initial version by KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
*
*****************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <linux/futex.h>
#include <libgen.h>
#include "futextest.h"
#include "kselftest_harness.h"
#define WAIT_US 5000000
static int child_blocked = 1;
static bool child_ret;
void *buf;
void *wait_thread(void *arg)
{
int res;
child_ret = true;
res = futex_wait(buf, 1, NULL, 0);
child_blocked = 0;
if (res != 0 && errno != EWOULDBLOCK) {
ksft_exit_fail_msg("futex failure\n");
child_ret = false;
}
pthread_exit(NULL);
}
TEST(futex_wait_uninitialized_heap)
{
long page_size;
pthread_t thr;
int ret;
page_size = sysconf(_SC_PAGESIZE);
buf = mmap(NULL, page_size, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
if (buf == (void *)-1)
ksft_exit_fail_msg("mmap\n");
ret = pthread_create(&thr, NULL, wait_thread, NULL);
if (ret)
ksft_exit_fail_msg("pthread_create\n");
ksft_print_dbg_msg("waiting %dus for child to return\n", WAIT_US);
usleep(WAIT_US);
if (child_blocked)
ksft_test_result_fail("child blocked in kernel\n");
if (!child_ret)
ksft_test_result_fail("child error\n");
}
TEST_HARNESS_MAIN
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
*
* Copyright © International Business Machines Corp., 2009
*
* DESCRIPTION
* Test if FUTEX_WAIT op returns -EWOULDBLOCK if the futex value differs
* from the expected one.
*
* AUTHOR
* Gowrishankar <gowrishankar.m@in.ibm.com>
*
* HISTORY
* 2009-Nov-14: Initial version by Gowrishankar <gowrishankar.m@in.ibm.com>
*
*****************************************************************************/
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "futextest.h"
#include "futex2test.h"
#include "kselftest_harness.h"
#define timeout_ns 100000
TEST(futex_wait_wouldblock)
{
struct timespec to = {.tv_sec = 0, .tv_nsec = timeout_ns};
futex_t f1 = FUTEX_INITIALIZER;
int res;
ksft_print_dbg_msg("Calling futex_wait on f1: %u @ %p with val=%u\n", f1, &f1, f1+1);
res = futex_wait(&f1, f1+1, &to, FUTEX_PRIVATE_FLAG);
if (!res || errno != EWOULDBLOCK) {
ksft_test_result_fail("futex_wait returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_wait\n");
}
}
TEST(futex_waitv_wouldblock)
{
struct timespec to = {.tv_sec = 0, .tv_nsec = timeout_ns};
futex_t f1 = FUTEX_INITIALIZER;
struct futex_waitv waitv = {
.uaddr = (uintptr_t)&f1,
.val = f1 + 1,
.flags = FUTEX_32,
.__reserved = 0,
};
int res;
if (clock_gettime(CLOCK_MONOTONIC, &to))
ksft_exit_fail_msg("clock_gettime failed %d\n", errno);
to.tv_nsec += timeout_ns;
if (to.tv_nsec >= 1000000000) {
to.tv_sec++;
to.tv_nsec -= 1000000000;
}
ksft_print_dbg_msg("Calling futex_waitv on f1: %u @ %p with val=%u\n", f1, &f1, f1+1);
res = futex_waitv(&waitv, 1, 0, &to, CLOCK_MONOTONIC);
if (!res || errno != EWOULDBLOCK) {
ksft_test_result_fail("futex_waitv returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv\n");
}
}
TEST_HARNESS_MAIN
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* futex_waitv() test by André Almeida <andrealmeid@collabora.com>
*
* Copyright 2021 Collabora Ltd.
*/
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/shm.h>
#include "futextest.h"
#include "futex2test.h"
#include "kselftest_harness.h"
#define WAKE_WAIT_US 10000
#define NR_FUTEXES 30
static struct futex_waitv waitv[NR_FUTEXES];
u_int32_t futexes[NR_FUTEXES] = {0};
void *waiterfn(void *arg)
{
struct timespec to;
int res;
/* setting absolute timeout for futex2 */
if (clock_gettime(CLOCK_MONOTONIC, &to))
ksft_exit_fail_msg("gettime64 failed\n");
to.tv_sec++;
res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC);
if (res < 0) {
ksft_test_result_fail("futex_waitv returned: %d %s\n",
errno, strerror(errno));
} else if (res != NR_FUTEXES - 1) {
ksft_test_result_fail("futex_waitv returned: %d, expecting %d\n",
res, NR_FUTEXES - 1);
}
return NULL;
}
TEST(private_waitv)
{
pthread_t waiter;
int res, i;
for (i = 0; i < NR_FUTEXES; i++) {
waitv[i].uaddr = (uintptr_t)&futexes[i];
waitv[i].flags = FUTEX_32 | FUTEX_PRIVATE_FLAG;
waitv[i].val = 0;
waitv[i].__reserved = 0;
}
/* Private waitv */
if (pthread_create(&waiter, NULL, waiterfn, NULL))
ksft_exit_fail_msg("pthread_create failed\n");
usleep(WAKE_WAIT_US);
res = futex_wake(u64_to_ptr(waitv[NR_FUTEXES - 1].uaddr), 1, FUTEX_PRIVATE_FLAG);
if (res != 1) {
ksft_test_result_fail("futex_wake private returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv private\n");
}
}
TEST(shared_waitv)
{
pthread_t waiter;
int res, i;
/* Shared waitv */
for (i = 0; i < NR_FUTEXES; i++) {
int shm_id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);
if (shm_id < 0) {
if (errno == ENOSYS)
ksft_exit_skip("shmget syscall not supported\n");
perror("shmget");
exit(1);
}
unsigned int *shared_data = shmat(shm_id, NULL, 0);
*shared_data = 0;
waitv[i].uaddr = (uintptr_t)shared_data;
waitv[i].flags = FUTEX_32;
waitv[i].val = 0;
waitv[i].__reserved = 0;
}
if (pthread_create(&waiter, NULL, waiterfn, NULL))
ksft_exit_fail_msg("pthread_create failed\n");
usleep(WAKE_WAIT_US);
res = futex_wake(u64_to_ptr(waitv[NR_FUTEXES - 1].uaddr), 1, 0);
if (res != 1) {
ksft_test_result_fail("futex_wake shared returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv shared\n");
}
for (i = 0; i < NR_FUTEXES; i++)
shmdt(u64_to_ptr(waitv[i].uaddr));
}
TEST(invalid_flag)
{
struct timespec to;
int res;
/* Testing a waiter without FUTEX_32 flag */
waitv[0].flags = FUTEX_PRIVATE_FLAG;
if (clock_gettime(CLOCK_MONOTONIC, &to))
ksft_exit_fail_msg("gettime64 failed\n");
to.tv_sec++;
res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC);
if (res == EINVAL) {
ksft_test_result_fail("futex_waitv private returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv without FUTEX_32\n");
}
}
TEST(unaligned_address)
{
struct timespec to;
int res;
/* Testing a waiter with an unaligned address */
waitv[0].flags = FUTEX_PRIVATE_FLAG | FUTEX_32;
waitv[0].uaddr = 1;
if (clock_gettime(CLOCK_MONOTONIC, &to))
ksft_exit_fail_msg("gettime64 failed\n");
to.tv_sec++;
res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC);
if (res == EINVAL) {
ksft_test_result_fail("futex_wake private returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv with an unaligned address\n");
}
}
TEST(null_address)
{
struct timespec to;
int res;
/* Testing a NULL address for waiters.uaddr */
waitv[0].uaddr = 0x00000000;
if (clock_gettime(CLOCK_MONOTONIC, &to))
ksft_exit_fail_msg("gettime64 failed\n");
to.tv_sec++;
res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC);
if (res == EINVAL) {
ksft_test_result_fail("futex_waitv private returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv NULL address in waitv.uaddr\n");
}
/* Testing a NULL address for *waiters */
if (clock_gettime(CLOCK_MONOTONIC, &to))
ksft_exit_fail_msg("gettime64 failed\n");
to.tv_sec++;
res = futex_waitv(NULL, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC);
if (res == EINVAL) {
ksft_test_result_fail("futex_waitv private returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv NULL address in *waiters\n");
}
}
TEST(invalid_clockid)
{
struct timespec to;
int res;
/* Testing an invalid clockid */
if (clock_gettime(CLOCK_MONOTONIC, &to))
ksft_exit_fail_msg("gettime64 failed\n");
to.tv_sec++;
res = futex_waitv(NULL, NR_FUTEXES, 0, &to, CLOCK_TAI);
if (res == EINVAL) {
ksft_test_result_fail("futex_waitv private returned: %d %s\n",
res ? errno : res,
res ? strerror(errno) : "");
} else {
ksft_test_result_pass("futex_waitv invalid clockid\n");
}
}
TEST_HARNESS_MAIN
@@ -0,0 +1,791 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2025 Igalia S.L.
*
* Robust list test by André Almeida <andrealmeid@igalia.com>
*
* The robust list uAPI allows userspace to create "robust" locks, in the sense
* that if the lock holder thread dies, the remaining threads that are waiting
* for the lock won't block forever, waiting for a lock that will never be
* released.
*
* This is achieve by userspace setting a list where a thread can enter all the
* locks (futexes) that it is holding. The robust list is a linked list, and
* userspace register the start of the list with the syscall set_robust_list().
* If such thread eventually dies, the kernel will walk this list, waking up one
* thread waiting for each futex and marking the futex word with the flag
* FUTEX_OWNER_DIED.
*
* See also
* man set_robust_list
* Documententation/locking/robust-futex-ABI.rst
* Documententation/locking/robust-futexes.rst
*/
#define _GNU_SOURCE
#include "futextest.h"
#include "../../kselftest_harness.h"
#include <dlfcn.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/wait.h>
#define STACK_SIZE (1024 * 1024)
#define FUTEX_TIMEOUT 3
#define SLEEP_US 100
#if __SIZEOF_LONG__ == 8
# define BUILD_64
#endif
static pthread_barrier_t barrier, barrier2;
static int set_robust_list(struct robust_list_head *head, size_t len)
{
return syscall(SYS_set_robust_list, head, len);
}
static int get_robust_list(int pid, struct robust_list_head **head, size_t *len_ptr)
{
return syscall(SYS_get_robust_list, pid, head, len_ptr);
}
static int sys_futex_robust_unlock(_Atomic(uint32_t) *uaddr, unsigned int op, int val,
void *list_op_pending, unsigned int val3)
{
return syscall(SYS_futex, uaddr, op, val, NULL, list_op_pending, val3, 0);
}
/*
* Basic lock struct, contains just the futex word and the robust list element
* Real implementations have also a *prev to easily walk in the list
*/
struct lock_struct {
_Atomic(unsigned int) futex;
struct robust_list list;
};
/*
* Helper function to spawn a child thread. Returns -1 on error, pid on success
*/
static int create_child(int (*fn)(void *arg), void *arg)
{
char *stack;
pid_t pid;
stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
if (stack == MAP_FAILED)
return -1;
stack += STACK_SIZE;
pid = clone(fn, stack, CLONE_VM | SIGCHLD, arg);
if (pid == -1)
return -1;
return pid;
}
/*
* Helper function to prepare and register a robust list
*/
static int set_list(struct robust_list_head *head)
{
int ret;
ret = set_robust_list(head, sizeof(*head));
if (ret)
return ret;
head->futex_offset = (size_t) offsetof(struct lock_struct, futex) -
(size_t) offsetof(struct lock_struct, list);
head->list.next = &head->list;
head->list_op_pending = NULL;
return 0;
}
/*
* A basic (and incomplete) mutex lock function with robustness
*/
static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, bool error_inject)
{
_Atomic(unsigned int) *futex = &lock->futex;
unsigned int zero = 0;
pid_t tid = gettid();
int ret = -1;
/*
* Set list_op_pending before starting the lock, so the kernel can catch
* the case where the thread died during the lock operation
*/
head->list_op_pending = &lock->list;
if (atomic_compare_exchange_strong(futex, &zero, tid)) {
/*
* We took the lock, insert it in the robust list
*/
struct robust_list *list = &head->list;
/* Error injection to test list_op_pending */
if (error_inject)
return 0;
while (list->next != &head->list)
list = list->next;
list->next = &lock->list;
lock->list.next = &head->list;
ret = 0;
} else {
/*
* We didn't take the lock, wait until the owner wakes (or dies)
*/
struct timespec to;
to.tv_sec = FUTEX_TIMEOUT;
to.tv_nsec = 0;
tid = atomic_load(futex);
/* Kernel ignores futexes without the waiters flag */
tid |= FUTEX_WAITERS;
atomic_store(futex, tid);
ret = futex_wait((futex_t *) futex, tid, &to, 0);
/*
* A real mutex_lock() implementation would loop here to finally
* take the lock. We don't care about that, so we stop here.
*/
}
head->list_op_pending = NULL;
return ret;
}
/*
* This child thread will succeed taking the lock, and then will exit holding it
*/
static int child_fn_lock(void *arg)
{
struct lock_struct *lock = arg;
struct robust_list_head head;
int ret;
ret = set_list(&head);
if (ret) {
ksft_test_result_fail("set_robust_list error\n");
return ret;
}
ret = mutex_lock(lock, &head, false);
if (ret) {
ksft_test_result_fail("mutex_lock error\n");
return ret;
}
pthread_barrier_wait(&barrier);
/*
* There's a race here: the parent thread needs to be inside
* futex_wait() before the child thread dies, otherwise it will miss the
* wakeup from handle_futex_death() that this child will emit. We wait a
* little bit just to make sure that this happens.
*/
usleep(SLEEP_US);
return 0;
}
/*
* Spawns a child thread that will set a robust list, take the lock, register it
* in the robust list and die. The parent thread will wait on this futex, and
* should be waken up when the child exits.
*/
TEST(test_robustness)
{
struct lock_struct lock = { .futex = 0 };
_Atomic(unsigned int) *futex = &lock.futex;
struct robust_list_head head;
int ret, pid, wstatus;
ret = set_list(&head);
ASSERT_EQ(ret, 0);
/*
* Lets use a barrier to ensure that the child thread takes the lock
* before the parent
*/
ret = pthread_barrier_init(&barrier, NULL, 2);
ASSERT_EQ(ret, 0);
pid = create_child(&child_fn_lock, &lock);
ASSERT_NE(pid, -1);
pthread_barrier_wait(&barrier);
ret = mutex_lock(&lock, &head, false);
/*
* futex_wait() should return 0 and the futex word should be marked with
* FUTEX_OWNER_DIED
*/
ASSERT_EQ(ret, 0);
ASSERT_TRUE(*futex & FUTEX_OWNER_DIED);
wait(&wstatus);
pthread_barrier_destroy(&barrier);
/* Pass only if the child hasn't return error */
if (!WEXITSTATUS(wstatus))
ksft_test_result_pass("%s\n", __func__);
}
/*
* The only valid value for len is sizeof(*head)
*/
TEST(test_set_robust_list_invalid_size)
{
struct robust_list_head head;
size_t head_size = sizeof(head);
int ret;
ret = set_robust_list(&head, head_size);
ASSERT_EQ(ret, 0);
ret = set_robust_list(&head, head_size * 2);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, EINVAL);
ret = set_robust_list(&head, head_size - 1);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, EINVAL);
ret = set_robust_list(&head, 0);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, EINVAL);
ksft_test_result_pass("%s\n", __func__);
}
/*
* Test get_robust_list with pid = 0, getting the list of the running thread
*/
TEST(test_get_robust_list_self)
{
struct robust_list_head head, head2, *get_head;
size_t head_size = sizeof(head), len_ptr;
int ret;
ret = set_robust_list(&head, head_size);
ASSERT_EQ(ret, 0);
ret = get_robust_list(0, &get_head, &len_ptr);
ASSERT_EQ(ret, 0);
ASSERT_EQ(get_head, &head);
ASSERT_EQ(head_size, len_ptr);
ret = set_robust_list(&head2, head_size);
ASSERT_EQ(ret, 0);
ret = get_robust_list(0, &get_head, &len_ptr);
ASSERT_EQ(ret, 0);
ASSERT_EQ(get_head, &head2);
ASSERT_EQ(head_size, len_ptr);
ksft_test_result_pass("%s\n", __func__);
}
static int child_list(void *arg)
{
struct robust_list_head *head = arg;
int ret;
ret = set_robust_list(head, sizeof(*head));
if (ret) {
ksft_test_result_fail("set_robust_list error\n");
return -1;
}
/*
* After setting the list head, wait until the main thread can call
* get_robust_list() for this thread before exiting.
*/
pthread_barrier_wait(&barrier);
pthread_barrier_wait(&barrier2);
return 0;
}
/*
* Test get_robust_list from another thread. We use two barriers here to ensure
* that:
* 1) the child thread set the list before we try to get it from the
* parent
* 2) the child thread still alive when we try to get the list from it
*/
TEST(test_get_robust_list_child)
{
struct robust_list_head head, *get_head;
int ret, wstatus;
size_t len_ptr;
pid_t tid;
ret = pthread_barrier_init(&barrier, NULL, 2);
ret = pthread_barrier_init(&barrier2, NULL, 2);
ASSERT_EQ(ret, 0);
tid = create_child(&child_list, &head);
ASSERT_NE(tid, -1);
pthread_barrier_wait(&barrier);
ret = get_robust_list(tid, &get_head, &len_ptr);
ASSERT_EQ(ret, 0);
ASSERT_EQ(&head, get_head);
pthread_barrier_wait(&barrier2);
wait(&wstatus);
pthread_barrier_destroy(&barrier);
pthread_barrier_destroy(&barrier2);
/* Pass only if the child hasn't return error */
if (!WEXITSTATUS(wstatus))
ksft_test_result_pass("%s\n", __func__);
}
static int child_fn_lock_with_error(void *arg)
{
struct lock_struct *lock = arg;
struct robust_list_head head;
int ret;
ret = set_list(&head);
if (ret) {
ksft_test_result_fail("set_robust_list error\n");
return -1;
}
ret = mutex_lock(lock, &head, true);
if (ret) {
ksft_test_result_fail("mutex_lock error\n");
return -1;
}
pthread_barrier_wait(&barrier);
/* See comment at child_fn_lock() */
usleep(SLEEP_US);
return 0;
}
/*
* Same as robustness test, but inject an error where the mutex_lock() exits
* earlier, just after setting list_op_pending and taking the lock, to test the
* list_op_pending mechanism
*/
TEST(test_set_list_op_pending)
{
struct lock_struct lock = { .futex = 0 };
_Atomic(unsigned int) *futex = &lock.futex;
struct robust_list_head head;
int ret, wstatus;
ret = set_list(&head);
ASSERT_EQ(ret, 0);
ret = pthread_barrier_init(&barrier, NULL, 2);
ASSERT_EQ(ret, 0);
ret = create_child(&child_fn_lock_with_error, &lock);
ASSERT_NE(ret, -1);
pthread_barrier_wait(&barrier);
ret = mutex_lock(&lock, &head, false);
ASSERT_EQ(ret, 0);
ASSERT_TRUE(*futex & FUTEX_OWNER_DIED);
wait(&wstatus);
pthread_barrier_destroy(&barrier);
/* Pass only if the child hasn't return error */
if (!WEXITSTATUS(wstatus))
ksft_test_result_pass("%s\n", __func__);
else
ksft_test_result_fail("%s\n", __func__);
}
#define CHILD_NR 10
static int child_lock_holder(void *arg)
{
struct lock_struct *locks = arg;
struct robust_list_head head;
int i;
set_list(&head);
for (i = 0; i < CHILD_NR; i++) {
locks[i].futex = 0;
mutex_lock(&locks[i], &head, false);
}
pthread_barrier_wait(&barrier);
pthread_barrier_wait(&barrier2);
/* See comment at child_fn_lock() */
usleep(SLEEP_US);
return 0;
}
static int child_wait_lock(void *arg)
{
struct lock_struct *lock = arg;
struct robust_list_head head;
int ret;
pthread_barrier_wait(&barrier2);
ret = mutex_lock(lock, &head, false);
if (ret) {
ksft_test_result_fail("mutex_lock error\n");
return -1;
}
if (!(lock->futex & FUTEX_OWNER_DIED)) {
ksft_test_result_fail("futex not marked with FUTEX_OWNER_DIED\n");
return -1;
}
return 0;
}
/*
* Test a robust list of more than one element. All the waiters should wake when
* the holder dies
*/
TEST(test_robust_list_multiple_elements)
{
struct lock_struct locks[CHILD_NR];
pid_t pids[CHILD_NR + 1];
int i, ret, wstatus;
ret = pthread_barrier_init(&barrier, NULL, 2);
ASSERT_EQ(ret, 0);
ret = pthread_barrier_init(&barrier2, NULL, CHILD_NR + 1);
ASSERT_EQ(ret, 0);
pids[0] = create_child(&child_lock_holder, &locks);
/* Wait until the locker thread takes the look */
pthread_barrier_wait(&barrier);
for (i = 0; i < CHILD_NR; i++)
pids[i+1] = create_child(&child_wait_lock, &locks[i]);
/* Wait for all children to return */
ret = 0;
for (i = 0; i < CHILD_NR; i++) {
waitpid(pids[i], &wstatus, 0);
if (WEXITSTATUS(wstatus))
ret = -1;
}
pthread_barrier_destroy(&barrier);
pthread_barrier_destroy(&barrier2);
/* Pass only if the child hasn't return error */
if (!ret)
ksft_test_result_pass("%s\n", __func__);
}
static int child_circular_list(void *arg)
{
static struct robust_list_head head;
struct lock_struct a, b, c;
int ret;
ret = set_list(&head);
if (ret) {
ksft_test_result_fail("set_list error\n");
return -1;
}
head.list.next = &a.list;
/*
* The last element should point to head list, but we short circuit it
*/
a.list.next = &b.list;
b.list.next = &c.list;
c.list.next = &a.list;
return 0;
}
/*
* Create a circular robust list. The kernel should be able to destroy the list
* while processing it so it won't be trapped in an infinite loop while handling
* a process exit
*/
TEST(test_circular_list)
{
int wstatus;
create_child(child_circular_list, NULL);
wait(&wstatus);
/* Pass only if the child hasn't return error */
if (!WEXITSTATUS(wstatus))
ksft_test_result_pass("%s\n", __func__);
}
/*
* Below are tests for the fix of robust release race condition. Please read the following
* thread to learn more about the issue in the first place and why the following functions fix it:
* https://lore.kernel.org/lkml/20260316162316.356674433@kernel.org/
*/
/*
* Auxiliary code for binding the vDSO functions
*/
static void *get_vdso_func_addr(const char *function)
{
const char *vdso_names[] = {
"linux-vdso.so.1", "linux-gate.so.1", "linux-vdso32.so.1", "linux-vdso64.so.1",
};
for (int i = 0; i < ARRAY_SIZE(vdso_names); i++) {
void *vdso = dlopen(vdso_names[i], RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (vdso)
return dlsym(vdso, function);
}
return NULL;
}
/*
* These are the real vDSO function signatures:
*
* __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop)
* __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop)
*
* So for the generic entry point we need to use a void pointer as the last argument
*/
FIXTURE(vdso_unlock)
{
uint32_t (*vdso)(_Atomic(uint32_t) *lock, uint32_t tid, void *pop);
};
FIXTURE_VARIANT(vdso_unlock)
{
bool is_32;
char func_name[];
};
FIXTURE_SETUP(vdso_unlock)
{
self->vdso = get_vdso_func_addr(variant->func_name);
}
FIXTURE_TEARDOWN(vdso_unlock) {}
FIXTURE_VARIANT_ADD(vdso_unlock, 32)
{
.func_name = "__vdso_futex_robust_list32_try_unlock",
.is_32 = true,
};
FIXTURE_VARIANT_ADD(vdso_unlock, 64)
{
.func_name = "__vdso_futex_robust_list64_try_unlock",
.is_32 = false,
};
/*
* Test the vDSO robust_listXX_try_unlock() for the uncontended case. The virtual syscall should
* return the thread ID of the lock owner, the lock word must be 0 and the list_op_pending should
* be NULL.
*/
TEST_F(vdso_unlock, test_robust_try_unlock_uncontended)
{
struct lock_struct lock = { .futex = 0 };
_Atomic(unsigned int) *futex = &lock.futex;
struct robust_list_head head;
uintptr_t exp = (uintptr_t) NULL;
pid_t tid = gettid();
int ret;
if (!self->vdso) {
ksft_test_result_skip("%s not found\n", variant->func_name);
return;
}
*futex = tid;
ret = set_list(&head);
if (ret)
ksft_test_result_fail("set_robust_list error\n");
head.list_op_pending = &lock.list;
ret = self->vdso(futex, tid, &head.list_op_pending);
ASSERT_EQ(ret, tid);
ASSERT_EQ(*futex, 0);
/* Check only the lower 32 bits for the 32-bit entry point */
if (variant->is_32) {
exp = (uintptr_t)(unsigned long)&lock.list;
exp &= ~0xFFFFFFFFULL;
}
ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp);
}
/*
* If the lock is contended, the operation fails. The return value is the value found at the
* futex word (tid | FUTEX_WAITERS), the futex word is not modified and the list_op_pending is_32
* not cleared.
*/
TEST_F(vdso_unlock, test_robust_try_unlock_contended)
{
struct lock_struct lock = { .futex = 0 };
_Atomic(unsigned int) *futex = &lock.futex;
struct robust_list_head head;
pid_t tid = gettid();
int ret;
if (!self->vdso) {
ksft_test_result_skip("%s not found\n", variant->func_name);
return;
}
*futex = tid | FUTEX_WAITERS;
ret = set_list(&head);
if (ret)
ksft_test_result_fail("set_robust_list error\n");
head.list_op_pending = &lock.list;
ret = self->vdso(futex, tid, &head.list_op_pending);
ASSERT_EQ(ret, tid | FUTEX_WAITERS);
ASSERT_EQ(*futex, tid | FUTEX_WAITERS);
ASSERT_EQ(head.list_op_pending, &lock.list);
}
FIXTURE(futex_op) {};
FIXTURE_VARIANT(futex_op)
{
unsigned int op;
unsigned int val3;
};
FIXTURE_SETUP(futex_op) {}
FIXTURE_TEARDOWN(futex_op) {}
FIXTURE_VARIANT_ADD(futex_op, wake)
{
.op = FUTEX_WAKE,
.val3 = 0,
};
FIXTURE_VARIANT_ADD(futex_op, wake_bitset)
{
.op = FUTEX_WAKE_BITSET,
.val3 = FUTEX_BITSET_MATCH_ANY,
};
FIXTURE_VARIANT_ADD(futex_op, unlock_pi)
{
.op = FUTEX_UNLOCK_PI,
.val3 = 0,
};
FIXTURE_VARIANT_ADD(futex_op, wake32)
{
.op = FUTEX_WAKE | FUTEX_ROBUST_LIST32,
.val3 = 0,
};
FIXTURE_VARIANT_ADD(futex_op, wake_bitset32)
{
.op = FUTEX_WAKE_BITSET | FUTEX_ROBUST_LIST32,
.val3 = FUTEX_BITSET_MATCH_ANY,
};
FIXTURE_VARIANT_ADD(futex_op, unlock_pi32)
{
.op = FUTEX_UNLOCK_PI | FUTEX_ROBUST_LIST32,
.val3 = 0,
};
/*
* The syscall should return the number of tasks waken (for this test, 0), clear the futex word and
* clear list_op_pending
*/
TEST_F(futex_op, test_futex_robust_unlock)
{
struct lock_struct lock = { .futex = 0 };
_Atomic(unsigned int) *futex = &lock.futex;
uintptr_t exp = (uintptr_t) NULL;
struct robust_list_head head;
pid_t tid = gettid();
int ret;
#ifndef BUILD_64
if (!(variant->op & FUTEX_ROBUST_LIST32)) {
ksft_test_result_skip("Not supported for 32 bit build\n");
return;
}
#endif
*futex = tid | FUTEX_WAITERS;
ret = set_list(&head);
if (ret)
ksft_test_result_fail("set_robust_list error\n");
head.list_op_pending = &lock.list;
ret = sys_futex_robust_unlock(futex, FUTEX_ROBUST_UNLOCK | variant->op, tid,
&head.list_op_pending, variant->val3);
ASSERT_EQ(ret, 0);
ASSERT_EQ(*futex, 0);
if (variant->op & FUTEX_ROBUST_LIST32) {
exp = (uint64_t)(unsigned long)&lock.list;
exp &= ~0xFFFFFFFFULL;
}
ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,53 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-or-later
###############################################################################
#
# Copyright © International Business Machines Corp., 2009
#
# DESCRIPTION
# Run tests in the current directory.
#
# AUTHOR
# Darren Hart <dvhart@linux.intel.com>
#
# HISTORY
# 2009-Nov-9: Initial version by Darren Hart <dvhart@linux.intel.com>
# 2010-Jan-6: Add futex_wait_uninitialized_heap and futex_wait_private_mapped_file
# by KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
#
###############################################################################
echo
./futex_requeue_pi
echo
./futex_requeue_pi_mismatched_ops
echo
./futex_requeue_pi_signal_restart
echo
./futex_wait_timeout
echo
./futex_wait_wouldblock
echo
./futex_wait_uninitialized_heap
./futex_wait_private_mapped_file
echo
./futex_wait
echo
./futex_requeue
echo
./futex_waitv
echo
./futex_priv_hash
echo
./futex_numa_mpol