selftests/futex: Add futex_numa_mpol
authorSebastian Andrzej Siewior <bigeasy@linutronix.de>
Wed, 16 Apr 2025 16:29:21 +0000 (18:29 +0200)
committerPeter Zijlstra <peterz@infradead.org>
Sat, 3 May 2025 10:02:10 +0000 (12:02 +0200)
Test the basic functionality for the NUMA and MPOL flags:
- FUTEX2_NUMA should take the NUMA node which is after the uaddr
  and use it.
- Only update the node if FUTEX_NO_NODE was set by the user
- FUTEX2_MPOL should use the memory based on the policy. I attempted to
  set the node with mbind() and then use this with MPOL but this fails
  and futex falls back to the default node for the current CPU.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/r/20250416162921.513656-22-bigeasy@linutronix.de
tools/testing/selftests/futex/functional/.gitignore
tools/testing/selftests/futex/functional/Makefile
tools/testing/selftests/futex/functional/futex_numa_mpol.c [new file with mode: 0644]
tools/testing/selftests/futex/functional/run.sh
tools/testing/selftests/futex/include/futex2test.h

index d37ae7c6e879ea3ae13e91f06e69e7818a54b2ac..7b24ae89594a9db211d4b8469ebcef8d1f7012d8 100644 (file)
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
+futex_numa_mpol
 futex_priv_hash
 futex_requeue
 futex_requeue_pi
index 67d9e16d8a1f888c6c272c5e4c01904dbc2a14a0..a4881fd2cd540336f93ad76f566f2105413008f8 100644 (file)
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 INCLUDES := -I../include -I../../ $(KHDR_INCLUDES)
 CFLAGS := $(CFLAGS) -g -O2 -Wall -pthread $(INCLUDES) $(KHDR_INCLUDES)
-LDLIBS := -lpthread -lrt
+LDLIBS := -lpthread -lrt -lnuma
 
 LOCAL_HDRS := \
        ../include/futextest.h \
@@ -18,6 +18,7 @@ TEST_GEN_PROGS := \
        futex_wait \
        futex_requeue \
        futex_priv_hash \
+       futex_numa_mpol \
        futex_waitv
 
 TEST_PROGS := run.sh
diff --git a/tools/testing/selftests/futex/functional/futex_numa_mpol.c b/tools/testing/selftests/futex/functional/futex_numa_mpol.c
new file mode 100644 (file)
index 0000000..dd70532
--- /dev/null
@@ -0,0 +1,232 @@
+// 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 <numa.h>
+#include <numaif.h>
+
+#include <linux/futex.h>
+#include <sys/mman.h>
+
+#include "logging.h"
+#include "futextest.h"
+#include "futex2test.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) {
+                       error("pthread_create failed\n", errno);
+                       exit(1);
+               }
+       }
+}
+
+static void join_max_threads(void)
+{
+       int i, ret;
+
+       for (i = 0; i < MAX_THREADS; i++) {
+               ret = pthread_join(threads[i], NULL);
+               if (ret) {
+                       error("pthread_join failed for thread %d\n", errno, i);
+                       exit(1);
+               }
+       }
+}
+
+static void __test_futex(void *futex_ptr, int must_fail, 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 (must_fail) {
+                       if (ret < 0)
+                               break;
+                       fail("Should fail, but didn't\n");
+                       exit(1);
+               }
+               if (ret < 0) {
+                       error("Failed futex2_wake(%d)\n", errno, to_wake);
+                       exit(1);
+               }
+               if (!ret)
+                       usleep(50);
+               to_wake -= ret;
+
+       } while (to_wake);
+       join_max_threads();
+
+       for (i = 0; i < MAX_THREADS; i++) {
+               if (must_fail && thread_args[i].result != -1) {
+                       fail("Thread %d should fail but succeeded (%d)\n", i, thread_args[i].result);
+                       need_exit = 1;
+               }
+               if (!must_fail && thread_args[i].result != 0) {
+                       fail("Thread %d failed (%d)\n", i, thread_args[i].result);
+                       need_exit = 1;
+               }
+       }
+       if (need_exit)
+               exit(1);
+}
+
+static void test_futex(void *futex_ptr, int must_fail)
+{
+       __test_futex(futex_ptr, must_fail, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA);
+}
+
+static void test_futex_mpol(void *futex_ptr, int must_fail)
+{
+       __test_futex(futex_ptr, must_fail, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA | FUTEX2_MPOL);
+}
+
+static void usage(char *prog)
+{
+       printf("Usage: %s\n", prog);
+       printf("  -c    Use color\n");
+       printf("  -h    Display this help message\n");
+       printf("  -v L  Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
+              VQUIET, VCRITICAL, VINFO);
+}
+
+int main(int argc, char *argv[])
+{
+       struct futex32_numa *futex_numa;
+       int mem_size, i;
+       void *futex_ptr;
+       char c;
+
+       while ((c = getopt(argc, argv, "chv:")) != -1) {
+               switch (c) {
+               case 'c':
+                       log_color(1);
+                       break;
+               case 'h':
+                       usage(basename(argv[0]));
+                       exit(0);
+                       break;
+               case 'v':
+                       log_verbosity(atoi(optarg));
+                       break;
+               default:
+                       usage(basename(argv[0]));
+                       exit(1);
+               }
+       }
+
+       mem_size = sysconf(_SC_PAGE_SIZE);
+       futex_ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+       if (futex_ptr == MAP_FAILED) {
+               error("mmap() for %d bytes failed\n", errno, mem_size);
+               return 1;
+       }
+       futex_numa = futex_ptr;
+
+       info("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) {
+               fail("NUMA node is left unitiliazed\n");
+               return 1;
+       }
+
+       info("Memory too small\n");
+       test_futex(futex_ptr + mem_size - 4, 1);
+
+       info("Memory out of range\n");
+       test_futex(futex_ptr + mem_size, 1);
+
+       futex_numa->numa = FUTEX_NO_NODE;
+       mprotect(futex_ptr, mem_size, PROT_READ);
+       info("Memory, RO\n");
+       test_futex(futex_ptr, 1);
+
+       mprotect(futex_ptr, mem_size, PROT_NONE);
+       info("Memory, no access\n");
+       test_futex(futex_ptr, 1);
+
+       mprotect(futex_ptr, mem_size, PROT_READ | PROT_WRITE);
+       info("Memory back to RW\n");
+       test_futex(futex_ptr, 0);
+
+       /* MPOL test. Does not work as expected */
+       for (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) {
+                       info("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)
+                               error("Failed to wake 0 with MPOL.\n", errno);
+                       if (0)
+                               test_futex_mpol(futex_numa, 0);
+                       if (futex_numa->numa != i) {
+                               fail("Returned NUMA node is %d expected %d\n",
+                                    futex_numa->numa, i);
+                       }
+               }
+       }
+       return 0;
+}
index f0f0d2b683d7e5cdecd39920489f7c2219a73e06..81739849f2994d52c336df00f1072db71b35573a 100755 (executable)
@@ -86,3 +86,6 @@ echo
 echo
 ./futex_priv_hash $COLOR
 ./futex_priv_hash -g $COLOR
