1235 lines · 29.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Resizable, Scalable, Concurrent Hash Table * * Copyright (c) 2015 Herbert Xu <herbert@gondor.apana.org.au> * Copyright (c) 2014-2015 Thomas Graf <tgraf@suug.ch> * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net> * * Code partially derived from nft_hash * Rewritten with rehash code from br_multicast plus single list * pointer as suggested by Josh Triplett */ #include <linux/atomic.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/log2.h>#include <linux/sched.h>#include <linux/rculist.h>#include <linux/slab.h>#include <linux/vmalloc.h>#include <linux/mm.h>#include <linux/jhash.h>#include <linux/random.h>#include <linux/rhashtable.h>#include <linux/err.h>#include <linux/export.h> #define HASH_DEFAULT_SIZE	64UL#define HASH_MIN_SIZE		4U union nested_table {	union nested_table __rcu *table;	struct rhash_lock_head *bucket;}; static u32 head_hashfn(struct rhashtable *ht,		       const struct bucket_table *tbl,		       const struct rhash_head *he){	return rht_head_hashfn(ht, tbl, he, ht->p);} #ifdef CONFIG_PROVE_LOCKING#define ASSERT_RHT_MUTEX(HT) BUG_ON(!lockdep_rht_mutex_is_held(HT)) int lockdep_rht_mutex_is_held(struct rhashtable *ht){	return (debug_locks) ? lockdep_is_held(&ht->mutex) : 1;}EXPORT_SYMBOL_GPL(lockdep_rht_mutex_is_held); int lockdep_rht_bucket_is_held(const struct bucket_table *tbl, u32 hash){	if (!debug_locks)		return 1;	if (unlikely(tbl->nest))		return 1;	return bit_spin_is_locked(0, (unsigned long *)&tbl->buckets[hash]);}EXPORT_SYMBOL_GPL(lockdep_rht_bucket_is_held);#else#define ASSERT_RHT_MUTEX(HT)#endif static void nested_table_free(union nested_table *ntbl, unsigned int size){	const unsigned int shift = PAGE_SHIFT - ilog2(sizeof(void *));	const unsigned int len = 1 << shift;	unsigned int i; 	ntbl = rcu_dereference_raw(ntbl->table);	if (!ntbl)		return; 	if (size > len) {		size >>= shift;		for (i = 0; i < len; i++)			nested_table_free(ntbl + i, size);	} 	kfree(ntbl);} static void nested_bucket_table_free(const struct bucket_table *tbl){	unsigned int size = tbl->size >> tbl->nest;	unsigned int len = 1 << tbl->nest;	union nested_table *ntbl;	unsigned int i; 	ntbl = (union nested_table *)rcu_dereference_raw(tbl->buckets[0]); 	for (i = 0; i < len; i++)		nested_table_free(ntbl + i, size); 	kfree(ntbl);} static void bucket_table_free(const struct bucket_table *tbl){	if (tbl->nest)		nested_bucket_table_free(tbl); 	kvfree(tbl);} static void bucket_table_free_rcu(struct rcu_head *head){	bucket_table_free(container_of(head, struct bucket_table, rcu));} static union nested_table *nested_table_alloc(struct rhashtable *ht,					      union nested_table __rcu **prev,					      bool leaf){	union nested_table *ntbl;	int i; 	ntbl = rcu_dereference(*prev);	if (ntbl)		return ntbl; 	ntbl = kzalloc(PAGE_SIZE, GFP_ATOMIC); 	if (ntbl && leaf) {		for (i = 0; i < PAGE_SIZE / sizeof(ntbl[0]); i++)			INIT_RHT_NULLS_HEAD(ntbl[i].bucket);	} 	if (cmpxchg((union nested_table **)prev, NULL, ntbl) == NULL)		return ntbl;	/* Raced with another thread. */	kfree(ntbl);	return rcu_dereference(*prev);} static struct bucket_table *nested_bucket_table_alloc(struct rhashtable *ht,						      size_t nbuckets,						      gfp_t gfp){	const unsigned int shift = PAGE_SHIFT - ilog2(sizeof(void *));	struct bucket_table *tbl;	size_t size; 	if (nbuckets < (1 << (shift + 1)))		return NULL; 	size = sizeof(*tbl) + sizeof(tbl->buckets[0]); 	tbl = kzalloc(size, gfp);	if (!tbl)		return NULL; 	if (!nested_table_alloc(ht, (union nested_table __rcu **)tbl->buckets,				false)) {		kfree(tbl);		return NULL;	} 	tbl->nest = (ilog2(nbuckets) - 1) % shift + 1; 	return tbl;} static struct bucket_table *bucket_table_alloc(struct rhashtable *ht,					       size_t nbuckets,					       gfp_t gfp){	struct bucket_table *tbl = NULL;	size_t size;	int i;	static struct lock_class_key __key; 	tbl = kvzalloc(struct_size(tbl, buckets, nbuckets), gfp); 	size = nbuckets; 	if (tbl == NULL && (gfp & ~__GFP_NOFAIL) != GFP_KERNEL) {		tbl = nested_bucket_table_alloc(ht, nbuckets, gfp);		nbuckets = 0;	} 	if (tbl == NULL)		return NULL; 	lockdep_init_map(&tbl->dep_map, "rhashtable_bucket", &__key, 0); 	tbl->size = size; 	rcu_head_init(&tbl->rcu);	INIT_LIST_HEAD(&tbl->walkers); 	tbl->hash_rnd = get_random_u32(); 	for (i = 0; i < nbuckets; i++)		INIT_RHT_NULLS_HEAD(tbl->buckets[i]); 	return tbl;} static struct bucket_table *rhashtable_last_table(struct rhashtable *ht,						  struct bucket_table *tbl){	struct bucket_table *new_tbl; 	do {		new_tbl = tbl;		tbl = rht_dereference_rcu(tbl->future_tbl, ht);	} while (tbl); 	return new_tbl;} static int rhashtable_rehash_one(struct rhashtable *ht,				 struct rhash_lock_head **bkt,				 unsigned int old_hash){	struct bucket_table *old_tbl = rht_dereference(ht->tbl, ht);	struct bucket_table *new_tbl = rhashtable_last_table(ht, old_tbl);	int err = -EAGAIN;	struct rhash_head *head, *next, *entry;	struct rhash_head __rcu **pprev = NULL;	unsigned int new_hash; 	if (new_tbl->nest)		goto out; 	err = -ENOENT; 	rht_for_each_from(entry, rht_ptr(bkt, old_tbl, old_hash),			  old_tbl, old_hash) {		err = 0;		next = rht_dereference_bucket(entry->next, old_tbl, old_hash); 		if (rht_is_a_nulls(next))			break; 		pprev = &entry->next;	} 	if (err)		goto out; 	new_hash = head_hashfn(ht, new_tbl, entry); 	rht_lock_nested(new_tbl, &new_tbl->buckets[new_hash], SINGLE_DEPTH_NESTING); 	head = rht_ptr(new_tbl->buckets + new_hash, new_tbl, new_hash); 	RCU_INIT_POINTER(entry->next, head); 	rht_assign_unlock(new_tbl, &new_tbl->buckets[new_hash], entry); 	if (pprev)		rcu_assign_pointer(*pprev, next);	else		/* Need to preserved the bit lock. */		rht_assign_locked(bkt, next); out:	return err;} static int rhashtable_rehash_chain(struct rhashtable *ht,				    unsigned int old_hash){	struct bucket_table *old_tbl = rht_dereference(ht->tbl, ht);	struct rhash_lock_head **bkt = rht_bucket_var(old_tbl, old_hash);	int err; 	if (!bkt)		return 0;	rht_lock(old_tbl, bkt); 	while (!(err = rhashtable_rehash_one(ht, bkt, old_hash)))		; 	if (err == -ENOENT)		err = 0;	rht_unlock(old_tbl, bkt); 	return err;} static int rhashtable_rehash_attach(struct rhashtable *ht,				    struct bucket_table *old_tbl,				    struct bucket_table *new_tbl){	/* Make insertions go into the new, empty table right away. Deletions	 * and lookups will be attempted in both tables until we synchronize.	 * As cmpxchg() provides strong barriers, we do not need	 * rcu_assign_pointer().	 */ 	if (cmpxchg((struct bucket_table **)&old_tbl->future_tbl, NULL,		    new_tbl) != NULL)		return -EEXIST; 	return 0;} static int rhashtable_rehash_table(struct rhashtable *ht){	struct bucket_table *old_tbl = rht_dereference(ht->tbl, ht);	struct bucket_table *new_tbl;	struct rhashtable_walker *walker;	unsigned int old_hash;	int err; 	new_tbl = rht_dereference(old_tbl->future_tbl, ht);	if (!new_tbl)		return 0; 	for (old_hash = 0; old_hash < old_tbl->size; old_hash++) {		err = rhashtable_rehash_chain(ht, old_hash);		if (err)			return err;		cond_resched();	} 	/* Publish the new table pointer. */	rcu_assign_pointer(ht->tbl, new_tbl); 	spin_lock(&ht->lock);	list_for_each_entry(walker, &old_tbl->walkers, list)		walker->tbl = NULL; 	/* Wait for readers. All new readers will see the new	 * table, and thus no references to the old table will	 * remain.	 * We do this inside the locked region so that	 * rhashtable_walk_stop() can use rcu_head_after_call_rcu()	 * to check if it should not re-link the table.	 */	call_rcu(&old_tbl->rcu, bucket_table_free_rcu);	spin_unlock(&ht->lock); 	return rht_dereference(new_tbl->future_tbl, ht) ? -EAGAIN : 0;} static int rhashtable_rehash_alloc(struct rhashtable *ht,				   struct bucket_table *old_tbl,				   unsigned int size){	struct bucket_table *new_tbl;	int err; 	ASSERT_RHT_MUTEX(ht); 	new_tbl = bucket_table_alloc(ht, size, GFP_KERNEL);	if (new_tbl == NULL)		return -ENOMEM; 	err = rhashtable_rehash_attach(ht, old_tbl, new_tbl);	if (err)		bucket_table_free(new_tbl); 	return err;} /** * rhashtable_shrink - Shrink hash table while allowing concurrent lookups * @ht:		the hash table to shrink * * This function shrinks the hash table to fit, i.e., the smallest * size would not cause it to expand right away automatically. * * The caller must ensure that no concurrent resizing occurs by holding * ht->mutex. * * The caller must ensure that no concurrent table mutations take place. * It is however valid to have concurrent lookups if they are RCU protected. * * It is valid to have concurrent insertions and deletions protected by per * bucket locks or concurrent RCU protected lookups and traversals. */static int rhashtable_shrink(struct rhashtable *ht){	struct bucket_table *old_tbl = rht_dereference(ht->tbl, ht);	unsigned int nelems = atomic_read(&ht->nelems);	unsigned int size = 0; 	if (nelems)		size = roundup_pow_of_two(nelems * 3 / 2);	if (size < ht->p.min_size)		size = ht->p.min_size; 	if (old_tbl->size <= size)		return 0; 	if (rht_dereference(old_tbl->future_tbl, ht))		return -EEXIST; 	return rhashtable_rehash_alloc(ht, old_tbl, size);} static void rht_deferred_worker(struct work_struct *work){	struct rhashtable *ht;	struct bucket_table *tbl;	int err = 0; 	ht = container_of(work, struct rhashtable, run_work);	mutex_lock(&ht->mutex); 	tbl = rht_dereference(ht->tbl, ht);	tbl = rhashtable_last_table(ht, tbl); 	if (rht_grow_above_75(ht, tbl))		err = rhashtable_rehash_alloc(ht, tbl, tbl->size * 2);	else if (ht->p.automatic_shrinking && rht_shrink_below_30(ht, tbl))		err = rhashtable_shrink(ht);	else if (tbl->nest)		err = rhashtable_rehash_alloc(ht, tbl, tbl->size); 	if (!err || err == -EEXIST) {		int nerr; 		nerr = rhashtable_rehash_table(ht);		err = err ?: nerr;	} 	mutex_unlock(&ht->mutex); 	if (err)		schedule_work(&ht->run_work);} static int rhashtable_insert_rehash(struct rhashtable *ht,				    struct bucket_table *tbl){	struct bucket_table *old_tbl;	struct bucket_table *new_tbl;	unsigned int size;	int err; 	old_tbl = rht_dereference_rcu(ht->tbl, ht); 	size = tbl->size; 	err = -EBUSY; 	if (rht_grow_above_75(ht, tbl))		size *= 2;	/* Do not schedule more than one rehash */	else if (old_tbl != tbl)		goto fail; 	err = -ENOMEM; 	new_tbl = bucket_table_alloc(ht, size, GFP_ATOMIC | __GFP_NOWARN);	if (new_tbl == NULL)		goto fail; 	err = rhashtable_rehash_attach(ht, tbl, new_tbl);	if (err) {		bucket_table_free(new_tbl);		if (err == -EEXIST)			err = 0;	} else		schedule_work(&ht->run_work); 	return err; fail:	/* Do not fail the insert if someone else did a rehash. */	if (likely(rcu_access_pointer(tbl->future_tbl)))		return 0; 	/* Schedule async rehash to retry allocation in process context. */	if (err == -ENOMEM)		schedule_work(&ht->run_work); 	return err;} static void *rhashtable_lookup_one(struct rhashtable *ht,				   struct rhash_lock_head **bkt,				   struct bucket_table *tbl, unsigned int hash,				   const void *key, struct rhash_head *obj){	struct rhashtable_compare_arg arg = {		.ht = ht,		.key = key,	};	struct rhash_head __rcu **pprev = NULL;	struct rhash_head *head;	int elasticity; 	elasticity = RHT_ELASTICITY;	rht_for_each_from(head, rht_ptr(bkt, tbl, hash), tbl, hash) {		struct rhlist_head *list;		struct rhlist_head *plist; 		elasticity--;		if (!key ||		    (ht->p.obj_cmpfn ?		     ht->p.obj_cmpfn(&arg, rht_obj(ht, head)) :		     rhashtable_compare(&arg, rht_obj(ht, head)))) {

Lines 1–500 of 1235.