Path: blob/master/tools/testing/selftests/futex/functional/futex_priv_hash.c
29271 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Copyright (C) 2025 Sebastian Andrzej Siewior <[email protected]>3*/45#define _GNU_SOURCE67#include <errno.h>8#include <pthread.h>9#include <stdio.h>10#include <stdlib.h>11#include <unistd.h>1213#include <linux/prctl.h>14#include <sys/prctl.h>1516#include "../../kselftest_harness.h"1718#define MAX_THREADS 641920static pthread_barrier_t barrier_main;21static pthread_mutex_t global_lock;22static pthread_t threads[MAX_THREADS];23static int counter;2425#ifndef PR_FUTEX_HASH26#define PR_FUTEX_HASH 7827# define PR_FUTEX_HASH_SET_SLOTS 128# define PR_FUTEX_HASH_GET_SLOTS 229#endif3031static int futex_hash_slots_set(unsigned int slots)32{33return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0);34}3536static int futex_hash_slots_get(void)37{38return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);39}4041static void futex_hash_slots_set_verify(int slots)42{43int ret;4445ret = futex_hash_slots_set(slots);46if (ret != 0) {47ksft_test_result_fail("Failed to set slots to %d: %m\n", slots);48ksft_finished();49}50ret = futex_hash_slots_get();51if (ret != slots) {52ksft_test_result_fail("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %m\n",53slots, ret);54ksft_finished();55}56ksft_test_result_pass("SET and GET slots %d passed\n", slots);57}5859static void futex_hash_slots_set_must_fail(int slots)60{61int ret;6263ret = futex_hash_slots_set(slots);64ksft_test_result(ret < 0, "futex_hash_slots_set(%d)\n",65slots);66}6768static void *thread_return_fn(void *arg)69{70return NULL;71}7273static void *thread_lock_fn(void *arg)74{75pthread_barrier_wait(&barrier_main);7677pthread_mutex_lock(&global_lock);78counter++;79usleep(20);80pthread_mutex_unlock(&global_lock);81return NULL;82}8384static void create_max_threads(void *(*thread_fn)(void *))85{86int i, ret;8788for (i = 0; i < MAX_THREADS; i++) {89ret = pthread_create(&threads[i], NULL, thread_fn, NULL);90if (ret)91ksft_exit_fail_msg("pthread_create failed: %m\n");92}93}9495static void join_max_threads(void)96{97int i, ret;9899for (i = 0; i < MAX_THREADS; i++) {100ret = pthread_join(threads[i], NULL);101if (ret)102ksft_exit_fail_msg("pthread_join failed for thread %d\n", i);103}104}105106#define SEC_IN_NSEC 1000000000107#define MSEC_IN_NSEC 1000000108109static void futex_dummy_op(void)110{111pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;112struct timespec timeout;113int ret;114115pthread_mutex_lock(&lock);116clock_gettime(CLOCK_REALTIME, &timeout);117timeout.tv_nsec += 100 * MSEC_IN_NSEC;118if (timeout.tv_nsec >= SEC_IN_NSEC) {119timeout.tv_nsec -= SEC_IN_NSEC;120timeout.tv_sec++;121}122ret = pthread_mutex_timedlock(&lock, &timeout);123if (ret == 0)124ksft_exit_fail_msg("Successfully locked an already locked mutex.\n");125126if (ret != ETIMEDOUT)127ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret);128}129130static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n";131static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n";132133TEST(priv_hash)134{135int futex_slots1, futex_slotsn, online_cpus;136pthread_mutexattr_t mutex_attr_pi;137int ret, retry = 20;138139ret = pthread_mutexattr_init(&mutex_attr_pi);140ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);141ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi);142if (ret != 0) {143ksft_exit_fail_msg("Failed to initialize pthread mutex.\n");144}145/* First thread, expect to be 0, not yet initialized */146ret = futex_hash_slots_get();147if (ret != 0)148ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret);149150ksft_test_result_pass("Basic get slots and immutable status.\n");151ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);152if (ret != 0)153ksft_exit_fail_msg("pthread_create() failed: %d, %m\n", ret);154155ret = pthread_join(threads[0], NULL);156if (ret != 0)157ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret);158159/* First thread, has to initialize private hash */160futex_slots1 = futex_hash_slots_get();161if (futex_slots1 <= 0) {162ksft_print_msg("Current hash buckets: %d\n", futex_slots1);163ksft_exit_fail_msg("%s", test_msg_auto_create);164}165166ksft_test_result_pass("%s", test_msg_auto_create);167168online_cpus = sysconf(_SC_NPROCESSORS_ONLN);169ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);170if (ret != 0)171ksft_exit_fail_msg("pthread_barrier_init failed: %m.\n");172173ret = pthread_mutex_lock(&global_lock);174if (ret != 0)175ksft_exit_fail_msg("pthread_mutex_lock failed: %m.\n");176177counter = 0;178create_max_threads(thread_lock_fn);179pthread_barrier_wait(&barrier_main);180181/*182* The current default size of hash buckets is 16. The auto increase183* works only if more than 16 CPUs are available.184*/185ksft_print_msg("Online CPUs: %d\n", online_cpus);186if (online_cpus > 16) {187retry_getslots:188futex_slotsn = futex_hash_slots_get();189if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {190retry--;191/*192* Auto scaling on thread creation can be slightly delayed193* because it waits for a RCU grace period twice. The new194* private hash is assigned upon the first futex operation195* after grace period.196* To cover all this for testing purposes the function197* below will acquire a lock and acquire it again with a198* 100ms timeout which must timeout. This ensures we199* sleep for 100ms and issue a futex operation.200*/201if (retry > 0) {202futex_dummy_op();203goto retry_getslots;204}205ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n",206futex_slots1, futex_slotsn);207ksft_exit_fail_msg("%s", test_msg_auto_inc);208}209ksft_test_result_pass("%s", test_msg_auto_inc);210} else {211ksft_test_result_skip("%s", test_msg_auto_inc);212}213ret = pthread_mutex_unlock(&global_lock);214215/* Once the user changes it, it has to be what is set */216futex_hash_slots_set_verify(2);217futex_hash_slots_set_verify(4);218futex_hash_slots_set_verify(8);219futex_hash_slots_set_verify(32);220futex_hash_slots_set_verify(16);221222ret = futex_hash_slots_set(15);223ksft_test_result(ret < 0, "Use 15 slots\n");224225futex_hash_slots_set_verify(2);226join_max_threads();227ksft_test_result(counter == MAX_THREADS, "Created and waited for %d of %d threads\n",228counter, MAX_THREADS);229counter = 0;230/* Once the user set something, auto resize must be disabled */231ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);232233create_max_threads(thread_lock_fn);234join_max_threads();235236ret = futex_hash_slots_get();237ksft_test_result(ret == 2, "No more auto-resize after manual setting, got %d\n",238ret);239240futex_hash_slots_set_must_fail(1 << 29);241futex_hash_slots_set_verify(4);242243/*244* Once the global hash has been requested, then this requested can not245* be undone.246*/247ret = futex_hash_slots_set(0);248ksft_test_result(ret == 0, "Global hash request\n");249if (ret != 0)250return;251252futex_hash_slots_set_must_fail(4);253futex_hash_slots_set_must_fail(8);254futex_hash_slots_set_must_fail(8);255futex_hash_slots_set_must_fail(0);256futex_hash_slots_set_must_fail(6);257258ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);259if (ret != 0)260ksft_exit_fail_msg("pthread_barrier_init failed: %m\n");261262create_max_threads(thread_lock_fn);263join_max_threads();264265ret = futex_hash_slots_get();266ksft_test_result(ret == 0, "Continue to use global hash\n");267}268269TEST_HARNESS_MAIN270271272