1156 lines · 27.0 KB · C
RawDownload
/* * 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 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #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#define BUCKET_LOCKS_PER_CPU	32UL union nested_table {	union nested_table __rcu *table;	struct rhash_head __rcu *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){	spinlock_t *lock = rht_bucket_lock(tbl, hash); 	return (debug_locks) ? lockdep_is_held(lock) : 1;}EXPORT_SYMBOL_GPL(lockdep_rht_bucket_is_held);#else#define ASSERT_RHT_MUTEX(HT)#endif  static int alloc_bucket_locks(struct rhashtable *ht, struct bucket_table *tbl,			      gfp_t gfp){	unsigned int i, size;#if defined(CONFIG_PROVE_LOCKING)	unsigned int nr_pcpus = 2;#else	unsigned int nr_pcpus = num_possible_cpus();#endif 	nr_pcpus = min_t(unsigned int, nr_pcpus, 64UL);	size = roundup_pow_of_two(nr_pcpus * ht->p.locks_mul); 	/* Never allocate more than 0.5 locks per bucket */	size = min_t(unsigned int, size, tbl->size >> 1); 	if (tbl->nest)		size = min(size, 1U << tbl->nest); 	if (sizeof(spinlock_t) != 0) {		if (gfpflags_allow_blocking(gfp))			tbl->locks = kvmalloc(size * sizeof(spinlock_t), gfp);		else			tbl->locks = kmalloc_array(size, sizeof(spinlock_t),						   gfp);		if (!tbl->locks)			return -ENOMEM;		for (i = 0; i < size; i++)			spin_lock_init(&tbl->locks[i]);	}	tbl->locks_mask = size - 1; 	return 0;} 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->locks);	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,					      unsigned int shifted,					      unsigned int nhash){	union nested_table *ntbl;	int i; 	ntbl = rcu_dereference(*prev);	if (ntbl)		return ntbl; 	ntbl = kzalloc(PAGE_SIZE, GFP_ATOMIC); 	if (ntbl && shifted) {		for (i = 0; i < PAGE_SIZE / sizeof(ntbl[0].bucket); i++)			INIT_RHT_NULLS_HEAD(ntbl[i].bucket, ht,					    (i << shifted) | nhash);	} 	rcu_assign_pointer(*prev, ntbl); 	return ntbl;} 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,				0, 0)) {		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; 	size = sizeof(*tbl) + nbuckets * sizeof(tbl->buckets[0]);	if (gfp != GFP_KERNEL)		tbl = kzalloc(size, gfp | __GFP_NOWARN | __GFP_NORETRY);	else		tbl = kvzalloc(size, gfp); 	size = nbuckets; 	if (tbl == NULL && gfp != GFP_KERNEL) {		tbl = nested_bucket_table_alloc(ht, nbuckets, gfp);		nbuckets = 0;	}	if (tbl == NULL)		return NULL; 	tbl->size = size; 	if (alloc_bucket_locks(ht, tbl, gfp) < 0) {		bucket_table_free(tbl);		return NULL;	} 	INIT_LIST_HEAD(&tbl->walkers); 	tbl->hash_rnd = get_random_u32(); 	for (i = 0; i < nbuckets; i++)		INIT_RHT_NULLS_HEAD(tbl->buckets[i], ht, 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, unsigned int old_hash){	struct bucket_table *old_tbl = rht_dereference(ht->tbl, ht);	struct bucket_table *new_tbl = rhashtable_last_table(ht,		rht_dereference_rcu(old_tbl->future_tbl, ht));	struct rhash_head __rcu **pprev = rht_bucket_var(old_tbl, old_hash);	int err = -EAGAIN;	struct rhash_head *head, *next, *entry;	spinlock_t *new_bucket_lock;	unsigned int new_hash; 	if (new_tbl->nest)		goto out; 	err = -ENOENT; 	rht_for_each(entry, 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); 	new_bucket_lock = rht_bucket_lock(new_tbl, new_hash); 	spin_lock_nested(new_bucket_lock, SINGLE_DEPTH_NESTING);	head = rht_dereference_bucket(new_tbl->buckets[new_hash],				      new_tbl, new_hash); 	RCU_INIT_POINTER(entry->next, head); 	rcu_assign_pointer(new_tbl->buckets[new_hash], entry);	spin_unlock(new_bucket_lock); 	rcu_assign_pointer(*pprev, 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);	spinlock_t *old_bucket_lock;	int err; 	old_bucket_lock = rht_bucket_lock(old_tbl, old_hash); 	spin_lock_bh(old_bucket_lock);	while (!(err = rhashtable_rehash_one(ht, old_hash)))		; 	if (err == -ENOENT) {		old_tbl->rehash++;		err = 0;	}	spin_unlock_bh(old_bucket_lock); 	return err;} static int rhashtable_rehash_attach(struct rhashtable *ht,				    struct bucket_table *old_tbl,				    struct bucket_table *new_tbl){	/* Protect future_tbl using the first bucket lock. */	spin_lock_bh(old_tbl->locks); 	/* Did somebody beat us to it? */	if (rcu_access_pointer(old_tbl->future_tbl)) {		spin_unlock_bh(old_tbl->locks);		return -EEXIST;	} 	/* Make insertions go into the new, empty table right away. Deletions	 * and lookups will be attempted in both tables until we synchronize.	 */	rcu_assign_pointer(old_tbl->future_tbl, new_tbl); 	spin_unlock_bh(old_tbl->locks); 	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;	} 	/* 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;	spin_unlock(&ht->lock); 	/* Wait for readers. All new readers will see the new	 * table, and thus no references to the old table will	 * remain.	 */	call_rcu(&old_tbl->rcu, bucket_table_free_rcu); 	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 = rhashtable_rehash_table(ht); 	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);	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;

Lines 1–500 of 1156.