Skip to content

Commit

Permalink
rhashtable: Introduce rhashtable_walk_*
Browse files Browse the repository at this point in the history
Some existing rhashtable users get too intimate with it by walking
the buckets directly.  This prevents us from easily changing the
internals of rhashtable.

This patch adds the helpers rhashtable_walk_init/exit/start/next/stop
which will replace these custom walkers.

They are meant to be usable for both procfs seq_file walks as well
as walking by a netlink dump.  The iterator structure should fit
inside a netlink dump cb structure, with at least one element to
spare.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Herbert Xu authored and David S. Miller committed Feb 5, 2015
1 parent 28134a5 commit f2dba9c
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 0 deletions.
35 changes: 35 additions & 0 deletions include/linux/rhashtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#ifndef _LINUX_RHASHTABLE_H
#define _LINUX_RHASHTABLE_H

#include <linux/compiler.h>
#include <linux/list_nulls.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
Expand Down Expand Up @@ -111,6 +112,7 @@ struct rhashtable_params {
* @p: Configuration parameters
* @run_work: Deferred worker to expand/shrink asynchronously
* @mutex: Mutex to protect current/future table swapping
* @walkers: List of active walkers
* @being_destroyed: True if table is set up for destruction
*/
struct rhashtable {
Expand All @@ -121,9 +123,36 @@ struct rhashtable {
struct rhashtable_params p;
struct work_struct run_work;
struct mutex mutex;
struct list_head walkers;
bool being_destroyed;
};

/**
* struct rhashtable_walker - Hash table walker
* @list: List entry on list of walkers
* @resize: Resize event occured
*/
struct rhashtable_walker {
struct list_head list;
bool resize;
};

/**
* struct rhashtable_iter - Hash table iterator, fits into netlink cb
* @ht: Table to iterate through
* @p: Current pointer
* @walker: Associated rhashtable walker
* @slot: Current slot
* @skip: Number of entries to skip in slot
*/
struct rhashtable_iter {
struct rhashtable *ht;
struct rhash_head *p;
struct rhashtable_walker *walker;
unsigned int slot;
unsigned int skip;
};

static inline unsigned long rht_marker(const struct rhashtable *ht, u32 hash)
{
return NULLS_MARKER(ht->p.nulls_base + hash);
Expand Down Expand Up @@ -179,6 +208,12 @@ bool rhashtable_lookup_compare_insert(struct rhashtable *ht,
bool (*compare)(void *, void *),
void *arg);

int rhashtable_walk_init(struct rhashtable *ht, struct rhashtable_iter *iter);
void rhashtable_walk_exit(struct rhashtable_iter *iter);
int rhashtable_walk_start(struct rhashtable_iter *iter) __acquires(RCU);
void *rhashtable_walk_next(struct rhashtable_iter *iter);
void rhashtable_walk_stop(struct rhashtable_iter *iter) __releases(RCU);

void rhashtable_destroy(struct rhashtable *ht);

#define rht_dereference(p, ht) \
Expand Down
163 changes: 163 additions & 0 deletions lib/rhashtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ static void rht_deferred_worker(struct work_struct *work)
{
struct rhashtable *ht;
struct bucket_table *tbl;
struct rhashtable_walker *walker;

ht = container_of(work, struct rhashtable, run_work);
mutex_lock(&ht->mutex);
Expand All @@ -492,6 +493,9 @@ static void rht_deferred_worker(struct work_struct *work)

tbl = rht_dereference(ht->tbl, ht);

list_for_each_entry(walker, &ht->walkers, list)
walker->resize = true;

if (ht->p.grow_decision && ht->p.grow_decision(ht, tbl->size))
rhashtable_expand(ht);
else if (ht->p.shrink_decision && ht->p.shrink_decision(ht, tbl->size))
Expand Down Expand Up @@ -822,6 +826,164 @@ bool rhashtable_lookup_compare_insert(struct rhashtable *ht,
}
EXPORT_SYMBOL_GPL(rhashtable_lookup_compare_insert);

/**
* rhashtable_walk_init - Initialise an iterator
* @ht: Table to walk over
* @iter: Hash table Iterator
*
* This function prepares a hash table walk.
*
* Note that if you restart a walk after rhashtable_walk_stop you
* may see the same object twice. Also, you may miss objects if
* there are removals in between rhashtable_walk_stop and the next
* call to rhashtable_walk_start.
*
* For a completely stable walk you should construct your own data
* structure outside the hash table.
*
* This function may sleep so you must not call it from interrupt
* context or with spin locks held.
*
* You must call rhashtable_walk_exit if this function returns
* successfully.
*/
int rhashtable_walk_init(struct rhashtable *ht, struct rhashtable_iter *iter)
{
iter->ht = ht;
iter->p = NULL;
iter->slot = 0;
iter->skip = 0;

iter->walker = kmalloc(sizeof(*iter->walker), GFP_KERNEL);
if (!iter->walker)
return -ENOMEM;

mutex_lock(&ht->mutex);
list_add(&iter->walker->list, &ht->walkers);
mutex_unlock(&ht->mutex);

return 0;
}
EXPORT_SYMBOL_GPL(rhashtable_walk_init);

/**
* rhashtable_walk_exit - Free an iterator
* @iter: Hash table Iterator
*
* This function frees resources allocated by rhashtable_walk_init.
*/
void rhashtable_walk_exit(struct rhashtable_iter *iter)
{
mutex_lock(&iter->ht->mutex);
list_del(&iter->walker->list);
mutex_unlock(&iter->ht->mutex);
kfree(iter->walker);
}
EXPORT_SYMBOL_GPL(rhashtable_walk_exit);

/**
* rhashtable_walk_start - Start a hash table walk
* @iter: Hash table iterator
*
* Start a hash table walk. Note that we take the RCU lock in all
* cases including when we return an error. So you must always call
* rhashtable_walk_stop to clean up.
*
* Returns zero if successful.
*
* Returns -EAGAIN if resize event occured. Note that the iterator
* will rewind back to the beginning and you may use it immediately
* by calling rhashtable_walk_next.
*/
int rhashtable_walk_start(struct rhashtable_iter *iter)
{
rcu_read_lock();

if (iter->walker->resize) {
iter->slot = 0;
iter->skip = 0;
iter->walker->resize = false;
return -EAGAIN;
}

return 0;
}
EXPORT_SYMBOL_GPL(rhashtable_walk_start);

/**
* rhashtable_walk_next - Return the next object and advance the iterator
* @iter: Hash table iterator
*
* Note that you must call rhashtable_walk_stop when you are finished
* with the walk.
*
* Returns the next object or NULL when the end of the table is reached.
*
* Returns -EAGAIN if resize event occured. Note that the iterator
* will rewind back to the beginning and you may continue to use it.
*/
void *rhashtable_walk_next(struct rhashtable_iter *iter)
{
const struct bucket_table *tbl;
struct rhashtable *ht = iter->ht;
struct rhash_head *p = iter->p;
void *obj = NULL;

tbl = rht_dereference_rcu(ht->tbl, ht);

if (p) {
p = rht_dereference_bucket_rcu(p->next, tbl, iter->slot);
goto next;
}

for (; iter->slot < tbl->size; iter->slot++) {
int skip = iter->skip;

rht_for_each_rcu(p, tbl, iter->slot) {
if (!skip)
break;
skip--;
}

next:
if (!rht_is_a_nulls(p)) {
iter->skip++;
iter->p = p;
obj = rht_obj(ht, p);
goto out;
}

iter->skip = 0;
}

iter->p = NULL;

out:
if (iter->walker->resize) {
iter->p = NULL;
iter->slot = 0;
iter->skip = 0;
iter->walker->resize = false;
return ERR_PTR(-EAGAIN);
}

return obj;
}
EXPORT_SYMBOL_GPL(rhashtable_walk_next);

/**
* rhashtable_walk_stop - Finish a hash table walk
* @iter: Hash table iterator
*
* Finish a hash table walk.
*/
void rhashtable_walk_stop(struct rhashtable_iter *iter)
{
rcu_read_unlock();
iter->p = NULL;
}
EXPORT_SYMBOL_GPL(rhashtable_walk_stop);

static size_t rounded_hashtable_size(struct rhashtable_params *params)
{
return max(roundup_pow_of_two(params->nelem_hint * 4 / 3),
Expand Down Expand Up @@ -894,6 +1056,7 @@ int rhashtable_init(struct rhashtable *ht, struct rhashtable_params *params)
memset(ht, 0, sizeof(*ht));
mutex_init(&ht->mutex);
memcpy(&ht->p, params, sizeof(*params));
INIT_LIST_HEAD(&ht->walkers);

if (params->locks_mul)
ht->p.locks_mul = roundup_pow_of_two(params->locks_mul);
Expand Down

0 comments on commit f2dba9c

Please sign in to comment.