FILE
futex_priv_hash.c
File preview with syntax highlighting.
linux-6.19.14/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 "kselftest_harness.h" 18 19#define MAX_THREADS 64 20 21static pthread_barrier_t barrier_main; 22static pthread_mutex_t global_lock; 23static pthread_t threads[MAX_THREADS]; 24static 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#endif 31 32static int futex_hash_slots_set(unsigned int slots) 33{ 34 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0); 35} 36 37static int futex_hash_slots_get(void) 38{ 39 return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS); 40} 41 42static void futex_hash_slots_set_verify(int slots) 43{ 44 int ret; 45 46 ret = futex_hash_slots_set(slots); 47 if (ret != 0) { 48 ksft_test_result_fail("Failed to set slots to %d: %m\n", slots); 49 ksft_finished(); 50 } 51 ret = futex_hash_slots_get(); 52 if (ret != slots) { 53 ksft_test_result_fail("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %m\n", 54 slots, ret); 55 ksft_finished(); 56 } 57 ksft_test_result_pass("SET and GET slots %d passed\n", slots); 58} 59 60static void futex_hash_slots_set_must_fail(int slots) 61{ 62 int ret; 63 64 ret = futex_hash_slots_set(slots); 65 ksft_test_result(ret < 0, "futex_hash_slots_set(%d)\n", 66 slots); 67} 68 69static void *thread_return_fn(void *arg) 70{ 71 return NULL; 72} 73 74static void *thread_lock_fn(void *arg) 75{ 76 pthread_barrier_wait(&barrier_main); 77 78 pthread_mutex_lock(&global_lock); 79 counter++; 80 usleep(20); 81 pthread_mutex_unlock(&global_lock); 82 return NULL; 83} 84 85static void create_max_threads(void *(*thread_fn)(void *)) 86{ 87 int i, ret; 88 89 for (i = 0; i < MAX_THREADS; i++) { 90 ret = pthread_create(&threads[i], NULL, thread_fn, NULL); 91 if (ret) 92 ksft_exit_fail_msg("pthread_create failed: %m\n"); 93 } 94} 95 96static void join_max_threads(void) 97{ 98 int i, ret; 99 100 for (i = 0; i < MAX_THREADS; i++) { 101 ret = pthread_join(threads[i], NULL); 102 if (ret) 103 ksft_exit_fail_msg("pthread_join failed for thread %d\n", i); 104 } 105} 106 107#define SEC_IN_NSEC 1000000000 108#define MSEC_IN_NSEC 1000000 109 110static void futex_dummy_op(void) 111{ 112 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 113 struct timespec timeout; 114 int ret; 115 116 pthread_mutex_lock(&lock); 117 clock_gettime(CLOCK_REALTIME, &timeout); 118 timeout.tv_nsec += 100 * MSEC_IN_NSEC; 119 if (timeout.tv_nsec >= SEC_IN_NSEC) { 120 timeout.tv_nsec -= SEC_IN_NSEC; 121 timeout.tv_sec++; 122 } 123 ret = pthread_mutex_timedlock(&lock, &timeout); 124 if (ret == 0) 125 ksft_exit_fail_msg("Successfully locked an already locked mutex.\n"); 126 127 if (ret != ETIMEDOUT) 128 ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret); 129} 130 131static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n"; 132static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n"; 133 134TEST(priv_hash) 135{ 136 int futex_slots1, futex_slotsn, online_cpus; 137 pthread_mutexattr_t mutex_attr_pi; 138 int ret, retry = 20; 139 140 ret = pthread_mutexattr_init(&mutex_attr_pi); 141 ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT); 142 ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi); 143 if (ret != 0) { 144 ksft_exit_fail_msg("Failed to initialize pthread mutex.\n"); 145 } 146 /* First thread, expect to be 0, not yet initialized */ 147 ret = futex_hash_slots_get(); 148 if (ret != 0) 149 ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret); 150 151 ksft_test_result_pass("Basic get slots and immutable status.\n"); 152 ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL); 153 if (ret != 0) 154 ksft_exit_fail_msg("pthread_create() failed: %d, %m\n", ret); 155 156 ret = pthread_join(threads[0], NULL); 157 if (ret != 0) 158 ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret); 159 160 /* First thread, has to initialize private hash */ 161 futex_slots1 = futex_hash_slots_get(); 162 if (futex_slots1 <= 0) { 163 ksft_print_msg("Current hash buckets: %d\n", futex_slots1); 164 ksft_exit_fail_msg("%s", test_msg_auto_create); 165 } 166 167 ksft_test_result_pass("%s", test_msg_auto_create); 168 169 online_cpus = sysconf(_SC_NPROCESSORS_ONLN); 170 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); 171 if (ret != 0) 172 ksft_exit_fail_msg("pthread_barrier_init failed: %m.\n"); 173 174 ret = pthread_mutex_lock(&global_lock); 175 if (ret != 0) 176 ksft_exit_fail_msg("pthread_mutex_lock failed: %m.\n"); 177 178 counter = 0; 179 create_max_threads(thread_lock_fn); 180 pthread_barrier_wait(&barrier_main); 181 182 /* 183 * The current default size of hash buckets is 16. The auto increase 184 * works only if more than 16 CPUs are available. 185 */ 186 ksft_print_msg("Online CPUs: %d\n", online_cpus); 187 if (online_cpus > 16) { 188retry_getslots: 189 futex_slotsn = futex_hash_slots_get(); 190 if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) { 191 retry--; 192 /* 193 * Auto scaling on thread creation can be slightly delayed 194 * because it waits for a RCU grace period twice. The new 195 * private hash is assigned upon the first futex operation 196 * after grace period. 197 * To cover all this for testing purposes the function 198 * below will acquire a lock and acquire it again with a 199 * 100ms timeout which must timeout. This ensures we 200 * sleep for 100ms and issue a futex operation. 201 */ 202 if (retry > 0) { 203 futex_dummy_op(); 204 goto retry_getslots; 205 } 206 ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n", 207 futex_slots1, futex_slotsn); 208 ksft_exit_fail_msg("%s", test_msg_auto_inc); 209 } 210 ksft_test_result_pass("%s", test_msg_auto_inc); 211 } else { 212 ksft_test_result_skip("%s", test_msg_auto_inc); 213 } 214 ret = pthread_mutex_unlock(&global_lock); 215 216 /* Once the user changes it, it has to be what is set */ 217 futex_hash_slots_set_verify(2); 218 futex_hash_slots_set_verify(4); 219 futex_hash_slots_set_verify(8); 220 futex_hash_slots_set_verify(32); 221 futex_hash_slots_set_verify(16); 222 223 ret = futex_hash_slots_set(15); 224 ksft_test_result(ret < 0, "Use 15 slots\n"); 225 226 futex_hash_slots_set_verify(2); 227 join_max_threads(); 228 ksft_test_result(counter == MAX_THREADS, "Created and waited for %d of %d threads\n", 229 counter, MAX_THREADS); 230 counter = 0; 231 /* Once the user set something, auto resize must be disabled */ 232 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 233 234 create_max_threads(thread_lock_fn); 235 join_max_threads(); 236 237 ret = futex_hash_slots_get(); 238 ksft_test_result(ret == 2, "No more auto-resize after manual setting, got %d\n", 239 ret); 240 241 futex_hash_slots_set_must_fail(1 << 29); 242 futex_hash_slots_set_verify(4); 243 244 /* 245 * Once the global hash has been requested, then this requested can not 246 * be undone. 247 */ 248 ret = futex_hash_slots_set(0); 249 ksft_test_result(ret == 0, "Global hash request\n"); 250 if (ret != 0) 251 return; 252 253 futex_hash_slots_set_must_fail(4); 254 futex_hash_slots_set_must_fail(8); 255 futex_hash_slots_set_must_fail(8); 256 futex_hash_slots_set_must_fail(0); 257 futex_hash_slots_set_must_fail(6); 258 259 ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); 260 if (ret != 0) 261 ksft_exit_fail_msg("pthread_barrier_init failed: %m\n"); 262 263 create_max_threads(thread_lock_fn); 264 join_max_threads(); 265 266 ret = futex_hash_slots_get(); 267 ksft_test_result(ret == 0, "Continue to use global hash\n"); 268} 269 270TEST_HARNESS_MAIN
~/
Read-only file preview.