rhashtable: Per bucket locks & deferred table resizing
Subsystems (7)
Thomas Graf
maintainerv2 cover rhashtable: Per bucket locks & deferred table resizing
Prepares for and introduces per bucket spinlocks and deferred table resizing. This allows for parallel table mutations in different hash buckets from atomic context. The resizing occurs in the background in a separate worker thread while lookups, inserts, and removals can continue.
Also modified the chain linked list to be terminated with a special nulls marker to allow entries to move between multiple lists.
Last but not least, reintroduces lockless netlink_lookup() with deferred Netlink socket destruction to avoid the side effect of increased netlink_release() runtime.
Thomas Graf (9):
rhashtable: Do hashing inside of rhashtable_lookup_compare()
rhashtable: Use rht_obj() instead of manual offset calculation
rhashtable: Convert bucket iterators to take table and index
rhashtable: Factor out bucket_tail() function
nft_hash: Remove rhashtable_remove_pprev()
spinlock: Add spin_lock_bh_nested()
rhashtable: Per bucket locks & deferred expansion/shrinking
rhashtable: Supports for nulls marker
netlink: Lockless lookup with RCU grace period in socket release
include/linux/list_nulls.h | 3 +- include/linux/rhashtable.h | 258 ++++++++++++----- include/linux/spinlock.h | 8 + include/linux/spinlock_api_smp.h | 2 + include/linux/spinlock_api_up.h | 1 + kernel/locking/spinlock.c | 8 + lib/rhashtable.c | 607 ++++++++++++++++++++++++++------------- net/netfilter/nft_hash.c | 92 +++--- net/netlink/af_netlink.c | 64 ++--- net/netlink/af_netlink.h | 1 + net/netlink/diag.c | 4 +- 11 files changed, 693 insertions(+), 355 deletions(-)
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 1/9 rhashtable: Do hashing inside of rhashtable_lookup_compare()
Hash the key inside of rhashtable_lookup_compare() like rhashtable_lookup() does. This allows to simplify the hashing functions and keep them private.
--- include/linux/rhashtable.h | 5 +-- lib/rhashtable.c | 91 +++++++++++++++------------------------------- net/netfilter/nft_hash.c | 46 ++++++++++++++--------- net/netlink/af_netlink.c | 5 +-- 4 files changed, 61 insertions(+), 86 deletions(-)
include/linux/rhashtable.h +1 -4
@@ -96,9 +96,6 @@ static inline int lockdep_rht_mutex_is_held(const struct rhashtable *ht)int rhashtable_init (struct rhashtable *ht,struct rhashtable_params *params); -u32 rhashtable_hashfn (const struct rhashtable *ht,const void *key,u32 len); -u32 rhashtable_obj_hashfn (const struct rhashtable *ht,void *ptr); -void rhashtable_insert (struct rhashtable *ht,struct rhash_head *node);bool rhashtable_remove (struct rhashtable *ht,struct rhash_head *node); void rhashtable_remove_pprev(struct rhashtable *ht, struct rhash_head *obj,
@@ -111,7 +108,7 @@ int rhashtable_expand(struct rhashtable *ht);int rhashtable_shrink (struct rhashtable *ht);void *rhashtable_lookup (const struct rhashtable *ht,const void *key); -void *rhashtable_lookup_compare (const struct rhashtable *ht,u32 hash, +void *rhashtable_lookup_compare (const struct rhashtable *ht,const void *key,bool (*compare)(void *,void *),void *arg);void rhashtable_destroy (const struct rhashtable *ht);
lib/rhashtable.c +30 -61
167 lines not on this page · raw message
net/netfilter/nft_hash.c +29 -17
57 lines not on this page · raw message
net/netlink/af_netlink.c +1 -4
@@ -1002,11 +1002,8 @@ static struct sock *__netlink_lookup(struct netlink_table *table, u32 portid, .net = net, .portid = portid, }; -u32 hash; - hash =rhashtable_hashfn (&table->hash, &portid,sizeof (portid)); - -return rhashtable_lookup_compare (&table->hash, hash, +return rhashtable_lookup_compare (&table->hash, &portid, &netlink_compare, &arg); }
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 2/9 rhashtable: Use rht_obj() instead of manual offset calculation
In reply to Thomas Graf--- lib/rhashtable.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
lib/rhashtable.c +2 -2
@@ -427,7 +427,7 @@ void *rhashtable_lookup(const struct rhashtable *ht, const void *key)if (memcmp (rht_obj (ht, he) + ht->p.key_offset, key, ht->p.key_len))continue ; -return (void *) he - ht->p.head_offset; +return rht_obj (ht, he); }return NULL ;
@@ -460,7 +460,7 @@ void *rhashtable_lookup_compare(const struct rhashtable *ht, const void *key,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 rht_obj (ht, he); }return NULL ;
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 3/9 rhashtable: Convert bucket iterators to take table and index
In reply to Thomas GrafThis patch is in preparation to introduce per bucket spinlocks. It extends all iterator macros to take the bucket table and bucket index. It also introduces a new rht_dereference_bucket() to handle protected accesses to buckets.
It introduces a barrier() to the RCU iterators to the prevent the compiler from caching the first element.
The lockdep verifier is introduced as stub which always succeeds and properly implement in the next patch when the locks are introduced.
--- include/linux/rhashtable.h | 173 ++++++++++++++++++++++++++++++--------------- lib/rhashtable.c | 30 +++++--- net/netfilter/nft_hash.c | 12 ++-- net/netlink/af_netlink.c | 12 ++-- net/netlink/diag.c | 4 +- 5 files changed, 152 insertions(+), 79 deletions(-)
include/linux/rhashtable.h +116 -57
@@ -87,11 +87,18 @@ struct rhashtable {#ifdef CONFIG_PROVE_LOCKING int lockdep_rht_mutex_is_held (const struct rhashtable *ht); +int lockdep_rht_bucket_is_held (const struct bucket_table *tbl,u32 hash);#else static inline int lockdep_rht_mutex_is_held (const struct rhashtable *ht) {return 1 ; } + +static inline int lockdep_rht_bucket_is_held (const struct bucket_table *tbl, +u32 hash) +{ +return 1 ; +}#endif /* CONFIG_PROVE_LOCKING */ int rhashtable_init (struct rhashtable *ht,struct rhashtable_params *params);
@@ -119,92 +126,144 @@ void rhashtable_destroy(const struct rhashtable *ht);#define rht_dereference_rcu (p, ht) \ rcu_dereference_check(p, lockdep_rht_mutex_is_held(ht)) -#define rht_entry (ptr, type, member) container_of(ptr, type, member) -#define rht_entry_safe (ptr, type, member) \ -({ \ - typeof(ptr) __ptr = (ptr); \ - __ptr ? rht_entry(__ptr, type, member) : NULL; \ -}) +#define rht_dereference_bucket (p, tbl, hash) \ + rcu_dereference_protected(p, lockdep_rht_bucket_is_held(tbl, hash)) -#define rht_next_entry_safe (pos, ht, member) \ -({ \ - pos ? rht_entry_safe(rht_dereference((pos)->member.next, ht), \ - typeof(*(pos)), member) : NULL; \ -}) +#define rht_dereference_bucket_rcu (p, tbl, hash) \ + rcu_dereference_check(p, lockdep_rht_bucket_is_held(tbl, hash)) + +#define rht_entry (tpos, pos, member) \ + ({ tpos = container_of(pos, typeof(*tpos), member); 1; })/** -* rht_for_each - iterate over hash chain -* @pos: &struct rhash_head to use as a loop cursor. -* @head: head of the hash chain (struct rhash_head *) -* @ht: pointer to your struct rhashtable +* rht_for_each_continue - continue iterating over hash chain +* @pos: the &struct rhash_head to use as a loop cursor. +* @head: the previous &struct rhash_head to continue from +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index */ -#define rht_for_each (pos, head, ht) \ - for (pos = rht_dereference(head, ht); \ +#define rht_for_each_continue (pos, head, tbl, hash) \ + for (pos = rht_dereference_bucket(head, tbl, hash); \ pos; \ - pos = rht_dereference((pos)->next, ht)) + pos = rht_dereference_bucket((pos)->next, tbl, hash)) + +/** +* rht_for_each - iterate over hash chain +* @pos: the &struct rhash_head to use as a loop cursor. +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index +*/ +#define rht_for_each (pos, tbl, hash) \ + rht_for_each_continue(pos, (tbl)->buckets[hash], tbl, hash) + +/** +* rht_for_each_entry_continue - continue iterating over hash chain +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct rhash_head to use as a loop cursor. +* @head: the previous &struct rhash_head to continue from +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index +* @member: name of the &struct rhash_head within the hashable struct. +*/ +#define rht_for_each_entry_continue (tpos, pos, head, tbl, hash, member) \ + for (pos = rht_dereference_bucket(head, tbl, hash); \ + pos && rht_entry(tpos, pos, member); \ + pos = rht_dereference_bucket((pos)->next, tbl, hash))/** * rht_for_each_entry - iterate over hash chain of given type -* @pos: type * to use as a loop cursor. -* @head: head of the hash chain (struct rhash_head *) -* @ht: pointer to your struct rhashtable -* @member: name of the rhash_head within the hashable struct. +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct rhash_head to use as a loop cursor. +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index +* @member: name of the &struct rhash_head within the hashable struct. */ -#define rht_for_each_entry (pos, head, ht, member) \ - for (pos = rht_entry_safe(rht_dereference(head, ht), \ - typeof(*(pos)), member); \ - pos; \ - pos = rht_next_entry_safe(pos, ht, member)) +#define rht_for_each_entry (tpos, pos, tbl, hash, member) \ + rht_for_each_entry_continue(tpos, pos, (tbl)->buckets[hash], \ + tbl, hash, member)/** * rht_for_each_entry_safe - safely iterate over hash chain of given type -* @pos: type * to use as a loop cursor. -* @n: type * to use for temporary next object storage -* @head: head of the hash chain (struct rhash_head *) -* @ht: pointer to your struct rhashtable -* @member: name of the rhash_head within the hashable struct. +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct rhash_head to use as a loop cursor. +* @next: the &struct rhash_head to use as next in loop cursor. +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index +* @member: name of the &struct rhash_head within the hashable struct. * * This hash chain list-traversal primitive allows for the looped code to * remove the loop cursor from the list. */ -#define rht_for_each_entry_safe (pos, n, head, ht, member) \ - for (pos = rht_entry_safe(rht_dereference(head, ht), \ - typeof(*(pos)), member), \ - n = rht_next_entry_safe(pos, ht, member); \ - pos; \ - pos = n, \ - n = rht_next_entry_safe(pos, ht, member)) +#define rht_for_each_entry_safe (tpos, pos, next, tbl, hash, member) \ + for (pos = rht_dereference_bucket((tbl)->buckets[hash], tbl, hash), \ + next = pos ? rht_dereference_bucket(pos->next, tbl, hash) \ + : NULL; \ + pos && rht_entry(tpos, pos, member); \ + pos = next) + +/** +* rht_for_each_rcu_continue - continue iterating over rcu hash chain +* @pos: the &struct rhash_head to use as a loop cursor. +* @head: the previous &struct rhash_head to continue from +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index +* +* This hash chain list-traversal primitive may safely run concurrently with +* the _rcu mutation primitives such as rhashtable_insert() as long as the +* traversal is guarded by rcu_read_lock(). +*/ +#define rht_for_each_rcu_continue (pos, head, tbl, hash) \ + for (({barrier(); }), \ + pos = rht_dereference_bucket_rcu(head, tbl, hash); \ + pos; \ + pos = rcu_dereference_raw(pos->next))/** * rht_for_each_rcu - iterate over rcu hash chain -* @pos: &struct rhash_head to use as a loop cursor. -* @head: head of the hash chain (struct rhash_head *) -* @ht: pointer to your struct rhashtable +* @pos: the &struct rhash_head to use as a loop cursor. +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index * * This hash chain list-traversal primitive may safely run concurrently with -* the _rcu fkht mutation primitives such as rht_insert() as long as the +* the _rcu mutation primitives such as rhashtable_insert() as long as the * traversal is guarded by rcu_read_lock(). */ -#define rht_for_each_rcu (pos, head, ht) \ - for (pos = rht_dereference_rcu(head, ht); \ - pos; \ - pos = rht_dereference_rcu((pos)->next, ht)) +#define rht_for_each_rcu (pos, tbl, hash) \ + rht_for_each_rcu_continue(pos, (tbl)->buckets[hash], tbl, hash) + +/** +* rht_for_each_entry_rcu_continue - continue iterating over rcu hash chain +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct rhash_head to use as a loop cursor. +* @head: the previous &struct rhash_head to continue from +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index +* @member: name of the &struct rhash_head within the hashable struct. +* +* This hash chain list-traversal primitive may safely run concurrently with +* the _rcu mutation primitives such as rhashtable_insert() as long as the +* traversal is guarded by rcu_read_lock(). +*/ +#define rht_for_each_entry_rcu_continue (tpos, pos, head, tbl, hash, member) \ + for (({barrier(); }), \ + pos = rht_dereference_bucket_rcu(head, tbl, hash); \ + pos && rht_entry(tpos, pos, member); \ + pos = rht_dereference_bucket_rcu(pos->next, tbl, hash))/** * rht_for_each_entry_rcu - iterate over rcu hash chain of given type -* @pos: type * to use as a loop cursor. -* @head: head of the hash chain (struct rhash_head *) -* @member: name of the rhash_head within the hashable struct. +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct rhash_head to use as a loop cursor. +* @tbl: the &struct bucket_table +* @hash: the hash value / bucket index +* @member: name of the &struct rhash_head within the hashable struct. * * This hash chain list-traversal primitive may safely run concurrently with -* the _rcu fkht mutation primitives such as rht_insert() as long as the +* the _rcu mutation primitives such as rhashtable_insert() as long as the * traversal is guarded by rcu_read_lock(). */ -#define rht_for_each_entry_rcu (pos, head, member) \ - for (pos = rht_entry_safe(rcu_dereference_raw(head), \ - typeof(*(pos)), member); \ - pos; \ - pos = rht_entry_safe(rcu_dereference_raw((pos)->member.next), \ - typeof(*(pos)), member)) +#define rht_for_each_entry_rcu (tpos, pos, tbl, hash, member) \ + rht_for_each_entry_rcu_continue(tpos, pos, (tbl)->buckets[hash],\ + tbl, hash, member)#endif /* _LINUX_RHASHTABLE_H */
lib/rhashtable.c +19 -11
@@ -35,6 +35,12 @@ int lockdep_rht_mutex_is_held(const struct rhashtable *ht) return ht->p.mutex_is_held(ht->p.parent); }EXPORT_SYMBOL_GPL (lockdep_rht_mutex_is_held); + +int lockdep_rht_bucket_is_held (const struct bucket_table *tbl,u32 hash) +{ +return 1 ; +} +EXPORT_SYMBOL_GPL (lockdep_rht_bucket_is_held);#endif static void *rht_obj (const struct rhashtable *ht,const struct rhash_head *he)
@@ -141,7 +147,7 @@ static void hashtable_chain_unzip(const struct rhashtable *ht,* previous node p. Call the previous node p; */ h =head_hashfn (ht, new_tbl, p); -rht_for_each (he, p->next, ht) { +rht_for_each_continue (he, p->next, old_tbl, n) {if (head_hashfn (ht, new_tbl, he) != h)break ; p = he;
@@ -153,7 +159,7 @@ static void hashtable_chain_unzip(const struct rhashtable *ht,*/ next =NULL ;if (he) { -rht_for_each (he, he->next, ht) { +rht_for_each_continue (he, he->next, old_tbl, n) {if (head_hashfn (ht, new_tbl, he) == h) { next = he;break ;
@@ -208,7 +214,7 @@ int rhashtable_expand(struct rhashtable *ht)*/ for (i =0 ; i < new_tbl->size; i++) { h =rht_bucket_index (old_tbl, i); -rht_for_each (he, old_tbl->buckets[h], ht) { +rht_for_each (he, old_tbl, h) {if (head_hashfn (ht, new_tbl, he) == i) {RCU_INIT_POINTER (new_tbl->buckets[i], he);break ;
@@ -286,7 +292,7 @@ int rhashtable_shrink(struct rhashtable *ht)* to the new bucket. */ for (pprev = &ntbl->buckets[i]; *pprev !=NULL ; - pprev = &rht_dereference (*pprev, ht)->next) + pprev = &rht_dereference_bucket (*pprev, ntbl, i)->next) ;RCU_INIT_POINTER (*pprev, tbl->buckets[i + ntbl->size]); }
@@ -386,7 +392,7 @@ bool rhashtable_remove(struct rhashtable *ht, struct rhash_head *obj) h =head_hashfn (ht, tbl, obj); pprev = &tbl->buckets[h]; -rht_for_each (he, tbl->buckets[h], ht) { +rht_for_each (he, tbl, h) {if (he != obj) { pprev = &he->next;continue ;
@@ -423,7 +429,7 @@ void *rhashtable_lookup(const struct rhashtable *ht, const void *key)BUG_ON (!ht->p.key_len); h =key_hashfn (ht, key, ht->p.key_len); -rht_for_each_rcu (he, tbl->buckets[h], ht) { +rht_for_each_rcu (he, tbl, h) {if (memcmp (rht_obj (ht, he) + ht->p.key_offset, key, ht->p.key_len))continue ;
@@ -457,7 +463,7 @@ void *rhashtable_lookup_compare(const struct rhashtable *ht, const void *key,u32 hash; hash =key_hashfn (ht, key, ht->p.key_len); -rht_for_each_rcu (he, tbl->buckets[hash], ht) { +rht_for_each_rcu (he, tbl, hash) {if (!compare (rht_obj (ht, he), arg))continue ;return rht_obj (ht, he);
@@ -625,6 +631,7 @@ static int __init test_rht_lookup(struct rhashtable *ht)static void test_bucket_stats (struct rhashtable *ht,bool quiet) {unsigned int cnt, rcu_cnt, i, total =0 ; +struct rhash_head *pos;struct test_obj *obj;struct bucket_table *tbl;
@@ -635,14 +642,14 @@ static void test_bucket_stats(struct rhashtable *ht, bool quiet)if (!quiet)pr_info (" [%#4x/%zu]" , i, tbl->size); -rht_for_each_entry_rcu (obj, tbl->buckets[i], node) { +rht_for_each_entry_rcu (obj, pos, tbl, i, node) { cnt++; total++;if (!quiet)pr_cont (" [%p]," , obj); } -rht_for_each_entry_rcu (obj, tbl->buckets[i], node) +rht_for_each_entry_rcu (obj, pos, tbl, i, node) rcu_cnt++;if (rcu_cnt != cnt)
@@ -664,7 +671,8 @@ static void test_bucket_stats(struct rhashtable *ht, bool quiet)static int __init test_rhashtable (struct rhashtable *ht) {struct bucket_table *tbl; -struct test_obj *obj, *next; +struct test_obj *obj; +struct rhash_head *pos, *next;int err;unsigned int i;
@@ -733,7 +741,7 @@ static int __init test_rhashtable(struct rhashtable *ht) 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) +rht_for_each_entry_safe (obj, pos, next, tbl, i, node)kfree (obj);return err;
net/netfilter/nft_hash.c +6 -6
@@ -142,7 +142,9 @@ static void nft_hash_walk(const struct nft_ctx *ctx, const struct nft_set *set, tbl =rht_dereference_rcu (priv->tbl, priv);for (i =0 ; i < tbl->size; i++) { -rht_for_each_entry_rcu (he, tbl->buckets[i], node) { +struct rhash_head *pos; + +rht_for_each_entry_rcu (he, pos, tbl, i, node) {if (iter->count < iter->skip) goto cont;
@@ -197,15 +199,13 @@ static void nft_hash_destroy(const struct nft_set *set) {const struct rhashtable *priv =nft_set_priv (set);const struct bucket_table *tbl = priv->tbl; -struct nft_hash_elem *he, *next; +struct nft_hash_elem *he; +struct rhash_head *pos, *next;unsigned int i;for (i =0 ; i < tbl->size; i++) { - for (he = rht_entry(tbl->buckets[i], struct nft_hash_elem, node); - he !=NULL ; he = next) { - next = rht_entry(he->node.next, struct nft_hash_elem, node); +rht_for_each_entry_safe (he, pos, next, tbl, i, node)nft_hash_elem_destroy (set, he); - } }rhashtable_destroy (priv); }
net/netlink/af_netlink.c +8 -4
@@ -2898,7 +2898,9 @@ static struct sock *netlink_seq_socket_idx(struct seq_file *seq, loff_t pos)const struct bucket_table *tbl =rht_dereference_rcu (ht->tbl, ht);for (j =0 ; j < tbl->size; j++) { -rht_for_each_entry_rcu (nlk, tbl->buckets[j], node) { +struct rhash_head *node; + +rht_for_each_entry_rcu (nlk, node, tbl, j, node) { s = (struct sock *)nlk;if (sock_net (s) !=seq_file_net (seq))
@@ -2926,6 +2928,8 @@ static void *netlink_seq_start(struct seq_file *seq, loff_t *pos)static void *netlink_seq_next (struct seq_file *seq,void *v,loff_t *pos) {struct rhashtable *ht; +const struct bucket_table *tbl; +struct rhash_head *node;struct netlink_sock *nlk;struct nl_seq_iter *iter;struct net *net;
@@ -2942,17 +2946,17 @@ static void *netlink_seq_next(struct seq_file *seq, void *v, loff_t *pos) i = iter->link; ht = &nl_table[i].hash; -rht_for_each_entry (nlk, nlk->node.next, ht, node) + tbl =rht_dereference_rcu (ht->tbl, ht); +rht_for_each_entry_rcu_continue (nlk, node, nlk->node.next, tbl, iter->hash_idx, node)if (net_eq (sock_net ((struct sock *)nlk), net))return nlk; j = iter->hash_idx +1 ; do { - const struct bucket_table *tbl = rht_dereference_rcu(ht->tbl, ht); for (; j < tbl->size; j++) { - rht_for_each_entry(nlk, tbl->buckets[j], ht, node) { + rht_for_each_entry_rcu(nlk, node, tbl, j, node) { if (net_eq(sock_net((struct sock *)nlk), net)) { iter->link = i; iter->hash_idx = j;
net/netlink/diag.c +3 -1
@@ -113,7 +113,9 @@ static int __netlink_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, req =nlmsg_data (cb->nlh);for (i =0 ; i < htbl->size; i++) { -rht_for_each_entry (nlsk, htbl->buckets[i], ht, node) { +struct rhash_head *pos; + +rht_for_each_entry (nlsk, pos, htbl, i, node) { sk = (struct sock *)nlsk;if (!net_eq (sock_net (sk), net))
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 4/9 rhashtable: Factor out bucket_tail() function
In reply to Thomas GrafSubsequent patches will require access to the bucket tail. Access to the tail is relatively cheap as the automatic resizing of the table should keep the number of entries per bucket to no more than 0.75 on average.
--- lib/rhashtable.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-)
lib/rhashtable.c +14 -9
@@ -83,6 +83,18 @@ static u32 head_hashfn(const struct rhashtable *ht,return rht_bucket_index (tbl,obj_raw_hashfn (ht,rht_obj (ht, he))); } +static struct rhash_head __rcu **bucket_tail (struct bucket_table *tbl,u32 n) +{ +struct rhash_head __rcu **pprev; + +for (pprev = &tbl->buckets[n]; +rht_dereference_bucket (*pprev, tbl, n); + pprev = &rht_dereference_bucket (*pprev, tbl, n)->next) + ; + +return pprev; +} +static struct bucket_table *bucket_table_alloc (size_t nbuckets) {struct bucket_table *tbl;
@@ -266,7 +278,6 @@ EXPORT_SYMBOL_GPL(rhashtable_expand);int rhashtable_shrink (struct rhashtable *ht) {struct bucket_table *ntbl, *tbl =rht_dereference (ht->tbl, ht); -struct rhash_head __rcu **pprev;unsigned int i;ASSERT_RHT_MUTEX (ht);
@@ -286,15 +297,9 @@ int rhashtable_shrink(struct rhashtable *ht)*/ for (i =0 ; i < ntbl->size; i++) { ntbl->buckets[i] = tbl->buckets[i]; +RCU_INIT_POINTER (*bucket_tail (ntbl, i), + tbl->buckets[i + ntbl->size]); -/* 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_bucket (*pprev, ntbl, i)->next) - ; -RCU_INIT_POINTER (*pprev, tbl->buckets[i + ntbl->size]); }/* Publish the new, valid hash table */
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 5/9 nft_hash: Remove rhashtable_remove_pprev()
In reply to Thomas GrafThe removal function of nft_hash currently stores a reference to the previous element during lookup which is used to optimize removal later on. This was possible because a lock is held throughout calling rhashtable_lookup() and rhashtable_remove().
With the introdution of deferred table resizing in parallel to lookups and insertions, the nftables lock will no longer synchronize all table mutations and the stored pprev may become invalid.
Removing this optimization makes removal slightly more expensive on average but allows taking the resize cost out of the insert and remove path.
--- include/linux/rhashtable.h | 2 -- lib/rhashtable.c | 34 +++++++--------------------------- net/netfilter/nft_hash.c | 11 +++-------- 3 files changed, 10 insertions(+), 37 deletions(-)
include/linux/rhashtable.h +0 -2
@@ -105,8 +105,6 @@ int rhashtable_init(struct rhashtable *ht, struct rhashtable_params *params);void rhashtable_insert (struct rhashtable *ht,struct rhash_head *node);bool rhashtable_remove (struct rhashtable *ht,struct rhash_head *node); -void rhashtable_remove_pprev (struct rhashtable *ht,struct rhash_head *obj, -struct rhash_head __rcu **pprev);bool rht_grow_above_75 (const struct rhashtable *ht,size_t new_size);bool rht_shrink_below_30 (const struct rhashtable *ht,size_t new_size);
lib/rhashtable.c +7 -27
@@ -345,32 +345,6 @@ void rhashtable_insert(struct rhashtable *ht, struct rhash_head *obj)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 -* -* 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) -{ -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); -} -EXPORT_SYMBOL_GPL (rhashtable_remove_pprev); - -/** * rhashtable_remove - remove object from hash table * @ht: hash table * @obj: pointer to hash head inside object
@@ -403,7 +377,13 @@ bool rhashtable_remove(struct rhashtable *ht, struct rhash_head *obj)continue ; } -rhashtable_remove_pprev (ht, he, pprev); +RCU_INIT_POINTER (*pprev, he->next); + ht->nelems--; + +if (ht->p.shrink_decision && + ht->p.shrink_decision (ht, tbl->size)) +rhashtable_shrink (ht); +return true; }
net/netfilter/nft_hash.c +3 -8
@@ -83,15 +83,10 @@ static void nft_hash_remove(const struct nft_set *set, const struct nft_set_elem *elem) {struct rhashtable *priv =nft_set_priv (set); -struct rhash_head *he,__rcu **pprev; - - pprev = elem->cookie; - he =rht_dereference ((*pprev), priv); - -rhashtable_remove_pprev (priv, he, pprev); +rhashtable_remove (priv, elem->cookie);synchronize_rcu (); -kfree (he); +kfree (elem->cookie); }struct nft_compare_arg {
@@ -105,7 +100,7 @@ static bool nft_hash_compare(void *ptr, void *arg)struct nft_compare_arg *x = arg;if (!nft_data_cmp (&he->key, &x->elem->key, x->set->klen)) { - x->elem->cookie = &he->node; + x->elem->cookie = he; x->elem->flags =0 ;if (x->set->flags &NFT_SET_MAP )nft_data_copy (&x->elem->data, he->data);
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 6/9 spinlock: Add spin_lock_bh_nested()
In reply to Thomas Graf--- include/linux/spinlock.h | 8 ++++++++ include/linux/spinlock_api_smp.h | 2 ++ include/linux/spinlock_api_up.h | 1 + kernel/locking/spinlock.c | 8 ++++++++ 4 files changed, 19 insertions(+)
include/linux/spinlock.h +8 -0
@@ -190,6 +190,8 @@ static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)#ifdef CONFIG_DEBUG_LOCK_ALLOC # define raw_spin_lock_nested (lock, subclass) \ _raw_spin_lock_nested(lock, subclass) +# define raw_spin_lock_bh_nested (lock, subclass) \ + _raw_spin_lock_bh_nested(lock, subclass)# define raw_spin_lock_nest_lock (lock, nest_lock) \ do { \
@@ -205,6 +207,7 @@ static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)# define raw_spin_lock_nested (lock, subclass) \ _raw_spin_lock(((void)(subclass), (lock)))# define raw_spin_lock_nest_lock (lock, nest_lock) _raw_spin_lock(lock) +# define raw_spin_lock_bh_nested (lock, subclass) _raw_spin_lock_bh(lock)#endif #if defined(CONFIG_SMP ) || defined(CONFIG_DEBUG_SPINLOCK )
@@ -324,6 +327,11 @@ do { \raw_spin_lock_nested (spinlock_check (lock), subclass); \ } while (0) +#define spin_lock_bh_nested (lock, subclass) \ +do { \ + raw_spin_lock_bh_nested(spinlock_check(lock), subclass);\ +} while (0) +#define spin_lock_nest_lock (lock, nest_lock) \ do { \ raw_spin_lock_nest_lock(spinlock_check(lock), nest_lock); \
include/linux/spinlock_api_smp.h +2 -0
@@ -22,6 +22,8 @@ int in_lock_functions(unsigned long addr); void __lockfunc _raw_spin_lock(raw_spinlock_t *lock) __acquires(lock); void __lockfunc _raw_spin_lock_nested(raw_spinlock_t *lock, int subclass) __acquires(lock); +void __lockfunc _raw_spin_lock_bh_nested(raw_spinlock_t *lock, int subclass) + __acquires(lock); void __lockfunc _raw_spin_lock_nest_lock(raw_spinlock_t *lock, struct lockdep_map *map) __acquires(lock);
include/linux/spinlock_api_up.h +1 -0
@@ -57,6 +57,7 @@#define _raw_spin_lock (lock) __LOCK(lock)#define _raw_spin_lock_nested (lock, subclass) __LOCK(lock) +#define _raw_spin_lock_bh_nested (lock, subclass) __LOCK(lock)#define _raw_read_lock (lock) __LOCK(lock)#define _raw_write_lock (lock) __LOCK(lock)#define _raw_spin_lock_bh (lock) __LOCK_BH(lock)
kernel/locking/spinlock.c +8 -0
@@ -363,6 +363,14 @@ void __lockfunc _raw_spin_lock_nested(raw_spinlock_t *lock, int subclass) }EXPORT_SYMBOL (_raw_spin_lock_nested); +void __lockfunc _raw_spin_lock_bh_nested(raw_spinlock_t *lock, int subclass) +{ +__local_bh_disable_ip (_RET_IP_,SOFTIRQ_LOCK_OFFSET ); +spin_acquire (&lock->dep_map, subclass,0 , _RET_IP_); +LOCK_CONTENDED (lock, do_raw_spin_trylock, do_raw_spin_lock); +} +EXPORT_SYMBOL (_raw_spin_lock_bh_nested); +unsigned long __lockfunc _raw_spin_lock_irqsave_nested (raw_spinlock_t *lock,int subclass) {
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 7/9 rhashtable: Per bucket locks & deferred expansion/shrinking
In reply to Thomas GrafIntroduces an array of spinlocks to protect bucket mutations. The number of spinlocks per CPU is configurable and selected based on the hash of the bucket. This allows for parallel insertions and removals of entries which do not share a lock.
The patch also defers expansion and shrinking to a worker queue which allows insertion and removal from atomic context. Insertions and deletions may occur in parallel to it and are only held up briefly while the particular bucket is linked or unzipped.
Mutations of the bucket table pointer is protected by a new mutex, read access is RCU protected.
In the event of an expansion or shrinking, the new bucket table allocated is exposed as a so called future table as soon as the resize process starts. Lookups, deletions, and insertions will briefly use both tables. The future table becomes the main table after an RCU grace period and initial linking of the old to the new table was performed. Optimization of the chains to make use of the new number of buckets follows only the new table is in use.
The side effect of this is that during that RCU grace period, a bucket traversal using any rht_for_each() variant on the main table will not see any insertions performed during the RCU grace period which would at that point land in the future table. The lookup will see them as it searches both tables if needed.
Having multiple insertions and removals occur in parallel requires nelems to become an atomic counter.
--- include/linux/rhashtable.h | 37 ++-- lib/rhashtable.c | 458 ++++++++++++++++++++++++++++++++++----------- net/netfilter/nft_hash.c | 27 ++- net/netlink/af_netlink.c | 15 +- 4 files changed, 384 insertions(+), 153 deletions(-)
include/linux/rhashtable.h +26 -11
@@ -19,6 +19,7 @@#define _LINUX_RHASHTABLE_H#include <linux/rculist.h> +#include <linux/workqueue.h> struct rhash_head {struct rhash_head __rcu *next;
@@ -26,8 +27,17 @@ struct rhash_head {#define INIT_HASH_HEAD (ptr) ((ptr)->next = NULL) +/** +* struct bucket_table - Table of hash buckets +* @size: Number of hash buckets +* @locks_mask: Mask to apply before accessing locks[] +* @locks: Array of spinlocks protecting individual buckets +* @buckets: size * hash buckets +*/ struct bucket_table {size_t size; +unsigned int locks_mask; +spinlock_t *locks;struct rhash_head __rcu *buckets[]; };
@@ -45,11 +55,11 @@ struct rhashtable;* @hash_rnd: Seed to use while hashing * @max_shift: Maximum number of shifts while expanding * @min_shift: Minimum number of shifts while shrinking +* @locks_mul: Number of bucket locks to allocate per cpu (default: 128) * @hashfn: Function to hash key * @obj_hashfn: Function to hash object * @grow_decision: If defined, may return true if table should expand * @shrink_decision: If defined, may return true if table should shrink -* @mutex_is_held: Must return true if protecting mutex is held */ struct rhashtable_params {size_t nelem_hint;
@@ -59,37 +69,42 @@ struct rhashtable_params {u32 hash_rnd;size_t max_shift;size_t min_shift; +size_t locks_mul;rht_hashfn_t hashfn;rht_obj_hashfn_t obj_hashfn;bool (*grow_decision)(const struct rhashtable *ht,size_t new_size);bool (*shrink_decision)(const struct rhashtable *ht,size_t new_size); -#ifdef CONFIG_PROVE_LOCKING -int (*mutex_is_held)(void *parent); -void *parent; -#endif };/** * struct rhashtable - Hash table handle * @tbl: Bucket table +* @future_tbl: Table under construction during expansion/shrinking * @nelems: Number of elements in table * @shift: Current size (1 << shift) * @p: Configuration parameters +* @run_work: Deferred worker to expand/shrink asynchronously +* @mutex: Mutex to protect current/future table swapping +* @being_destroyed: True if table is set up for destruction */ struct rhashtable {struct bucket_table __rcu *tbl; -size_t nelems; +struct bucket_table __rcu *future_tbl; +atomic_t nelems;size_t shift;struct rhashtable_params p; +struct delayed_work run_work; +struct mutex mutex; +bool being_destroyed; };#ifdef CONFIG_PROVE_LOCKING -int lockdep_rht_mutex_is_held (const struct rhashtable *ht); +int lockdep_rht_mutex_is_held (struct rhashtable *ht);int lockdep_rht_bucket_is_held (const struct bucket_table *tbl,u32 hash);#else -static inline int lockdep_rht_mutex_is_held (const struct rhashtable *ht) +static inline int lockdep_rht_mutex_is_held (struct rhashtable *ht) {return 1 ; }
@@ -112,11 +127,11 @@ bool rht_shrink_below_30(const struct rhashtable *ht, size_t new_size);int rhashtable_expand (struct rhashtable *ht);int rhashtable_shrink (struct rhashtable *ht); -void *rhashtable_lookup (const struct rhashtable *ht,const void *key); -void *rhashtable_lookup_compare (const struct rhashtable *ht,const void *key, +void *rhashtable_lookup (struct rhashtable *ht,const void *key); +void *rhashtable_lookup_compare (struct rhashtable *ht,const void *key,bool (*compare)(void *,void *),void *arg); -void rhashtable_destroy (const struct rhashtable *ht); +void rhashtable_destroy (struct rhashtable *ht);#define rht_dereference (p, ht) \ rcu_dereference_protected(p, lockdep_rht_mutex_is_held(ht))
lib/rhashtable.c +344 -114
748 lines not on this page · raw message
net/netfilter/nft_hash.c +12 -15
@@ -33,7 +33,7 @@ static bool nft_hash_lookup(const struct nft_set *set,const struct nft_data *key, struct nft_data *data) { - const struct rhashtable *priv = nft_set_priv(set); + struct rhashtable *priv = nft_set_priv(set);const struct nft_hash_elem *he; he =rhashtable_lookup (priv, key);
@@ -113,7 +113,7 @@ static bool nft_hash_compare(void *ptr, void *arg)static int nft_hash_get (const struct nft_set *set,struct nft_set_elem *elem) { -const struct rhashtable *priv =nft_set_priv (set); +struct rhashtable *priv =nft_set_priv (set);struct nft_compare_arg arg = { .set = set, .elem = elem,
@@ -129,7 +129,7 @@ static int nft_hash_get(const struct nft_set *set, struct nft_set_elem *elem)static void nft_hash_walk (const struct nft_ctx *ctx,const struct nft_set *set,struct nft_set_iter *iter) { -const struct rhashtable *priv =nft_set_priv (set); +struct rhashtable *priv =nft_set_priv (set);const struct bucket_table *tbl;const struct nft_hash_elem *he;struct nft_set_elem elem;
@@ -162,13 +162,6 @@ static unsigned int nft_hash_privsize(const struct nlattr * const nla[])return sizeof (struct rhashtable ); } -#ifdef CONFIG_PROVE_LOCKING -static int lockdep_nfnl_lock_is_held (void *parent) -{ -return lockdep_nfnl_is_held (NFNL_SUBSYS_NFTABLES ); -} -#endif -static int nft_hash_init (const struct nft_set *set,const struct nft_set_desc *desc,const struct nlattr *const tb[])
@@ -182,9 +175,6 @@ static int nft_hash_init(const struct nft_set *set, .hashfn = jhash, .grow_decision = rht_grow_above_75, .shrink_decision = rht_shrink_below_30, -#ifdef CONFIG_PROVE_LOCKING - .mutex_is_held = lockdep_nfnl_lock_is_held, -#endif };return rhashtable_init (priv, ¶ms);
@@ -192,16 +182,23 @@ static int nft_hash_init(const struct nft_set *set,static void nft_hash_destroy (const struct nft_set *set) { -const struct rhashtable *priv =nft_set_priv (set); -const struct bucket_table *tbl = priv->tbl; +struct rhashtable *priv =nft_set_priv (set); +const struct bucket_table *tbl;struct nft_hash_elem *he;struct rhash_head *pos, *next;unsigned int i; +/* Stop an eventual async resizing */ + priv->being_destroyed = true; +mutex_lock (&priv->mutex); + + tbl =rht_dereference (priv->tbl, priv);for (i =0 ; i < tbl->size; i++) {rht_for_each_entry_safe (he, pos, next, tbl, i, node)nft_hash_elem_destroy (set, he); } +mutex_unlock (&priv->mutex); +rhashtable_destroy (priv); }
net/netlink/af_netlink.c +2 -13
@@ -114,15 +114,6 @@ static atomic_t nl_table_users = ATOMIC_INIT(0);DEFINE_MUTEX (nl_sk_hash_lock);EXPORT_SYMBOL_GPL (nl_sk_hash_lock); -#ifdef CONFIG_PROVE_LOCKING -static int lockdep_nl_sk_hash_is_held (void *parent) -{ -if (debug_locks) -return lockdep_is_held (&nl_sk_hash_lock) ||lockdep_is_held (&nl_table_lock); -return 1 ; -} -#endif -static ATOMIC_NOTIFIER_HEAD (netlink_chain);static DEFINE_SPINLOCK (netlink_tap_lock);
@@ -1063,7 +1054,8 @@ static int netlink_insert(struct sock *sk, struct net *net, u32 portid) goto err; err = -ENOMEM ; -if (BITS_PER_LONG >32 &&unlikely (table->hash.nelems >=UINT_MAX )) +if (BITS_PER_LONG >32 && +unlikely (atomic_read (&table->hash.nelems) >=UINT_MAX )) goto err;nlk_sk (sk)->portid = portid;
@@ -3122,9 +3114,6 @@ static int __init netlink_proto_init(void) .max_shift = 16, /* 64K */ .grow_decision = rht_grow_above_75, .shrink_decision = rht_shrink_below_30, -#ifdef CONFIG_PROVE_LOCKING - .mutex_is_held = lockdep_nl_sk_hash_is_held, -#endif };if (err !=0 )
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 8/9 rhashtable: Supports for nulls marker
In reply to Thomas GrafIn order to allow for wider usage of rhashtable, use a special nulls marker to terminate each chain. The reason for not using the existing nulls_list is that the prev pointer usage would not be valid as entries can be linked in two different buckets at the same time.
The 4 nulls base bits can be set through the rhashtable_params structure like this:
struct rhashtable_params params = {
[...]
.nulls_base = (1U << RHT_BASE_SHIFT),
};This reduces the hash length from 32 bits to 27 bits.
--- include/linux/list_nulls.h | 3 ++- include/linux/rhashtable.h | 57 ++++++++++++++++++++++++++++++++++++++-------- lib/rhashtable.c | 37 ++++++++++++++++++++++++------ 3 files changed, 79 insertions(+), 18 deletions(-)
include/linux/list_nulls.h +2 -1
@@ -21,8 +21,9 @@ struct hlist_nulls_head {struct hlist_nulls_node {struct hlist_nulls_node *next, **pprev; }; +#define NULLS_MARKER (value) (1UL | (((long)value) << 1))#define INIT_HLIST_NULLS_HEAD (ptr, nulls) \ - ((ptr)->first = (struct hlist_nulls_node *) (1UL | (((long)nulls) << 1))) + ((ptr)->first = (struct hlist_nulls_node *) NULLS_MARKER(nulls))#define hlist_nulls_entry (ptr, type, member) container_of(ptr,type,member)/**
include/linux/rhashtable.h +47 -10
117 lines not on this page · raw message
lib/rhashtable.c +30 -7
@@ -28,6 +28,9 @@#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 ,
@@ -86,7 +89,7 @@ static u32 obj_raw_hashfn(const struct rhashtable *ht, const void *ptr) hash = ht->p.hashfn (ptr + ht->p.key_offset, ht->p.key_len, ht->p.hash_rnd); -return hash; +return hash >>HASH_RESERVED_SPACE ; }static u32 key_hashfn (struct rhashtable *ht,const void *key,u32 len)
@@ -95,6 +98,7 @@ static u32 key_hashfn(struct rhashtable *ht, const void *key, u32 len)u32 hash; hash = ht->p.hashfn (key, len, ht->p.hash_rnd); + hash >>=HASH_RESERVED_SPACE ;return rht_bucket_index (tbl, hash); }
@@ -111,7 +115,7 @@ static struct rhash_head __rcu **bucket_tail(struct bucket_table *tbl, u32 n)struct rhash_head __rcu **pprev;for (pprev = &tbl->buckets[n]; -rht_dereference_bucket (*pprev, tbl, n); + !rht_is_a_nulls (rht_dereference_bucket (*pprev, tbl, n)); pprev = &rht_dereference_bucket (*pprev, tbl, n)->next) ;
@@ -164,6 +168,7 @@ static struct bucket_table *bucket_table_alloc(struct rhashtable *ht, {struct bucket_table *tbl;size_t size; +int i; size =sizeof (*tbl) + nbuckets *sizeof (tbl->buckets[0 ]); tbl =kzalloc (size,GFP_KERNEL | __GFP_NOWARN);
@@ -180,6 +185,9 @@ static struct bucket_table *bucket_table_alloc(struct rhashtable *ht,return NULL ; } +for (i =0 ; i < nbuckets; i++) +INIT_RHT_NULLS_HEAD (tbl->buckets[i], ht, i); +return tbl; }
@@ -221,7 +229,7 @@ static void hashtable_chain_unzip(const struct rhashtable *ht,/* Old bucket empty, no work needed. */ p =rht_dereference_bucket (old_tbl->buckets[old_hash], old_tbl, old_hash); -if (!p) +if (rht_is_a_nulls (p))return ; new_hash = new_hash2 =head_hashfn (ht, new_tbl, p);
@@ -252,8 +260,8 @@ static void hashtable_chain_unzip(const struct rhashtable *ht,/* 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) { +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;
@@ -369,11 +377,15 @@ int rhashtable_expand(struct rhashtable *ht)*/ complete = true;for (old_hash =0 ; old_hash < old_tbl->size; old_hash++) { +struct rhash_head *head; + old_bucket_lock =bucket_lock (old_tbl, old_hash);spin_lock_bh (old_bucket_lock);hashtable_chain_unzip (ht, new_tbl, old_tbl, old_hash); -if (old_tbl->buckets[old_hash] !=NULL ) + head =rht_dereference_bucket (old_tbl->buckets[old_hash], + old_tbl, old_hash); +if (!rht_is_a_nulls (head)) complete = false;spin_unlock_bh (old_bucket_lock);
@@ -498,6 +510,7 @@ static void rht_deferred_worker(struct work_struct *work)void rhashtable_insert (struct rhashtable *ht,struct rhash_head *obj) {struct bucket_table *tbl; +struct rhash_head *head;spinlock_t *lock;unsigned hash;
@@ -508,7 +521,12 @@ void rhashtable_insert(struct rhashtable *ht, struct rhash_head *obj) lock =bucket_lock (tbl, hash);spin_lock_bh (lock); -RCU_INIT_POINTER (obj->next, tbl->buckets[hash]); + head =rht_dereference_bucket (tbl->buckets[hash], tbl, hash); +if (rht_is_a_nulls (head)) +INIT_RHT_NULLS_HEAD (obj->next, ht, hash); +else +RCU_INIT_POINTER (obj->next, head); +rcu_assign_pointer (tbl->buckets[hash], obj);spin_unlock_bh (lock);
@@ -709,6 +727,7 @@ static size_t rounded_hashtable_size(struct rhashtable_params *params)* .key_offset = offsetof(struct test_obj, key), * .key_len = sizeof(int), * .hashfn = jhash, +* .nulls_base = (1U << RHT_BASE_SHIFT), * }; * * Configuration Example 2: Variable length keys
@@ -741,6 +760,9 @@ int rhashtable_init(struct rhashtable *ht, struct rhashtable_params *params) (!params->key_len && !params->obj_hashfn))return -EINVAL ; +if (params->nulls_base && params->nulls_base < (1U <<RHT_BASE_SHIFT )) +return -EINVAL ; + params->min_shift =max_t (size_t, params->min_shift,ilog2 (HASH_MIN_SIZE ));
@@ -974,6 +996,7 @@ static int __init test_rht_init(void) .key_offset = offsetof(struct test_obj, value), .key_len = sizeof(int), .hashfn = jhash, + .nulls_base = (3U << RHT_BASE_SHIFT), .grow_decision = rht_grow_above_75, .shrink_decision = rht_shrink_below_30, };
Signature · 1 line
1.9.3
Thomas Graf
maintainerPATCH v2 9/9 netlink: Lockless lookup with RCU grace period in socket release
In reply to Thomas GrafDefers the release of the socket reference using call_rcu() to allow using an RCU read-side protected call to rhashtable_lookup()
This restores behaviour and performance gains as previously introduced by e341694 ("netlink: Convert netlink_lookup() to use RCU protected hash table") without the side effect of severely delayed socket destruction.
--- net/netlink/af_netlink.c | 32 ++++++++++++++++---------------- net/netlink/af_netlink.h | 1 + 2 files changed, 17 insertions(+), 16 deletions(-)
net/netlink/af_netlink.c +16 -16
@@ -97,12 +97,12 @@ static int netlink_dump(struct sock *sk);static void netlink_skb_destructor (struct sk_buff *skb);/* nl_table locking explained: -* Lookup and traversal are protected with nl_sk_hash_lock or nl_table_lock -* combined with an RCU read-side lock. Insertion and removal are protected -* with nl_sk_hash_lock while using RCU list modification primitives and may -* run in parallel to nl_table_lock protected lookups. Destruction of the -* Netlink socket may only occur *after* nl_table_lock has been acquired -* either during or after the socket has been removed from the list. +* Lookup and traversal are protected with an RCU read-side lock. Insertion +* and removal are protected with nl_sk_hash_lock while using RCU list +* modification primitives and may run in parallel to RCU protected lookups. +* Destruction of the Netlink socket may only occur *after* nl_table_lock has +* been acquired * either during or after the socket has been removed from +* the list and after an RCU grace period. */ DEFINE_RWLOCK (nl_table_lock);EXPORT_SYMBOL_GPL (nl_table_lock);
@@ -1003,13 +1003,11 @@ static struct sock *netlink_lookup(struct net *net, int protocol, u32 portid)struct netlink_table *table = &nl_table[protocol];struct sock *sk; -read_lock (&nl_table_lock);rcu_read_lock (); sk =__netlink_lookup (table, portid, net);if (sk)sock_hold (sk);rcu_read_unlock (); -read_unlock (&nl_table_lock);return sk; }
@@ -1183,6 +1181,13 @@ out_module: goto out; } +static void deferred_put_nlk_sk (struct rcu_head *head) +{ + struct netlink_sock *nlk = container_of(head, struct netlink_sock, rcu); + +sock_put (&nlk->sk); +} +static int netlink_release (struct socket *sock) {struct sock *sk = sock->sk;
@@ -1248,7 +1253,7 @@ static int netlink_release(struct socket *sock)local_bh_disable ();sock_prot_inuse_add (sock_net (sk), &netlink_proto,-1 );local_bh_enable (); -sock_put (sk); +call_rcu (&nlk->rcu, deferred_put_nlk_sk);return 0 ; }
@@ -1263,7 +1268,6 @@ static int netlink_autobind(struct socket *sock) retry:cond_resched (); -netlink_table_grab ();rcu_read_lock ();if (__netlink_lookup (table, portid, net)) {/* Bind collision, search negative portid values. */
@@ -1271,11 +1275,9 @@ retry:if (rover >-4097 ) rover =-4097 ;rcu_read_unlock (); -netlink_table_ungrab (); goto retry; }rcu_read_unlock (); -netlink_table_ungrab (); err =netlink_insert (sk, net, portid);if (err == -EADDRINUSE )
@@ -2910,9 +2912,8 @@ static struct sock *netlink_seq_socket_idx(struct seq_file *seq, loff_t pos) }static void *netlink_seq_start (struct seq_file *seq,loff_t *pos) -__acquires (nl_table_lock)__acquires (RCU ) +__acquires (RCU ) { -read_lock (&nl_table_lock);rcu_read_lock ();return *pos ?netlink_seq_socket_idx (seq, *pos -1 ) :SEQ_START_TOKEN ; }
@@ -2964,10 +2965,9 @@ static void *netlink_seq_next(struct seq_file *seq, void *v, loff_t *pos) }static void netlink_seq_stop (struct seq_file *seq,void *v) -__releases (RCU )__releases (nl_table_lock) +__releases (RCU ) {rcu_read_unlock (); -read_unlock (&nl_table_lock); }
net/netlink/af_netlink.h +1 -0
@@ -50,6 +50,7 @@ struct netlink_sock {#endif /* CONFIG_NETLINK_MMAP */ struct rhash_head node; +struct rcu_head rcu; };static inline struct netlink_sock *nlk_sk (struct sock *sk)
Signature · 1 line
1.9.3
RFC 1/1 netlink: get rid of nl_table_lock
As a follow on to Thomas's patch I think this would complete the transistion to RCU for netlink.
Compile tested only.
This patch gets rid of the reader/writer nl_table_lock and replaces it with exclusively using RCU for reading, and a mutex for writing.
include/net/sock.h +2 -0
@@ -666,6 +666,8 @@ static inline void sk_add_bind_node(stru hlist_for_each_entry_safe(__sk, tmp, list, sk_node)#define sk_for_each_bound (__sk, list) \ hlist_for_each_entry(__sk, list, sk_bind_node) +#define sk_for_each_bound_rcu (__sk, list) \ + hlist_for_each_entry_rcu(__sk, list, sk_bind_node)/** * sk_nulls_for_each_entry_offset - iterate over a list at a given struct offset
net/netlink/af_netlink.c +18 -68
147 lines not on this page · raw message
net/netlink/diag.c +4 -4
@@ -136,7 +136,7 @@ static int __netlink_diag_dump(struct sk } } -sk_for_each_bound (sk, &tbl->mc_list) { +sk_for_each_bound_rcu (sk, &tbl->mc_list) {if (sk_hashed (sk))continue ;if (!net_eq (sock_net (sk), net))
@@ -171,7 +171,7 @@ static int netlink_diag_dump(struct sk_b req =nlmsg_data (cb->nlh);mutex_lock (&nl_sk_hash_lock); -read_lock (&nl_table_lock); +rcu_read_lock ();if (req->sdiag_protocol ==NDIAG_PROTO_ALL ) {int i;
@@ -183,7 +183,7 @@ static int netlink_diag_dump(struct sk_b } }else {if (req->sdiag_protocol >=MAX_LINKS ) { -read_unlock (&nl_table_lock); +rcu_read_unlock ();mutex_unlock (&nl_sk_hash_lock);return -ENOENT ; }
@@ -191,7 +191,7 @@ static int netlink_diag_dump(struct sk_b__netlink_diag_dump (skb, cb, req->sdiag_protocol, s_num); } -read_unlock (&nl_table_lock); +rcu_read_unlock ();mutex_unlock (&nl_sk_hash_lock);return skb->len;
David Miller
maintainerFrom: Thomas Graf <[email protected]>
Date: Fri, 2 Jan 2015 23:00:13 +0100
Quoted 6 lines
Prepares for and introduces per bucket spinlocks and deferred table resizing. This allows for parallel table mutations in different hash buckets from atomic context. The resizing occurs in the background in a separate worker thread while lookups, inserts, and removals can continue.
Quoted 6 lines
Also modified the chain linked list to be terminated with a special nulls marker to allow entries to move between multiple lists.
Last but not least, reintroduces lockless
netlink_lookup()with deferred Netlink socket destruction to avoid the side effect of increasednetlink_release()runtime.
I'm sure that this might need a minor tweak or two still, but I really like how you implemented this so I'll apply this series now.
Thanks!
David Miller
maintainerRe: [RFC] netlink: get rid of nl_table_lock
In reply to Stephen HemmingerFrom: Stephen Hemminger <[email protected]>
Date: Sat, 3 Jan 2015 11:02:11 -0800
Quoted 8 lines
As a follow on to Thomas's patch I think this would complete the transistion to RCU for netlink.
Compile tested only.This patch gets rid of the reader/writer
nl_table_lockand replaces it with exclusively using RCU for reading, and a mutex for writing.Signed-off-by Stephen Hemminger <[email protected]>
FWIW, this approach looks fine to me.
Thomas can you review this?
Thomas Graf
maintainerRe: [RFC] netlink: get rid of nl_table_lock
In reply to Stephen HemmingerQuoted 10 lines · Stephen Hemminger
As a follow on to Thomas's patch I think this would complete the transistion to RCU for netlink.
Compile tested only.This patch gets rid of the reader/writer
nl_table_lockand replaces it with exclusively using RCU for reading, and a mutex for writing.Signed-off-by Stephen Hemminger <[email protected]>
I like it. One thing I noticed it that it leaves a mix of native mutex unlocks and unlocks via netlink_table_ungrab().
The Open vSwitch upcall is a pretty good real world Netlink benchmark. I'll run the tests I have to see if this has any unexpected side effects.
Quoted 15 lines
void
netlink_table_grab(void)net/netlink/af_netlink.c +0
-13-__acquires (nl_table_lock) { -might_sleep (); - -write_lock_irq (&nl_table_lock); - -if (atomic_read (&nl_table_users)) { -DECLARE_WAITQUEUE (wait, current); - -add_wait_queue_exclusive (&nl_table_wait, &wait); -for (;;) { -set_current_state (TASK_UNINTERRUPTIBLE ); -if (atomic_read (&nl_table_users) ==0 ) -break ;
net/netlink/af_netlink.c static void netlink_sock_destruct(struct · line 931
Quoted 10 lines
net/netlink/af_netlink.c +1
-8-write_unlock_irq (&nl_table_lock); -schedule (); -write_lock_irq (&nl_table_lock); - } - -__set_current_state (TASK_RUNNING ); -remove_wait_queue (&nl_table_wait, &wait); - } +mutex_lock (&nl_table_mutex); }
I left this untouched so far as I wasn't clear on what side effect it would have to remove this as it does explicitely relax writers right now.
Re: [PATCH 1/9] rhashtable: Do hashing inside of rhashtable_lookup_compare()
In reply to Thomas GrafQuoted 3 lines · Thomas Graf
Hash the key inside of
rhashtable_lookup_compare()likerhashtable_lookup()does. This allows to simplify the hashing functions and keep them private.
One more question:
Quoted 13 lines
net/netfilter/nft_hash.c +5
-0@@ -94,28 +94,40 @@ static void nft_hash_remove(const struct nft_set *set,kfree (he); } +struct nft_compare_arg { +const struct nft_set *set; +struct nft_set_elem *elem; +}; +
net/netfilter/nft_hash.c static void nft_hash_remove(const struct nft_set *set, · line 111
Quoted 10 lines
net/netfilter/nft_hash.c +10
-0+static bool nft_hash_compare (void *ptr,void *arg) +{ +struct nft_hash_elem *he = ptr; +struct nft_compare_arg *x = arg; + +if (!nft_data_cmp (&he->key, &x->elem->key, x->set->klen)) { + x->elem->cookie = &he->node; + x->elem->flags =0 ; +if (x->set->flags &NFT_SET_MAP ) +nft_data_copy (&x->elem->data, he->data);
Is there any reason why we need to perform the assignments in the compare function? The reason why I'm asking is because to add timeout support, I need another compare function for nft_hash_lookup() and I'd prefer to use a single one for both cases.
Quoted 39 lines
net/netfilter/nft_hash.c +14
-17+ +return true; + } + +return false; +} +static int nft_hash_get (const struct nft_set *set,struct nft_set_elem *elem) {const struct rhashtable *priv =nft_set_priv (set); -const struct bucket_table *tbl =rht_dereference_rcu (priv->tbl, priv); -struct rhash_head __rcu *const *pprev; -struct nft_hash_elem *he; -u32 h; - - h =rhashtable_hashfn (priv, &elem->key, set->klen); - pprev = &tbl->buckets[h]; -rht_for_each_entry_rcu (he, tbl->buckets[h], node) { -if (nft_data_cmp (&he->key, &elem->key, set->klen)) { - pprev = &he->node.next; -continue ; - } +struct nft_compare_arg arg = { + .set = set, + .elem = elem, + }; - elem->cookie = (void *)pprev; - elem->flags =0 ; -if (set->flags &NFT_SET_MAP ) -nft_data_copy (&elem->data, he->data); +if (rhashtable_lookup_compare (priv, &elem->key, + &nft_hash_compare, &arg))return 0 ; - } +return -ENOENT ; }
Thomas Graf
maintainerRe: [PATCH 1/9] rhashtable: Do hashing inside of rhashtable_lookup_compare()
Quoted 15 lines · Patrick McHardy
net/netfilter/nft_hash.c static void nft_hash_remove(const struct nft_set *set, · line 111
Quoted 9 lines · Thomas Graf
net/netfilter/nft_hash.c +9
-0+{ +struct nft_hash_elem *he = ptr; +struct nft_compare_arg *x = arg; + +if (!nft_data_cmp (&he->key, &x->elem->key, x->set->klen)) { + x->elem->cookie = &he->node; + x->elem->flags =0 ; +if (x->set->flags &NFT_SET_MAP ) +nft_data_copy (&x->elem->data, he->data);Is there any reason why we need to perform the assignments in the compare function? The reason why I'm asking is because to add timeout support, I need another compare function for
nft_hash_lookup()and I'd prefer to use a single one for both cases.
No. I kept the nft_hash code as intact as possible without changing any semantics (aside from the lost optimiztion of keeping the pprev pointer from the lookup when doing removals). No reason speaks against moving it out.