| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798 |
- /*
- * Resizable, Scalable, Concurrent Hash Table
- *
- * Copyright (c) 2014 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/slab.h>
- #include <linux/vmalloc.h>
- #include <linux/mm.h>
- #include <linux/hash.h>
- #include <linux/random.h>
- #include <linux/rhashtable.h>
- #define HASH_DEFAULT_SIZE 64UL
- #define HASH_MIN_SIZE 4UL
- #define ASSERT_RHT_MUTEX(HT) BUG_ON(!lockdep_rht_mutex_is_held(HT))
- #ifdef CONFIG_PROVE_LOCKING
- int lockdep_rht_mutex_is_held(const struct rhashtable *ht)
- {
- return ht->p.mutex_is_held();
- }
- EXPORT_SYMBOL_GPL(lockdep_rht_mutex_is_held);
- #endif
- static void *rht_obj(const struct rhashtable *ht, const struct rhash_head *he)
- {
- return (void *) he - ht->p.head_offset;
- }
- static u32 __hashfn(const struct rhashtable *ht, const void *key,
- u32 len, u32 hsize)
- {
- u32 h;
- h = ht->p.hashfn(key, len, ht->p.hash_rnd);
- return h & (hsize - 1);
- }
- /**
- * rhashtable_hashfn - compute hash for key of given length
- * @ht: hash table to compute for
- * @key: pointer to key
- * @len: length of key
- *
- * Computes the hash value using the hash function provided in the 'hashfn'
- * of struct rhashtable_params. The returned value is guaranteed to be
- * smaller than the number of buckets in the hash table.
- *
- * The caller must ensure that no concurrent table mutations occur.
- */
- u32 rhashtable_hashfn(const struct rhashtable *ht, const void *key, u32 len)
- {
- struct bucket_table *tbl = rht_dereference_rcu(ht->tbl, ht);
- return __hashfn(ht, key, len, tbl->size);
- }
- EXPORT_SYMBOL_GPL(rhashtable_hashfn);
- static u32 obj_hashfn(const struct rhashtable *ht, const void *ptr, u32 hsize)
- {
- if (unlikely(!ht->p.key_len)) {
- u32 h;
- h = ht->p.obj_hashfn(ptr, ht->p.hash_rnd);
- return h & (hsize - 1);
- }
- return __hashfn(ht, ptr + ht->p.key_offset, ht->p.key_len, hsize);
- }
- /**
- * rhashtable_obj_hashfn - compute hash for hashed object
- * @ht: hash table to compute for
- * @ptr: pointer to hashed object
- *
- * Computes the hash value using the hash function `hashfn` respectively
- * 'obj_hashfn' depending on whether the hash table is set up to work with
- * a fixed length key. The returned value is guaranteed to be smaller than
- * the number of buckets in the hash table.
- *
- * The caller must ensure that no concurrent table mutations occur.
- */
- u32 rhashtable_obj_hashfn(const struct rhashtable *ht, void *ptr)
- {
- struct bucket_table *tbl = rht_dereference_rcu(ht->tbl, ht);
- return obj_hashfn(ht, ptr, tbl->size);
- }
- EXPORT_SYMBOL_GPL(rhashtable_obj_hashfn);
- static u32 head_hashfn(const struct rhashtable *ht,
- const struct rhash_head *he, u32 hsize)
- {
- return obj_hashfn(ht, rht_obj(ht, he), hsize);
- }
- static struct bucket_table *bucket_table_alloc(size_t nbuckets, gfp_t flags)
- {
- struct bucket_table *tbl;
- size_t size;
- size = sizeof(*tbl) + nbuckets * sizeof(tbl->buckets[0]);
- tbl = kzalloc(size, flags);
- if (tbl == NULL)
- tbl = vzalloc(size);
- if (tbl == NULL)
- return NULL;
- tbl->size = nbuckets;
- return tbl;
- }
- static void bucket_table_free(const struct bucket_table *tbl)
- {
- kvfree(tbl);
- }
- /**
- * rht_grow_above_75 - returns true if nelems > 0.75 * table-size
- * @ht: hash table
- * @new_size: new table size
- */
- bool rht_grow_above_75(const struct rhashtable *ht, size_t new_size)
- {
- /* Expand table when exceeding 75% load */
- return ht->nelems > (new_size / 4 * 3);
- }
- EXPORT_SYMBOL_GPL(rht_grow_above_75);
- /**
- * rht_shrink_below_30 - returns true if nelems < 0.3 * table-size
- * @ht: hash table
- * @new_size: new table size
- */
- bool rht_shrink_below_30(const struct rhashtable *ht, size_t new_size)
- {
- /* Shrink table beneath 30% load */
- return ht->nelems < (new_size * 3 / 10);
- }
- EXPORT_SYMBOL_GPL(rht_shrink_below_30);
- static void hashtable_chain_unzip(const struct rhashtable *ht,
- const struct bucket_table *new_tbl,
- struct bucket_table *old_tbl, size_t n)
- {
- struct rhash_head *he, *p, *next;
- unsigned int h;
- /* Old bucket empty, no work needed. */
- p = rht_dereference(old_tbl->buckets[n], ht);
- if (!p)
- return;
- /* 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;
- */
- h = head_hashfn(ht, p, new_tbl->size);
- rht_for_each(he, p->next, ht) {
- if (head_hashfn(ht, he, new_tbl->size) != h)
- break;
- p = he;
- }
- RCU_INIT_POINTER(old_tbl->buckets[n], p->next);
- /* Find the subsequent node which does hash to the same
- * bucket as node P, or NULL if no such node exists.
- */
- next = NULL;
- if (he) {
- rht_for_each(he, he->next, ht) {
- if (head_hashfn(ht, he, new_tbl->size) == h) {
- 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_INIT_POINTER(p->next, next);
- }
- /**
- * rhashtable_expand - Expand hash table while allowing concurrent lookups
- * @ht: the hash table to expand
- * @flags: allocation flags
- *
- * 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 table mutations take place.
- * It is however valid to have concurrent lookups if they are RCU protected.
- */
- int rhashtable_expand(struct rhashtable *ht, gfp_t flags)
- {
- struct bucket_table *new_tbl, *old_tbl = rht_dereference(ht->tbl, ht);
- struct rhash_head *he;
- unsigned int i, h;
- bool complete;
- ASSERT_RHT_MUTEX(ht);
- if (ht->p.max_shift && ht->shift >= ht->p.max_shift)
- return 0;
- new_tbl = bucket_table_alloc(old_tbl->size * 2, flags);
- if (new_tbl == NULL)
- return -ENOMEM;
- ht->shift++;
- /* For each new bucket, search the corresponding old bucket
- * for the first entry that hashes to the new bucket, and
- * link the new bucket 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 (i = 0; i < new_tbl->size; i++) {
- h = i & (old_tbl->size - 1);
- rht_for_each(he, old_tbl->buckets[h], ht) {
- if (head_hashfn(ht, he, new_tbl->size) == i) {
- RCU_INIT_POINTER(new_tbl->buckets[i], he);
- break;
- }
- }
- }
- /* Publish the new table pointer. Lookups may now traverse
- * the new table, but they will not benefit from any
- * additional efficiency until later steps unzip the buckets.
- */
- rcu_assign_pointer(ht->tbl, new_tbl);
- /* Unzip interleaved hash chains */
- do {
- /* 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 (i = 0; i < old_tbl->size; i++) {
- hashtable_chain_unzip(ht, new_tbl, old_tbl, i);
- if (old_tbl->buckets[i] != NULL)
- complete = false;
- }
- } while (!complete);
- 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
- * @flags: allocation flags
- *
- * 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 table mutations take place.
- * It is however valid to have concurrent lookups if they are RCU protected.
- */
- int rhashtable_shrink(struct rhashtable *ht, gfp_t flags)
- {
- struct bucket_table *ntbl, *tbl = rht_dereference(ht->tbl, ht);
- struct rhash_head __rcu **pprev;
- unsigned int i;
- ASSERT_RHT_MUTEX(ht);
- if (ht->shift <= ht->p.min_shift)
- return 0;
- ntbl = bucket_table_alloc(tbl->size / 2, flags);
- if (ntbl == NULL)
- return -ENOMEM;
- ht->shift--;
- /* Link each bucket in the new table to the first bucket
- * in the old table that contains entries which will hash
- * to the new bucket.
- */
- for (i = 0; i < ntbl->size; i++) {
- ntbl->buckets[i] = tbl->buckets[i];
- /* Link each bucket in the new table to the first bucket
- * in the old table that contains entries which will hash
- * to the new bucket.
- */
- for (pprev = &ntbl->buckets[i]; *pprev != NULL;
- pprev = &rht_dereference(*pprev, ht)->next)
- ;
- RCU_INIT_POINTER(*pprev, tbl->buckets[i + ntbl->size]);
- }
- /* Publish the new, valid hash table */
- rcu_assign_pointer(ht->tbl, ntbl);
- /* Wait for readers. No new readers will have references to the
- * old hash table.
- */
- synchronize_rcu();
- bucket_table_free(tbl);
- return 0;
- }
- EXPORT_SYMBOL_GPL(rhashtable_shrink);
- /**
- * rhashtable_insert - insert object into hash hash table
- * @ht: hash table
- * @obj: pointer to hash head inside object
- * @flags: allocation flags (table expansion)
- *
- * Will automatically grow the table via rhashtable_expand() if the the
- * grow_decision function specified at rhashtable_init() returns true.
- *
- * The caller must ensure that no concurrent table mutations occur. It is
- * however valid to have concurrent lookups if they are RCU protected.
- */
- void rhashtable_insert(struct rhashtable *ht, struct rhash_head *obj,
- gfp_t flags)
- {
- struct bucket_table *tbl = rht_dereference(ht->tbl, ht);
- u32 hash;
- ASSERT_RHT_MUTEX(ht);
- hash = head_hashfn(ht, obj, tbl->size);
- RCU_INIT_POINTER(obj->next, tbl->buckets[hash]);
- rcu_assign_pointer(tbl->buckets[hash], obj);
- ht->nelems++;
- if (ht->p.grow_decision && ht->p.grow_decision(ht, tbl->size))
- rhashtable_expand(ht, flags);
- }
- EXPORT_SYMBOL_GPL(rhashtable_insert);
- /**
- * rhashtable_remove_pprev - remove object from hash table given previous element
- * @ht: hash table
- * @obj: pointer to hash head inside object
- * @pprev: pointer to previous element
- * @flags: allocation flags (table expansion)
- *
- * Identical to rhashtable_remove() but caller is alreayd aware of the element
- * in front of the element to be deleted. This is in particular useful for
- * deletion when combined with walking or lookup.
- */
- void rhashtable_remove_pprev(struct rhashtable *ht, struct rhash_head *obj,
- struct rhash_head __rcu **pprev, gfp_t flags)
- {
- struct bucket_table *tbl = rht_dereference(ht->tbl, ht);
- ASSERT_RHT_MUTEX(ht);
- RCU_INIT_POINTER(*pprev, obj->next);
- ht->nelems--;
- if (ht->p.shrink_decision &&
- ht->p.shrink_decision(ht, tbl->size))
- rhashtable_shrink(ht, flags);
- }
- EXPORT_SYMBOL_GPL(rhashtable_remove_pprev);
- /**
- * rhashtable_remove - remove object from hash table
- * @ht: hash table
- * @obj: pointer to hash head inside object
- * @flags: allocation flags (table expansion)
- *
- * Since the hash chain is single linked, the removal operation needs to
- * walk the bucket chain upon removal. The removal operation is thus
- * considerable slow if the hash table is not correctly sized.
- *
- * Will automatically shrink the table via rhashtable_expand() if the the
- * shrink_decision function specified at rhashtable_init() returns true.
- *
- * The caller must ensure that no concurrent table mutations occur. It is
- * however valid to have concurrent lookups if they are RCU protected.
- */
- bool rhashtable_remove(struct rhashtable *ht, struct rhash_head *obj,
- gfp_t flags)
- {
- struct bucket_table *tbl = rht_dereference(ht->tbl, ht);
- struct rhash_head __rcu **pprev;
- struct rhash_head *he;
- u32 h;
- ASSERT_RHT_MUTEX(ht);
- h = head_hashfn(ht, obj, tbl->size);
- pprev = &tbl->buckets[h];
- rht_for_each(he, tbl->buckets[h], ht) {
- if (he != obj) {
- pprev = &he->next;
- continue;
- }
- rhashtable_remove_pprev(ht, he, pprev, flags);
- return true;
- }
- return false;
- }
- EXPORT_SYMBOL_GPL(rhashtable_remove);
- /**
- * rhashtable_lookup - lookup key in hash table
- * @ht: hash table
- * @key: pointer to key
- *
- * Computes the hash value for the key and traverses the bucket chain looking
- * for a entry with an identical key. The first matching entry is returned.
- *
- * This lookup function may only be used for fixed key hash table (key_len
- * paramter set). It will BUG() if used inappropriately.
- *
- * Lookups may occur in parallel with hash mutations as long as the lookup is
- * guarded by rcu_read_lock(). The caller must take care of this.
- */
- void *rhashtable_lookup(const struct rhashtable *ht, const void *key)
- {
- const struct bucket_table *tbl = rht_dereference_rcu(ht->tbl, ht);
- struct rhash_head *he;
- u32 h;
- BUG_ON(!ht->p.key_len);
- h = __hashfn(ht, key, ht->p.key_len, tbl->size);
- rht_for_each_rcu(he, tbl->buckets[h], ht) {
- if (memcmp(rht_obj(ht, he) + ht->p.key_offset, key,
- ht->p.key_len))
- continue;
- return (void *) he - ht->p.head_offset;
- }
- return NULL;
- }
- EXPORT_SYMBOL_GPL(rhashtable_lookup);
- /**
- * rhashtable_lookup_compare - search hash table with compare function
- * @ht: hash table
- * @key: pointer to key
- * @compare: compare function, must return true on match
- * @arg: argument passed on to compare function
- *
- * Traverses the bucket chain behind the provided hash value and calls the
- * specified compare function for each entry.
- *
- * Lookups may occur in parallel with hash mutations as long as the lookup is
- * guarded by rcu_read_lock(). The caller must take care of this.
- *
- * Returns the first entry on which the compare function returned true.
- */
- void *rhashtable_lookup_compare(const struct rhashtable *ht, void *key,
- bool (*compare)(void *, void *), void *arg)
- {
- const struct bucket_table *tbl = rht_dereference_rcu(ht->tbl, ht);
- struct rhash_head *he;
- u32 hash;
- hash = __hashfn(ht, key, ht->p.key_len, tbl->size);
- rht_for_each_rcu(he, tbl->buckets[hash], ht) {
- if (!compare(rht_obj(ht, he), arg))
- continue;
- return (void *) he - ht->p.head_offset;
- }
- return NULL;
- }
- EXPORT_SYMBOL_GPL(rhashtable_lookup_compare);
- static size_t rounded_hashtable_size(struct rhashtable_params *params)
- {
- return max(roundup_pow_of_two(params->nelem_hint * 4 / 3),
- 1UL << params->min_shift);
- }
- /**
- * rhashtable_init - initialize a new hash table
- * @ht: hash table to be initialized
- * @params: configuration parameters
- *
- * Initializes a new hash table based on the provided configuration
- * parameters. A table can be configured either with a variable or
- * fixed length key:
- *
- * Configuration Example 1: Fixed length keys
- * struct test_obj {
- * int key;
- * void * my_member;
- * struct rhash_head node;
- * };
- *
- * struct rhashtable_params params = {
- * .head_offset = offsetof(struct test_obj, node),
- * .key_offset = offsetof(struct test_obj, key),
- * .key_len = sizeof(int),
- * .hashfn = arch_fast_hash,
- * .mutex_is_held = &my_mutex_is_held,
- * };
- *
- * Configuration Example 2: Variable length keys
- * struct test_obj {
- * [...]
- * struct rhash_head node;
- * };
- *
- * u32 my_hash_fn(const void *data, u32 seed)
- * {
- * struct test_obj *obj = data;
- *
- * return [... hash ...];
- * }
- *
- * struct rhashtable_params params = {
- * .head_offset = offsetof(struct test_obj, node),
- * .hashfn = arch_fast_hash,
- * .obj_hashfn = my_hash_fn,
- * .mutex_is_held = &my_mutex_is_held,
- * };
- */
- int rhashtable_init(struct rhashtable *ht, struct rhashtable_params *params)
- {
- struct bucket_table *tbl;
- size_t size;
- size = HASH_DEFAULT_SIZE;
- if ((params->key_len && !params->hashfn) ||
- (!params->key_len && !params->obj_hashfn))
- return -EINVAL;
- params->min_shift = max_t(size_t, params->min_shift,
- ilog2(HASH_MIN_SIZE));
- if (params->nelem_hint)
- size = rounded_hashtable_size(params);
- tbl = bucket_table_alloc(size, GFP_KERNEL);
- if (tbl == NULL)
- return -ENOMEM;
- memset(ht, 0, sizeof(*ht));
- ht->shift = ilog2(tbl->size);
- memcpy(&ht->p, params, sizeof(*params));
- RCU_INIT_POINTER(ht->tbl, tbl);
- if (!ht->p.hash_rnd)
- get_random_bytes(&ht->p.hash_rnd, sizeof(ht->p.hash_rnd));
- return 0;
- }
- EXPORT_SYMBOL_GPL(rhashtable_init);
- /**
- * rhashtable_destroy - destroy hash table
- * @ht: the hash table to destroy
- *
- * Frees the bucket array. This function is not rcu safe, therefore the caller
- * has to make sure that no resizing may happen by unpublishing the hashtable
- * and waiting for the quiescent cycle before releasing the bucket array.
- */
- void rhashtable_destroy(const struct rhashtable *ht)
- {
- bucket_table_free(ht->tbl);
- }
- EXPORT_SYMBOL_GPL(rhashtable_destroy);
- /**************************************************************************
- * Self Test
- **************************************************************************/
- #ifdef CONFIG_TEST_RHASHTABLE
- #define TEST_HT_SIZE 8
- #define TEST_ENTRIES 2048
- #define TEST_PTR ((void *) 0xdeadbeef)
- #define TEST_NEXPANDS 4
- static int test_mutex_is_held(void)
- {
- return 1;
- }
- struct test_obj {
- void *ptr;
- int value;
- struct rhash_head node;
- };
- static int __init test_rht_lookup(struct rhashtable *ht)
- {
- unsigned int i;
- for (i = 0; i < TEST_ENTRIES * 2; i++) {
- struct test_obj *obj;
- bool expected = !(i % 2);
- u32 key = i;
- obj = rhashtable_lookup(ht, &key);
- if (expected && !obj) {
- pr_warn("Test failed: Could not find key %u\n", key);
- return -ENOENT;
- } else if (!expected && obj) {
- pr_warn("Test failed: Unexpected entry found for key %u\n",
- key);
- return -EEXIST;
- } else if (expected && obj) {
- if (obj->ptr != TEST_PTR || obj->value != i) {
- pr_warn("Test failed: Lookup value mismatch %p!=%p, %u!=%u\n",
- obj->ptr, TEST_PTR, obj->value, i);
- return -EINVAL;
- }
- }
- }
- return 0;
- }
- static void test_bucket_stats(struct rhashtable *ht,
- struct bucket_table *tbl,
- bool quiet)
- {
- unsigned int cnt, i, total = 0;
- struct test_obj *obj;
- for (i = 0; i < tbl->size; i++) {
- cnt = 0;
- if (!quiet)
- pr_info(" [%#4x/%zu]", i, tbl->size);
- rht_for_each_entry_rcu(obj, tbl->buckets[i], node) {
- cnt++;
- total++;
- if (!quiet)
- pr_cont(" [%p],", obj);
- }
- if (!quiet)
- pr_cont("\n [%#x] first element: %p, chain length: %u\n",
- i, tbl->buckets[i], cnt);
- }
- pr_info(" Traversal complete: counted=%u, nelems=%zu, entries=%d\n",
- total, ht->nelems, TEST_ENTRIES);
- }
- static int __init test_rhashtable(struct rhashtable *ht)
- {
- struct bucket_table *tbl;
- struct test_obj *obj, *next;
- int err;
- unsigned int i;
- /*
- * Insertion Test:
- * Insert TEST_ENTRIES into table with all keys even numbers
- */
- pr_info(" Adding %d keys\n", TEST_ENTRIES);
- for (i = 0; i < TEST_ENTRIES; i++) {
- struct test_obj *obj;
- obj = kzalloc(sizeof(*obj), GFP_KERNEL);
- if (!obj) {
- err = -ENOMEM;
- goto error;
- }
- obj->ptr = TEST_PTR;
- obj->value = i * 2;
- rhashtable_insert(ht, &obj->node, GFP_KERNEL);
- }
- rcu_read_lock();
- tbl = rht_dereference_rcu(ht->tbl, ht);
- test_bucket_stats(ht, tbl, true);
- test_rht_lookup(ht);
- rcu_read_unlock();
- for (i = 0; i < TEST_NEXPANDS; i++) {
- pr_info(" Table expansion iteration %u...\n", i);
- rhashtable_expand(ht, GFP_KERNEL);
- rcu_read_lock();
- pr_info(" Verifying lookups...\n");
- test_rht_lookup(ht);
- rcu_read_unlock();
- }
- for (i = 0; i < TEST_NEXPANDS; i++) {
- pr_info(" Table shrinkage iteration %u...\n", i);
- rhashtable_shrink(ht, GFP_KERNEL);
- rcu_read_lock();
- pr_info(" Verifying lookups...\n");
- test_rht_lookup(ht);
- rcu_read_unlock();
- }
- pr_info(" Deleting %d keys\n", TEST_ENTRIES);
- for (i = 0; i < TEST_ENTRIES; i++) {
- u32 key = i * 2;
- obj = rhashtable_lookup(ht, &key);
- BUG_ON(!obj);
- rhashtable_remove(ht, &obj->node, GFP_KERNEL);
- kfree(obj);
- }
- return 0;
- error:
- tbl = rht_dereference_rcu(ht->tbl, ht);
- for (i = 0; i < tbl->size; i++)
- rht_for_each_entry_safe(obj, next, tbl->buckets[i], ht, node)
- kfree(obj);
- return err;
- }
- static int __init test_rht_init(void)
- {
- struct rhashtable ht;
- struct rhashtable_params params = {
- .nelem_hint = TEST_HT_SIZE,
- .head_offset = offsetof(struct test_obj, node),
- .key_offset = offsetof(struct test_obj, value),
- .key_len = sizeof(int),
- .hashfn = arch_fast_hash,
- .mutex_is_held = &test_mutex_is_held,
- .grow_decision = rht_grow_above_75,
- .shrink_decision = rht_shrink_below_30,
- };
- int err;
- pr_info("Running resizable hashtable tests...\n");
- err = rhashtable_init(&ht, ¶ms);
- if (err < 0) {
- pr_warn("Test failed: Unable to initialize hashtable: %d\n",
- err);
- return err;
- }
- err = test_rhashtable(&ht);
- rhashtable_destroy(&ht);
- return err;
- }
- subsys_initcall(test_rht_init);
- #endif /* CONFIG_TEST_RHASHTABLE */
|