Skip to content

Commit cda95fa

Browse files
Sebastian Andrzej SiewiorPeter Zijlstra
authored andcommitted
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 <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 8b4a5c2 commit cda95fa

File tree

4 files changed

+323
-2
lines changed

4 files changed

+323
-2
lines changed
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# SPDX-License-Identifier: GPL-2.0-only
2+
futex_priv_hash
3+
futex_requeue
24
futex_requeue_pi
35
futex_requeue_pi_mismatched_ops
46
futex_requeue_pi_signal_restart
7+
futex_wait
58
futex_wait_private_mapped_file
69
futex_wait_timeout
710
futex_wait_uninitialized_heap
811
futex_wait_wouldblock
9-
futex_wait
10-
futex_requeue
1112
futex_waitv

tools/testing/selftests/futex/functional/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ TEST_GEN_PROGS := \
1717
futex_wait_private_mapped_file \
1818
futex_wait \
1919
futex_requeue \
20+
futex_priv_hash \
2021
futex_waitv
2122

2223
TEST_PROGS := run.sh
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2025 Sebastian Andrzej Siewior <[email protected]>
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+
}

tools/testing/selftests/futex/functional/run.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,7 @@ echo
8282

8383
echo
8484
./futex_waitv $COLOR
85+
86+
echo
87+
./futex_priv_hash $COLOR
88+
./futex_priv_hash -g $COLOR

0 commit comments

Comments
 (0)