+
+echo
+./futex_numa_mpol $COLOR
index 9ee3592477a430bf8262db342a23a10c20cf5e2d..ea79662405bc5af1d1a622a34b791376bdbffd27 100644 (file)
@@ -18,14 +18,43 @@ struct futex_waitv {
 };
 #endif
 
+#ifndef __NR_futex_wake
+#define __NR_futex_wake 454
+#endif
+
+#ifndef __NR_futex_wait
+#define __NR_futex_wait 455
+#endif
+
 #ifndef FUTEX2_SIZE_U32
 #define FUTEX2_SIZE_U32 0x02
 #endif
 
+#ifndef FUTEX2_NUMA
+#define FUTEX2_NUMA 0x04
+#endif
+
+#ifndef FUTEX2_MPOL
+#define FUTEX2_MPOL 0x08
+#endif
+
+#ifndef FUTEX2_PRIVATE
+#define FUTEX2_PRIVATE FUTEX_PRIVATE_FLAG
+#endif
+
+#ifndef FUTEX2_NO_NODE
+#define FUTEX_NO_NODE (-1)
+#endif
+
 #ifndef FUTEX_32
 #define FUTEX_32 FUTEX2_SIZE_U32
 #endif
 
+struct futex32_numa {
+       futex_t futex;
+       futex_t numa;
+};
+
 /**
  * futex_waitv - Wait at multiple futexes, wake on any
  * @waiters:    Array of waiters
@@ -38,3 +67,26 @@ static inline int futex_waitv(volatile struct futex_waitv *waiters, unsigned lon
 {
        return syscall(__NR_futex_waitv, waiters, nr_waiters, flags, timo, clockid);
 }
+
+/*
+ * futex_wait() - block on uaddr with optional timeout
+ * @val:       Expected value
+ * @flags:     FUTEX2 flags
+ * @timeout:   Relative timeout
+ * @clockid:   Clock id for the timeout
+ */
+static inline int futex2_wait(void *uaddr, long val, unsigned int flags,
+                             struct timespec *timeout, clockid_t clockid)
+{
+       return syscall(__NR_futex_wait, uaddr, val, ~0U, flags, timeout, clockid);
+}
+
+/*
+ * futex2_wake() - Wake a number of futexes
+ * @nr:                Number of threads to wake at most
+ * @flags:     FUTEX2 flags
+ */
+static inline int futex2_wake(void *uaddr, int nr, unsigned int flags)
+{
+       return syscall(__NR_futex_wake, uaddr, ~0U, nr, flags);
+}