Merge tag 'locking-futex-2025-07-29' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull futex updates from Thomas Gleixner:

 - Switch the reference counting to a RCU based per-CPU reference to
   address a performance bottleneck vs the single instance rcuref
   variant

 - Make the futex selftest build on 32-bit architectures which only
   support 64-bit time_t, e.g. RISCV-32

 - Cleanups and improvements in selftests and futex bench

* tag 'locking-futex-2025-07-29' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  selftests/futex: Fix spelling mistake "Succeffuly" -> "Successfully"
  selftests/futex: Define SYS_futex on 32-bit architectures with 64-bit time_t
  perf bench futex: Remove support for IMMUTABLE
  selftests/futex: Remove support for IMMUTABLE
  futex: Remove support for IMMUTABLE
  futex: Make futex_private_hash_get() static
  futex: Use RCU-based per-CPU reference counting instead of rcuref_t
  selftests/futex: Adapt the private hash test to RCU related changes
This commit is contained in:
Linus Torvalds
2025-07-29 14:39:42 -07:00
19 changed files with 326 additions and 149 deletions

View File

@@ -85,18 +85,12 @@ int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)
#ifdef CONFIG_FUTEX_PRIVATE_HASH
int futex_hash_allocate_default(void);
void futex_hash_free(struct mm_struct *mm);
static inline void futex_mm_init(struct mm_struct *mm)
{
RCU_INIT_POINTER(mm->futex_phash, NULL);
mm->futex_phash_new = NULL;
mutex_init(&mm->futex_hash_lock);
}
int futex_mm_init(struct mm_struct *mm);
#else /* !CONFIG_FUTEX_PRIVATE_HASH */
static inline int futex_hash_allocate_default(void) { return 0; }
static inline void futex_hash_free(struct mm_struct *mm) { }
static inline void futex_mm_init(struct mm_struct *mm) { }
static inline int futex_hash_free(struct mm_struct *mm) { return 0; }
static inline int futex_mm_init(struct mm_struct *mm) { return 0; }
#endif /* CONFIG_FUTEX_PRIVATE_HASH */
#else /* !CONFIG_FUTEX */
@@ -118,8 +112,8 @@ static inline int futex_hash_allocate_default(void)
{
return 0;
}
static inline void futex_hash_free(struct mm_struct *mm) { }
static inline void futex_mm_init(struct mm_struct *mm) { }
static inline int futex_hash_free(struct mm_struct *mm) { return 0; }
static inline int futex_mm_init(struct mm_struct *mm) { return 0; }
#endif

View File

