/* * Resizable, Scalable, Concurrent Hash Table * * Copyright (c) 2014-2015 Thomas Graf <tgraf@suug.ch> * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net> * * Based on the following paper: * https://www.usenix.org/legacy/event/atc11/tech/final_files/Triplett.pdf * * Code partially derived from nft_hash * * 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/kernel.h>#include <linux/init.h>#include <linux/log2.h>#include <linux/sched.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> #define HASH_DEFAULT_SIZE 64UL#define HASH_MIN_SIZE 4UL#define BUCKET_LOCKS_PER_CPU 128UL /* Base bits plus 1 bit for nulls marker */#define HASH_RESERVED_SPACE (RHT_BASE_BITS + 1) enum { RHT_LOCK_NORMAL, RHT_LOCK_NESTED,}; /* The bucket lock is selected based on the hash and protects mutations * on a group of hash buckets. * * A maximum of tbl->size/2 bucket locks is allocated. This ensures that * a single lock always covers both buckets which may both contains * entries which link to the same bucket of the old table during resizing. * This allows to simplify the locking as locking the bucket in both * tables during resize always guarantee protection. * * IMPORTANT: When holding the bucket lock of both the old and new table * during expansions and shrinking, the old bucket lock must always be * acquired first. */static spinlock_t *bucket_lock(const struct bucket_table *tbl, u32 hash){ return &tbl->locks[hash & tbl->locks_mask];} static void *rht_obj(const struct rhashtable *ht, const struct rhash_head *he){ return (void *) he - ht->p.head_offset;} static u32 rht_bucket_index(const struct bucket_table *tbl, u32 hash){ return hash & (tbl->size - 1);} static u32 obj_raw_hashfn(const struct rhashtable *ht, const void *ptr){ u32 hash; if (unlikely(!ht->p.key_len)) hash = ht->p.obj_hashfn(ptr, ht->p.hash_rnd); else hash = ht->p.hashfn(ptr + ht->p.key_offset, ht->p.key_len, ht->p.hash_rnd); return hash >> HASH_RESERVED_SPACE;} static u32 key_hashfn(struct rhashtable *ht, const void *key, u32 len){ return ht->p.hashfn(key, len, ht->p.hash_rnd) >> HASH_RESERVED_SPACE;} static u32 head_hashfn(const struct rhashtable *ht, const struct bucket_table *tbl, const struct rhash_head *he){ return rht_bucket_index(tbl, obj_raw_hashfn(ht, rht_obj(ht, he)));} #ifdef CONFIG_PROVE_LOCKINGstatic void debug_dump_buckets(const struct rhashtable *ht, const struct bucket_table *tbl){ struct rhash_head *he; unsigned int i, hash; for (i = 0; i < tbl->size; i++) { pr_warn(" [Bucket %d] ", i); rht_for_each_rcu(he, tbl, i) { hash = head_hashfn(ht, tbl, he); pr_cont("[hash = %#x, lock = %p] ", hash, bucket_lock(tbl, hash)); } pr_cont("\n"); } } static void debug_dump_table(struct rhashtable *ht, const struct bucket_table *tbl, unsigned int hash){ struct bucket_table *old_tbl, *future_tbl; pr_emerg("BUG: lock for hash %#x in table %p not held\n", hash, tbl); rcu_read_lock(); future_tbl = rht_dereference_rcu(ht->future_tbl, ht); old_tbl = rht_dereference_rcu(ht->tbl, ht); if (future_tbl != old_tbl) { pr_warn("Future table %p (size: %zd)\n", future_tbl, future_tbl->size); debug_dump_buckets(ht, future_tbl); } pr_warn("Table %p (size: %zd)\n", old_tbl, old_tbl->size); debug_dump_buckets(ht, old_tbl); rcu_read_unlock();} #define ASSERT_RHT_MUTEX(HT) BUG_ON(!lockdep_rht_mutex_is_held(HT))#define ASSERT_BUCKET_LOCK(HT, TBL, HASH) \ do { \ if (unlikely(!lockdep_rht_bucket_is_held(TBL, HASH))) { \ debug_dump_table(HT, TBL, HASH); \ BUG(); \ } \ } while (0) 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 = 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)#define ASSERT_BUCKET_LOCK(HT, TBL, HASH)#endif static struct rhash_head __rcu **bucket_tail(struct bucket_table *tbl, u32 n){ struct rhash_head __rcu **pprev; for (pprev = &tbl->buckets[n]; !rht_is_a_nulls(rht_dereference_bucket(*pprev, tbl, n)); pprev = &rht_dereference_bucket(*pprev, tbl, n)->next) ; return pprev;} static int alloc_bucket_locks(struct rhashtable *ht, struct bucket_table *tbl){ 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, 32UL); 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 (sizeof(spinlock_t) != 0) {#ifdef CONFIG_NUMA if (size * sizeof(spinlock_t) > PAGE_SIZE) tbl->locks = vmalloc(size * sizeof(spinlock_t)); else#endif tbl->locks = kmalloc_array(size, sizeof(spinlock_t), GFP_KERNEL); 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 bucket_table_free(const struct bucket_table *tbl){ if (tbl) kvfree(tbl->locks); kvfree(tbl);} static struct bucket_table *bucket_table_alloc(struct rhashtable *ht, size_t nbuckets){ struct bucket_table *tbl = NULL; size_t size; int i; size = sizeof(*tbl) + nbuckets * sizeof(tbl->buckets[0]); if (size <= (PAGE_SIZE << PAGE_ALLOC_COSTLY_ORDER)) tbl = kzalloc(size, GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY); if (tbl == NULL) tbl = vzalloc(size); if (tbl == NULL) return NULL; tbl->size = nbuckets; if (alloc_bucket_locks(ht, tbl) < 0) { bucket_table_free(tbl); return NULL; } for (i = 0; i < nbuckets; i++) INIT_RHT_NULLS_HEAD(tbl->buckets[i], ht, i); return tbl;} /** * rht_grow_above_75 - returns true if nelems > 0.75 * table-size * @ht: hash table * @new_size: new table size */static bool rht_grow_above_75(const struct rhashtable *ht, size_t new_size){ /* Expand table when exceeding 75% load */ return atomic_read(&ht->nelems) > (new_size / 4 * 3) && (!ht->p.max_shift || atomic_read(&ht->shift) < ht->p.max_shift);} /** * rht_shrink_below_30 - returns true if nelems < 0.3 * table-size * @ht: hash table * @new_size: new table size */static bool rht_shrink_below_30(const struct rhashtable *ht, size_t new_size){ /* Shrink table beneath 30% load */ return atomic_read(&ht->nelems) < (new_size * 3 / 10) && (atomic_read(&ht->shift) > ht->p.min_shift);} static void lock_buckets(struct bucket_table *new_tbl, struct bucket_table *old_tbl, unsigned int hash) __acquires(old_bucket_lock){ spin_lock_bh(bucket_lock(old_tbl, hash)); if (new_tbl != old_tbl) spin_lock_bh_nested(bucket_lock(new_tbl, hash), RHT_LOCK_NESTED);} static void unlock_buckets(struct bucket_table *new_tbl, struct bucket_table *old_tbl, unsigned int hash) __releases(old_bucket_lock){ if (new_tbl != old_tbl) spin_unlock_bh(bucket_lock(new_tbl, hash)); spin_unlock_bh(bucket_lock(old_tbl, hash));} /** * Unlink entries on bucket which hash to different bucket. * * Returns true if no more work needs to be performed on the bucket. */static bool hashtable_chain_unzip(struct rhashtable *ht, const struct bucket_table *new_tbl, struct bucket_table *old_tbl, size_t old_hash){ struct rhash_head *he, *p, *next; unsigned int new_hash, new_hash2; ASSERT_BUCKET_LOCK(ht, old_tbl, old_hash); /* Old bucket empty, no work needed. */ p = rht_dereference_bucket(old_tbl->buckets[old_hash], old_tbl, old_hash); if (rht_is_a_nulls(p)) return false; new_hash = head_hashfn(ht, new_tbl, p); ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash); /* Advance the old bucket pointer one or more times until it * reaches a node that doesn't hash to the same bucket as the * previous node p. Call the previous node p; */ rht_for_each_continue(he, p->next, old_tbl, old_hash) { new_hash2 = head_hashfn(ht, new_tbl, he); ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash2); if (new_hash != new_hash2) break; p = he; } rcu_assign_pointer(old_tbl->buckets[old_hash], p->next); /* Find the subsequent node which does hash to the same * bucket as node P, or NULL if no such node exists. */ INIT_RHT_NULLS_HEAD(next, ht, old_hash); if (!rht_is_a_nulls(he)) { rht_for_each_continue(he, he->next, old_tbl, old_hash) { if (head_hashfn(ht, new_tbl, he) == new_hash) { next = he; break; } } } /* Set p's next pointer to that subsequent node pointer, * bypassing the nodes which do not hash to p's bucket */ rcu_assign_pointer(p->next, next); p = rht_dereference_bucket(old_tbl->buckets[old_hash], old_tbl, old_hash); return !rht_is_a_nulls(p);} static void link_old_to_new(struct rhashtable *ht, struct bucket_table *new_tbl, unsigned int new_hash, struct rhash_head *entry){ ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash); rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), entry);} /** * rhashtable_expand - Expand hash table while allowing concurrent lookups * @ht: the hash table to expand * * A secondary bucket array is allocated and the hash entries are migrated * while keeping them on both lists until the end of the RCU grace period. * * This function may only be called in a context where it is safe to call * synchronize_rcu(), e.g. not within a rcu_read_lock() section. * * The caller must ensure that no concurrent resizing occurs by holding * ht->mutex. * * It is valid to have concurrent insertions and deletions protected by per * bucket locks or concurrent RCU protected lookups and traversals. */int rhashtable_expand(struct rhashtable *ht){ struct bucket_table *new_tbl, *old_tbl = rht_dereference(ht->tbl, ht); struct rhash_head *he; unsigned int new_hash, old_hash; bool complete = false; ASSERT_RHT_MUTEX(ht); new_tbl = bucket_table_alloc(ht, old_tbl->size * 2); if (new_tbl == NULL) return -ENOMEM; atomic_inc(&ht->shift); /* Make insertions go into the new, empty table right away. Deletions * and lookups will be attempted in both tables until we synchronize. * The synchronize_rcu() guarantees for the new table to be picked up * so no new additions go into the old table while we relink. */ rcu_assign_pointer(ht->future_tbl, new_tbl); synchronize_rcu(); /* For each new bucket, search the corresponding old bucket for the * first entry that hashes to the new bucket, and link the end of * newly formed bucket chain (containing entries added to future * table) to that entry. Since all the entries which will end up in * the new bucket appear in the same old bucket, this constructs an * entirely valid new hash table, but with multiple buckets * "zipped" together into a single imprecise chain. */ for (new_hash = 0; new_hash < new_tbl->size; new_hash++) { old_hash = rht_bucket_index(old_tbl, new_hash); lock_buckets(new_tbl, old_tbl, new_hash); rht_for_each(he, old_tbl, old_hash) { if (head_hashfn(ht, new_tbl, he) == new_hash) { link_old_to_new(ht, new_tbl, new_hash, he); break; } } unlock_buckets(new_tbl, old_tbl, new_hash); cond_resched(); } /* Unzip interleaved hash chains */ while (!complete && !ht->being_destroyed) { /* Wait for readers. All new readers will see the new * table, and thus no references to the old table will * remain. */ synchronize_rcu(); /* For each bucket in the old table (each of which * contains items from multiple buckets of the new * table): ... */ complete = true; for (old_hash = 0; old_hash < old_tbl->size; old_hash++) { lock_buckets(new_tbl, old_tbl, old_hash); if (hashtable_chain_unzip(ht, new_tbl, old_tbl, old_hash)) complete = false; unlock_buckets(new_tbl, old_tbl, old_hash); cond_resched(); } } rcu_assign_pointer(ht->tbl, new_tbl); synchronize_rcu(); bucket_table_free(old_tbl); return 0;}EXPORT_SYMBOL_GPL(rhashtable_expand); /** * rhashtable_shrink - Shrink hash table while allowing concurrent lookups * @ht: the hash table to shrink * * This function may only be called in a context where it is safe to call * synchronize_rcu(), e.g. not within a rcu_read_lock() section. * * 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. */int rhashtable_shrink(struct rhashtable *ht){ struct bucket_table *new_tbl, *tbl = rht_dereference(ht->tbl, ht); unsigned int new_hash; ASSERT_RHT_MUTEX(ht); new_tbl = bucket_table_alloc(ht, tbl->size / 2); if (new_tbl == NULL) return -ENOMEM; rcu_assign_pointer(ht->future_tbl, new_tbl); synchronize_rcu(); /* Link the first entry in the old bucket to the end of the * bucket in the new table. As entries are concurrently being * added to the new table, lock down the new bucket. As we * always divide the size in half when shrinking, each bucket * in the new table maps to exactly two buckets in the old * table. */ for (new_hash = 0; new_hash < new_tbl->size; new_hash++) { lock_buckets(new_tbl, tbl, new_hash); rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), tbl->buckets[new_hash]); ASSERT_BUCKET_LOCK(ht, tbl, new_hash + new_tbl->size); rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), tbl->buckets[new_hash + new_tbl->size]); unlock_buckets(new_tbl, tbl, new_hash); cond_resched(); }Lines 1–500 of 1134. Load more
/* * Resizable, Scalable, Concurrent Hash Table * * Copyright (c) 2014-2015 Thomas Graf <tgraf@suug.ch> * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net> * * Based on the following paper: * https://www.usenix.org/legacy/event/atc11/tech/final_files/Triplett.pdf * * Code partially derived from nft_hash * * 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/kernel.h> #include <linux/init.h> #include <linux/log2.h> #include <linux/sched.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> #define HASH_DEFAULT_SIZE 64UL #define HASH_MIN_SIZE 4UL #define BUCKET_LOCKS_PER_CPU 128UL /* Base bits plus 1 bit for nulls marker */ #define HASH_RESERVED_SPACE (RHT_BASE_BITS + 1) enum { RHT_LOCK_NORMAL, RHT_LOCK_NESTED, }; /* The bucket lock is selected based on the hash and protects mutations * on a group of hash buckets. * * A maximum of tbl->size/2 bucket locks is allocated. This ensures that * a single lock always covers both buckets which may both contains * entries which link to the same bucket of the old table during resizing. * This allows to simplify the locking as locking the bucket in both * tables during resize always guarantee protection. * * IMPORTANT: When holding the bucket lock of both the old and new table * during expansions and shrinking, the old bucket lock must always be * acquired first. */ static spinlock_t *bucket_lock(const struct bucket_table *tbl, u32 hash) { return &tbl->locks[hash & tbl->locks_mask]; } static void *rht_obj(const struct rhashtable *ht, const struct rhash_head *he) { return (void *) he - ht->p.head_offset; } static u32 rht_bucket_index(const struct bucket_table *tbl, u32 hash) { return hash & (tbl->size - 1); } static u32 obj_raw_hashfn(const struct rhashtable *ht, const void *ptr) { u32 hash; if (unlikely(!ht->p.key_len)) hash = ht->p.obj_hashfn(ptr, ht->p.hash_rnd); else hash = ht->p.hashfn(ptr + ht->p.key_offset, ht->p.key_len, ht->p.hash_rnd); return hash >> HASH_RESERVED_SPACE; } static u32 key_hashfn(struct rhashtable *ht, const void *key, u32 len) { return ht->p.hashfn(key, len, ht->p.hash_rnd) >> HASH_RESERVED_SPACE; } static u32 head_hashfn(const struct rhashtable *ht, const struct bucket_table *tbl, const struct rhash_head *he) { return rht_bucket_index(tbl, obj_raw_hashfn(ht, rht_obj(ht, he))); } #ifdef CONFIG_PROVE_LOCKING static void debug_dump_buckets(const struct rhashtable *ht, const struct bucket_table *tbl) { struct rhash_head *he; unsigned int i, hash; for (i = 0; i < tbl->size; i++) { pr_warn(" [Bucket %d] ", i); rht_for_each_rcu(he, tbl, i) { hash = head_hashfn(ht, tbl, he); pr_cont("[hash = %#x, lock = %p] ", hash, bucket_lock(tbl, hash)); } pr_cont("\n"); } } static void debug_dump_table(struct rhashtable *ht, const struct bucket_table *tbl, unsigned int hash) { struct bucket_table *old_tbl, *future_tbl; pr_emerg("BUG: lock for hash %#x in table %p not held\n", hash, tbl); rcu_read_lock(); future_tbl = rht_dereference_rcu(ht->future_tbl, ht); old_tbl = rht_dereference_rcu(ht->tbl, ht); if (future_tbl != old_tbl) { pr_warn("Future table %p (size: %zd)\n", future_tbl, future_tbl->size); debug_dump_buckets(ht, future_tbl); } pr_warn("Table %p (size: %zd)\n", old_tbl, old_tbl->size); debug_dump_buckets(ht, old_tbl); rcu_read_unlock(); } #define ASSERT_RHT_MUTEX(HT) BUG_ON(!lockdep_rht_mutex_is_held(HT)) #define ASSERT_BUCKET_LOCK(HT, TBL, HASH) \ do { \ if (unlikely(!lockdep_rht_bucket_is_held(TBL, HASH))) { \ debug_dump_table(HT, TBL, HASH); \ BUG(); \ } \ } while (0) 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 = 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) #define ASSERT_BUCKET_LOCK(HT, TBL, HASH) #endif static struct rhash_head __rcu **bucket_tail(struct bucket_table *tbl, u32 n) { struct rhash_head __rcu **pprev; for (pprev = &tbl->buckets[n]; !rht_is_a_nulls(rht_dereference_bucket(*pprev, tbl, n)); pprev = &rht_dereference_bucket(*pprev, tbl, n)->next) ; return pprev; } static int alloc_bucket_locks(struct rhashtable *ht, struct bucket_table *tbl) { 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, 32UL); 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 (sizeof(spinlock_t) != 0) { #ifdef CONFIG_NUMA if (size * sizeof(spinlock_t) > PAGE_SIZE) tbl->locks = vmalloc(size * sizeof(spinlock_t)); else #endif tbl->locks = kmalloc_array(size, sizeof(spinlock_t), GFP_KERNEL); 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 bucket_table_free(const struct bucket_table *tbl) { if (tbl) kvfree(tbl->locks); kvfree(tbl); } static struct bucket_table *bucket_table_alloc(struct rhashtable *ht, size_t nbuckets) { struct bucket_table *tbl = NULL; size_t size; int i; size = sizeof(*tbl) + nbuckets * sizeof(tbl->buckets[0]); if (size <= (PAGE_SIZE << PAGE_ALLOC_COSTLY_ORDER)) tbl = kzalloc(size, GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY); if (tbl == NULL) tbl = vzalloc(size); if (tbl == NULL) return NULL; tbl->size = nbuckets; if (alloc_bucket_locks(ht, tbl) < 0) { bucket_table_free(tbl); return NULL; } for (i = 0; i < nbuckets; i++) INIT_RHT_NULLS_HEAD(tbl->buckets[i], ht, i); return tbl; } /** * rht_grow_above_75 - returns true if nelems > 0.75 * table-size * @ht: hash table * @new_size: new table size */ static bool rht_grow_above_75(const struct rhashtable *ht, size_t new_size) { /* Expand table when exceeding 75% load */ return atomic_read(&ht->nelems) > (new_size / 4 * 3) && (!ht->p.max_shift || atomic_read(&ht->shift) < ht->p.max_shift); } /** * rht_shrink_below_30 - returns true if nelems < 0.3 * table-size * @ht: hash table * @new_size: new table size */ static bool rht_shrink_below_30(const struct rhashtable *ht, size_t new_size) { /* Shrink table beneath 30% load */ return atomic_read(&ht->nelems) < (new_size * 3 / 10) && (atomic_read(&ht->shift) > ht->p.min_shift); } static void lock_buckets(struct bucket_table *new_tbl, struct bucket_table *old_tbl, unsigned int hash) __acquires(old_bucket_lock) { spin_lock_bh(bucket_lock(old_tbl, hash)); if (new_tbl != old_tbl) spin_lock_bh_nested(bucket_lock(new_tbl, hash), RHT_LOCK_NESTED); } static void unlock_buckets(struct bucket_table *new_tbl, struct bucket_table *old_tbl, unsigned int hash) __releases(old_bucket_lock) { if (new_tbl != old_tbl) spin_unlock_bh(bucket_lock(new_tbl, hash)); spin_unlock_bh(bucket_lock(old_tbl, hash)); } /** * Unlink entries on bucket which hash to different bucket. * * Returns true if no more work needs to be performed on the bucket. */ static bool hashtable_chain_unzip(struct rhashtable *ht, const struct bucket_table *new_tbl, struct bucket_table *old_tbl, size_t old_hash) { struct rhash_head *he, *p, *next; unsigned int new_hash, new_hash2; ASSERT_BUCKET_LOCK(ht, old_tbl, old_hash); /* Old bucket empty, no work needed. */ p = rht_dereference_bucket(old_tbl->buckets[old_hash], old_tbl, old_hash); if (rht_is_a_nulls(p)) return false; new_hash = head_hashfn(ht, new_tbl, p); ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash); /* Advance the old bucket pointer one or more times until it * reaches a node that doesn't hash to the same bucket as the * previous node p. Call the previous node p; */ rht_for_each_continue(he, p->next, old_tbl, old_hash) { new_hash2 = head_hashfn(ht, new_tbl, he); ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash2); if (new_hash != new_hash2) break; p = he; } rcu_assign_pointer(old_tbl->buckets[old_hash], p->next); /* Find the subsequent node which does hash to the same * bucket as node P, or NULL if no such node exists. */ INIT_RHT_NULLS_HEAD(next, ht, old_hash); if (!rht_is_a_nulls(he)) { rht_for_each_continue(he, he->next, old_tbl, old_hash) { if (head_hashfn(ht, new_tbl, he) == new_hash) { next = he; break; } } } /* Set p's next pointer to that subsequent node pointer, * bypassing the nodes which do not hash to p's bucket */ rcu_assign_pointer(p->next, next); p = rht_dereference_bucket(old_tbl->buckets[old_hash], old_tbl, old_hash); return !rht_is_a_nulls(p); } static void link_old_to_new(struct rhashtable *ht, struct bucket_table *new_tbl, unsigned int new_hash, struct rhash_head *entry) { ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash); rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), entry); } /** * rhashtable_expand - Expand hash table while allowing concurrent lookups * @ht: the hash table to expand * * A secondary bucket array is allocated and the hash entries are migrated * while keeping them on both lists until the end of the RCU grace period. * * This function may only be called in a context where it is safe to call * synchronize_rcu(), e.g. not within a rcu_read_lock() section. * * The caller must ensure that no concurrent resizing occurs by holding * ht->mutex. * * It is valid to have concurrent insertions and deletions protected by per * bucket locks or concurrent RCU protected lookups and traversals. */ int rhashtable_expand(struct rhashtable *ht) { struct bucket_table *new_tbl, *old_tbl = rht_dereference(ht->tbl, ht); struct rhash_head *he; unsigned int new_hash, old_hash; bool complete = false; ASSERT_RHT_MUTEX(ht); new_tbl = bucket_table_alloc(ht, old_tbl->size * 2); if (new_tbl == NULL) return -ENOMEM; atomic_inc(&ht->shift); /* Make insertions go into the new, empty table right away. Deletions * and lookups will be attempted in both tables until we synchronize. * The synchronize_rcu() guarantees for the new table to be picked up * so no new additions go into the old table while we relink. */ rcu_assign_pointer(ht->future_tbl, new_tbl); synchronize_rcu(); /* For each new bucket, search the corresponding old bucket for the * first entry that hashes to the new bucket, and link the end of * newly formed bucket chain (containing entries added to future * table) to that entry. Since all the entries which will end up in * the new bucket appear in the same old bucket, this constructs an * entirely valid new hash table, but with multiple buckets * "zipped" together into a single imprecise chain. */ for (new_hash = 0; new_hash < new_tbl->size; new_hash++) { old_hash = rht_bucket_index(old_tbl, new_hash); lock_buckets(new_tbl, old_tbl, new_hash); rht_for_each(he, old_tbl, old_hash) { if (head_hashfn(ht, new_tbl, he) == new_hash) { link_old_to_new(ht, new_tbl, new_hash, he); break; } } unlock_buckets(new_tbl, old_tbl, new_hash); cond_resched(); } /* Unzip interleaved hash chains */ while (!complete && !ht->being_destroyed) { /* Wait for readers. All new readers will see the new * table, and thus no references to the old table will * remain. */ synchronize_rcu(); /* For each bucket in the old table (each of which * contains items from multiple buckets of the new * table): ... */ complete = true; for (old_hash = 0; old_hash < old_tbl->size; old_hash++) { lock_buckets(new_tbl, old_tbl, old_hash); if (hashtable_chain_unzip(ht, new_tbl, old_tbl, old_hash)) complete = false; unlock_buckets(new_tbl, old_tbl, old_hash); cond_resched(); } } rcu_assign_pointer(ht->tbl, new_tbl); synchronize_rcu(); bucket_table_free(old_tbl); return 0; } EXPORT_SYMBOL_GPL(rhashtable_expand); /** * rhashtable_shrink - Shrink hash table while allowing concurrent lookups * @ht: the hash table to shrink * * This function may only be called in a context where it is safe to call * synchronize_rcu(), e.g. not within a rcu_read_lock() section. * * 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. */ int rhashtable_shrink(struct rhashtable *ht) { struct bucket_table *new_tbl, *tbl = rht_dereference(ht->tbl, ht); unsigned int new_hash; ASSERT_RHT_MUTEX(ht); new_tbl = bucket_table_alloc(ht, tbl->size / 2); if (new_tbl == NULL) return -ENOMEM; rcu_assign_pointer(ht->future_tbl, new_tbl); synchronize_rcu(); /* Link the first entry in the old bucket to the end of the * bucket in the new table. As entries are concurrently being * added to the new table, lock down the new bucket. As we * always divide the size in half when shrinking, each bucket * in the new table maps to exactly two buckets in the old * table. */ for (new_hash = 0; new_hash < new_tbl->size; new_hash++) { lock_buckets(new_tbl, tbl, new_hash); rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), tbl->buckets[new_hash]); ASSERT_BUCKET_LOCK(ht, tbl, new_hash + new_tbl->size); rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), tbl->buckets[new_hash + new_tbl->size]); unlock_buckets(new_tbl, tbl, new_hash); cond_resched(); }