Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/futex: Add futex_priv_hash

Test the basic functionality of the private hash:
- Upon start, with no threads there is no private hash.
- The first thread initializes the private hash.
- More than four threads will increase the size of the private hash if
the system has more than 16 CPUs online.
- Once the user sets the size of private hash, auto scaling is disabled.
- The user is only allowed to use numbers to the power of two.
- The user may request the global or make the hash immutable.
- Once the global hash has been set or the hash has been made immutable,
further changes are not allowed.
- Futex operations should work the whole time. It must be possible to
hold a lock, such a PI initialised mutex, during the resize operation.

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-21-bigeasy@linutronix.de

authored by

Sebastian Andrzej Siewior and committed by
Peter Zijlstra
cda95fae 8b4a5c24

+323 -2
+3 -2
tools/testing/selftests/futex/functional/.gitignore
··· 1 1 # SPDX-License-Identifier: GPL-2.0-only 2 + futex_priv_hash 3 + futex_requeue 2 4 futex_requeue_pi 3 5 futex_requeue_pi_mismatched_ops 4 6 futex_requeue_pi_signal_restart 7 + futex_wait 5 8 futex_wait_private_mapped_file 6 9 futex_wait_timeout 7 10 futex_wait_uninitialized_heap 8 11 futex_wait_wouldblock 9 - futex_wait 10 - futex_requeue 11 12 futex_waitv
+1
tools/testing/selftests/futex/functional/Makefile
··· 17 17 futex_wait_private_mapped_file \ 18 18 futex_wait \ 19 19 futex_requeue \ 20 + futex_priv_hash \ 20 21 futex_waitv 21 22 22 23 TEST_PROGS := run.sh
+315
tools/testing/selftests/futex/functional/futex_priv_hash.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de> 4 + */ 5 + 6 + #define _GNU_SOURCE 7 + 8 + #include <errno.h> 9 + #include <pthread.h> 10 + #include <stdio.h> 11 + #include <stdlib.h> 12 + #include <unistd.h> 13 + 14 + #include <linux/prctl.h> 15 + #include <sys/prctl.h> 16 + 17 + #include "logging.h" 18 + 19 + #define MAX_THREADS 64 20 + 21 + static pthread_barrier_t barrier_main; 22 + static pthread_mutex_t global_lock; 23 + static pthread_t threads[MAX_THREADS]; 24 + static int counter; 25 + 26 + #ifndef PR_FUTEX_HASH 27 + #define PR_FUTEX_HASH 78 28 + # define PR_FUTEX_HASH_SET_SLOTS 1 29 + # define PR_FUTEX_HASH_GET_SLOTS 2 30 + # define PR_FUTEX_HASH_GET_IMMUTABLE 3 31 + #endif 32 + 33 + static int futex_hash_slots_set(unsigned int slots, int immutable) 34 + { 35 + return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, immutable); 36 + } 37 + 38 + static int futex_hash_slots_get(void) 39 + { 40 + return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS); 41 + } 42 + 43 + static int futex_hash_immutable_get(void) 44 + { 45 + return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_IMMUTABLE); 46 + } 47 + 48 + static void futex_hash_slots_set_verify(int slots) 49 + { 50 + int ret; 51 + 52 + ret = futex_hash_slots_set(slots, 0); 53 + if (ret != 0) { 54 + error("Failed to set slots to %d\n", errno, slots); 55 + exit(1); 56 + } 57 + ret = futex_hash_slots_get(); 58 + if (ret != slots) { 59 + error("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d\n", 60 + errno, slots, ret); 61 + exit(1); 62 + } 63 + } 64 + 65 + static void futex_hash_slots_set_must_fail(int slots, int immutable) 66 + { 67 + int ret; 68 + 69 + ret = futex_hash_slots_set(slots, immutable); 70 + if (ret < 0) 71 + return; 72 + 73 + fail("futex_hash_slots_set(%d, %d) expected to fail but succeeded.\n", 74 + slots, immutable); 75 + exit(1); 76 + } 77 + 78 + static void *thread_return_fn(void *arg) 79 + { 80 + return NULL; 81 + } 82 + 83 + static void *thread_lock_fn(void *arg) 84 + { 85 + pthread_barrier_wait(&barrier_main); 86 + 87 + pthread_mutex_lock(&global_lock); 88 + counter++; 89 + usleep(20); 90 + pthread_mutex_unlock(&global_lock); 91 + return NULL; 92 + } 93 + 94 + static void create_max_threads(void *(*thread_fn)(void *)) 95 + { 96 + int i, ret; 97 + 98 + for (i = 0; i < MAX_THREADS; i++) { 99 + ret = pthread_create(&threads[i], NULL, thread_fn, NULL); 100 + if (ret) { 101 + error("pthread_create failed\n", errno); 102 + exit(1); 103 + } 104 + } 105 + } 106 + 107 + static void join_max_threads(void) 108 + { 109 + int i, ret; 110 + 111 + for (i = 0; i < MAX_THREADS; i++) { 112 + ret = pthread_join(threads[i], NULL); 113 + if (ret) { 114 + error("pthread_join failed for thread %d\n", errno, i); 115 + exit(1); 116 + } 117 + } 118 + } 119 + 120 + static void usage(char *prog) 121 + { 122 + printf("Usage: %s\n", prog); 123 + printf(" -c Use color\n"); 124 + printf(" -g Test global hash instead intead local immutable \n"); 125 + printf(" -h Display this help message\n"); 126 + printf(" -v L Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n", 127 + VQUIET, VCRITICAL, VINFO); 128 + } 129 + 130 + int main(int argc, char *argv[]) 131 + { 132 + int futex_slots1, futex_slotsn, online_cpus; 133 + pthread_mutexattr_t mutex_attr_pi; 134 + int use_global_hash = 0; 135 + int ret; 136 + char c; 137 + 138 + while ((c = getopt(argc, argv, "cghv:")) != -1) { 139 + switch (c) { 140 + case 'c': 141 + log_color(1); 142 + break; 143 + case 'g': 144 + use_global_hash = 1; 145 + break; 146 + case 'h': 147 + usage(basename(argv[0])); 148 + exit(0); 149 + break; 150 + case 'v': 151 + log_verbosity(atoi(optarg)); 152 + break; 153 + default: 154 + usage(basename(argv[0])); 155 + exit(1); 156 + } 157 + } 158 + 159 + 160 + ret = pthread_mutexattr_init(&mutex_attr_pi); 161 + ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT); 162 + ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi); 163 + if (ret != 0) { 164 + fail("Failed to initialize pthread mutex.\n"); 165 + return 1; 166 + } 167 + 168 + /* First thread, expect to be 0, not yet initialized */ 169 + ret = futex_hash_slots_get(); 170 + if (ret != 0) { 171 + error("futex_hash_slots_get() failed: %d\n", errno, ret); 172 + return 1; 173 + } 174 + ret = futex_hash_immutable_get(); 175 + if (ret != 0) { 176 + error("futex_hash_immutable_get() failed: %d\n", errno, ret); 177 + return 1; 178 + } 179 + 180 + ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL); 181 + if (ret != 0) { 182 + error("pthread_create() failed: %d\n", errno, ret); 183 + return 1; 184 + } 185 + ret = pthread_join(threads[0], NULL); 186 + if (ret != 0) { 187 + error("pthread_join() failed: %d\n", errno, ret); 188 + return 1; 189 + } 190 + /* First thread, has to initialiaze private hash */ 191 + futex_slots1 = futex_hash_slots_get(); 192 + if (futex_slots1 <= 0) { 193 + fail("Expected > 0 hash buckets, got: %d\n", futex_slots1); 194 + return 1; 195 + } 196 + 197 + online_cpus = sysconf(_SC_NPROCESSORS_ONLN); 198 + ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); 199 + if (ret != 0) { 200 + error("pthread_barrier_init failed.\n", errno); 201 + return 1; 202 + } 203 + 204 + ret = pthread_mutex_lock(&global_lock); 205 + if (ret != 0) { 206 + error("pthread_mutex_lock failed.\n", errno); 207 + return 1; 208 + } 209 + 210 + counter = 0; 211 + create_max_threads(thread_lock_fn); 212 + pthread_barrier_wait(&barrier_main); 213 + 214 + /* 215 + * The current default size of hash buckets is 16. The auto increase 216 + * works only if more than 16 CPUs are available. 217 + */ 218 + if (online_cpus > 16) { 219 + futex_slotsn = futex_hash_slots_get(); 220 + if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) { 221 + fail("Expected increase of hash buckets but got: %d -> %d\n", 222 + futex_slots1, futex_slotsn); 223 + info("Online CPUs: %d\n", online_cpus); 224 + return 1; 225 + } 226 + } 227 + ret = pthread_mutex_unlock(&global_lock); 228 + 229 + /* Once the user changes it, it has to be what is set */ 230 + futex_hash_slots_set_verify(2); 231 + futex_hash_slots_set_verify(4); 232 + futex_hash_slots_set_verify(8); 233 + futex_hash_slots_set_verify(32); 234 + futex_hash_slots_set_verify(16); 235 + 236 + ret = futex_hash_slots_set(15, 0); 237 + if (ret >= 0) { 238 + fail("Expected to fail with 15 slots but succeeded: %d.\n", ret); 239 + return 1; 240 + } 241 + futex_hash_slots_set_verify(2); 242 + join_max_threads(); 243 + if (counter != MAX_THREADS) { 244 + fail("Expected thread counter at %d but is %d\n", 245 + MAX_THREADS, counter); 246 + return 1; 247 + } 248 + counter = 0; 249 + /* Once the user set something, auto reisze must be disabled */ 250 + ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 251 + 252 + create_max_threads(thread_lock_fn); 253 + join_max_threads(); 254 + 255 + ret = futex_hash_slots_get(); 256 + if (ret != 2) { 257 + printf("Expected 2 slots, no auto-resize, got %d\n", ret); 258 + return 1; 259 + } 260 + 261 + futex_hash_slots_set_must_fail(1 << 29, 0); 262 + 263 + /* 264 + * Once the private hash has been made immutable or global hash has been requested, 265 + * then this requested can not be undone. 266 + */ 267 + if (use_global_hash) { 268 + ret = futex_hash_slots_set(0, 0); 269 + if (ret != 0) { 270 + printf("Can't request global hash: %m\n"); 271 + return 1; 272 + } 273 + } else { 274 + ret = futex_hash_slots_set(4, 1); 275 + if (ret != 0) { 276 + printf("Immutable resize to 4 failed: %m\n"); 277 + return 1; 278 + } 279 + } 280 + 281 + futex_hash_slots_set_must_fail(4, 0); 282 + futex_hash_slots_set_must_fail(4, 1); 283 + futex_hash_slots_set_must_fail(8, 0); 284 + futex_hash_slots_set_must_fail(8, 1); 285 + futex_hash_slots_set_must_fail(0, 1); 286 + futex_hash_slots_set_must_fail(6, 1); 287 + 288 + ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 289 + if (ret != 0) { 290 + error("pthread_barrier_init failed.\n", errno); 291 + return 1; 292 + } 293 + create_max_threads(thread_lock_fn); 294 + join_max_threads(); 295 + 296 + ret = futex_hash_slots_get(); 297 + if (use_global_hash) { 298 + if (ret != 0) { 299 + error("Expected global hash, got %d\n", errno, ret); 300 + return 1; 301 + } 302 + } else { 303 + if (ret != 4) { 304 + error("Expected 4 slots, no auto-resize, got %d\n", errno, ret); 305 + return 1; 306 + } 307 + } 308 + 309 + ret = futex_hash_immutable_get(); 310 + if (ret != 1) { 311 + fail("Expected immutable private hash, got %d\n", ret); 312 + return 1; 313 + } 314 + return 0; 315 + }
+4
tools/testing/selftests/futex/functional/run.sh
··· 82 82 83 83 echo 84 84 ./futex_waitv $COLOR 85 + 86 + echo 87 + ./futex_priv_hash $COLOR 88 + ./futex_priv_hash -g $COLOR