@@ -1070,6 +1070,11 @@ struct mm_struct {
struct mutex futex_hash_lock;
struct futex_private_hash __rcu *futex_phash;
struct futex_private_hash *futex_phash_new;
/* futex-ref */
unsigned long futex_batches;
struct rcu_head futex_rcu;
atomic_long_t futex_atomic;
unsigned int __percpu *futex_ref;
#endif
unsigned long hiwater_rss; /* High-watermark of RSS usage */

View File

@@ -140,7 +140,7 @@ static inline bool mmget_not_zero(struct mm_struct *mm)
/* mmput gets rid of the mappings and all user-space */
extern void mmput(struct mm_struct *);
#ifdef CONFIG_MMU
#if defined(CONFIG_MMU) || defined(CONFIG_FUTEX_PRIVATE_HASH)
/* same as above but performs the slow path from the async context. Can
* be called from the atomic context as well
*/

View File

@@ -367,8 +367,6 @@ struct prctl_mm_map {
/* FUTEX hash management */
#define PR_FUTEX_HASH 78
# define PR_FUTEX_HASH_SET_SLOTS 1
# define FH_FLAG_IMMUTABLE (1ULL << 0)
# define PR_FUTEX_HASH_GET_SLOTS 2
# define PR_FUTEX_HASH_GET_IMMUTABLE 3
#endif /* _LINUX_PRCTL_H */

View File

@@ -1716,13 +1716,9 @@ config FUTEX_PI
depends on FUTEX && RT_MUTEXES
default y
#
# marked broken for performance reasons; gives us one more cycle to sort things out.
#
config FUTEX_PRIVATE_HASH
bool
depends on FUTEX && !BASE_SMALL && MMU
depends on BROKEN
default y
config FUTEX_MPOL

View File

@@ -1046,7 +1046,6 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
RCU_INIT_POINTER(mm->exe_file, NULL);
mmu_notifier_subscriptions_init(mm);
init_tlb_flush_pending(mm);
futex_mm_init(mm);
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !defined(CONFIG_SPLIT_PMD_PTLOCKS)
mm->pmd_huge_pte = NULL;
#endif
@@ -1061,6 +1060,9 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
mm->def_flags = 0;
}
if (futex_mm_init(mm))
goto fail_mm_init;
if (mm_alloc_pgd(mm))
goto fail_nopgd;
@@ -1090,6 +1092,8 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
fail_noid:
mm_free_pgd(mm);
fail_nopgd:
futex_hash_free(mm);
fail_mm_init:
free_mm(mm);
return NULL;
}
@@ -1145,7 +1149,7 @@ void mmput(struct mm_struct *mm)
}
EXPORT_SYMBOL_GPL(mmput);
#ifdef CONFIG_MMU
#if defined(CONFIG_MMU) || defined(CONFIG_FUTEX_PRIVATE_HASH)
static void mmput_async_fn(struct work_struct *work)
{
struct mm_struct *mm = container_of(work, struct mm_struct,

View File

@@ -42,7 +42,6 @@
#include <linux/fault-inject.h>
#include <linux/slab.h>
#include <linux/prctl.h>
#include <linux/rcuref.h>
#include <linux/mempolicy.h>
#include <linux/mmap_lock.h>
@@ -65,12 +64,11 @@ static struct {
#define futex_queues (__futex_data.queues)
struct futex_private_hash {
rcuref_t users;
int state;
unsigned int hash_mask;
struct rcu_head rcu;
void *mm;
bool custom;
bool immutable;
struct futex_hash_bucket queues[];
};
@@ -129,6 +127,12 @@ static struct futex_hash_bucket *
__futex_hash(union futex_key *key, struct futex_private_hash *fph);
#ifdef CONFIG_FUTEX_PRIVATE_HASH
static bool futex_ref_get(struct futex_private_hash *fph);
static bool futex_ref_put(struct futex_private_hash *fph);
static bool futex_ref_is_dead(struct futex_private_hash *fph);
enum { FR_PERCPU = 0, FR_ATOMIC };
static inline bool futex_key_is_private(union futex_key *key)
{
/*
@@ -138,19 +142,14 @@ static inline bool futex_key_is_private(union futex_key *key)
return !(key->both.offset & (FUT_OFF_INODE | FUT_OFF_MMSHARED));
}
bool futex_private_hash_get(struct futex_private_hash *fph)
static bool futex_private_hash_get(struct futex_private_hash *fph)
{
if (fph->immutable)
return true;
return rcuref_get(&fph->users);
return futex_ref_get(fph);
}
void futex_private_hash_put(struct futex_private_hash *fph)
{
/* Ignore return value, last put is verified via rcuref_is_dead() */
if (fph->immutable)
return;
if (rcuref_put(&fph->users))
if (futex_ref_put(fph))
wake_up_var(fph->mm);
}
@@ -243,14 +242,18 @@ static bool __futex_pivot_hash(struct mm_struct *mm,
fph = rcu_dereference_protected(mm->futex_phash,
lockdep_is_held(&mm->futex_hash_lock));
if (fph) {
if (!rcuref_is_dead(&fph->users)) {
if (!futex_ref_is_dead(fph)) {
mm->futex_phash_new = new;
return false;
}
futex_rehash_private(fph, new);
}
rcu_assign_pointer(mm->futex_phash, new);
new->state = FR_PERCPU;
scoped_guard(rcu) {
mm->futex_batches = get_state_synchronize_rcu();
rcu_assign_pointer(mm->futex_phash, new);
}
kvfree_rcu(fph, rcu);
return true;
}
@@ -289,9 +292,7 @@ struct futex_private_hash *futex_private_hash(void)
if (!fph)
return NULL;
if (fph->immutable)
return fph;
if (rcuref_get(&fph->users))
if (futex_private_hash_get(fph))
return fph;
}
futex_pivot_hash(mm);
@@ -1524,19 +1525,221 @@ static void futex_hash_bucket_init(struct futex_hash_bucket *fhb,
}
#define FH_CUSTOM 0x01
#define FH_IMMUTABLE 0x02
#ifdef CONFIG_FUTEX_PRIVATE_HASH
/*
* futex-ref
*
* Heavily inspired by percpu-rwsem/percpu-refcount; not reusing any of that
* code because it just doesn't fit right.
*
* Dual counter, per-cpu / atomic approach like percpu-refcount, except it
* re-initializes the state automatically, such that the fph swizzle is also a
* transition back to per-cpu.
*/
static void futex_ref_rcu(struct rcu_head *head);
static void __futex_ref_atomic_begin(struct futex_private_hash *fph)
{
struct mm_struct *mm = fph->mm;
/*
* The counter we're about to switch to must have fully switched;
* otherwise it would be impossible for it to have reported success
* from futex_ref_is_dead().
*/
WARN_ON_ONCE(atomic_long_read(&mm->futex_atomic) != 0);
/*
* Set the atomic to the bias value such that futex_ref_{get,put}()
* will never observe 0. Will be fixed up in __futex_ref_atomic_end()
* when folding in the percpu count.
*/
atomic_long_set(&mm->futex_atomic, LONG_MAX);
smp_store_release(&fph->state, FR_ATOMIC);
call_rcu_hurry(&mm->futex_rcu, futex_ref_rcu);
}
static void __futex_ref_atomic_end(struct futex_private_hash *fph)
{
struct mm_struct *mm = fph->mm;
unsigned int count = 0;
long ret;
int cpu;
/*
* Per __futex_ref_atomic_begin() the state of the fph must be ATOMIC
* and per this RCU callback, everybody must now observe this state and
* use the atomic variable.
*/
WARN_ON_ONCE(fph->state != FR_ATOMIC);
/*
* Therefore the per-cpu counter is now stable, sum and reset.
*/
for_each_possible_cpu(cpu) {
unsigned int *ptr = per_cpu_ptr(mm->futex_ref, cpu);
count += *ptr;
*ptr = 0;
}
/*
* Re-init for the next cycle.
*/
this_cpu_inc(*mm->futex_ref); /* 0 -> 1 */
/*
* Add actual count, subtract bias and initial refcount.
*
* The moment this atomic operation happens, futex_ref_is_dead() can
* become true.
*/
ret = atomic_long_add_return(count - LONG_MAX - 1, &mm->futex_atomic);
if (!ret)
wake_up_var(mm);
WARN_ON_ONCE(ret < 0);
mmput_async(mm);
}
static void futex_ref_rcu(struct rcu_head *head)
{
struct mm_struct *mm = container_of(head, struct mm_struct, futex_rcu);
struct futex_private_hash *fph = rcu_dereference_raw(mm->futex_phash);
if (fph->state == FR_PERCPU) {
/*
* Per this extra grace-period, everybody must now observe
* fph as the current fph and no previously observed fph's
* are in-flight.
*
* Notably, nobody will now rely on the atomic
* futex_ref_is_dead() state anymore so we can begin the
* migration of the per-cpu counter into the atomic.
*/
__futex_ref_atomic_begin(fph);
return;
}
__futex_ref_atomic_end(fph);
}
/*
* Drop the initial refcount and transition to atomics.
*/
static void futex_ref_drop(struct futex_private_hash *fph)
{
struct mm_struct *mm = fph->mm;
/*
* Can only transition the current fph;
*/
WARN_ON_ONCE(rcu_dereference_raw(mm->futex_phash) != fph);
/*
* We enqueue at least one RCU callback. Ensure mm stays if the task
* exits before the transition is completed.
*/
mmget(mm);
/*
* In order to avoid the following scenario:
*
* futex_hash() __futex_pivot_hash()
* guard(rcu); guard(mm->futex_hash_lock);
* fph = mm->futex_phash;
* rcu_assign_pointer(&mm->futex_phash, new);
* futex_hash_allocate()
* futex_ref_drop()
* fph->state = FR_ATOMIC;
* atomic_set(, BIAS);
*
* futex_private_hash_get(fph); // OOPS
*
* Where an old fph (which is FR_ATOMIC) and should fail on
* inc_not_zero, will succeed because a new transition is started and
* the atomic is bias'ed away from 0.
*
* There must be at least one full grace-period between publishing a
* new fph and trying to replace it.
*/
if (poll_state_synchronize_rcu(mm->futex_batches)) {
/*
* There was a grace-period, we can begin now.
*/
__futex_ref_atomic_begin(fph);
return;
}
call_rcu_hurry(&mm->futex_rcu, futex_ref_rcu);
}
static bool futex_ref_get(struct futex_private_hash *fph)
{
struct mm_struct *mm = fph->mm;
guard(rcu)();
if (smp_load_acquire(&fph->state) == FR_PERCPU) {
this_cpu_inc(*mm->futex_ref);
return true;
}
return atomic_long_inc_not_zero(&mm->futex_atomic);
}
static bool futex_ref_put(struct futex_private_hash *fph)
{
struct mm_struct *mm = fph->mm;
guard(rcu)();
if (smp_load_acquire(&fph->state) == FR_PERCPU) {
this_cpu_dec(*mm->futex_ref);
return false;
}
return atomic_long_dec_and_test(&mm->futex_atomic);
}
static bool futex_ref_is_dead(struct futex_private_hash *fph)
{
struct mm_struct *mm = fph->mm;
guard(rcu)();
if (smp_load_acquire(&fph->state) == FR_PERCPU)
return false;
return atomic_long_read(&mm->futex_atomic) == 0;
}
int futex_mm_init(struct mm_struct *mm)
{
mutex_init(&mm->futex_hash_lock);
RCU_INIT_POINTER(mm->futex_phash, NULL);
mm->futex_phash_new = NULL;
/* futex-ref */
atomic_long_set(&mm->futex_atomic, 0);
mm->futex_batches = get_state_synchronize_rcu();
mm->futex_ref = alloc_percpu(unsigned int);
if (!mm->futex_ref)
return -ENOMEM;
this_cpu_inc(*mm->futex_ref); /* 0 -> 1 */
return 0;
}
void futex_hash_free(struct mm_struct *mm)
{
struct futex_private_hash *fph;
free_percpu(mm->futex_ref);
kvfree(mm->futex_phash_new);
fph = rcu_dereference_raw(mm->futex_phash);
if (fph) {
WARN_ON_ONCE(rcuref_read(&fph->users) > 1);
if (fph)
kvfree(fph);
}
}
static bool futex_pivot_pending(struct mm_struct *mm)
@@ -1549,7 +1752,7 @@ static bool futex_pivot_pending(struct mm_struct *mm)
return true;
fph = rcu_dereference(mm->futex_phash);
return rcuref_is_dead(&fph->users);
return futex_ref_is_dead(fph);
}
static bool futex_hash_less(struct futex_private_hash *a,
@@ -1591,21 +1794,20 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
*/
scoped_guard(rcu) {
fph = rcu_dereference(mm->futex_phash);
if (fph && (!fph->hash_mask || fph->immutable)) {
if (fph && !fph->hash_mask) {
if (custom)
return -EBUSY;
return 0;
}
}
fph = kvzalloc(struct_size(fph, queues, hash_slots), GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
fph = kvzalloc(struct_size(fph, queues, hash_slots),
GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
if (!fph)
return -ENOMEM;
rcuref_init(&fph->users, 1);
fph->hash_mask = hash_slots ? hash_slots - 1 : 0;
fph->custom = custom;
fph->immutable = !!(flags & FH_IMMUTABLE);
fph->mm = mm;
for (i = 0; i < hash_slots; i++)
@@ -1629,7 +1831,7 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
mm->futex_phash_new = NULL;
if (fph) {
if (cur && (!cur->hash_mask || cur->immutable)) {
if (cur && !cur->hash_mask) {
/*
* If two threads simultaneously request the global
* hash then the first one performs the switch,
@@ -1645,7 +1847,7 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
* allocated a replacement hash, drop the initial
* reference on the existing hash.
*/
futex_private_hash_put(cur);
futex_ref_drop(cur);
}
if (new) {
@@ -1722,19 +1924,6 @@ static int futex_hash_get_slots(void)
return 0;
}
static int futex_hash_get_immutable(void)
{
struct futex_private_hash *fph;
guard(rcu)();
fph = rcu_dereference(current->mm->futex_phash);
if (fph && fph->immutable)
return 1;
if (fph && !fph->hash_mask)
return 1;
return 0;
}
#else
static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags)
@@ -1747,10 +1936,6 @@ static int futex_hash_get_slots(void)
return 0;
}
static int futex_hash_get_immutable(void)
{
return 0;
}
#endif
int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)
@@ -1760,10 +1945,8 @@ int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)
switch (arg2) {
case PR_FUTEX_HASH_SET_SLOTS:
if (arg4 & ~FH_FLAG_IMMUTABLE)
if (arg4)
return -EINVAL;
if (arg4 & FH_FLAG_IMMUTABLE)
flags |= FH_IMMUTABLE;
ret = futex_hash_allocate(arg3, flags);
break;
@@ -1771,10 +1954,6 @@ int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4)
ret = futex_hash_get_slots();
break;
case PR_FUTEX_HASH_GET_IMMUTABLE:
ret = futex_hash_get_immutable();
break;
default:
ret = -EINVAL;
break;

View File

@@ -228,14 +228,12 @@ extern void futex_hash_get(struct futex_hash_bucket *hb);
extern void futex_hash_put(struct futex_hash_bucket *hb);
extern struct futex_private_hash *futex_private_hash(void);
extern bool futex_private_hash_get(struct futex_private_hash *fph);
extern void futex_private_hash_put(struct futex_private_hash *fph);
#else /* !CONFIG_FUTEX_PRIVATE_HASH */
static inline void futex_hash_get(struct futex_hash_bucket *hb) { }
static inline void futex_hash_put(struct futex_hash_bucket *hb) { }
static inline struct futex_private_hash *futex_private_hash(void) { return NULL; }
static inline bool futex_private_hash_get(void) { return false; }
static inline void futex_private_hash_put(struct futex_private_hash *fph) { }
#endif

View File

@@ -367,8 +367,6 @@ struct prctl_mm_map {
/* FUTEX hash management */
#define PR_FUTEX_HASH 78
# define PR_FUTEX_HASH_SET_SLOTS 1
# define FH_FLAG_IMMUTABLE (1ULL << 0)
# define PR_FUTEX_HASH_GET_SLOTS 2
# define PR_FUTEX_HASH_GET_IMMUTABLE 3
#endif /* _LINUX_PRCTL_H */

View File

@@ -56,7 +56,6 @@ static struct bench_futex_parameters params = {
static const struct option options[] = {
OPT_INTEGER( 'b', "buckets", &params.nbuckets, "Specify amount of hash buckets"),
OPT_BOOLEAN( 'I', "immutable", &params.buckets_immutable, "Make the hash buckets immutable"),
OPT_UINTEGER('t', "threads", &params.nthreads, "Specify amount of threads"),
OPT_UINTEGER('r', "runtime", &params.runtime, "Specify runtime (in seconds)"),
OPT_UINTEGER('f', "futexes", &params.nfutexes, "Specify amount of futexes per threads"),

View File

@@ -47,7 +47,6 @@ static struct bench_futex_parameters params = {
static const struct option options[] = {
OPT_INTEGER( 'b', "buckets", &params.nbuckets, "Specify amount of hash buckets"),
OPT_BOOLEAN( 'I', "immutable", &params.buckets_immutable, "Make the hash buckets immutable"),
OPT_UINTEGER('t', "threads", &params.nthreads, "Specify amount of threads"),
OPT_UINTEGER('r', "runtime", &params.runtime, "Specify runtime (in seconds)"),
OPT_BOOLEAN( 'M', "multi", &params.multi, "Use multiple futexes"),

View File

@@ -52,7 +52,6 @@ static struct bench_futex_parameters params = {
static const struct option options[] = {
OPT_INTEGER( 'b', "buckets", &params.nbuckets, "Specify amount of hash buckets"),
OPT_BOOLEAN( 'I', "immutable", &params.buckets_immutable, "Make the hash buckets immutable"),
OPT_UINTEGER('t', "threads", &params.nthreads, "Specify amount of threads"),
OPT_UINTEGER('q', "nrequeue", &params.nrequeue, "Specify amount of threads to requeue at once"),
OPT_BOOLEAN( 's', "silent", &params.silent, "Silent mode: do not display data/details"),

View File

@@ -63,7 +63,6 @@ static struct bench_futex_parameters params = {
static const struct option options[] = {
OPT_INTEGER( 'b', "buckets", &params.nbuckets, "Specify amount of hash buckets"),
OPT_BOOLEAN( 'I', "immutable", &params.buckets_immutable, "Make the hash buckets immutable"),
OPT_UINTEGER('t', "threads", &params.nthreads, "Specify amount of threads"),
OPT_UINTEGER('w', "nwakers", &params.nwakes, "Specify amount of waking threads"),
OPT_BOOLEAN( 's', "silent", &params.silent, "Silent mode: do not display data/details"),

View File

@@ -52,7 +52,6 @@ static struct bench_futex_parameters params = {
static const struct option options[] = {
OPT_INTEGER( 'b', "buckets", &params.nbuckets, "Specify amount of hash buckets"),
OPT_BOOLEAN( 'I', "immutable", &params.buckets_immutable, "Make the hash buckets immutable"),
OPT_UINTEGER('t', "threads", &params.nthreads, "Specify amount of threads"),
OPT_UINTEGER('w', "nwakes", &params.nwakes, "Specify amount of threads to wake at once"),
OPT_BOOLEAN( 's', "silent", &params.silent, "Silent mode: do not display data/details"),

View File

@@ -9,21 +9,17 @@
#ifndef PR_FUTEX_HASH
#define PR_FUTEX_HASH 78
# define PR_FUTEX_HASH_SET_SLOTS 1
# define FH_FLAG_IMMUTABLE (1ULL << 0)
# define PR_FUTEX_HASH_GET_SLOTS 2
# define PR_FUTEX_HASH_GET_IMMUTABLE 3
#endif // PR_FUTEX_HASH
void futex_set_nbuckets_param(struct bench_futex_parameters *params)
{
unsigned long flags;
int ret;
if (params->nbuckets < 0)
return;
flags = params->buckets_immutable ? FH_FLAG_IMMUTABLE : 0;
ret = prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, params->nbuckets, flags);
ret = prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, params->nbuckets, 0);
if (ret) {
printf("Requesting %d hash buckets failed: %d/%m\n",
params->nbuckets, ret);
@@ -47,18 +43,11 @@ void futex_print_nbuckets(struct bench_futex_parameters *params)
printf("Requested: %d in usage: %d\n", params->nbuckets, ret);
err(EXIT_FAILURE, "prctl(PR_FUTEX_HASH)");
}
if (params->nbuckets == 0) {
if (params->nbuckets == 0)
ret = asprintf(&futex_hash_mode, "Futex hashing: global hash");
} else {
ret = prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_IMMUTABLE);
if (ret < 0) {
printf("Can't check if the hash is immutable: %m\n");
err(EXIT_FAILURE, "prctl(PR_FUTEX_HASH)");
}
ret = asprintf(&futex_hash_mode, "Futex hashing: %d hash buckets %s",
params->nbuckets,
ret == 1 ? "(immutable)" : "");
}
else
ret = asprintf(&futex_hash_mode, "Futex hashing: %d hash buckets",
params->nbuckets);
} else {
if (ret <= 0) {
ret = asprintf(&futex_hash_mode, "Futex hashing: global hash");

View File

@@ -26,7 +26,6 @@ struct bench_futex_parameters {
unsigned int nwakes;
unsigned int nrequeue;
int nbuckets;
bool buckets_immutable;
};
/**

View File

@@ -367,8 +367,6 @@ struct prctl_mm_map {
/* FUTEX hash management */
#define PR_FUTEX_HASH 78
# define PR_FUTEX_HASH_SET_SLOTS 1
# define FH_FLAG_IMMUTABLE (1ULL << 0)
# define PR_FUTEX_HASH_GET_SLOTS 2
# define PR_FUTEX_HASH_GET_IMMUTABLE 3
#endif /* _LINUX_PRCTL_H */

View File

@@ -26,14 +26,12 @@ static int counter;
#ifndef PR_FUTEX_HASH
#define PR_FUTEX_HASH 78
# define PR_FUTEX_HASH_SET_SLOTS 1
# define FH_FLAG_IMMUTABLE (1ULL << 0)
# define PR_FUTEX_HASH_GET_SLOTS 2
# define PR_FUTEX_HASH_GET_IMMUTABLE 3
#endif
static int futex_hash_slots_set(unsigned int slots, int flags)
static int futex_hash_slots_set(unsigned int slots)
{
return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, flags);
return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0);
}
static int futex_hash_slots_get(void)
@@ -41,16 +39,11 @@ static int futex_hash_slots_get(void)
return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);
}
static int futex_hash_immutable_get(void)
{
return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_IMMUTABLE);
}
static void futex_hash_slots_set_verify(int slots)
{
int ret;
ret = futex_hash_slots_set(slots, 0);
ret = futex_hash_slots_set(slots);
if (ret != 0) {
ksft_test_result_fail("Failed to set slots to %d: %m\n", slots);
ksft_finished();
@@ -64,13 +57,13 @@ static void futex_hash_slots_set_verify(int slots)
ksft_test_result_pass("SET and GET slots %d passed\n", slots);
}
static void futex_hash_slots_set_must_fail(int slots, int flags)
static void futex_hash_slots_set_must_fail(int slots)
{
int ret;
ret = futex_hash_slots_set(slots, flags);
ksft_test_result(ret < 0, "futex_hash_slots_set(%d, %d)\n",
slots, flags);
ret = futex_hash_slots_set(slots);
ksft_test_result(ret < 0, "futex_hash_slots_set(%d)\n",
slots);
}
static void *thread_return_fn(void *arg)
@@ -111,6 +104,30 @@ static void join_max_threads(void)
}
}
#define SEC_IN_NSEC 1000000000
#define MSEC_IN_NSEC 1000000
static void futex_dummy_op(void)
{
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
struct timespec timeout;
int ret;
pthread_mutex_lock(&lock);
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_nsec += 100 * MSEC_IN_NSEC;
if (timeout.tv_nsec >= SEC_IN_NSEC) {
timeout.tv_nsec -= SEC_IN_NSEC;
timeout.tv_sec++;
}
ret = pthread_mutex_timedlock(&lock, &timeout);
if (ret == 0)
ksft_exit_fail_msg("Successfully locked an already locked mutex.\n");
if (ret != ETIMEDOUT)
ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret);
}
static void usage(char *prog)
{
printf("Usage: %s\n", prog);
@@ -128,18 +145,14 @@ int main(int argc, char *argv[])
{
int futex_slots1, futex_slotsn, online_cpus;
pthread_mutexattr_t mutex_attr_pi;
int use_global_hash = 0;
int ret;
int ret, retry = 20;
int c;
while ((c = getopt(argc, argv, "cghv:")) != -1) {
while ((c = getopt(argc, argv, "chv:")) != -1) {
switch (c) {
case 'c':
log_color(1);
break;
case 'g':
use_global_hash = 1;
break;
case 'h':
usage(basename(argv[0]));
exit(0);
@@ -154,7 +167,7 @@ int main(int argc, char *argv[])
}
ksft_print_header();
ksft_set_plan(22);
ksft_set_plan(21);
ret = pthread_mutexattr_init(&mutex_attr_pi);
ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
@@ -167,10 +180,6 @@ int main(int argc, char *argv[])
if (ret != 0)
ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret);
ret = futex_hash_immutable_get();
if (ret != 0)
ksft_exit_fail_msg("futex_hash_immutable_get() failed: %d, %m\n", ret);
ksft_test_result_pass("Basic get slots and immutable status.\n");
ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
if (ret != 0)
@@ -208,8 +217,24 @@ int main(int argc, char *argv[])
*/
ksft_print_msg("Online CPUs: %d\n", online_cpus);
if (online_cpus > 16) {
retry_getslots:
futex_slotsn = futex_hash_slots_get();
if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
retry--;
/*
* Auto scaling on thread creation can be slightly delayed
* because it waits for a RCU grace period twice. The new
* private hash is assigned upon the first futex operation
* after grace period.
* To cover all this for testing purposes the function
* below will acquire a lock and acquire it again with a
* 100ms timeout which must timeout. This ensures we
* sleep for 100ms and issue a futex operation.
*/
if (retry > 0) {
futex_dummy_op();
goto retry_getslots;
}
ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n",
futex_slots1, futex_slotsn);
ksft_exit_fail_msg(test_msg_auto_inc);
@@ -227,7 +252,7 @@ int main(int argc, char *argv[])
futex_hash_slots_set_verify(32);
futex_hash_slots_set_verify(16);
ret = futex_hash_slots_set(15, 0);
ret = futex_hash_slots_set(15);
ksft_test_result(ret < 0, "Use 15 slots\n");
futex_hash_slots_set_verify(2);
@@ -245,28 +270,23 @@ int main(int argc, char *argv[])
ksft_test_result(ret == 2, "No more auto-resize after manaul setting, got %d\n",
ret);
futex_hash_slots_set_must_fail(1 << 29, 0);
futex_hash_slots_set_must_fail(1 << 29);
futex_hash_slots_set_verify(4);
/*
* Once the private hash has been made immutable or global hash has been requested,
* then this requested can not be undone.
* Once the global hash has been requested, then this requested can not
* be undone.
*/
if (use_global_hash) {
ret = futex_hash_slots_set(0, 0);
ksft_test_result(ret == 0, "Global hash request\n");
} else {
ret = futex_hash_slots_set(4, FH_FLAG_IMMUTABLE);
ksft_test_result(ret == 0, "Immutable resize to 4\n");
}
ret = futex_hash_slots_set(0);
ksft_test_result(ret == 0, "Global hash request\n");
if (ret != 0)
goto out;
futex_hash_slots_set_must_fail(4, 0);
futex_hash_slots_set_must_fail(4, FH_FLAG_IMMUTABLE);
futex_hash_slots_set_must_fail(8, 0);
futex_hash_slots_set_must_fail(8, FH_FLAG_IMMUTABLE);
futex_hash_slots_set_must_fail(0, FH_FLAG_IMMUTABLE);
futex_hash_slots_set_must_fail(6, FH_FLAG_IMMUTABLE);
futex_hash_slots_set_must_fail(4);
futex_hash_slots_set_must_fail(8);
futex_hash_slots_set_must_fail(8);
futex_hash_slots_set_must_fail(0);
futex_hash_slots_set_must_fail(6);
ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
if (ret != 0) {
@@ -277,14 +297,7 @@ int main(int argc, char *argv[])
join_max_threads();
ret = futex_hash_slots_get();
if (use_global_hash) {
ksft_test_result(ret == 0, "Continue to use global hash\n");
} else {
ksft_test_result(ret == 4, "Continue to use the 4 hash buckets\n");
}
ret = futex_hash_immutable_get();
ksft_test_result(ret == 1, "Hash reports to be immutable\n");
ksft_test_result(ret == 0, "Continue to use global hash\n");
out:
ksft_finished();

View File

@@ -47,6 +47,17 @@ typedef volatile u_int32_t futex_t;
FUTEX_PRIVATE_FLAG)
#endif
/*
* SYS_futex is expected from system C library, in glibc some 32-bit
* architectures (e.g. RV32) are using 64-bit time_t, therefore it doesn't have
* SYS_futex defined but just SYS_futex_time64. Define SYS_futex as
* SYS_futex_time64 in this situation to ensure the compilation and the
* compatibility.
*/
#if !defined(SYS_futex) && defined(SYS_futex_time64)
#define SYS_futex SYS_futex_time64
#endif
/**
* futex() - SYS_futex syscall wrapper
* @uaddr: address of first futex