Convert the UDP hash lock to RCU
Subsystems (7)
PATCH 3/3 Convert the UDP hash lock to RCU
Change the UDP hash lock from an rwlock to RCU.
--- include/net/udp.h | 9 +++++---- net/ipv4/udp.c | 47 +++++++++++++++++++++++++++-------------------- net/ipv6/udp.c | 17 +++++++++-------- 3 files changed, 41 insertions(+), 32 deletions(-)
include/net/udp.h +5 -4
@@ -51,7 +51,7 @@ struct udp_skb_cb {#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb))extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +extern spinlock_t udp_hash_wlock;/* Note: this must match 'valbool' in sock_setsockopt */
@@ -112,12 +112,13 @@ static inline void udp_lib_hash(struct sock *sk)static inline void udp_lib_unhash (struct sock *sk) { -write_lock_bh (&udp_hash_lock); -if (sk_del_node_init (sk)) { +spin_lock_bh (&udp_hash_wlock); +if (sk_del_node_init_rcu (sk)) {inet_sk (sk)->num =0 ;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); } -write_unlock_bh (&udp_hash_lock); +spin_unlock_bh (&udp_hash_wlock); +synchronize_rcu (); }static inline void udp_lib_close (struct sock *sk,long timeout)
net/ipv4/udp.c +27 -20
138 lines not on this page · raw message
net/ipv6/udp.c +9 -8
@@ -64,8 +64,8 @@ static struct sock *__udp6_lib_lookup(struct net *net,unsigned short hnum =ntohs (dport);int badness =-1 ; -read_lock (&udp_hash_lock); -sk_for_each (sk, node, &udptable[udp_hashfn (net, hnum)]) { +rcu_read_lock (); +sk_for_each_rcu (sk, node, &udptable[udp_hashfn (net, hnum)]) {struct inet_sock *inet =inet_sk (sk); if (net_eq(sock_net(sk), net) && sk->sk_hash == hnum &&
@@ -101,9 +101,10 @@ static struct sock *__udp6_lib_lookup(struct net *net, } } } +/* See comment in __udp4_lib_lookup on why this is safe. */ if (result)sock_hold (result); -read_unlock (&udp_hash_lock); +rcu_read_unlock ();return result; }
@@ -322,7 +323,7 @@ static struct sock *udp_v6_mcast_next(struct sock *sk,struct sock *s = sk;unsigned short num =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_for_each_from_rcu (s, node) {struct inet_sock *inet =inet_sk (s);if (sock_net (s) !=sock_net (sk))
@@ -365,8 +366,8 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,const struct udphdr *uh =udp_hdr (skb);int dif; -read_lock (&udp_hash_lock); - sk =sk_head (&udptable[udp_hashfn (net,ntohs (uh->dest))]); +rcu_read_lock (); + sk =sk_head_rcu (&udptable[udp_hashfn (net,ntohs (uh->dest))]); dif =inet6_iif (skb); sk =udp_v6_mcast_next (sk, uh->dest, daddr, uh->source, saddr, dif);if (!sk) {
@@ -375,7 +376,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } sk2 = sk; -while ((sk2 =udp_v6_mcast_next (sk_next (sk2), uh->dest, daddr, +while ((sk2 =udp_v6_mcast_next (sk_next_rcu (sk2), uh->dest, daddr, uh->source, saddr, dif))) {struct sk_buff *buff =skb_clone (skb,GFP_ATOMIC );if (buff) {
@@ -394,7 +395,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,sk_add_backlog (sk, skb);bh_unlock_sock (sk); out: -read_unlock (&udp_hash_lock); +rcu_read_unlock ();return 0 ; }
Signature · 1 line
1.5.6.5
Quoted 26 lines · Corey Minyard
Change the UDP hash lock from an rwlock to RCU.
Signed-off-by Corey Minyard <[email protected]>--- include/net/udp.h | 9 +++++---- net/ipv4/udp.c | 47 +++++++++++++++++++++++++++-------------------- net/ipv6/udp.c | 17 +++++++++-------- 3 files changed, 41 insertions(+), 32 deletions(-)include/net/udp.h +1
-1@@ -51,7 +51,7 @@ struct udp_skb_cb {#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb))extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +extern spinlock_t udp_hash_wlock;/* Note: this must match 'valbool' in sock_setsockopt */ @@ -112,12 +112,13 @@ static inline void udp_lib_hash(struct sock *sk)static inline void udp_lib_unhash (struct sock *sk) {
include/net/udp.h static inline void udp_lib_hash(struct sock *sk) · line 121
Quoted 10 lines
include/net/udp.h +4
-3-write_lock_bh (&udp_hash_lock); -if (sk_del_node_init (sk)) { +spin_lock_bh (&udp_hash_wlock); +if (sk_del_node_init_rcu (sk)) {inet_sk (sk)->num =0 ;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); } -write_unlock_bh (&udp_hash_lock); +spin_unlock_bh (&udp_hash_wlock); +synchronize_rcu ();
UDP central rwlock can hurt performance, because of cache line ping pong, so your patch really makes sense.
Me wondering what impact this synchronize_rcu() can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?
For "struct file" freeing, we chose call_rcu() instead of synchronize_rcu()
Maybe we could add a generic rcu head to struct sock, and use call_rcu() in sk_prot_free() for sockets needing RCU (udp after your patch is applied, maybe tcp on future patches, while I believe previous work on the subject concluded RCU was not giving good results for short lived http sessions) ?
Or just add SLAB_DESTROY_BY_RCU to slab creation in proto_register()
for "struct proto udp_prot/udpv6_prot" so that kmem_cache_free() done in sk_prot_free() can defer freeing to RCU...
David Miller
maintainerFrom: Eric Dumazet <[email protected]>
Date: Mon, 06 Oct 2008 23:22:31 +0200
Quoted 3 lines
Me wondering what impact this
synchronize_rcu()can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?
The cost is enormous, we really can't use it here.
I have a patch that did top-level socket destruction using RCU, and that didn't use synchronize_rcu(), and that killed connection rates by up to %20.
I can only imagine what the cost would be if I had to add such a call in there.
Really, I can't consider these changes seriously, as-is.
Quoted 11 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Mon, 06 Oct 2008 23:22:31 +0200Quoted 4 lines
Me wondering what impact this
synchronize_rcu()can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?The cost is enormous, we really can't use it here.
Quoted 10 lines
I have a patch that did top-level socket destruction using RCU, and that didn't use
synchronize_rcu(), and that killed connection rates by up to %20.I can only imagine what the cost would be if I had to add such a call in there.
Really, I can't consider these changes seriously, as-is.
Would using SLAB_DESTROY_BY_RCU be ok, or would that be too expensive?
-corey
Quoted 1 line · Corey Minyard
Would using
SLAB_DESTROY_BY_RCUbe ok, or would that be too expensive?
I tested skb destruction via RCU path, and got 2.5 times worse numbers with small-packets-bulk-transfer workload.
For more details
http://tservice.net.ru/~s0mbre/blog/devel/networking/2006_12_05_1.html
Signature · 1 line
Evgeniy Polyakov
Quoted 5 lines · Evgeniy Polyakov
Quoted 1 line · Corey Minyard
Would using
SLAB_DESTROY_BY_RCUbe ok, or would that be too expensive?I tested skb destruction via RCU path, and got 2.5 times worse numbers with small-packets-bulk-transfer workload.
Was this with regular RCU freeing? This will cool down the cacheline before frees. You need SLAB_DESTROY_BY_RCU to keep the objects cache hot.
Quoted 9 lines · Christoph Lameter
Quoted 5 lines · Evgeniy Polyakov
Quoted 1 line · Corey Minyard
Would using
SLAB_DESTROY_BY_RCUbe ok, or would that be too expensive?I tested skb destruction via RCU path, and got 2.5 times worse numbers with small-packets-bulk-transfer workload.
Was this with regular RCU freeing? This will cool down the cacheline before frees. You need
SLAB_DESTROY_BY_RCUto keep the objects cache hot.
Indeed!
But care is required -- SLAB_DESTROY_BY_RCU permits objects to be freed and reallocated while a reader holds a reference. The only guarantee is that the -type- of the data structure will not change while a reader holds a reference. With something like UDP, this might well be sufficient.
Just be careful! ;-)
Thanx, Paul
Quoted 4 lines · Paul E. McKenney
But care is required --
SLAB_DESTROY_BY_RCUpermits objects to be freed and reallocated while a reader holds a reference. The only guarantee is that the -type- of the data structure will not change while a reader holds a reference. With something like UDP, this might well be sufficient.
Right so after the hash lookup operation you are not assured that the object has not been freed or even reallocated for a different purpose. So after finding the pointer to the object two things need to happen (under rcu_lock):
1. Verify that the object is still in use
2. Verify that the object is matching the hash
If not then the operation needs to be redone because we have a stale hash pointer.
Quoted 7 lines · Christoph Lameter
Quoted 4 lines · Paul E. McKenney
But care is required --
SLAB_DESTROY_BY_RCUpermits objects to be freed and reallocated while a reader holds a reference. The only guarantee is that the -type- of the data structure will not change while a reader holds a reference. With something like UDP, this might well be sufficient.
Quoted 8 lines
Right so after the hash lookup operation you are not assured that the object has not been freed or even reallocated for a different purpose. So after finding the pointer to the object two things need to happen (under
rcu_lock):1. Verify that the object is still in use
2. Verify that the object is matching the hashIf not then the operation needs to be redone because we have a stale hash pointer.
OK... so restart full lookup at the begining of hash chain if we detect points 1 or 2 invalid.
Not that expensive since everything should be cache hot :)
One has to take care to group all components (keys to compute the hash, and the *next* pointer) in one cache line to minimize cache misses, since we now need to access them all to compute/check hash value.
Now if a freed object is re-inserted with same hash value, same hash chain, we'll also restart the lookup, but it is harmless.
Quoted 11 lines · Christoph Lameter
Quoted 4 lines · Paul E. McKenney
But care is required --
SLAB_DESTROY_BY_RCUpermits objects to be freed and reallocated while a reader holds a reference. The only guarantee is that the -type- of the data structure will not change while a reader holds a reference. With something like UDP, this might well be sufficient.Right so after the hash lookup operation you are not assured that the object has not been freed or even reallocated for a different purpose. So after finding the pointer to the object two things need to happen (under
rcu_lock):
Quoted 5 lines
1. Verify that the object is still in use
2. Verify that the object is matching the hashIf not then the operation needs to be redone because we have a stale hash pointer.
There is also the possibility that the element will be reused, but placed in the same list that it resided in last time. A reader referencing that item during this process might then be relocated in the list, which could either cause the reader to skip elements in the list (if the new element is relocated farther down the list) or endlessly loop through the list (if new elements were relocated closer to the list head and this free-reallocate process repeated and the reader was insanely unlucky).
Thanx, Paul
Quoted 5 lines · Christoph Lameter
Quoted 2 lines
I tested skb destruction via RCU path, and got 2.5 times worse numbers with small-packets-bulk-transfer workload.
Was this with regular RCU freeing? This will cool down the cacheline before frees. You need
SLAB_DESTROY_BY_RCUto keep the objects cache hot.
I believe there were no SLAB_DESTROY_BY_RCU 2 yars ago :)
It was pure call_rcu(&skb->rcu, real_skb_freeing), where real_skb_freeing() just did usual kfree().
Signature · 1 line
Evgeniy Polyakov
Quoted 7 lines · Evgeniy Polyakov
Quoted 4 lines · Christoph Lameter
Quoted 2 lines
I tested skb destruction via RCU path, and got 2.5 times worse numbers with small-packets-bulk-transfer workload.
Was this with regular RCU freeing? This will cool down the cacheline before frees. You need
SLAB_DESTROY_BY_RCUto keep the objects cache hot.I believe there were no
SLAB_DESTROY_BY_RCU2 yars ago :)
Its been awhile. Hugh created it
Quoted 2 lines
It was pure
call_rcu(&skb->rcu, real_skb_freeing), wherereal_skb_freeing()just did usualkfree().
Right. That results in cacheline cooldown. You'd want to recycle the object as they are cache hot on a per cpu basis. That is screwed up by the delayed regular rcu processing. We have seen multiple regressions due to cacheline cooldown.
The only choice in cacheline hot sensitive areas is to deal with the complexity that comes with SLAB_DESTROY_BY_RCU or give up on RCU.
Quoted 9 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Mon, 06 Oct 2008 23:22:31 +0200Quoted 3 lines
Me wondering what impact this
synchronize_rcu()can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?The cost is enormous, we really can't use it here.
Quoted 8 lines
I have a patch that did top-level socket destruction using RCU, and that didn't use
synchronize_rcu(), and that killed connection rates by up to %20.I can only imagine what the cost would be if I had to add such a call in there.
Really, I can't consider these changes seriously, as-is.
Yes, I suppose you are right about TCP sessions, that should stay as they are.
Then if we use call_rcu() RCU freeing only for UDP sockets, we should get rid of taking this rwlock each time we handle an incoming datagram, and introduce no extra cost for other sockets.
Most UDP sockets are setup for long periods (RTP trafic), or if an application really wants to {open/send or receive one UDP frame/close} many sockets, it already hits RCU handling of its file structures and should not be slowed down that much.
By 'long period' I mean thousand of packets sent/received by each RTP session, being voice (50 packets/second) or even worse video...
Quoted 6 lines · Eric Dumazet
Most UDP sockets are setup for long periods (RTP trafic), or if an application really wants to {open/send or receive one UDP frame/close} many sockets, it already hits RCU handling of its file structures and should not be slowed down that much.
By 'long period' I mean thousand of packets sent/received by each RTP session, being voice (50 packets/second) or even worse video...
Does DNS with port randomization need short lived sockets?
/Benny
Quoted 6 lines · Benny Amorsen
Quoted 4 lines · Eric Dumazet
Most UDP sockets are setup for long periods (RTP trafic), or if an application really wants to {open/send or receive one UDP frame/close} many sockets, it already hits RCU handling of its file structures and should not be slowed down that much.
I should have say 'Many' instead of 'Most' :)
Quoted 5 lines
Quoted 2 lines
By 'long period' I mean thousand of packets sent/received by each RTP session, being voice (50 packets/second) or even worse video...
Does DNS with port randomization need short lived sockets?
Yes very true, but current allocation of a random port can be very expensive, since we scan all the UDP hash table to select the smaller hash chain.
We stop the scan if we find an empty slot, but on machines with say more than 200 bound UDP sockets, they are probably no empty slots. (UDP_HTABLE_SIZE is 128)
bind(NULL port) algo is then O(N), N being number of bound UDP sockets.
So heavy DNS servers/proxies probably use a pool/range of pre-allocated sockets to avoid costs of allocating/freeing them ? If they dont care about that cost, the extra call_rcu() will be unnoticed.
For pathological (yet very common :) ) cases like single DNS query/answer, RCU would mean :
Pros :
- one few rwlock hit when receiving the answer (if any) Cons :
- one call_rcu() to delay socket freeing/reuse after RCU period.
So it might be a litle bit more expensive than without RCU
I agree I am more interested in optimizing UDP stack for heavy users like RTP servers/proxies handling xxx.000 packets/second than DNS users/servers. Shame on me :)
(2 weeks ago, Corey mentioned a 10x increase on UDP throughput on a 16-way machine, that sounds promising)
Quoted 36 lines · On Tue, 07 Oct 2008 14:59:20 +0200 Eric Dumazet <[email protected]> wrote:
Quoted 6 lines · Benny Amorsen
Quoted 4 lines · Eric Dumazet
Most UDP sockets are setup for long periods (RTP trafic), or if an application really wants to {open/send or receive one UDP frame/close} many sockets, it already hits RCU handling of its file structures and should not be slowed down that much.
I should have say 'Many' instead of 'Most' :)
Quoted 5 lines
Quoted 2 lines
By 'long period' I mean thousand of packets sent/received by each RTP session, being voice (50 packets/second) or even worse video...
Does DNS with port randomization need short lived sockets?
Yes very true, but current allocation of a random port can be very expensive, since we scan all the UDP hash table to select the smaller hash chain.
We stop the scan if we find an empty slot, but on machines with say more than 200 bound UDP sockets, they are probably no empty slots. (
UDP_HTABLE_SIZEis 128)bind(NULL port) algo is then O(N), N being number of bound UDP sockets.
So heavy DNS servers/proxies probably use a pool/range of pre-allocated sockets to avoid costs of allocating/freeing them ? If they dont care about that cost, the extra
call_rcu()will be unnoticed.For pathological (yet very common :) ) cases like single DNS query/answer, RCU would mean :
Pros :
- one few rwlock hit when receiving the answer (if any) Cons :
- onecall_rcu()to delay socket freeing/reuse after RCU period.
Quoted 8 lines
So it might be a litle bit more expensive than without RCU
I agree I am more interested in optimizing UDP stack for heavy users like RTP servers/proxies handling xxx.000 packets/second than DNS users/servers. Shame on me :)
(2 weeks ago, Corey mentioned a 10x increase on UDP throughput on a 16-way machine, that sounds promising)
The idea of keeping chains short is the problem. That code should just be pulled because it doesn't help that much, and also creates bias on the port randomization.
David Miller
maintainerFrom: Stephen Hemminger <[email protected]>
Date: Tue, 7 Oct 2008 16:07:29 +0200
Quoted 3 lines
The idea of keeping chains short is the problem. That code should just be pulled because it doesn't help that much, and also creates bias on the port randomization.
I have that patch from Vitaly Mayatskikh which does exactly this.
I keep looking at it, but I can't bring myself to apply it since I'm not completely convinced.
Quoted 11 lines · On Tue, 07 Oct 2008 13:55:48 -0700 (PDT) David Miller <[email protected]>…
From: Stephen Hemminger <[email protected]>
Date: Tue, 7 Oct 2008 16:07:29 +0200Quoted 3 lines
The idea of keeping chains short is the problem. That code should just be pulled because it doesn't help that much, and also creates bias on the port randomization.
I have that patch from Vitaly Mayatskikh which does exactly this.
I keep looking at it, but I can't bring myself to apply it since I'm not completely convinced.
Some one on a busy server should run it and measure the delta in hash chains. I would, but don't run anything that has more than a few UDP's open.
Quoted 11 lines · David Miller
From: Stephen Hemminger <[email protected]>
Date: Tue, 7 Oct 2008 16:07:29 +0200Quoted 3 lines
The idea of keeping chains short is the problem. That code should just be pulled because it doesn't help that much, and also creates bias on the port randomization.
I have that patch from Vitaly Mayatskikh which does exactly this.
I keep looking at it, but I can't bring myself to apply it since I'm not completely convinced.
Vitaly patch might be appropriate if only few UDP ports are opened.
We could zap the code to search short chains and extend Vitaly's idea with following patch :
[PATCH] udp: Improve port randomization
Current UDP port allocation is suboptimal.
We select the shortest chain to chose a port (out of 512) that will hash in this shortest chain.
First, it can lead to give not so ramdom ports and ease give attackers more opportunities to break the system.
Second, it can consume a lot of CPU to scan all table in order to find the shortest chain.
Third, in some pathological cases we can fail to find a free port even if they are plenty of them.
This patch zap the search for a short chain and only use one random seed. Problem of getting long chains should be addressed in another way, since we can obtain long chains with non random ports.
Based on a report and patch from Vitaly Mayatskikh
net/ipv4/udp.c +12 -44
@@ -155,55 +155,23 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,write_lock_bh (&udp_hash_lock);if (!snum) { -int i, low, high, remaining; -unsigned rover, best, best_size_so_far; +int low, high, remaining; +unsigned rand; +unsigned short first;inet_get_local_port_range (&low, &high); remaining = (high - low) +1 ; - best_size_so_far =UINT_MAX ; - best = rover =net_random () % remaining + low; - -/* 1st pass: look for empty (or shortest) hash chain */ -for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { -int size =0 ; - - head = &udptable[udp_hashfn (net, rover)]; -if (hlist_empty (head)) - goto gotit; - -sk_for_each (sk2, node, head) { -if (++size >= best_size_so_far) - goto next; - } - best_size_so_far = size; - best = rover; - next: -/* fold back if end of range */ -if (++rover > high) - rover = low + ((rover - low) - & (UDP_HTABLE_SIZE -1 )); - - - } - -/* 2nd pass: find hole in shortest hash chain */ - rover = best; -for (i =0 ; i < (1 <<16 ) /UDP_HTABLE_SIZE ; i++) { -if (!__udp_lib_lport_inuse (net, rover, udptable)) - goto gotit; - rover +=UDP_HTABLE_SIZE ; -if (rover > high) - rover = low + ((rover - low) - & (UDP_HTABLE_SIZE -1 )); + rand =net_random (); + snum = first = rand % remaining + low; + rand |=1 ; +while (__udp_lib_lport_inuse (net, snum, udptable)) { +do { + snum = snum + rand; + }while (snum < low || snum > high); +if (snum == first) + goto fail; } - - -/* All ports in use! */ - goto fail; - -gotit: - snum = rover; }else { head = &udptable[udp_hashfn (net, snum)];
David Miller
maintainerFrom: Eric Dumazet <[email protected]>
Date: Wed, 08 Oct 2008 15:55:36 +0200
Quoted 7 lines
Quoted 6 lines · David Miller
From: Stephen Hemminger <[email protected]>
Date: Tue, 7 Oct 2008 16:07:29 +0200Quoted 3 lines
The idea of keeping chains short is the problem. That code should just be pulled because it doesn't help that much, and also creates bias on the port randomization.
Quoted 8 lines
Quoted 3 lines
I have that patch from Vitaly Mayatskikh which does exactly this. I keep looking at it, but I can't bring myself to apply it since I'm not completely convinced.
Vitaly patch might be appropriate if only few UDP ports are opened.
We could zap the code to search short chains and extend Vitaly's idea with following patch :
I really like this, and I've applied it to net-next-2.6
I think the "increment until back in range" do/while loop can be improved a bit. It can spin for more than 60,000 iterations in some edge case scenerios as-is :-)
Ugh, there's also that expensive divide in there for the modulus.
PATCH 1/2 udp: introduce struct udp_table and multiple rwlocks
UDP sockets are hashed in a 128 slots hash table.
This hash table is protected by *one* rwlock.
This rwlock is readlocked each time an incoming UDP message is handled.
This rwlock is writelocked each time a socket must be inserted in hash table (bind time), or deleted from this table (close time)
This is not scalable on SMP machines :
1) Even in read mode, lock() and unlock() are atomic operations and must dirty a contended cache line, shared by all cpus.
2) A writer might be starved if many readers are 'in flight'. This can happen on a machine with some NIC receiving many UDP messages. User process can be delayed a long time at socket creation/dismantle time.
This patch prepares RCU migration, by introducing 'struct udp_table and struct udp_hslot', and using one rwlock per chain, to reduce contention on central rwlock.
Introducing one rwlock per chain reduces latencies, for port randomization on heavily loaded UDP servers.
Some cleanups were done to ease review of following patch.
---
include/net/sock.h | 2 include/net/udp.h | 25 ++-- include/net/udplite.h | 2 net/ipv4/udp.c | 208 +++++++++++++++++++++++----------------- net/ipv4/udp_impl.h | 4 net/ipv4/udplite.c | 13 +- net/ipv6/udp.c | 112 +++++++++++---------- net/ipv6/udp_impl.h | 4 net/ipv6/udplite.c | 8 - 9 files changed, 214 insertions(+), 164 deletions(-)
include/net/sock.h +1 -1
@@ -597,7 +597,7 @@ struct proto {union {struct inet_hashinfo *hashinfo; -struct hlist_head *udp_hash; +struct udp_table *udp_table;struct raw_hashinfo *raw_hash; } h;
include/net/udp.h +12 -13
@@ -50,8 +50,15 @@ struct udp_skb_cb { };#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb)) -extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +struct udp_hslot { +struct hlist_head head; +rwlock_t lock; +}; +struct udp_table { +struct udp_hslot hash[UDP_HTABLE_SIZE ]; +}; +extern struct udp_table udp_table; +extern void udp_table_init (struct udp_table *);/* Note: this must match 'valbool' in sock_setsockopt */
@@ -110,15 +117,7 @@ static inline void udp_lib_hash(struct sock *sk)BUG (); } -static inline void udp_lib_unhash (struct sock *sk) -{ -write_lock_bh (&udp_hash_lock); -if (sk_del_node_init (sk)) { -inet_sk (sk)->num =0 ; -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); - } -write_unlock_bh (&udp_hash_lock); -} +extern void udp_lib_unhash (struct sock *sk);static inline void udp_lib_close (struct sock *sk,long timeout) {
@@ -187,7 +186,7 @@ extern struct sock *udp4_lib_lookup(struct net *net, __be32 saddr, __be16 sport,struct udp_seq_afinfo {char *name;sa_family_t family; -struct hlist_head *hashtable; +struct udp_table *udp_table;struct file_operations seq_fops;struct seq_operations seq_ops; };
@@ -196,7 +195,7 @@ struct udp_iter_state {struct seq_net_private p;sa_family_t family;int bucket; -struct hlist_head *hashtable; +struct udp_table *udp_table; };#ifdef CONFIG_PROC_FS
include/net/udplite.h +1 -1
@@ -11,7 +11,7 @@#define UDPLITE_RECV_CSCOV 11/* receiver partial coverage (threshold ) */ extern struct proto udplite_prot; -extern struct hlist_head udplite_hash[UDP_HTABLE_SIZE ]; +extern struct udp_table udplite_table;/* * Checksum computation is all in software, hence simpler getfrag.
net/ipv4/udp.c +123 -85
408 lines not on this page · raw message
net/ipv4/udp_impl.h +2 -2
@@ -5,8 +5,8 @@#include <net/protocol.h> #include <net/inet_common.h> -extern int __udp4_lib_rcv (struct sk_buff *,struct hlist_head [],int ); -extern void __udp4_lib_err (struct sk_buff *,u32 ,struct hlist_head []); +extern int __udp4_lib_rcv (struct sk_buff *,struct udp_table *,int ); +extern void __udp4_lib_err (struct sk_buff *,u32 ,struct udp_table *);extern int udp_v4_get_port (struct sock *sk,unsigned short snum);
net/ipv4/udplite.c +7 -6
@@ -12,16 +12,17 @@*/ #include "udp_impl.h" -struct hlist_head udplite_hash[UDP_HTABLE_SIZE ]; +struct udp_table udplite_table; +EXPORT_SYMBOL (udplite_table);static int udplite_rcv (struct sk_buff *skb) { -return __udp4_lib_rcv (skb, udplite_hash,IPPROTO_UDPLITE ); +return __udp4_lib_rcv (skb, &udplite_table,IPPROTO_UDPLITE ); }static void udplite_err (struct sk_buff *skb,u32 info) { -__udp4_lib_err (skb, info, udplite_hash); +__udp4_lib_err (skb, info, &udplite_table); }static struct net_protocol udplite_protocol = {
@@ -50,7 +51,7 @@ struct proto udplite_prot = { .unhash = udp_lib_unhash, .get_port = udp_v4_get_port, .obj_size = sizeof(struct udp_sock), - .h.udp_hash = udplite_hash, + .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt, .compat_getsockopt = compat_udp_getsockopt,
@@ -71,7 +72,7 @@ static struct inet_protosw udplite4_protosw = {static struct udp_seq_afinfo udplite4_seq_afinfo = { .name ="udplite" , .family =AF_INET , - .hashtable = udplite_hash, + .udp_table = &udplite_table, .seq_fops = { .owner =THIS_MODULE , },
@@ -108,6 +109,7 @@ static inline int udplite4_proc_init(void)void __init udplite4_register (void ) { +udp_table_init (&udplite_table);if (proto_register (&udplite_prot,1 )) goto out_register_err;
@@ -126,5 +128,4 @@ out_register_err:printk (KERN_CRIT "%s: Cannot add UDP-Lite protocol.\n" , __func__); } -EXPORT_SYMBOL (udplite_hash);EXPORT_SYMBOL (udplite_prot);
net/ipv6/udp.c +62 -50
187 lines not on this page · raw message
net/ipv6/udp_impl.h +2 -2
@@ -7,9 +7,9 @@#include <net/inet_common.h> #include <net/transp_v6.h> -extern int __udp6_lib_rcv (struct sk_buff *,struct hlist_head [],int ); +extern int __udp6_lib_rcv (struct sk_buff *,struct udp_table *,int );extern void __udp6_lib_err (struct sk_buff *,struct inet6_skb_parm *, -int ,int ,int ,__be32 ,struct hlist_head []); +int ,int ,int ,__be32 ,struct udp_table *);extern int udp_v6_get_port (struct sock *sk,unsigned short snum);
net/ipv6/udplite.c +4 -4
@@ -15,14 +15,14 @@static int udplitev6_rcv (struct sk_buff *skb) { -return __udp6_lib_rcv (skb, udplite_hash,IPPROTO_UDPLITE ); +return __udp6_lib_rcv (skb, &udplite_table,IPPROTO_UDPLITE ); }static void udplitev6_err (struct sk_buff *skb,struct inet6_skb_parm *opt,int type,int code,int offset,__be32 info) { -__udp6_lib_err (skb, opt, type, code, offset, info, udplite_hash); +__udp6_lib_err (skb, opt, type, code, offset, info, &udplite_table); }static struct inet6_protocol udplitev6_protocol = {
@@ -49,7 +49,7 @@ struct proto udplitev6_prot = { .unhash = udp_lib_unhash, .get_port = udp_v6_get_port, .obj_size = sizeof(struct udp6_sock), - .h.udp_hash = udplite_hash, + .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt, .compat_getsockopt = compat_udpv6_getsockopt,
@@ -95,7 +95,7 @@ void udplitev6_exit(void)static struct udp_seq_afinfo udplite6_seq_afinfo = { .name ="udplite6" , .family =AF_INET6 , - .hashtable = udplite_hash, + .udp_table = &udplite_table, .seq_fops = { .owner =THIS_MODULE , },
Re: [PATCH 1/2] udp: introduce struct udp_table and multiple rwlocks
include/net/udp.h struct udp_skb_cb { · line 56
Quoted 6 lines · Eric Dumazet
include/net/udp.h +4
-2-extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +struct udp_hslot { +struct hlist_head head; +rwlock_t lock; +};
This structure should be aligned up to cacheline to reduce false sharing of more than one hslot.
net/ipv4/udp.c int udp_lib_get_port(struct sock *sk, unsigned short snum, · line 185
Quoted 5 lines
net/ipv4/udp.c +5
-0+ }else { + hslot = &udptable->hash[udp_hashfn (net, snum)]; +write_lock_bh (&hslot->lock); +if (udp_lib_lport_inuse (net, snum, hslot, sk, saddr_comp)) + goto fail;
The fail: label below should still unlock_bh when the above condition fails.
Quoted 13 lines
(quoted diff) +1
-0+ }inet_sk(sk)->num = snum; sk->sk_hash = snum; if (sk_unhashed(sk)) {net/ipv4/udp.c +1
-1-sk_add_node (sk, &udptable[udp_hashfn (net, snum)]); +sk_add_node (sk, &hslot->head);sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); }(quoted diff) +1
-0+ write_unlock_bh(&hslot->lock);error = 0;fail:
(quoted diff) +0
-1- write_unlock_bh(&udp_hash_lock);
Quoted 2 lines
return error; }
cheers,
. . christian
Re: [PATCH 1/2] udp: introduce struct udp_table and multiple rwlocks
Quoted 8 lines · Christian Bell
net/ipv4/udp.c int udp_lib_get_port(struct sock *sk, unsigned short snum, · line 185
Quoted 5 lines
net/ipv4/udp.c +5
-0+ }else { + hslot = &udptable->hash[udp_hashfn (net, snum)]; +write_lock_bh (&hslot->lock); +if (udp_lib_lport_inuse (net, snum, hslot, sk, saddr_comp)) + goto fail;The fail: label below should still
unlock_bhwhen the above condition fails.
This should be new label actually, since it is also used for non-locked fail.
Signature · 1 line
Evgeniy Polyakov
Re: [PATCH 1/2] udp: introduce struct udp_table and multiple rwlocks
In reply to Christian BellQuoted 11 lines · Christian Bell
include/net/udp.h struct udp_skb_cb { · line 56
Quoted 6 lines · Eric Dumazet
include/net/udp.h +4
-2-extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +struct udp_hslot { +struct hlist_head head; +rwlock_t lock; +};This structure should be aligned up to cacheline to reduce false sharing of more than one hslot.
Yes, I though about that. But : a full cache line is a waste of memory, and choosing a power of two alignement is not easy because of 32bit/64bit arches, and fact that sozepf(wrlock_t) can be > 4 if DEBUG
Quoted 24 lines
net/ipv4/udp.c int udp_lib_get_port(struct sock *sk, unsigned short snum, · line 185
Quoted 6 lines
net/ipv4/udp.c +5
-0+ }else { + hslot = &udptable->hash[udp_hashfn (net, snum)]; +write_lock_bh (&hslot->lock); +if (udp_lib_lport_inuse (net, snum, hslot, sk, saddr_comp)) + goto fail;The fail: label below should still
unlock_bhwhen the above condition fails.Quoted 15 lines
net/ipv4/udp.c +3
-1+ }inet_sk (sk)->num = snum; sk->sk_hash = snum;if (sk_unhashed (sk)) { -sk_add_node (sk, &udptable[udp_hashfn (net, snum)]); +sk_add_node (sk, &hslot->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 ); } +write_unlock_bh (&hslot->lock); error =0 ;fail:
- write_unlock_bh(&udp_hash_lock); return error; }
Good spoting, the write_unlock_bh(&hslot->lock); must be moved after the "fail:" label.
Thanks a lot
Re: [PATCH 1/2] udp: introduce struct udp_table and multiple rwlocks
In reply to Eric DumazetHi.
Quoted 6 lines · Eric Dumazet
Introducing one rwlock per chain reduces latencies, for port randomization on heavily loaded UDP servers.
Some cleanups were done to ease review of following patch.
Signed-off-by Eric Dumazet <[email protected]>
Ugh-ogh, that's a very cool change!
So far reviewed only this and things look very promising.
Signature · 1 line
Evgeniy Polyakov
PATCH 2/2 udp: RCU handling for Unicast packets.
In reply to David MillerRCUification of UDP hash tables
Goals are :
1) Optimizing handling of incoming Unicast UDP frames, so that no memory writes should happen in the fast path. Using an array of rwlocks (one per slot for example is not an option in this regard)
Note: Multicasts and broadcasts still will need to take a lock, because doing a full lockless lookup in this case is difficult.
2) No expensive operations in the socket bind/unhash phases :
- No expensive synchronize_rcu() calls.
- No added rcu_head in socket structure, increasing memory needs, but more important, forcing us to use call_rcu() calls, that have the bad property of making sockets structure cold. (rcu grace period between socket freeing and its potential reuse make this socket being cold in CPU cache).
David did a previous patch using call_rcu() and noticed a 20% impact on TCP connection rates.
Quoting Cristopher Lameter :
"Right. That results in cacheline cooldown. You'd want to recycle
the object as they are cache hot on a per cpu basis. That is screwed up by the delayed regular rcu processing. We have seen multiple regressions due to cacheline cooldown.
The only choice in cacheline hot sensitive areas is to deal with the complexity that comes with SLAB_DESTROY_BY_RCU or give up on RCU."
- Because udp sockets are allocated from dedicated kmem_cache, use of SLAB_DESTROY_BY_RCU can help here.
Theory of operation :
---------------------
As the lookup is lockfree (using rcu_read_lock()/rcu_read_unlock()), special attention must be taken by readers and writers.
Use of SLAB_DESTROY_BY_RCU is tricky too, because a socket can be freed, reused, inserted in a different chain or in worst case in the same chain while readers could do lookups in the same time.
In order to avoid loops, a reader must check each socket found in a chain really belongs to the chain the reader was traversing. If it finds a mismatch, lookup must start again at the begining. This *restart* loop is the reason we had to use rdlock for the multicast case, because we dont want to send same message several times to the same socket.
We use RCU only for fast path. Thus, /proc/net/udp still take rdlocks.
---
include/net/sock.h | 37 ++++++++++++++++++++++++++++++++++++- net/core/sock.c | 3 ++- net/ipv4/udp.c | 35 ++++++++++++++++++++++++++--------- net/ipv4/udplite.c | 1 + net/ipv6/udp.c | 25 ++++++++++++++++++------- net/ipv6/udplite.c | 1 + 6 files changed, 84 insertions(+), 18 deletions(-)
include/net/sock.h +36 -1
@@ -361,6 +361,27 @@ static __inline__ int sk_del_node_init(struct sock *sk)return rc; } +static __inline__int __sk_del_node_init_rcu (struct sock *sk) +{ +if (sk_hashed (sk)) { +hlist_del_init_rcu (&sk->sk_node); +return 1 ; + } +return 0 ; +} + +static __inline__int sk_del_node_init_rcu (struct sock *sk) +{ +int rc =__sk_del_node_init_rcu (sk); + +if (rc) { +/* paranoid for a while -acme */ +WARN_ON (atomic_read (&sk->sk_refcnt) ==1 ); +__sock_put (sk); + } +return rc; +} +static __inline__void __sk_add_node (struct sock *sk,struct hlist_head *list) {hlist_add_head (&sk->sk_node, list);
@@ -372,6 +393,17 @@ static __inline__ void sk_add_node(struct sock *sk, struct hlist_head *list)__sk_add_node (sk, list); } +static __inline__void __sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +{ +hlist_add_head_rcu (&sk->sk_node, list); +} + +static __inline__void sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +{ +sock_hold (sk); +__sk_add_node_rcu (sk, list); +} +static __inline__void __sk_del_bind_node (struct sock *sk) {__hlist_del (&sk->sk_bind_node);
@@ -385,6 +417,8 @@ static __inline__ void sk_add_bind_node(struct sock *sk,#define sk_for_each (__sk, node, list) \ hlist_for_each_entry(__sk, node, list, sk_node) +#define sk_for_each_rcu (__sk, node, list) \ + hlist_for_each_entry_rcu(__sk, node, list, sk_node)#define sk_for_each_from (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_from(__sk, node, sk_node)
@@ -587,8 +621,9 @@ struct proto {int *sysctl_rmem;int max_header; -struct kmem_cache *slab; +struct kmem_cache *slab;unsigned int obj_size; +int slab_flags;atomic_t *orphan_count;
net/core/sock.c +2 -1
@@ -2042,7 +2042,8 @@ int proto_register(struct proto *prot, int alloc_slab)if (alloc_slab) { prot->slab =kmem_cache_create (prot->name, prot->obj_size,0 , -SLAB_HWCACHE_ALIGN ,NULL ); +SLAB_HWCACHE_ALIGN | prot->slab_flags, +NULL );if (prot->slab ==NULL ) { printk(KERN_CRIT "%s: Can't create sock SLAB cache!\n",
net/ipv4/udp.c +26 -9
@@ -187,7 +187,7 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,inet_sk (sk)->num = snum; sk->sk_hash = snum;if (sk_unhashed (sk)) { -sk_add_node (sk, &hslot->head); +sk_add_node_rcu (sk, &hslot->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 ); }write_unlock_bh (&hslot->lock);
@@ -252,15 +252,24 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, __be16 sport, __be32 daddr, __be16 dport, int dif, struct udp_table *udptable) { - struct sock *sk, *result = NULL; + struct sock *sk, *result;struct hlist_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash]; -int score, badness =-1 ; +int score, badness; -read_lock (&hslot->lock); -sk_for_each (sk, node, &hslot->head) { +rcu_read_lock (); +begin: + result =NULL ; + badness =-1 ; +sk_for_each_rcu (sk, node, &hslot->head) { +/* +* lockless reader, and SLAB_DESTROY_BY_RCU items: +* We must check this item was not moved to another chain +*/ +if (udp_hashfn (net, sk->sk_hash) != hash) + goto begin; score =compute_score (sk, net, saddr, hnum, sport, daddr, dport, dif);if (score > badness) {
@@ -268,9 +277,16 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, badness = score; } } -if (result) -sock_hold (result); -read_unlock (&hslot->lock); +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, saddr, hnum, sport, + daddr, dport, dif) < badness)) { +sock_put (result); + goto begin; + } + } +rcu_read_unlock ();return result; }
@@ -952,7 +968,7 @@ void udp_lib_unhash(struct sock *sk)struct udp_hslot *hslot = &udptable->hash[hash];write_lock (&hslot->lock); -if (sk_del_node_init (sk)) { +if (sk_del_node_init_rcu (sk)) {inet_sk (sk)->num =0 ;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); }
@@ -1516,6 +1532,7 @@ struct proto udp_prot = { .sysctl_wmem = &sysctl_udp_wmem_min, .sysctl_rmem = &sysctl_udp_rmem_min, .obj_size = sizeof(struct udp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udp_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt,
net/ipv4/udplite.c +1 -0
@@ -51,6 +51,7 @@ struct proto udplite_prot = { .unhash = udp_lib_unhash, .get_port = udp_v4_get_port, .obj_size = sizeof(struct udp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt,
net/ipv6/udp.c +18 -7
@@ -97,24 +97,34 @@ static struct sock *__udp6_lib_lookup(struct net *net, struct in6_addr *daddr, __be16 dport, int dif, struct udp_table *udptable) { - struct sock *sk, *result = NULL; + struct sock *sk, *result;struct hlist_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash]; -int score, badness =-1 ; +int score, badness; -read_lock (&hslot->lock); -sk_for_each (sk, node, &hslot->head) { +rcu_read_lock (); +begin: + result =NULL ; + badness =-1 ; +sk_for_each_rcu (sk, node, &hslot->head) { score =compute_score (sk, net, hnum, saddr, sport, daddr, dport, dif);if (score > badness) { result = sk; badness = score; } } -if (result) -sock_hold (result); -read_unlock (&hslot->lock); +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, hnum, saddr, sport, + daddr, dport, dif) < badness)) { +sock_put (result); + goto begin; + } + } +rcu_read_unlock ();return result; }
@@ -1062,6 +1072,7 @@ struct proto udpv6_prot = { .sysctl_wmem = &sysctl_udp_wmem_min, .sysctl_rmem = &sysctl_udp_rmem_min, .obj_size = sizeof(struct udp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udp_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt,
net/ipv6/udplite.c +1 -0
@@ -49,6 +49,7 @@ struct proto udplitev6_prot = { .unhash = udp_lib_unhash, .get_port = udp_v6_get_port, .obj_size = sizeof(struct udp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt,
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 51 lines · Eric Dumazet
RCUification of UDP hash tables
Goals are :
1) Optimizing handling of incoming Unicast UDP frames, so that no memory writes should happen in the fast path. Using an array of rwlocks (one per slot for example is not an option in this regard)
Note: Multicasts and broadcasts still will need to take a lock, because doing a full lockless lookup in this case is difficult.
2) No expensive operations in the socket bind/unhash phases :
- No expensivesynchronize_rcu()calls.- No added
rcu_headin socket structure, increasing memory needs, but more important, forcing us to usecall_rcu()calls, that have the bad property of making sockets structure cold. (rcu grace period between socket freeing and its potential reuse make this socket being cold in CPU cache).
David did a previous patch usingcall_rcu()and noticed a 20% impact on TCP connection rates.
Quoting Cristopher Lameter :
"Right. That results in cacheline cooldown. You'd want to recycle
the object as they are cache hot on a per cpu basis. That is screwed up by the delayed regular rcu processing. We have seen multiple regressions due to cacheline cooldown.
The only choice in cacheline hot sensitive areas is to deal with the complexity that comes withSLAB_DESTROY_BY_RCUor give up on RCU."- Because udp sockets are allocated from dedicated
kmem_cache, use ofSLAB_DESTROY_BY_RCUcan help here.Theory of operation :
---------------------As the lookup is lockfree (using rcu_read_lock()/rcu_read_unlock()), special attention must be taken by readers and writers.
Use of
SLAB_DESTROY_BY_RCUis tricky too, because a socket can be freed, reused, inserted in a different chain or in worst case in the same chain while readers could do lookups in the same time.In order to avoid loops, a reader must check each socket found in a chain really belongs to the chain the reader was traversing. If it finds a mismatch, lookup must start again at the begining. This *restart* loop is the reason we had to use rdlock for the multicast case, because we dont want to send same message several times to the same socket.
We use RCU only for fast path. Thus,
/proc/net/udpstill take rdlocks.Signed-off-by Eric Dumazet <[email protected]>
Quoted 9 lines
---
include/net/sock.h | 37 ++++++++++++++++++++++++++++++++++++- net/core/sock.c | 3 ++- net/ipv4/udp.c | 35 ++++++++++++++++++++++++++--------- net/ipv4/udplite.c | 1 + net/ipv6/udp.c | 25 ++++++++++++++++++------- net/ipv6/udplite.c | 1 + 6 files changed, 84 insertions(+), 18 deletions(-)
On ipv6 side, I forgot to add a check before compute_score(), like I did on ipv4
+ rcu_read_lock();
+begin:
+ result = NULL;
+ badness = -1;
+ sk_for_each_rcu(sk, node, &hslot->head) {
< BEGIN HERE missing part --->
/* * lockless reader, and SLAB_DESTROY_BY_RCU items: * We must check this item was not moved to another chain */ if (udp_hashfn(net, sk->sk_hash) != hash) goto begin;
< END missing part --->
score = compute_score(sk, net, hnum, saddr, sport, daddr, dport, dif);
if (score > badness) {
result = sk;
badness = score;
}
}- if (result)
- sock_hold(result);
- read_unlock(&hslot->lock);
+ if (result) {
+ if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt)))
+ result = NULL;
+ else if (unlikely(compute_score(result, net, hnum, saddr, sport,
+ daddr, dport, dif) < badness)) {
+ sock_put(result);
+ goto begin;
+ }
+ }
I will submit a new patch serie tomorrow, with :
Patch 1 : spinlocks instead of rwlocks, and bug spotted by Christian Bell
Patch 2 : splited on two parts (2 & 3) , one for IPV4, one for IPV6,
Thanks
David Miller
maintainerRe: [PATCH 2/2] udp: RCU handling for Unicast packets.
From: Eric Dumazet <[email protected]>
Date: Tue, 28 Oct 2008 23:45:15 +0100
Quoted 5 lines
I will submit a new patch serie tomorrow, with :
Patch 1 : spinlocks instead of rwlocks, and bug spotted by Christian Bell
Patch 2 : splited on two parts (2 & 3) , one for IPV4, one for IPV6,
I very much look forward to this :-)
I like these changes and can't wait to add them to net-next-2.6
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 5 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Tue, 28 Oct 2008 23:45:15 +0100Quoted 2 lines
I will submit a new patch serie tomorrow, with :
Quoted 7 lines
Quoted 3 lines
Patch 1 : spinlocks instead of rwlocks, and bug spotted by Christian Bell
Patch 2 : splited on two parts (2 & 3) , one for IPV4, one for IPV6,
I very much look forward to this :-)
I like these changes and can't wait to add them to net-next-2.6
Thanks David, please find first updated patch 1
Thanks to Christian Bell and Stephen for their usefull review.
[PATCH] udp: introduce struct udp_table and multiple spinlocks
UDP sockets are hashed in a 128 slots hash table.
This hash table is protected by *one* rwlock.
This rwlock is readlocked each time an incoming UDP message is handled.
This rwlock is writelocked each time a socket must be inserted in hash table (bind time), or deleted from this table (close time)
This is not scalable on SMP machines :
1) Even in read mode, lock() and unlock() are atomic operations and must dirty a contended cache line, shared by all cpus.
2) A writer might be starved if many readers are 'in flight'. This can happen on a machine with some NIC receiving many UDP messages. User process can be delayed a long time at socket creation/dismantle time.
This patch prepares RCU migration, by introducing 'struct udp_table and struct udp_hslot', and using one spinlock per chain, to reduce contention on central rwlock.
Introducing one spinlock per chain reduces latencies, for port randomization on heavily loaded UDP servers. This also speedup bindings to specific ports.
udp_lib_unhash() was uninlined, becoming to big.
Some cleanups were done to ease review of following patch (RCUification of UDP Unicast lookups)
---
include/net/sock.h | 2 include/net/udp.h | 25 ++-- include/net/udplite.h | 2 net/ipv4/udp.c | 209 +++++++++++++++++++++++----------------- net/ipv4/udp_impl.h | 4 net/ipv4/udplite.c | 13 +- net/ipv6/udp.c | 112 +++++++++++---------- net/ipv6/udp_impl.h | 4 net/ipv6/udplite.c | 8 - 9 files changed, 215 insertions(+), 164 deletions(-)
include/net/sock.h +1 -1
@@ -599,7 +599,7 @@ struct proto {union {struct inet_hashinfo *hashinfo; -struct hlist_head *udp_hash; +struct udp_table *udp_table;struct raw_hashinfo *raw_hash; } h;
include/net/udp.h +12 -13
@@ -50,8 +50,15 @@ struct udp_skb_cb { };#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb)) -extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +struct udp_hslot { +struct hlist_head head; +spinlock_t lock; +} __attribute__((aligned (2 *sizeof (long )))); +struct udp_table { +struct udp_hslot hash[UDP_HTABLE_SIZE ]; +}; +extern struct udp_table udp_table; +extern void udp_table_init (struct udp_table *);/* Note: this must match 'valbool' in sock_setsockopt */
@@ -110,15 +117,7 @@ static inline void udp_lib_hash(struct sock *sk)BUG (); } -static inline void udp_lib_unhash (struct sock *sk) -{ -write_lock_bh (&udp_hash_lock); -if (sk_del_node_init (sk)) { -inet_sk (sk)->num =0 ; -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); - } -write_unlock_bh (&udp_hash_lock); -} +extern void udp_lib_unhash (struct sock *sk);static inline void udp_lib_close (struct sock *sk,long timeout) {
@@ -187,7 +186,7 @@ extern struct sock *udp4_lib_lookup(struct net *net, __be32 saddr, __be16 sport,struct udp_seq_afinfo {char *name;sa_family_t family; -struct hlist_head *hashtable; +struct udp_table *udp_table;struct file_operations seq_fops;struct seq_operations seq_ops; };
@@ -196,7 +195,7 @@ struct udp_iter_state {struct seq_net_private p;sa_family_t family;int bucket; -struct hlist_head *hashtable; +struct udp_table *udp_table; };#ifdef CONFIG_PROC_FS
include/net/udplite.h +1 -1
@@ -11,7 +11,7 @@#define UDPLITE_RECV_CSCOV 11/* receiver partial coverage (threshold ) */ extern struct proto udplite_prot; -extern struct hlist_head udplite_hash[UDP_HTABLE_SIZE ]; +extern struct udp_table udplite_table;/* * Checksum computation is all in software, hence simpler getfrag.
net/ipv4/udp.c +124 -85
409 lines not on this page · raw message
net/ipv4/udp_impl.h +2 -2
@@ -5,8 +5,8 @@#include <net/protocol.h> #include <net/inet_common.h> -extern int __udp4_lib_rcv (struct sk_buff *,struct hlist_head [],int ); -extern void __udp4_lib_err (struct sk_buff *,u32 ,struct hlist_head []); +extern int __udp4_lib_rcv (struct sk_buff *,struct udp_table *,int ); +extern void __udp4_lib_err (struct sk_buff *,u32 ,struct udp_table *);extern int udp_v4_get_port (struct sock *sk,unsigned short snum);
net/ipv4/udplite.c +7 -6
@@ -12,16 +12,17 @@*/ #include "udp_impl.h" -struct hlist_head udplite_hash[UDP_HTABLE_SIZE ]; +struct udp_table udplite_table; +EXPORT_SYMBOL (udplite_table);static int udplite_rcv (struct sk_buff *skb) { -return __udp4_lib_rcv (skb, udplite_hash,IPPROTO_UDPLITE ); +return __udp4_lib_rcv (skb, &udplite_table,IPPROTO_UDPLITE ); }static void udplite_err (struct sk_buff *skb,u32 info) { -__udp4_lib_err (skb, info, udplite_hash); +__udp4_lib_err (skb, info, &udplite_table); }static struct net_protocol udplite_protocol = {
@@ -50,7 +51,7 @@ struct proto udplite_prot = { .unhash = udp_lib_unhash, .get_port = udp_v4_get_port, .obj_size = sizeof(struct udp_sock), - .h.udp_hash = udplite_hash, + .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt, .compat_getsockopt = compat_udp_getsockopt,
@@ -71,7 +72,7 @@ static struct inet_protosw udplite4_protosw = {static struct udp_seq_afinfo udplite4_seq_afinfo = { .name ="udplite" , .family =AF_INET , - .hashtable = udplite_hash, + .udp_table = &udplite_table, .seq_fops = { .owner =THIS_MODULE , },
@@ -108,6 +109,7 @@ static inline int udplite4_proc_init(void)void __init udplite4_register (void ) { +udp_table_init (&udplite_table);if (proto_register (&udplite_prot,1 )) goto out_register_err;
@@ -126,5 +128,4 @@ out_register_err:printk (KERN_CRIT "%s: Cannot add UDP-Lite protocol.\n" , __func__); } -EXPORT_SYMBOL (udplite_hash);EXPORT_SYMBOL (udplite_prot);
net/ipv6/udp.c +62 -50
187 lines not on this page · raw message
net/ipv6/udp_impl.h +2 -2
@@ -7,9 +7,9 @@#include <net/inet_common.h> #include <net/transp_v6.h> -extern int __udp6_lib_rcv (struct sk_buff *,struct hlist_head [],int ); +extern int __udp6_lib_rcv (struct sk_buff *,struct udp_table *,int );extern void __udp6_lib_err (struct sk_buff *,struct inet6_skb_parm *, -int ,int ,int ,__be32 ,struct hlist_head []); +int ,int ,int ,__be32 ,struct udp_table *);extern int udp_v6_get_port (struct sock *sk,unsigned short snum);
net/ipv6/udplite.c +4 -4
@@ -15,14 +15,14 @@static int udplitev6_rcv (struct sk_buff *skb) { -return __udp6_lib_rcv (skb, udplite_hash,IPPROTO_UDPLITE ); +return __udp6_lib_rcv (skb, &udplite_table,IPPROTO_UDPLITE ); }static void udplitev6_err (struct sk_buff *skb,struct inet6_skb_parm *opt,int type,int code,int offset,__be32 info) { -__udp6_lib_err (skb, opt, type, code, offset, info, udplite_hash); +__udp6_lib_err (skb, opt, type, code, offset, info, &udplite_table); }static struct inet6_protocol udplitev6_protocol = {
@@ -49,7 +49,7 @@ struct proto udplitev6_prot = { .unhash = udp_lib_unhash, .get_port = udp_v6_get_port, .obj_size = sizeof(struct udp6_sock), - .h.udp_hash = udplite_hash, + .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt, .compat_getsockopt = compat_udpv6_getsockopt,
@@ -95,7 +95,7 @@ void udplitev6_exit(void)static struct udp_seq_afinfo udplite6_seq_afinfo = { .name ="udplite6" , .family =AF_INET6 , - .hashtable = udplite_hash, + .udp_table = &udplite_table, .seq_fops = { .owner =THIS_MODULE , },
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
From: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 09:23:03 +0100
Quoted 6 lines
Quoted 5 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Tue, 28 Oct 2008 23:45:15 +0100Quoted 2 lines
I will submit a new patch serie tomorrow, with :
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
Patch 1 : spinlocks instead of rwlocks, and bug spotted by Christian Bell
Patch 2 : splited on two parts (2 & 3) , one for IPV4, one for IPV6,
I very much look forward to this :-)
I like these changes and can't wait to add them to net-next-2.6Thanks David, please find first updated patch 1
Applied, please (re-)send the current version of patch 2 as well.
Thanks.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 9 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 09:23:03 +0100Quoted 6 lines
Quoted 5 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Tue, 28 Oct 2008 23:45:15 +0100Quoted 2 lines
I will submit a new patch serie tomorrow, with :
Quoted 8 lines
Quoted 6 lines
Quoted 5 lines
Quoted 3 lines
Patch 1 : spinlocks instead of rwlocks, and bug spotted by Christian Bell
Patch 2 : splited on two parts (2 & 3) , one for IPV4, one for IPV6,
I very much look forward to this :-)
I like these changes and can't wait to add them to net-next-2.6Thanks David, please find first updated patch 1
Applied, please (re-)send the current version of patch 2 as well.
I found a fatal bug in /proc/net/udp handling, sorry.
[PATCH] udp: udp_get_next() should use spin_unlock_bh()
net/ipv4/udp.c +1 -1
@@ -1579,7 +1579,7 @@ static struct sock *udp_get_next(struct seq_file *seq, struct sock *sk) }while (sk && (!net_eq (sock_net (sk), net) || sk->sk_family != state->family));if (!sk) { -spin_unlock (&state->udp_table->hash[state->bucket].lock); +spin_unlock_bh (&state->udp_table->hash[state->bucket].lock);return udp_get_first (seq, state->bucket +1 ); }return sk;
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
From: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 11:19:50 +0100
Quoted 3 lines
[PATCH] udp:
udp_get_next()should usespin_unlock_bh()Signed-off-by Eric Dumazet <[email protected]>
Applied, thanks Eric.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Eric DumazetQuoted 6 lines · Eric Dumazet
Quoted 5 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Tue, 28 Oct 2008 23:45:15 +0100Quoted 2 lines
I will submit a new patch serie tomorrow, with :
Quoted 8 lines
Quoted 7 lines
Quoted 3 lines
Patch 1 : spinlocks instead of rwlocks, and bug spotted by Christian Bell
Patch 2 : splited on two parts (2 & 3) , one for IPV4, one for IPV6,
I very much look forward to this :-)
I like these changes and can't wait to add them to net-next-2.6
Please find updated patch 2
Missing check in __udp6_lib_lookup() was added,
and based on spinlock version ([PATCH] udp: introduce struct udp_table and multiple spinlocks)
Thank you
[PATCH] udp: RCU handling for Unicast packets.
Goals are :
1) Optimizing handling of incoming Unicast UDP frames, so that no memory writes should happen in the fast path.
Note: Multicasts and broadcasts still will need to take a lock, because doing a full lockless lookup in this case is difficult.
2) No expensive operations in the socket bind/unhash phases :
- No expensive synchronize_rcu() calls.
- No added rcu_head in socket structure, increasing memory needs, but more important, forcing us to use call_rcu() calls, that have the bad property of making sockets structure cold. (rcu grace period between socket freeing and its potential reuse make this socket being cold in CPU cache).
David did a previous patch using call_rcu() and noticed a 20% impact on TCP connection rates.
Quoting Cristopher Lameter :
"Right. That results in cacheline cooldown. You'd want to recycle
the object as they are cache hot on a per cpu basis. That is screwed up by the delayed regular rcu processing. We have seen multiple regressions due to cacheline cooldown.
The only choice in cacheline hot sensitive areas is to deal with the complexity that comes with SLAB_DESTROY_BY_RCU or give up on RCU."
- Because udp sockets are allocated from dedicated kmem_cache, use of SLAB_DESTROY_BY_RCU can help here.
Theory of operation :
---------------------
As the lookup is lockfree (using rcu_read_lock()/rcu_read_unlock()), special attention must be taken by readers and writers.
Use of SLAB_DESTROY_BY_RCU is tricky too, because a socket can be freed, reused, inserted in a different chain or in worst case in the same chain while readers could do lookups in the same time.
In order to avoid loops, a reader must check each socket found in a chain really belongs to the chain the reader was traversing. If it finds a mismatch, lookup must start again at the begining. This *restart* loop is the reason we had to use rdlock for the multicast case, because we dont want to send same message several times to the same socket.
We use RCU only for fast path.
Thus, /proc/net/udp still takes spinlocks.
---
include/net/sock.h | 37 ++++++++++++++++++++++++++++++++++++- net/core/sock.c | 3 ++- net/ipv4/udp.c | 35 ++++++++++++++++++++++++++--------- net/ipv4/udplite.c | 1 + net/ipv6/udp.c | 31 ++++++++++++++++++++++++------- net/ipv6/udplite.c | 1 + 6 files changed, 90 insertions(+), 18 deletions(-)
include/net/sock.h +36 -1
@@ -363,6 +363,27 @@ static __inline__ int sk_del_node_init(struct sock *sk)return rc; } +static __inline__int __sk_del_node_init_rcu (struct sock *sk) +{ +if (sk_hashed (sk)) { +hlist_del_init_rcu (&sk->sk_node); +return 1 ; + } +return 0 ; +} + +static __inline__int sk_del_node_init_rcu (struct sock *sk) +{ +int rc =__sk_del_node_init_rcu (sk); + +if (rc) { +/* paranoid for a while -acme */ +WARN_ON (atomic_read (&sk->sk_refcnt) ==1 ); +__sock_put (sk); + } +return rc; +} +static __inline__void __sk_add_node (struct sock *sk,struct hlist_head *list) {hlist_add_head (&sk->sk_node, list);
@@ -374,6 +395,17 @@ static __inline__ void sk_add_node(struct sock *sk, struct hlist_head *list)__sk_add_node (sk, list); } +static __inline__void __sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +{ +hlist_add_head_rcu (&sk->sk_node, list); +} + +static __inline__void sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +{ +sock_hold (sk); +__sk_add_node_rcu (sk, list); +} +static __inline__void __sk_del_bind_node (struct sock *sk) {__hlist_del (&sk->sk_bind_node);
@@ -387,6 +419,8 @@ static __inline__ void sk_add_bind_node(struct sock *sk,#define sk_for_each (__sk, node, list) \ hlist_for_each_entry(__sk, node, list, sk_node) +#define sk_for_each_rcu (__sk, node, list) \ + hlist_for_each_entry_rcu(__sk, node, list, sk_node)#define sk_for_each_from (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_from(__sk, node, sk_node)
@@ -589,8 +623,9 @@ struct proto {int *sysctl_rmem;int max_header; -struct kmem_cache *slab; +struct kmem_cache *slab;unsigned int obj_size; +int slab_flags;atomic_t *orphan_count;
net/core/sock.c +2 -1
@@ -2042,7 +2042,8 @@ int proto_register(struct proto *prot, int alloc_slab)if (alloc_slab) { prot->slab =kmem_cache_create (prot->name, prot->obj_size,0 , -SLAB_HWCACHE_ALIGN ,NULL ); +SLAB_HWCACHE_ALIGN | prot->slab_flags, +NULL );if (prot->slab ==NULL ) { printk(KERN_CRIT "%s: Can't create sock SLAB cache!\n",
net/ipv4/udp.c +26 -9
@@ -187,7 +187,7 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,inet_sk (sk)->num = snum; sk->sk_hash = snum;if (sk_unhashed (sk)) { -sk_add_node (sk, &hslot->head); +sk_add_node_rcu (sk, &hslot->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 ); } error =0 ;
@@ -253,15 +253,24 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, __be16 sport, __be32 daddr, __be16 dport, int dif, struct udp_table *udptable) { - struct sock *sk, *result = NULL; + struct sock *sk, *result;struct hlist_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash]; -int score, badness =-1 ; +int score, badness; -spin_lock (&hslot->lock); -sk_for_each (sk, node, &hslot->head) { +rcu_read_lock (); +begin: + result =NULL ; + badness =-1 ; +sk_for_each_rcu (sk, node, &hslot->head) { +/* +* lockless reader, and SLAB_DESTROY_BY_RCU items: +* We must check this item was not moved to another chain +*/ +if (udp_hashfn (net, sk->sk_hash) != hash) + goto begin; score =compute_score (sk, net, saddr, hnum, sport, daddr, dport, dif);if (score > badness) {
@@ -269,9 +278,16 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, badness = score; } } -if (result) -sock_hold (result); -spin_unlock (&hslot->lock); +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, saddr, hnum, sport, + daddr, dport, dif) < badness)) { +sock_put (result); + goto begin; + } + } +rcu_read_unlock ();return result; }
@@ -953,7 +969,7 @@ void udp_lib_unhash(struct sock *sk)struct udp_hslot *hslot = &udptable->hash[hash];spin_lock (&hslot->lock); -if (sk_del_node_init (sk)) { +if (sk_del_node_init_rcu (sk)) {inet_sk (sk)->num =0 ;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); }
@@ -1517,6 +1533,7 @@ struct proto udp_prot = { .sysctl_wmem = &sysctl_udp_wmem_min, .sysctl_rmem = &sysctl_udp_rmem_min, .obj_size = sizeof(struct udp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udp_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt,
net/ipv4/udplite.c +1 -0
@@ -51,6 +51,7 @@ struct proto udplite_prot = { .unhash = udp_lib_unhash, .get_port = udp_v4_get_port, .obj_size = sizeof(struct udp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt,
net/ipv6/udp.c +24 -7
@@ -97,24 +97,40 @@ static struct sock *__udp6_lib_lookup(struct net *net, struct in6_addr *daddr, __be16 dport, int dif, struct udp_table *udptable) { - struct sock *sk, *result = NULL; + struct sock *sk, *result;struct hlist_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash]; -int score, badness =-1 ; +int score, badness; -spin_lock (&hslot->lock); -sk_for_each (sk, node, &hslot->head) { +rcu_read_lock (); +begin: + result =NULL ; + badness =-1 ; +sk_for_each_rcu (sk, node, &hslot->head) { +/* +* lockless reader, and SLAB_DESTROY_BY_RCU items: +* We must check this item was not moved to another chain +*/ +if (udp_hashfn (net, sk->sk_hash) != hash) + goto begin; score =compute_score (sk, net, hnum, saddr, sport, daddr, dport, dif);if (score > badness) { result = sk; badness = score; } } -if (result) -sock_hold (result); -spin_unlock (&hslot->lock); +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, hnum, saddr, sport, + daddr, dport, dif) < badness)) { +sock_put (result); + goto begin; + } + } +rcu_read_unlock ();return result; }
@@ -1062,6 +1078,7 @@ struct proto udpv6_prot = { .sysctl_wmem = &sysctl_udp_wmem_min, .sysctl_rmem = &sysctl_udp_rmem_min, .obj_size = sizeof(struct udp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udp_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt,
net/ipv6/udplite.c +1 -0
@@ -49,6 +49,7 @@ struct proto udplitev6_prot = { .unhash = udp_lib_unhash, .get_port = udp_v6_get_port, .obj_size = sizeof(struct udp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt,
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
From: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 10:04:24 +0100
Quoted 13 lines
Quoted 12 lines · Eric Dumazet
Quoted 11 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Tue, 28 Oct 2008 23:45:15 +0100Quoted 5 lines
I will submit a new patch serie tomorrow, with :
Patch 1 : spinlocks instead of rwlocks, and bug spotted by Christian Bell
Patch 2 : splited on two parts (2 & 3) , one for IPV4, one for IPV6,
I very much look forward to this :-)
Quoted 7 lines
Quoted 2 lines
Quoted 1 line
I like these changes and can't wait to add them to net-next-2.6
Please find updated patch 2
Missing check in
__udp6_lib_lookup()was added,
and based on spinlock version ([PATCH] udp: introduce structudp_tableand multiple spinlocks)
Applied, thanks Eric.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Eric DumazetI believe there is a race in this patch:
+ sk_for_each_rcu(sk, node, &hslot->head) {
+ /*
+ * lockless reader, and SLAB_DESTROY_BY_RCU items:
+ * We must check this item was not moved to another chain
+ */
+ if (udp_hashfn(net, sk->sk_hash) != hash)
+ goto begin;
score = compute_score(sk, net, hnum, saddr, sport, daddr, dport, dif);
if (score > badness) {
result = sk;
badness = score;
}
}If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash.
I also might be nice to have a way to avoid recomputing the score the second time, perhaps using a sequence number of some type.
-corey
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 17 lines · Corey Minyard
I believe there is a race in this patch:
(quoted diff) +7
-0+ sk_for_each_rcu(sk, node, &hslot->head) { + /* + * lockless reader, and SLAB_DESTROY_BY_RCU items: + * We must check this item was not moved to another chain + */ + if (udp_hashfn(net, sk->sk_hash) != hash) + goto begin; score = compute_score(sk, net, hnum, saddr, sport, daddr, dport,dif); if (score > badness) { result = sk; badness = score; } }
Quoted 2 lines
If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash.
You are absolutely right. As we *need* next pointer anyway for the prefetch(), we can store it, so that its value is kept.
Quoted 1 line
I also might be nice to have a way to avoid recomputing the score the second time, perhaps using a sequence number of some type.
Well, computing score is really cheap, everything is in cache, granted the chain was not really huge and high score item at the beginning.
Adding yet another field in sock structure should be avoided if possible.
Thanks
[PATCH] udp: introduce sk_for_each_rcu_safenext()
Corey Minyard found a race added in commit 271b72c7fa82c2c7a795bc16896149933110672d (udp: RCU handling for Unicast packets.)
"If the socket is moved from one list to another list in-between the time
the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new sk_for_each_rcu_safenext() macro.
---
include/linux/rculist.h | 17 +++++++++++++++++ include/net/sock.h | 4 ++-- net/ipv4/udp.c | 4 ++-- net/ipv6/udp.c | 4 ++-- 4 files changed, 23 insertions(+), 6 deletions(-)
include/linux/rculist.h +17 -0
@@ -383,5 +383,22 @@ static inline void hlist_add_after_rcu(struct hlist_node *prev, ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ; }); \ pos = rcu_dereference(pos->next)) +/** +* hlist_for_each_entry_rcu_safenext - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* @next: the &struct hlist_node to use as a next cursor +* +* Special version of hlist_for_each_entry_rcu that make sure +* each next pointer is fetched before each iteration. +*/ +#define hlist_for_each_entry_rcu_safenext (tpos, pos, head, member, next) \ + for (pos = rcu_dereference((head)->first); \ + pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference(next)) +#endif /* __KERNEL__ */ #endif
include/net/sock.h +2 -2
@@ -419,8 +419,8 @@ static __inline__ void sk_add_bind_node(struct sock *sk,#define sk_for_each (__sk, node, list) \ hlist_for_each_entry(__sk, node, list, sk_node) -#define sk_for_each_rcu (__sk, node, list) \ - hlist_for_each_entry_rcu(__sk, node, list, sk_node) +#define sk_for_each_rcu_safenext (__sk, node, list, next) \ + hlist_for_each_entry_rcu_safenext(__sk, node, list, sk_node, next)#define sk_for_each_from (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_from(__sk, node, sk_node)
net/ipv4/udp.c +2 -2
@@ -256,7 +256,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node; +struct hlist_node *node, *next;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];
@@ -266,7 +266,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu (sk, node, &hslot->head) { +sk_for_each_rcu_safenext (sk, node, &hslot->head, next) {/* * lockless reader, and SLAB_DESTROY_BY_RCU items: * We must check this item was not moved to another chain
net/ipv6/udp.c +2 -2
@@ -98,7 +98,7 @@ static struct sock *__udp6_lib_lookup(struct net *net, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node; +struct hlist_node *node, *next;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];
@@ -108,7 +108,7 @@ static struct sock *__udp6_lib_lookup(struct net *net, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu (sk, node, &hslot->head) { +sk_for_each_rcu_safenext (sk, node, &hslot->head, next) {/* * lockless reader, and SLAB_DESTROY_BY_RCU items: * We must check this item was not moved to another chain
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 7 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.
You also need the appropriate smp_wmb() in udp_lib_get_port() after sk_hash is set, I think, so the next field is guaranteed to be changed after the hash value is changed.
-corey
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 11 lines · Corey Minyard
Quoted 7 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.
Not sure about this one Corey.
If a reader catches previous value of item->sk_hash, two cases are to be taken into :
1) its udp_hashfn(net, sk->sk_hash) is != hash
-> goto begin : Reader will redo its scan
2) its udp_hashfn(net, sk->sk_hash) is == hash
-> next pointer is good enough : it points to next item in same hash chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP message could be lost.
If we force a smp_wmb(), reader would fetch pointer to begin of list.
1) its udp_hashfn(net, sk->sk_hash) is != hash
-> goto begin : Reader will redo its scan : next pointer value had no meaning
2) its udp_hashfn(net, sk->sk_hash) is == hash
->next pointer "force reader" to rescan the chain, but wont find new items.
In any case, we cannot lost an UDP message sent to a stable port (previously bound)
Thanks
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 30 lines · Eric Dumazet
Quoted 14 lines · Corey Minyard
Quoted 13 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d
(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time
the hash is calculated and the next field is accessed, and the socket
has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."Quoted 6 lines
Quoted 3 lines
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()
macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader
will redo its scan
Quoted 6 lines
2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.
3) its udp_hashfn(net, sk-sk_hash) is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.
You might well cover this (will examine your code in detail on my plane flight starting about 20 hours from now), but thought I should point it out. ;-)
Thanx, Paul
Quoted 20 lines
If we force a
smp_wmb(), reader would fetch pointer to begin of list.1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader
will redo its scan : next pointer value had no meaning2) its
udp_hashfn(net, sk->sk_hash)is == hash
->next pointer "force reader" to rescan the chain, but wont find new
items.In any case, we cannot lost an UDP message sent to a stable port (previously bound)
Thanks
--
To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 32 lines · Paul E. McKenney
Quoted 20 lines · Eric Dumazet
Quoted 5 lines · Corey Minyard
Quoted 3 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)Quoted 9 lines
Quoted 7 lines
"If the socket is moved from one list to another list in-between the time
the hash is calculated and the next field is accessed, and the socket
has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :Quoted 9 lines
1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader will redo its scan2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.
Quoted 4 lines
3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.
If I understand this, without the smp_wmb(), it is possible that the next field can be written to main memory before the hash value is written. If that happens, the following can occur:
CPU1 CPU2
next is set to NULL (end of new list)
fetch next
calculate hash and compare to sk_hash
sk_hash is set to new value
So I think in the above cases, your case #2 is not necessarily valid without the barrier.
And another possible issue. If sk_hash is written before next, and CPU1 is interrupted before CPU2, CPU2 will continually spin on the list until CPU1 comes back and moves it to the new list. Note sure if that is an issue.
-corey
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 31 lines · Corey Minyard
Quoted 30 lines · Paul E. McKenney
Quoted 27 lines · Eric Dumazet
Quoted 5 lines · Corey Minyard
Quoted 3 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)Quoted 7 lines
Quoted 5 lines
"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader will redo its scan2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.
Quoted 8 lines
Quoted 4 lines
3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.If I understand this, without the
smp_wmb(), it is possible that the next field can be written to main memory before the hash value is written. If that happens, the following can occur:CPU1 CPU2
next is set to NULL (end of new list)
Well, if this item is injected to the same chain, next wont be set to NULL.
That would mean previous writers deleted all items from the chain.
In this case, readers can see NULL, it is not a problem at all. List is/was empty.
An application cannot complain a packet is not handled if its bind() syscall is not yet completed :)
If item is injected on another chain, we will detect hash mismatch and redo full scan.
Quoted 7 lines
fetch next
calculate hash and compare tosk_hash
sk_hashis set to new valueSo I think in the above cases, your case #2 is not necessarily valid without the barrier.
And another possible issue. If
sk_hashis written before next, and CPU1 is interrupted before CPU2, CPU2 will continually spin on the list until CPU1 comes back and moves it to the new list. Note sure if that is an issue.
Probably not. Previously, readers were spining on read_lock(), when a writer was inside its critical section (write_lock()/write_unlock()). So instead of spining inside read_unlock(), issuing stupid memory transactions, the readers can now spin reading hash chain and populate cpu cache :)
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 36 lines · Eric Dumazet
Quoted 35 lines · Corey Minyard
Quoted 34 lines · Paul E. McKenney
Quoted 18 lines · Eric Dumazet
Quoted 5 lines · Corey Minyard
Quoted 3 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)Quoted 7 lines
Quoted 5 lines
"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :Quoted 9 lines
1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader will redo its scan2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same
hash chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this
UDP message could be lost.3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.
Quoted 8 lines
Quoted 4 lines
If I understand this, without the
smp_wmb(), it is possible that the next field can be written to main memory before the hash value is written. If that happens, the following can occur:CPU1 CPU2
next is set to NULL (end of new list)Well, if this item is injected to the same chain, next wont be set to NULL.
That would mean previous writers deleted all items from the chain.
I put my comment in the wrong place, I wasn't talking about being injected into the same chain.
Quoted 7 lines
In this case, readers can see NULL, it is not a problem at all. List is/was empty.
An application cannot complain a packet is not handled if itsbind()syscall is not yet completed :)If item is injected on another chain, we will detect hash mismatch and redo full scan.
If the item is injected onto the end of another chain, the next field will be NULL and you won't detect a hash mismatch. It's basically the same issue as the previous race, though a lot more subtle and unlikely. If you get (from the previous socket) an old value of "sk_hash" and (from the new socket) a new value of "next" that is NULL, you will terminate the loop when you should have restarted it. I'm pretty sure that can occur without the write barrier.
Quoted 5 lines
Quoted 5 lines
fetch next
calculate hash and compare tosk_hash
sk_hashis set to new value
Quoted 7 lines
Quoted 3 lines
So I think in the above cases, your case #2 is not necessarily valid without the barrier.
And another possible issue. If
sk_hashis written before next, and CPU1 is interrupted before CPU2, CPU2 will continually spin on the list until CPU1 comes back and moves it to the new list. Note sure if that is an issue.Probably not. Previously, readers were spining on
read_lock(), when a writer was inside its critical section (write_lock()/write_unlock()). So instead of spining insideread_unlock(), issuing stupid memory transactions, the readers can now spin reading hash chain and populate cpu cache :)
Yes, I thought about that and thought I would point it out, but I agree, what you have is certainly better than spinning on a lock :).
-corey
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 72 lines · Corey Minyard
Quoted 53 lines · Eric Dumazet
Quoted 52 lines · Corey Minyard
Quoted 43 lines · Paul E. McKenney
Quoted 33 lines · Eric Dumazet
Quoted 15 lines · Corey Minyard
Quoted 13 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d
(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
Quoted 8 lines
Quoted 4 lines
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()
macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader
will redo its scanQuoted 7 lines
2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.Quoted 4 lines
3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.If I understand this, without the
smp_wmb(), it is possible that the next field can be written to main memory before the hash value is written. If that happens, the following can occur:Quoted 7 lines
Quoted 2 lines
CPU1 CPU2
next is set to NULL (end of new list)Well, if this item is injected to the same chain, next wont be set to NULL.
That would mean previous writers deleted all items from the chain.
I put my comment in the wrong place, I wasn't talking about being injected into the same chain.
Quoted 8 lines
In this case, readers can see NULL, it is not a problem at all. List is/was empty.
An application cannot complain a packet is not handled if itsbind()syscall is not yet completed :)If item is injected on another chain, we will detect hash mismatch and redo full scan.
Quoted 7 lines
If the item is injected onto the end of another chain, the next field will be NULL and you won't detect a hash mismatch. It's basically the same issue as the previous race, though a lot more subtle and unlikely. If you get (from the previous socket) an old value of "
sk_hash" and (from the new socket) a new value of "next" that is NULL, you will terminate the loop when you should have restarted it. I'm pretty sure that can occur without the write barrier.
One way of dealing with this is to keep a tail pointer. Then, if the element containing the NULL pointer doesn't match the tail pointer seen at the start of the search, or if the tail pointer has changed, restart the search. Memory barriers will be needed. ;-)
Thanx, Paul
Quoted 22 lines
Quoted 12 lines
Quoted 11 lines
fetch next
calculate hash and compare tosk_hash
sk_hashis set to new valueSo I think in the above cases, your case #2 is not necessarily valid without the barrier.
And another possible issue. If
sk_hashis written before next, and CPU1 is interrupted before CPU2, CPU2 will continually spin on the list until CPU1 comes back and moves it to the new list. Note sure if that is an issue.Quoted 5 lines
Probably not. Previously, readers were spining on
read_lock(), when a writer was inside its critical section (write_lock()/write_unlock()). So instead of spining insideread_unlock(), issuing stupid memory transactions, the readers can now spin reading hash chain and populate cpu cache :)Yes, I thought about that and thought I would point it out, but I agree, what you have is certainly better than spinning on a lock :).
-corey
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 51 lines · Paul E. McKenney
Quoted 50 lines · Corey Minyard
Quoted 35 lines · Eric Dumazet
Quoted 34 lines · Corey Minyard
Quoted 33 lines · Paul E. McKenney
Quoted 18 lines · Eric Dumazet
Quoted 5 lines · Corey Minyard
Quoted 3 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)Quoted 7 lines
Quoted 5 lines
"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :Quoted 9 lines
1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader will redo its scan2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.Quoted 7 lines
Quoted 4 lines
If I understand this, without the
smp_wmb(), it is possible that the next field can be written to main memory before the hash value is written. If that happens, the following can occur:CPU1 CPU2
next is set to NULL (end of new list)Well, if this item is injected to the same chain, next wont be set to NULL.
That would mean previous writers deleted all items from the chain.
I put my comment in the wrong place, I wasn't talking about being injected into the same chain.
Quoted 5 lines
In this case, readers can see NULL, it is not a problem at all. List is/was empty.
An application cannot complain a packet is not handled if itsbind()syscall is not yet completed :)
Quoted 8 lines
Quoted 2 lines
Quoted 1 line
If item is injected on another chain, we will detect hash mismatch and redo full scan.
If the item is injected onto the end of another chain, the next field will be NULL and you won't detect a hash mismatch. It's basically the same issue as the previous race, though a lot more subtle and unlikely. If you get (from the previous socket) an old value of "
sk_hash" and (from the new socket) a new value of "next" that is NULL, you will terminate the loop when you should have restarted it. I'm pretty sure that can occur without the write barrier.One way of dealing with this is to keep a tail pointer. Then, if the element containing the NULL pointer doesn't match the tail pointer seen at the start of the search, or if the tail pointer has changed, restart the search. Memory barriers will be needed. ;-)
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in sk_add_node_rcu(sk, &hslot->head); should be enough)
What do you think ?
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 95 lines · Eric Dumazet
Quoted 78 lines · Paul E. McKenney
Quoted 70 lines · Corey Minyard
Quoted 53 lines · Eric Dumazet
Quoted 52 lines · Corey Minyard
Quoted 42 lines · Paul E. McKenney
Quoted 33 lines · Eric Dumazet
Quoted 15 lines · Corey Minyard
Quoted 13 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d
(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
Quoted 8 lines
Quoted 4 lines
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()
macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin :
Reader will redo its scanQuoted 7 lines
2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.Quoted 5 lines
3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value,
possibly carrying the reader to a new position in the same list.If I understand this, without the
smp_wmb(), it is possible that the next field can be written to main memory before the hash value is written. If that happens, the following can occur:Quoted 6 lines
Quoted 2 lines
CPU1 CPU2
next is set to NULL (end of new list)Well, if this item is injected to the same chain, next wont be set to NULL.
That would mean previous writers deleted all items from the chain.
I put my comment in the wrong place, I wasn't talking about being injected into the same chain.
Quoted 7 lines
In this case, readers can see NULL, it is not a problem at all. List is/was empty.
An application cannot complain a packet is not handled if itsbind()syscall is not yet completed :)If item is injected on another chain, we will detect hash mismatch and redo full scan.
Quoted 7 lines
If the item is injected onto the end of another chain, the next field will be NULL and you won't detect a hash mismatch. It's basically the same issue as the previous race, though a lot more subtle and unlikely. If you get (from the previous socket) an old value of "
sk_hash" and (from the new socket) a new value of "next" that is NULL, you will terminate the loop when you should have restarted it. I'm pretty sure that can occur without the write barrier.Quoted 4 lines
One way of dealing with this is to keep a tail pointer. Then, if the element containing the NULL pointer doesn't match the tail pointer seen at the start of the search, or if the tail pointer has changed, restart the search. Memory barriers will be needed. ;-)
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
If not -> restart the loop, aka "goto begin;" :)
Quoted 7 lines
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers.
(existing one insk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
Then the rcu_dereference() would be supplying the needed memory barriers.
Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Thanx, Paul
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 1 line · Paul E. McKenney
O
..snip
Quoted 13 lines
Quoted 13 lines
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
Quoted 8 lines
Quoted 5 lines
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in
sk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
My thought exactly ;-).
Quoted 10 lines
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
I believe that is harmless, as re-scanning the same data should be fine.
Quoted 7 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Kind of my thought, too. That's a lot of work to avoid a single smb_wmb() on the socket creation path. Plus this could be extra confusing.
-corey
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 47 lines · Corey Minyard
Quoted 1 line · Paul E. McKenney
O
..snip
Quoted 14 lines
Quoted 14 lines
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it
can be the 128 values : [ (void*)0 .. (void *)127 ]Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
Quoted 9 lines
Quoted 6 lines
In the two cases Corey mentioned, this trick could let us avoid memory barriers.
(existing one insk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
My thought exactly ;-).
Quoted 10 lines
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
I believe that is harmless, as re-scanning the same data should be fine.
Quoted 7 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Quoted 2 lines
Kind of my thought, too. That's a lot of work to avoid a single
smb_wmb()on the socket creation path. Plus this could be extra confusing.
Just to be clear, I was fulminating against any potential failure to clear the pseudo-NULL pointer, not against the pseudo-pointer itself. This sort of trick is already used in some of the RCU-protected trees (for example, FIB tree, IIRC), so I would look a bit funny fulminating too hard against it. ;-)
The only other high-level approach I have come up with thus far is to maintain separate hash tables for the long-lived UDP sockets (protected by RCU) and for the short-lived UDP sockets (protected by locking). Given the usual bimodal traffic pattern, most of the sockets are short lived, but most of the data is transmitted over long-lived sockets. If a socket receives more than N packets (10? 50? 100?), it is moved from the short-lived table to the long-lived table. Sockets on the short-lived table may be freed directly, while sockets on the long-lived table must be RCU freed -- but this added overhead should be in the noise for a long-lived connection. Lookups hit the RCU-protected table, then the lock protected table, then the RCU-protected table again, but still holding the lock. (Clearly, only search until you find the desired socket.)
However, I am not certain that this short-term/long-term approach is better than the approach that Eric is proposing. It might in fact be worse. But I throw it out anyway on the off-chance that it is helpful as a comparison or as a solution to some future problem.
Thanx, Paul
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Corey MinyardQuoted 38 lines · Corey Minyard
Quoted 1 line · Paul E. McKenney
O
..snip
Quoted 13 lines
Quoted 13 lines
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
Quoted 8 lines
Quoted 5 lines
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in
sk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
My thought exactly ;-).
Quoted 10 lines
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
I believe that is harmless, as re-scanning the same data should be fine.
Quoted 9 lines
Quoted 7 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Kind of my thought, too. That's a lot of work to avoid a single
smb_wmb()on the socket creation path. Plus this could be extra confusing.
Sure this smp_wmb() seems harmless (but, remember this infrastructure will next be deployed for TCP sockets as well ;) )
But saving smp_rmb() at lookup time, for each item is a clear win, no ?
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Paul E. McKenneyQuoted 31 lines · Paul E. McKenney
Quoted 9 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
Quoted 8 lines
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in
sk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
Quoted 6 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Well, for UDP case, hash table will be <= 65536 anyway, we can assume no dynamic kernel memory is in the range [0 .. 65535]
Here is a patch (untested yet, its really time for a sleep for me ;) )
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
We define 'NULL' values as ((unsigned long)values < UDP_HTABLE_SIZE)
This saves memory barriers (a read barrier to fetch 'next' pointers
*before* checking key values) we added in commit 96631ed16c514cf8b28fab991a076985ce378c26 (udp: introduce sk_for_each_rcu_safenext())
This also saves a missing memory barrier spotted by Corey Minyard (a write one in udp_lib_get_port(), between sk_hash update and ->next update)
---
include/linux/list.h | 32 ++++++++++++++++++++++++++++++++ include/linux/rculist.h | 15 ++++++++------- include/net/sock.h | 9 +++++++-- net/ipv4/udp.c | 19 +++++++++++++------ net/ipv6/udp.c | 16 ++++++++++++---- 5 files changed, 72 insertions(+), 19 deletions(-)
include/linux/list.h +32 -0
@@ -654,6 +654,22 @@ static inline void hlist_move_list(struct hlist_head *old, pos && ({prefetch (pos->next);1 ;}) && \ ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ;}); \ pos = pos->next) +/** +* hlist_for_each_entry_nulls - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* @nullval: the iteration should stop if a pointer is < nullval +* +* Special version of hlist_for_each_entry where the end pointer +* can be NULL but also any value < nullval. +*/ +#define hlist_for_each_entry_nulls (tpos, pos, head, member, nullval) \ + for (pos = (head)->first; \ + ((unsigned long)pos >= nullval) && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next)/** * hlist_for_each_entry_continue - iterate over a hlist continuing after current point
@@ -679,6 +695,22 @@ static inline void hlist_move_list(struct hlist_head *old, pos = pos->next)/** +* hlist_for_each_entry_from_nulls - iterate over a hlist continuing from current point +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @member: the name of the hlist_node within the struct. +* @nullval: the iteration should stop if a pointer is < nullval +* +* Special version of hlist_for_each_entry_from where the end pointer +* can be NULL but also any value < nullval. +*/ +#define hlist_for_each_entry_from_nulls (tpos, pos, member, nullval) \ + for (; ((unsigned long)pos >= nullval) && \ + ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @tpos: the type * to use as a loop cursor. * @pos: the &struct hlist_node to use as a loop cursor.
include/linux/rculist.h +8 -7
@@ -384,21 +384,22 @@ static inline void hlist_add_after_rcu(struct hlist_node *prev, pos = rcu_dereference(pos->next))/** -* hlist_for_each_entry_rcu_safenext - iterate over rcu list of given type +* hlist_for_each_entry_rcu_nulls - iterate over rcu list of given type * @tpos: the type * to use as a loop cursor. * @pos: the &struct hlist_node to use as a loop cursor. * @head: the head for your list. * @member: the name of the hlist_node within the struct. -* @next: the &struct hlist_node to use as a next cursor +* @nullval: the iteration should stop if a pointer is < nullval * -* Special version of hlist_for_each_entry_rcu that make sure -* each next pointer is fetched before each iteration. +* Special version of hlist_for_each_entry_rcu where the end pointer +* can be NULL but also any value < nullval. */ -#define hlist_for_each_entry_rcu_safenext (tpos, pos, head, member, next) \ +#define hlist_for_each_entry_rcu_nulls (tpos, pos, head, member, nullval) \ for (pos = rcu_dereference((head)->first); \ - pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && \ + ((unsigned long)pos >= nullval) && \ + ({ prefetch(pos->next); 1; }) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ - pos = rcu_dereference(next)) + pos = rcu_dereference(pos->next))#endif /* __KERNEL__ */ #endif
include/net/sock.h +7 -2
@@ -419,11 +419,16 @@ static __inline__ void sk_add_bind_node(struct sock *sk,#define sk_for_each (__sk, node, list) \ hlist_for_each_entry(__sk, node, list, sk_node) -#define sk_for_each_rcu_safenext (__sk, node, list, next) \ - hlist_for_each_entry_rcu_safenext(__sk, node, list, sk_node, next) +#define sk_for_each_nulls (__sk, node, list, nullval) \ + hlist_for_each_entry_nulls(__sk, node, list, sk_node, nullval) +#define sk_for_each_rcu_nulls (__sk, node, list, nullval) \ + hlist_for_each_entry_rcu_nulls(__sk, node, list, sk_node, nullval)#define sk_for_each_from (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_from(__sk, node, sk_node) +#define sk_for_each_from_nulls (__sk, node, nullval) \ + if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ + hlist_for_each_entry_from_nulls(__sk, node, sk_node, nullval)#define sk_for_each_continue (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_continue(__sk, node, sk_node)
net/ipv4/udp.c +13 -6
@@ -129,7 +129,7 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num,struct sock *sk2;struct hlist_node *node; -sk_for_each (sk2, node, &hslot->head) +sk_for_each_nulls (sk2, node, &hslot->head,UDP_HTABLE_SIZE ) if (net_eq(sock_net(sk2), net) && sk2 != sk && sk2->sk_hash == num &&
@@ -256,7 +256,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node, *next; +struct hlist_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];
@@ -266,7 +266,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { +sk_for_each_rcu_nulls (sk, node, &hslot->head,UDP_HTABLE_SIZE ) {/* * lockless reader, and SLAB_DESTROY_BY_RCU items: * We must check this item was not moved to another chain
@@ -280,6 +280,13 @@ begin: badness = score; } } +/* +* if the 'NULL' pointer we got at the end of this lookup is +* not the expected one, we must restart lookup. +*/ +if ((unsigned long )node != hash) + goto begin; +if (result) {if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) result =NULL ;
@@ -324,7 +331,7 @@ static inline struct sock *udp_v4_mcast_next(struct sock *sk,struct sock *s = sk;unsigned short hnum =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_for_each_from_nulls (s, node,UDP_HTABLE_SIZE ) {struct inet_sock *inet =inet_sk (s); if (s->sk_hash != hnum ||
@@ -1556,7 +1563,7 @@ static struct sock *udp_get_first(struct seq_file *seq, int start)struct hlist_node *node;struct udp_hslot *hslot = &state->udp_table->hash[state->bucket];spin_lock_bh (&hslot->lock); -sk_for_each (sk, node, &hslot->head) { +sk_for_each_nulls (sk, node, &hslot->head,UDP_HTABLE_SIZE ) {if (!net_eq (sock_net (sk), net))continue ;if (sk->sk_family == state->family)
@@ -1746,7 +1753,7 @@ void __init udp_table_init(struct udp_table *table)int i;for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { -INIT_HLIST_HEAD (&table->hash[i].head); + table->hash[i].head.first = (struct hlist_node *)i;spin_lock_init (&table->hash[i].lock); } }
net/ipv6/udp.c +12 -4
@@ -98,7 +98,7 @@ static struct sock *__udp6_lib_lookup(struct net *net, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node, *next; +struct hlist_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];
@@ -108,19 +108,27 @@ static struct sock *__udp6_lib_lookup(struct net *net, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { +sk_for_each_rcu_nulls (sk, node, &hslot->head,UDP_HTABLE_SIZE ) {/* * lockless reader, and SLAB_DESTROY_BY_RCU items: * We must check this item was not moved to another chain */ if (udp_hashfn (net, sk->sk_hash) != hash) goto begin; - score =compute_score (sk, net, hnum, saddr, sport, daddr, dport, dif); + score =compute_score (sk, net, hnum, saddr, sport, + daddr, dport, dif);if (score > badness) { result = sk; badness = score; } } +/* +* if the 'NULL' pointer we got at the end of this lookup is +* not the expected one, we must restart lookup. +*/ +if ((unsigned long )node != hash) + goto begin; +if (result) {if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) result =NULL ;
@@ -364,7 +372,7 @@ static struct sock *udp_v6_mcast_next(struct sock *sk,struct sock *s = sk;unsigned short num =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_for_each_from_nulls (s, node,UDP_HTABLE_SIZE ) {struct inet_sock *inet =inet_sk (s);if (sock_net (s) !=sock_net (sk))
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 51 lines · Eric Dumazet
Quoted 31 lines · Paul E. McKenney
Quoted 9 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
Quoted 8 lines
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in
sk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
Quoted 6 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Well, for UDP case, hash table will be <= 65536 anyway, we can assume no dynamic kernel memory is in the range [0 .. 65535]
Here is a patch (untested yet, its really time for a sleep for me ;) )
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
Quoted 5 lines
We define 'NULL' values as ((unsigned long)values <
UDP_HTABLE_SIZE)This saves memory barriers (a read barrier to fetch 'next' pointers
*before* checking key values) we added in commit96631ed16c514cf8b28fab991a076985ce378c26(udp: introducesk_for_each_rcu_safenext()) This also saves a missing memory barrier spotted by Corey Minyard (a write one inudp_lib_get_port(), betweensk_hashupdate and ->next update)
I think you are right, this will certainly perform a lot better without the read barrier in the list traversal. I haven't seen any problems with this approach, though it's unusual enough to perhaps warrant some extra comments in the code.
You do need to modify udp_lib_unhash(), as sk_del_node_init_rcu() will do a NULL check on the ->next value, so you will need a special version of that as well.
-corey
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 52 lines · Corey Minyard
Quoted 51 lines · Eric Dumazet
Quoted 31 lines · Paul E. McKenney
Quoted 9 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
Quoted 8 lines
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in
sk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
Quoted 6 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Well, for UDP case, hash table will be <= 65536 anyway, we can assume no dynamic kernel memory is in the range [0 .. 65535]
Here is a patch (untested yet, its really time for a sleep for me ;) )
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
Quoted 9 lines
Quoted 5 lines
We define 'NULL' values as ((unsigned long)values <
UDP_HTABLE_SIZE)This saves memory barriers (a read barrier to fetch 'next' pointers
*before* checking key values) we added in commit96631ed16c514cf8b28fab991a076985ce378c26(udp: introducesk_for_each_rcu_safenext()) This also saves a missing memory barrier spotted by Corey Minyard (a write one inudp_lib_get_port(), betweensk_hashupdate and ->next update)I think you are right, this will certainly perform a lot better without the read barrier in the list traversal. I haven't seen any problems with this approach, though it's unusual enough to perhaps warrant some extra comments in the code.
You do need to modify
udp_lib_unhash(), assk_del_node_init_rcu()will do a NULL check on the ->next value, so you will need a special version of that as well.
Yes, we need many new macros, like sk_next_nulls(), sk_head_nulls(), ...
I have a working patch now, but not yet presentable for lkml :)
This patch need to touch files outside of netdev scope, so will need really good shape and documentation.
(Probably a new file : include/linux/list_nulls.h ?)
Maybe in the meantime, we can commit a temporary patch doing the smp_wmb() you suggested ?
Thanks
[PATCH] udp: add a missing smp_wmb() in udp_lib_get_port()
Corey Minyard spotted a missing memory barrier in udp_lib_get_port()
We need to make sure a reader cannot read the new 'sk->sk_next' value and previous value of 'sk->sk_hash'. Or else, an item could be deleted from a chain, and inserted into another chain. If new chain was empty before the move, 'next' pointer is NULL, and lockless reader can not detect it missed following items in original chain.
This patch is temporary, since we expect an upcoming patch to introduce another way of handling the problem.
net/ipv4/udp.c +5 -0
@@ -189,6 +189,11 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,inet_sk (sk)->num = snum; sk->sk_hash = snum;if (sk_unhashed (sk)) { +/* +* We need that previous write to sk->sk_hash committed +* before write to sk->next done in following add_node() variant +*/ +smp_wmb ();sk_add_node_rcu (sk, &hslot->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 ); }
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
From: Eric Dumazet <[email protected]>
Date: Thu, 30 Oct 2008 06:50:07 +0100
Quoted 10 lines
[PATCH] udp: add a missing
smp_wmb()inudp_lib_get_port()Corey Minyard spotted a missing memory barrier in
udp_lib_get_port()We need to make sure a reader cannot read the new '
sk->sk_next' value and previous value of 'sk->sk_hash'. Or else, an item could be deleted from a chain, and inserted into another chain. If new chain was empty before the move, 'next' pointer is NULL, and lockless reader can not detect it missed following items in original chain.
Quoted 4 lines
This patch is temporary, since we expect an upcoming patch to introduce another way of handling the problem.
Signed-off-by Eric Dumazet <[email protected]>
I've applied this to net-next-2.6
David Miller
maintainerRe: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Eric DumazetFrom: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 23:08:30 +0100
net/ipv4/udp.c void __init udp_table_init(struct udp_table *table) · line 1756
Quoted 6 lines
net/ipv4/udp.c +1
-1@@ -1746,7 +1753,7 @@ void __init udp_table_init(struct udp_table *table)int i;for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { -INIT_HLIST_HEAD (&table->hash[i].head); + table->hash[i].head.first = (struct hlist_node *)i;
Please hide this behind some list.h interface macro, even something as simple as INIT_HLIST_HEAD_NULLS(X, index) would suffice.
And as Corey said, the code needs more comments for something as clever as this! :-)
Thanks.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 13 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 23:08:30 +0100net/ipv4/udp.c void __init udp_table_init(struct udp_table *table) · line 1756
Quoted 6 lines
net/ipv4/udp.c +1
-1@@ -1746,7 +1753,7 @@ void __init udp_table_init(struct udp_table *table)int i;for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { -INIT_HLIST_HEAD (&table->hash[i].head); + table->hash[i].head.first = (struct hlist_node *)i;Please hide this behind some
list.hinterface macro, even something as simple asINIT_HLIST_HEAD_NULLS(X, index)would suffice.
Quoted 3 lines
And as Corey said, the code needs more comments for something as clever as this! :-)
Yes I agree 100%, please give me one day to prepare a real patch, or else akpm will kill us :)
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 15 lines · Eric Dumazet
Quoted 14 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 23:08:30 +0100net/ipv4/udp.c void __init udp_table_init(struct udp_table · line 1757
Quoted 7 lines
net/ipv4/udp.c +1
-1@@ -1746,7 +1753,7 @@ void __init udp_table_init(struct udp_table *table)int i;for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { -INIT_HLIST_HEAD (&table->hash[i].head); + table->hash[i].head.first = (struct hlist_node *)i;Please hide this behind some
list.hinterface macro, even something as simple asINIT_HLIST_HEAD_NULLS(X, index)would suffice.
Quoted 7 lines
Quoted 3 lines
And as Corey said, the code needs more comments for something as clever as this! :-)
Yes I agree 100%, please give me one day to prepare a real patch, or else akpm will kill us :)
If we design something that could be reused, say for TCP sockets, we need to be able to handle very large number of 'NULL' pointers, say, up to 64*1024*1024
So lets use the low order bit, set to one for "NULL" pointers, and 0 for regular pointers.
This gives us 31 bits (or 63 bits) to store any valuable info :)
and all ...._nulls() macros would not need to know the max value (UDP_HTABLE_SIZE in UDP case), since all they have to do is a test ((unsigned long)pos & 1)
At iterator exit, pos would contain the 'index' value, (pos >> 1), to hide this implementation detail.
David Miller
maintainerRe: [PATCH 2/2] udp: RCU handling for Unicast packets.
From: Eric Dumazet <[email protected]>
Date: Thu, 30 Oct 2008 08:04:42 +0100
Quoted 5 lines
If we design something that could be reused, say for TCP sockets, we need to be able to handle very large number of 'NULL' pointers, say, up to 64*1024*1024
So lets use the low order bit, set to one for "NULL" pointers, and 0 for regular pointers.
Quoted 7 lines
This gives us 31 bits (or 63 bits) to store any valuable info :)
and all ...._nulls() macros would not need to know the max value (
UDP_HTABLE_SIZEin UDP case), since all they have to do is a test ((unsigned long)pos & 1)At iterator exit, pos would contain the 'index' value, (pos >> 1), to hide this implementation detail.
This sound fine to me. Quite an improvement in fact :)
PATCH 1/1 udp: Introduce special NULL pointers for hlist termination
In reply to Eric DumazetQuoted 32 lines · Eric Dumazet
Quoted 31 lines · Paul E. McKenney
Quoted 9 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
Quoted 8 lines
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in
sk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
Quoted 7 lines
Quoted 6 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Ok, here is an updated and tested patch.
Thanks everybody
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
We introduce a new type of hlist implementation, named hlist_nulls, were we use the least significant bit of the 'ptr' to tell if its a "NULL" value or a pointer to an object. We expect to use this new hlist variant for TCP as well.
For UDP/UDP-Lite hash table, we use 128 different "NULL" values, (UDP_HTABLE_SIZE=128)
Using hlist_nulls saves memory barriers (a read barrier to fetch 'next'
pointers *before* checking key values) we added in commit 96631ed16c514cf8b28fab991a076985ce378c26 (udp: introduce sk_for_each_rcu_safenext())
This also saves a write memory barrier in udp_lib_get_port(), between sk->sk_hash update and sk->next update)
---
include/linux/list_nulls.h | 97 ++++++++++++++++++++++++++++++++ include/linux/rculist.h | 17 ----- include/linux/rculist_nulls.h | 55 ++++++++++++++++++ include/net/sock.h | 50 ++++++++++++---- include/net/udp.h | 2 net/ipv4/udp.c | 40 ++++++------- net/ipv6/udp.c | 22 ++++--- 7 files changed, 228 insertions(+), 55 deletions(-)
include/linux/list_nulls.h +97 -0
@@ -0,0 +1,97 @@ +#ifndef _LINUX_LIST_NULLS_H +#define _LINUX_LIST_NULLS_H + +#include <linux/list.h> + +/* +* Special versions of lists, where a NULL pointer may have different values. +* (up to 2^31 different values guaranteed on all platforms) +* +* The least significant bit of 'ptr' is used to encode the 'NULL' value. +* Set to 1 : This is a NULL value (ptr >> 1) +* Set to 0 : This is a pointer to some object (ptr) +* +* Used for UDP sockets. +*/ + +struct hlist_nulls_head { +struct hlist_nulls_node *first; +}; + +struct hlist_nulls_node { +struct hlist_nulls_node *next, **pprev; +}; + +#define INIT_HLIST_NULLS_HEAD (ptr, nulls) \ + ((ptr)->first = (struct hlist_nulls_node *) (1UL | ((nulls) << 1))) + +#define hlist_nulls_entry (ptr, type, member) container_of(ptr,type,member) + +/** +* ptr_is_a_nulls - Test if a ptr to struct hlist_nulls_node is a nulls +* @ptr: ptr to be tested +* +*/ +static inline int is_a_nulls (struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr &1 ); +} + +/** +* get_nulls_value - Returns the null associated with a ptr +* @ptr: ptr to struct hlist_nulls_node +* +* Caller must check is_a_nulls(ptr) is true before calling this. +*/ +static inline unsigned long get_nulls_value (struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr) >>1 ; +} + +static inline int hlist_nulls_unhashed (const struct hlist_nulls_node *h) +{ +return !h->pprev; +} + +static inline int hlist_nulls_empty (const struct hlist_nulls_head *h) +{ +return is_a_nulls (h->first); +} + +static inline void __hlist_nulls_del (struct hlist_nulls_node *n) +{ +struct hlist_nulls_node *next = n->next; +struct hlist_nulls_node **pprev = n->pprev; + *pprev = next; +if (!is_a_nulls (next)) + next->pprev = pprev; +} + +/** +* hlist_nulls_for_each_entry - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry (tpos, pos, head, member) \ + for (pos = (head)->first; \ + (!is_a_nulls(pos)) && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** +* hlist_nulls_for_each_entry_from - iterate over a hlist continuing from current point +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry_from (tpos, pos, member) \ + for (; (!is_a_nulls(pos)) && \ + ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif
include/linux/rculist.h +0 -17
@@ -383,22 +383,5 @@ static inline void hlist_add_after_rcu(struct hlist_node *prev, ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ; }); \ pos = rcu_dereference(pos->next)) -/** -* hlist_for_each_entry_rcu_safenext - iterate over rcu list of given type -* @tpos: the type * to use as a loop cursor. -* @pos: the &struct hlist_node to use as a loop cursor. -* @head: the head for your list. -* @member: the name of the hlist_node within the struct. -* @next: the &struct hlist_node to use as a next cursor -* -* Special version of hlist_for_each_entry_rcu that make sure -* each next pointer is fetched before each iteration. -*/ -#define hlist_for_each_entry_rcu_safenext (tpos, pos, head, member, next) \ - for (pos = rcu_dereference((head)->first); \ - pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && \ - ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ - pos = rcu_dereference(next)) -#endif /* __KERNEL__ */ #endif
include/linux/rculist_nulls.h +55 -0
55 lines not on this page · raw message
include/net/sock.h +39 -11
126 lines not on this page · raw message
include/net/udp.h +1 -1
@@ -51,7 +51,7 @@ struct udp_skb_cb {#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb))struct udp_hslot { -struct hlist_head head; +struct hlist_nulls_head head;spinlock_t lock; } __attribute__((aligned (2 *sizeof (long ))));struct udp_table {
net/ipv4/udp.c +21 -19
@@ -127,9 +127,9 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num, const struct sock *sk2)) {struct sock *sk2; -struct hlist_node *node; +struct hlist_nulls_node *node; -sk_for_each (sk2, node, &hslot->head) +sk_nulls_for_each (sk2, node, &hslot->head) if (net_eq(sock_net(sk2), net) && sk2 != sk && sk2->sk_hash == num &&
@@ -189,12 +189,7 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,inet_sk (sk)->num = snum; sk->sk_hash = snum;if (sk_unhashed (sk)) { -/* -* We need that previous write to sk->sk_hash committed -* before write to sk->next done in following add_node() variant -*/ -smp_wmb (); -sk_add_node_rcu (sk, &hslot->head); +sk_nulls_add_node_rcu (sk, &hslot->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 ); } error =0 ;
@@ -261,7 +256,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node, *next; +struct hlist_nulls_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];
@@ -271,7 +266,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { +sk_nulls_for_each_rcu (sk, node, &hslot->head) {/* * lockless reader, and SLAB_DESTROY_BY_RCU items: * We must check this item was not moved to another chain
@@ -285,6 +280,13 @@ begin: badness = score; } } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +*/ +if (get_nulls_value (node) != hash) + goto begin; +if (result) {if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) result =NULL ;
@@ -325,11 +327,11 @@ static inline struct sock *udp_v4_mcast_next(struct sock *sk, __be16 rmt_port, __be32 rmt_addr, int dif) { - struct hlist_node *node; + struct hlist_nulls_node *node;struct sock *s = sk;unsigned short hnum =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_nulls_for_each_from (s, node) {struct inet_sock *inet =inet_sk (s); if (s->sk_hash != hnum ||
@@ -976,7 +978,7 @@ void udp_lib_unhash(struct sock *sk)struct udp_hslot *hslot = &udptable->hash[hash];spin_lock_bh (&hslot->lock); -if (sk_del_node_init_rcu (sk)) { +if (sk_nulls_del_node_init_rcu (sk)) {inet_sk (sk)->num =0 ;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); }
@@ -1129,7 +1131,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,int dif;spin_lock (&hslot->lock); - sk =sk_head (&hslot->head); + sk =sk_nulls_head (&hslot->head); dif = skb->dev->ifindex; sk =udp_v4_mcast_next (sk, uh->dest, daddr, uh->source, saddr, dif);if (sk) {
@@ -1138,7 +1140,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, do { struct sk_buff *skb1 = skb; - sknext = udp_v4_mcast_next(sk_next(sk), uh->dest, daddr, + sknext = udp_v4_mcast_next(sk_nulls_next(sk), uh->dest, daddr, uh->source, saddr, dif); if (sknext) skb1 = skb_clone(skb, GFP_ATOMIC);
@@ -1558,10 +1560,10 @@ static struct sock *udp_get_first(struct seq_file *seq, int start)struct net *net =seq_file_net (seq);for (state->bucket = start; state->bucket <UDP_HTABLE_SIZE ; ++state->bucket) { -struct hlist_node *node; +struct hlist_nulls_node *node;struct udp_hslot *hslot = &state->udp_table->hash[state->bucket];spin_lock_bh (&hslot->lock); -sk_for_each (sk, node, &hslot->head) { +sk_nulls_for_each (sk, node, &hslot->head) {if (!net_eq (sock_net (sk), net))continue ;if (sk->sk_family == state->family)
@@ -1580,7 +1582,7 @@ static struct sock *udp_get_next(struct seq_file *seq, struct sock *sk)struct net *net =seq_file_net (seq);do { - sk =sk_next (sk); + sk =sk_nulls_next (sk); }while (sk && (!net_eq (sock_net (sk), net) || sk->sk_family != state->family));if (!sk) {
@@ -1751,7 +1753,7 @@ void __init udp_table_init(struct udp_table *table)int i;for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { -INIT_HLIST_HEAD (&table->hash[i].head); +INIT_HLIST_NULLS_HEAD (&table->hash[i].head, i);spin_lock_init (&table->hash[i].lock); } }
net/ipv6/udp.c +15 -7
@@ -98,7 +98,7 @@ static struct sock *__udp6_lib_lookup(struct net *net, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node, *next; +struct hlist_nulls_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];
@@ -108,19 +108,27 @@ static struct sock *__udp6_lib_lookup(struct net *net, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { +sk_nulls_for_each_rcu (sk, node, &hslot->head) {/* * lockless reader, and SLAB_DESTROY_BY_RCU items: * We must check this item was not moved to another chain */ if (udp_hashfn (net, sk->sk_hash) != hash) goto begin; - score =compute_score (sk, net, hnum, saddr, sport, daddr, dport, dif); + score =compute_score (sk, net, hnum, saddr, sport, + daddr, dport, dif);if (score > badness) { result = sk; badness = score; } } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +*/ +if (get_nulls_value (node) != hash) + goto begin; +if (result) {if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) result =NULL ;
@@ -360,11 +368,11 @@ static struct sock *udp_v6_mcast_next(struct sock *sk, __be16 rmt_port, struct in6_addr *rmt_addr, int dif) { - struct hlist_node *node; + struct hlist_nulls_node *node;struct sock *s = sk;unsigned short num =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_nulls_for_each_from (s, node) {struct inet_sock *inet =inet_sk (s);if (sock_net (s) !=sock_net (sk))
@@ -409,7 +417,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,int dif;spin_lock (&hslot->lock); - sk =sk_head (&hslot->head); + sk =sk_nulls_head (&hslot->head); dif =inet6_iif (skb); sk =udp_v6_mcast_next (sk, uh->dest, daddr, uh->source, saddr, dif);if (!sk) {
@@ -418,7 +426,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } sk2 = sk; -while ((sk2 =udp_v6_mcast_next (sk_next (sk2), uh->dest, daddr, +while ((sk2 =udp_v6_mcast_next (sk_nulls_next (sk2), uh->dest, daddr, uh->source, saddr, dif))) {struct sk_buff *buff =skb_clone (skb,GFP_ATOMIC );if (buff) {
Re: [PATCH] udp: Introduce special NULL pointers for hlist termination
Quoted 67 lines · On Thu, 30 Oct 2008 16:40:01 +0100 Eric Dumazet <[email protected]> wrote:
Quoted 34 lines · Eric Dumazet
Quoted 33 lines · Paul E. McKenney
Quoted 12 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it
can be the 128 values : [ (void*)0 .. (void *)127 ]Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
If not -> restart the loop, aka "goto begin;" :)
Quoted 7 lines
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers.
(existing one insk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
Quoted 7 lines
Quoted 6 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Ok, here is an updated and tested patch.
Thanks everybody
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
We introduce a new type of hlist implementation, named
hlist_nulls, were we use the least significant bit of the 'ptr' to tell if its a "NULL" value or a pointer to an object. We expect to use this new hlist variant for TCP as well.For UDP/UDP-Lite hash table, we use 128 different "NULL" values, (UDP_HTABLE_SIZE=128)
Using
hlist_nullssaves memory barriers (a read barrier to fetch 'next' pointers *before* checking key values) we added in commit96631ed16c514cf8b28fab991a076985ce378c26
(udp: introducesk_for_each_rcu_safenext())
Quoted 5 lines
This also saves a write memory barrier in
udp_lib_get_port(), betweensk->sk_hashupdate andsk->nextupdate)Signed-off-by Eric Dumazet <[email protected]>---
IMHO this goes over the edge into tricky hack. Is it really worth it? Is there a better simpler way?
Re: [PATCH] udp: Introduce special NULL pointers for hlist termination
Quoted 73 lines · Stephen Hemminger
Quoted 69 lines · On Thu, 30 Oct 2008 16:40:01 +0100 Eric Dumazet <[email protected]> wrote:
Quoted 34 lines · Eric Dumazet
Quoted 32 lines · Paul E. McKenney
Quoted 9 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Then, when performing a lookup, a reader should check the "NULL" pointer it get at the end of its lookup has is the "hash" value of its chain.
Quoted 9 lines
If not -> restart the loop, aka "goto begin;" :)
We could avoid memory barriers then.
In the two cases Corey mentioned, this trick could let us avoid memory barriers. (existing one in
sk_add_node_rcu(sk, &hslot->head); should be enough)What do you think ?
Kinky!!! ;-)
Then the
rcu_dereference()would be supplying the needed memory barriers.Hmmm... I guess that the only confusion would be if the element got removed and then added to the same list. But then if its pointer was pseudo-NULL, then that would mean that all subsequent elements had been removed, and all preceding ones added after the scan started.
Which might well be harmless, but I must defer to you on this one at the moment.
Quoted 7 lines
Quoted 7 lines
If you need a larger hash table, another approach would be to set the pointer's low-order bit, allowing the upper bits to be a full-sized index -- or even a pointer to the list header. Just make very sure to clear the pointer when freeing, or an element on the freelist could end up looking like a legitimate end of list... Which again might well be safe, but why inflict this on oneself?
Ok, here is an updated and tested patch.
Thanks everybody
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
We introduce a new type of hlist implementation, named
hlist_nulls, were we use the least significant bit of the 'ptr' to tell if its a "NULL" value or a pointer to an object. We expect to use this new hlist variant for TCP as well.For UDP/UDP-Lite hash table, we use 128 different "NULL" values, (UDP_HTABLE_SIZE=128)
Using
hlist_nullssaves memory barriers (a read barrier to fetch 'next'
pointers *before* checking key values) we added in commit96631ed16c514cf8b28fab991a076985ce378c26(udp: introducesk_for_each_rcu_safenext())This also saves a write memory barrier in
udp_lib_get_port(), betweensk->sk_hashupdate andsk->nextupdate)
Quoted 7 lines
Quoted 3 lines
Signed-off-by Eric Dumazet <[email protected]>---
IMHO this goes over the edge into tricky hack. Is it really worth it? Is there a better simpler way?
The only think I've thought of is to do a single smp_rmb() after the loop scanning the list and check the sk_hash value again. That's better than the read barrier for every list element, but still not as good as this list from a performance point of view.
IMHO, this is a tricky hack, but it if is well abstracted and documented I think it's ok. I'd guess something like this will become more often used as we get larger numbers of processors on systems.
It is annoying that it doesn't help the performance for multicast. However, I think the current patch will solve the DOS issue for multicast, since it switches to a normal spinlock and has a per-list lock.
-corey
Re: [PATCH] udp: Introduce special NULL pointers for hlist termination
Quoted 2 lines · Corey Minyard
It is annoying that it doesn't help the performance for multicast. However, I think the current patch will solve the DOS issue for multicast, since it switches to a normal spinlock and has a per-list lock.
About multicast, it should be possible to do something about it, if it happens to be an issue.
That is, do a lockless lookup and accumulate matching sockets ptr in a table
(incrementing their refcount if not zero, checking key, adding in a local stack).
If lookup must be restarted, forget all accumulated sockets (sock_put(ptrs))
goto begin;
Then, send the (cloned) packet to all accumulated sockets, and sock_put() them to release the refcount.
Well, looking at current implementation, I found that udp_v4_mcast_next() doesnt take into account the 'struct net *net', so we have a bug here...
udp_v6_mcast_next() is buggy too (or at least its caller is)
David, please find a patch against net-2.6
Thanks
[PATCH] udp: multicast packets need to check namespace
Current UDP multicast delivery is not namespace aware.
---net/ipv4/udp.c | 14 ++++++++------net/ipv6/udp.c | 8 ++++----
2 files changed, 12 insertions(+), 10 deletions(-)
net/ipv4/udp.c +8 -6
@@ -284,7 +284,7 @@ struct sock *udp4_lib_lookup(struct net *net, __be32 saddr, __be16 sport, }EXPORT_SYMBOL_GPL (udp4_lib_lookup); -static inline struct sock *udp_v4_mcast_next (struct sock *sk, +static inline struct sock *udp_v4_mcast_next (struct net *net,struct sock *sk,__be16 loc_port,__be32 loc_addr,__be16 rmt_port,__be32 rmt_addr,int dif)
@@ -295,8 +295,9 @@ static inline struct sock *udp_v4_mcast_next(struct sock *sk,sk_for_each_from (s, node) {struct inet_sock *inet =inet_sk (s); - - if (s->sk_hash != hnum || + + if (!net_eq(sock_net(s), net) || + s->sk_hash != hnum || (inet->daddr && inet->daddr != rmt_addr) || (inet->dport != rmt_port && inet->dport) || (inet->rcv_saddr && inet->rcv_saddr != loc_addr) ||
@@ -1079,15 +1080,16 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,read_lock (&udp_hash_lock); sk =sk_head (&udptable[udp_hashfn (net,ntohs (uh->dest))]); dif = skb->dev->ifindex; - sk =udp_v4_mcast_next (sk, uh->dest, daddr, uh->source, saddr, dif); + sk =udp_v4_mcast_next (net, sk, uh->dest, daddr, uh->source, saddr, dif);if (sk) {struct sock *sknext =NULL ; do { struct sk_buff *skb1 = skb; - sknext = udp_v4_mcast_next(sk_next(sk), uh->dest, daddr, - uh->source, saddr, dif); + sknext = udp_v4_mcast_next(net, sk_next(sk), uh->dest, + daddr, uh->source, saddr, + dif); if (sknext) skb1 = skb_clone(skb, GFP_ATOMIC);
net/ipv6/udp.c +4 -4
@@ -328,7 +328,7 @@ drop:return -1 ; } -static struct sock *udp_v6_mcast_next (struct sock *sk, +static struct sock *udp_v6_mcast_next (struct net *net,struct sock *sk,__be16 loc_port,struct in6_addr *loc_addr,__be16 rmt_port,struct in6_addr *rmt_addr,int dif)
@@ -340,7 +340,7 @@ static struct sock *udp_v6_mcast_next(struct sock *sk,sk_for_each_from (s, node) {struct inet_sock *inet =inet_sk (s); -if (sock_net (s) !=sock_net (sk)) +if (!net_eq (sock_net (s), net))continue ;if (s->sk_hash == num && s->sk_family ==PF_INET6 ) {
@@ -383,14 +383,14 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,read_lock (&udp_hash_lock); sk =sk_head (&udptable[udp_hashfn (net,ntohs (uh->dest))]); dif =inet6_iif (skb); - sk =udp_v6_mcast_next (sk, uh->dest, daddr, uh->source, saddr, dif); + sk =udp_v6_mcast_next (net, sk, uh->dest, daddr, uh->source, saddr, dif);if (!sk) {kfree_skb (skb); goto out; } sk2 = sk; -while ((sk2 =udp_v6_mcast_next (sk_next (sk2), uh->dest, daddr, +while ((sk2 =udp_v6_mcast_next (net,sk_next (sk2), uh->dest, daddr, uh->source, saddr, dif))) {struct sk_buff *buff =skb_clone (skb,GFP_ATOMIC );if (buff) {
Pavel Emelyanov
Acked-byRe: [PATCH] udp: Introduce special NULL pointers for hlist termination
Quoted 7 lines
Well, looking at current implementation, I found that
udp_v4_mcast_next()doesnt take into account the 'struct net *net', so we have a bug here...
udp_v6_mcast_next()is buggy too (or at least its caller is)David, please find a patch against net-2.6
Quoted 8 lines
Thanks
[PATCH] udp: multicast packets need to check namespace
Current UDP multicast delivery is not namespace aware.
Signed-off-by Eric Dumazet <[email protected]>
Thanks!
Quoted 5 lines
--- net/ipv4/udp.c | 14 ++++++++------ net/ipv6/udp.c | 8 ++++---- 2 files changed, 12 insertions(+), 10 deletions(-)
Re: [PATCH] udp: Introduce special NULL pointers for hlist termination
From: Pavel Emelyanov <[email protected]>
Date: Fri, 31 Oct 2008 17:55:50 +0300
Quoted 9 lines
Quoted 9 lines
Well, looking at current implementation, I found that
udp_v4_mcast_next()doesnt take into account the 'struct net *net', so we have a bug here...
udp_v6_mcast_next()is buggy too (or at least its caller is)David, please find a patch against net-2.6
Thanks
Quoted 8 lines
Quoted 6 lines
[PATCH] udp: multicast packets need to check namespace
Current UDP multicast delivery is not namespace aware.
Signed-off-by Eric Dumazet <[email protected]>Acked-by Pavel Emelyanov <[email protected]>
Applied, thanks everyone.
Re: [PATCH] udp: Introduce special NULL pointers for hlist termination
In reply to Stephen HemmingerQuoted 23 lines · Stephen Hemminger
Quoted 20 lines · On Thu, 30 Oct 2008 16:40:01 +0100 Eric Dumazet <[email protected]> wrote:
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
We introduce a new type of hlist implementation, named
hlist_nulls, were we use the least significant bit of the 'ptr' to tell if its a "NULL" value or a pointer to an object. We expect to use this new hlist variant for TCP as well.For UDP/UDP-Lite hash table, we use 128 different "NULL" values, (UDP_HTABLE_SIZE=128)
Using
hlist_nullssaves memory barriers (a read barrier to fetch 'next'
pointers *before* checking key values) we added in commit96631ed16c514cf8b28fab991a076985ce378c26(udp: introducesk_for_each_rcu_safenext())
Quoted 8 lines
Quoted 5 lines
This also saves a write memory barrier in
udp_lib_get_port(), betweensk->sk_hashupdate andsk->nextupdate)Signed-off-by Eric Dumazet <[email protected]>---
IMHO this goes over the edge into tricky hack. Is it really worth it? Is there a better simpler way?
rwlocks , spinlocks, seqlocks :)
More seriously Stephen, if the infrastructure is clean, and well tested on a relative simple case (UDP), it can then be deployed on a much more interesting protocol : TCP
The moment we switch to RCU, we have to accept the pain of really understand what we did. Details are scary yes.
David Miller
maintainerRe: [PATCH] udp: Introduce special NULL pointers for hlist termination
From: Eric Dumazet <[email protected]>
Date: Thu, 30 Oct 2008 18:12:03 +0100
Quoted 3 lines
More seriously Stephen, if the infrastructure is clean, and well tested on a relative simple case (UDP), it can then be deployed on a much more interesting protocol : TCP
I'm really looking forward to someone finally tackling that problem :)
Quoted 2 lines
The moment we switch to RCU, we have to accept the pain of really understand what we did. Details are scary yes.
Agreed.
Re: [PATCH] udp: Introduce special NULL pointers for hlist termination
In reply to Eric DumazetQuoted 25 lines · Eric Dumazet
[PATCH] udp: Introduce special NULL pointers for hlist termination
In order to safely detect changes in chains, we would like to have different 'NULL' pointers. Each chain in hash table is terminated by an unique 'NULL' value, so that the lockless readers can detect their lookups evaded from their starting chain.
We introduce a new type of hlist implementation, named
hlist_nulls, were we use the least significant bit of the 'ptr' to tell if its a "NULL" value or a pointer to an object. We expect to use this new hlist variant for TCP as well.For UDP/UDP-Lite hash table, we use 128 different "NULL" values, (UDP_HTABLE_SIZE=128)
Using
hlist_nullssaves memory barriers (a read barrier to fetch 'next' pointers *before* checking key values) we added in commit96631ed16c514cf8b28fab991a076985ce378c26
(udp: introducesk_for_each_rcu_safenext())This also saves a write memory barrier in
udp_lib_get_port(), betweensk->sk_hashupdate andsk->nextupdate)Signed-off-by Eric Dumazet <[email protected]>
Quoted 9 lines
--- include/linux/list_nulls.h | 97 ++++++++++++++++++++++++++++++++ include/linux/rculist.h | 17 ----- include/linux/rculist_nulls.h | 55 ++++++++++++++++++ include/net/sock.h | 50 ++++++++++++---- include/net/udp.h | 2 net/ipv4/udp.c | 40 ++++++------- net/ipv6/udp.c | 22 ++++--- 7 files changed, 228 insertions(+), 55 deletions(-)
If we're going to do this, It'd be good to have the list_nulls stuff in their own patch, as clearly they are not UDP specific.
Also, I think it would be very good to have some extensive comments in the list_nulls files describing their use in clear and concise language, because the above changelog doesn't even begin to explain things for those not following this thread.
Re: [PATCH] udp: Introduce special NULL pointers for hlist termination
In reply to Eric DumazetQuoted 2 lines · Eric Dumazet (on Thu, 30 Oct 2008 16:40:01 +0100) wrote:
+/**
+ *ptr_is_a_nulls- Test if a ptr to structhlist_nulls_nodeis a nulls
Typo: comment says 'ptr_is_a_nulls' but function is called 'is_a_nulls'.
Quoted 7 lines
+ * @ptr: ptr to be tested
+ *
+ */include/linux/list_nulls.h +4
-0+static inline int is_a_nulls (struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr &1 ); +}
cover net: RCU lookups for UDP, DCCP and TCP protocol
In reply to Eric DumazetHi all
Here is a serie of three patches (based on net-next-2.6), to continue work with RCU on UDP/TCP/DCCP stacks
Many thanks for all usefull reviews and comments, especially from Paul and Corey.
1) Introduce hlist_nulls variant of hlist
hlist uses NULL value to finish a chain.
hlist_nulls variant use the low order bit set to 1 to signal an end marker. This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
2) Use hlist_nulls in UDP RCU code
This is a straightforward patch, using hlist_nulls infrastructure. RCU-ification already done on UDP two weeks ago, so hlist_nulls permits us to avoid some memory barriers, both at lookup time and delete time. Patch is large because it adds new macros to include/net/sock.h. These macros will be used by TCP & DCCP too.
3) Convert TCP & DCCP hash tables to use RCU & hlist_nulls
RCU was added to UDP lookups, using a fast infrastructure :
- sockets kmem_cache use SLAB_DESTROY_BY_RCU and dont pay the price of call_rcu() at freeing time.
- hlist_nulls permits to use few memory barriers.
This patch uses same infrastructure for TCP/DCCP established and timewait sockets.
Thanks to SLAB_DESTROY_BY_RCU, no slowdown for applications using short lived TCP connections. A followup patch, converting rwlocks to spinlocks will even speedup this case.
__inet_lookup_established() is pretty fast now we dont have to dirty a contended cache line (read_lock/read_unlock)
Only established and timewait hashtable are converted to RCU
(bind table and listen table are still using traditional locking)
Re: [PATCH 0/3] net: RCU lookups for UDP, DCCP and TCP protocol
Quoted 7 lines · Eric Dumazet
1) Introduce
hlist_nullsvariant of hlisthlist uses NULL value to finish a chain.
hlist_nullsvariant use the low order bit set to 1 to signal an end marker. This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
Do you have any numbers that demonstrate the read memory barriers being a performance problem? At least on x86 they should be very cheap because they're normally nops.
-Andi
Signature · 1 line
[email protected]
David Miller
maintainerRe: [PATCH 0/3] net: RCU lookups for UDP, DCCP and TCP protocol
In reply to Eric DumazetFrom: Eric Dumazet <[email protected]>
Date: Thu, 13 Nov 2008 14:13:20 +0100
Quoted 2 lines
Here is a serie of three patches (based on net-next-2.6), to continue work with RCU on UDP/TCP/DCCP stacks
All 4 patches applied to net-next-2.6, thanks a lot Eric!
These patches are incredibly cool!
Re: [PATCH 0/3] net: RCU lookups for UDP, DCCP and TCP protocol
Quoted 1 line · David Miller
These patches are incredibly cool!
How much does this give us on the aim9 udp test?
PATCH 1/3 rcu: Introduce hlist_nulls variant of hlist
In reply to Eric Dumazethlist uses NULL value to finish a chain.
hlist_nulls variant use the low order bit set to 1 to signal an end-of-list marker.
This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
Two new files are added :
include/linux/list_nulls.h
- mimics hlist part of include/linux/list.h, derived to hlist_nulls variant
include/linux/rculist_nulls.h
- mimics hlist part of include/linux/rculist.h, derived to hlist_nulls variant
Only four helpers are declared for the moment :
hlist_nulls_del_init_rcu(), hlist_nulls_del_rcu(), hlist_nulls_add_head_rcu() and hlist_nulls_for_each_entry_rcu()
prefetches() were removed, since an end of list is not anymore NULL value. prefetches() could trigger useless (and possibly dangerous) memory transactions.
Example of use (extracted from __udp4_lib_lookup())
struct sock *sk, *result;
struct hlist_nulls_node *node;
unsigned short hnum = ntohs(dport);
unsigned int hash = udp_hashfn(net, hnum);
struct udp_hslot *hslot = &udptable->hash[hash];
int score, badness;
rcu_read_lock();
begin:
result = NULL;
badness = -1;
sk_nulls_for_each_rcu(sk, node, &hslot->head) {
score = compute_score(sk, net, saddr, hnum, sport,
daddr, dport, dif);
if (score > badness) {
result = sk;
badness = score;
}
}
/*
* if the nulls value we got at the end of this lookup is
* not the expected one, we must restart lookup.
* We probably met an item that was moved to another chain.
*/
if (get_nulls_value(node) != hash)
goto begin;
if (result) {
if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt)))
result = NULL;
else if (unlikely(compute_score(result, net, saddr, hnum, sport,
daddr, dport, dif) < badness)) {
sock_put(result);
goto begin;
}
}
rcu_read_unlock();
return result;---include/linux/list_nulls.h | 94 +++++++++++++++++++++++++++include/linux/rculist_nulls.h | 110 ++++++++++++++++++++++++++++++++
2 files changed, 204 insertions(+)
include/linux/list_nulls.h new file +94 -0
@@ -0,0 +1,94 @@ +#ifndef _LINUX_LIST_NULLS_H +#define _LINUX_LIST_NULLS_H + +/* +* Special version of lists, where end of list is not a NULL pointer, +* but a 'nulls' marker, which can have many different values. +* (up to 2^31 different values guaranteed on all platforms) +* +* In the standard hlist, termination of a list is the NULL pointer. +* In this special 'nulls' variant, we use the fact that objects stored in +* a list are aligned on a word (4 or 8 bytes alignment). +* We therefore use the last significant bit of 'ptr' : +* Set to 1 : This is a 'nulls' end-of-list marker (ptr >> 1) +* Set to 0 : This is a pointer to some object (ptr) +*/ + +struct hlist_nulls_head { +struct hlist_nulls_node *first; +}; + +struct hlist_nulls_node { +struct hlist_nulls_node *next, **pprev; +}; +#define INIT_HLIST_NULLS_HEAD (ptr, nulls) \ + ((ptr)->first = (struct hlist_nulls_node *) (1UL | (((long)nulls) << 1))) + +#define hlist_nulls_entry (ptr, type, member) container_of(ptr,type,member) +/** +* ptr_is_a_nulls - Test if a ptr is a nulls +* @ptr: ptr to be tested +* +*/ +static inline int is_a_nulls (const struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr &1 ); +} + +/** +* get_nulls_value - Get the 'nulls' value of the end of chain +* @ptr: end of chain +* +* Should be called only if is_a_nulls(ptr); +*/ +static inline unsigned long get_nulls_value (const struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr) >>1 ; +} + +static inline int hlist_nulls_unhashed (const struct hlist_nulls_node *h) +{ +return !h->pprev; +} + +static inline int hlist_nulls_empty (const struct hlist_nulls_head *h) +{ +return is_a_nulls (h->first); +} + +static inline void __hlist_nulls_del (struct hlist_nulls_node *n) +{ +struct hlist_nulls_node *next = n->next; +struct hlist_nulls_node **pprev = n->pprev; + *pprev = next; +if (!is_a_nulls (next)) + next->pprev = pprev; +} + +/** +* hlist_nulls_for_each_entry - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry (tpos, pos, head, member) \ + for (pos = (head)->first; \ + (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** +* hlist_nulls_for_each_entry_from - iterate over a hlist continuing from current point +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry_from (tpos, pos, member) \ + for (; (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif
include/linux/rculist_nulls.h new file +110 -0
110 lines not on this page · raw message
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
Quoted 4 lines · Eric Dumazet
hlist uses NULL value to finish a chain.
hlist_nullsvariant use the low order bit set to 1 to signal an end-of-list marker.
Quoted 8 lines
This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
Two new files are added :
include/linux/list_nulls.h
- mimics hlist part ofinclude/linux/list.h, derived tohlist_nullsvariant
How is the !rcu variant useful?
Quoted 31 lines
include/linux/rculist_nulls.h
- mimics hlist part ofinclude/linux/rculist.h, derived tohlist_nullsvariantOnly four helpers are declared for the moment :
hlist_nulls_del_init_rcu(),hlist_nulls_del_rcu(),hlist_nulls_add_head_rcu()andhlist_nulls_for_each_entry_rcu()
prefetches()were removed, since an end of list is not anymore NULL value.prefetches()could trigger useless (and possibly dangerous) memory transactions.Example of use (extracted from
__udp4_lib_lookup())struct sock *sk, *result; struct hlist_nulls_node *node; unsigned short hnum = ntohs(dport); unsigned int hash = udp_hashfn(net, hnum); struct udp_hslot *hslot = &udptable->hash[hash]; int score, badness; rcu_read_lock(); begin: result = NULL; badness = -1; sk_nulls_for_each_rcu(sk, node, &hslot->head) { score = compute_score(sk, net, saddr, hnum, sport, daddr, dport, dif); if (score > badness) { result = sk; badness = score; }
Quoted 8 lines
}
/*
* if the nulls value we got at the end of this lookup is
* not the expected one, we must restart lookup.
* We probably met an item that was moved to another chain.
*/
if (get_nulls_value(node) != hash)
goto begin;So by not using some memory barriers (would be nice to have it illustrated which ones), we can race and end up on the wrong chain, in case that happens we detect this by using this per-chain terminator and try again.
It would be really good to have it explained in the rculist_nulls.h comments what memory barriers are missing, what races they open, and how the this special terminator trick closes that race.
I'm sure most of us understand it now, but will we still in a few months? - how about new people?
Other than that, very cool stuff! :-)
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
Quoted 9 lines · Peter Zijlstra
Quoted 8 lines · Eric Dumazet
hlist uses NULL value to finish a chain.
hlist_nullsvariant use the low order bit set to 1 to signal an end-of-list marker.This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
Quoted 6 lines
Quoted 4 lines
Two new files are added :
include/linux/list_nulls.h
- mimics hlist part ofinclude/linux/list.h, derived tohlist_nullsvariantHow is the !rcu variant useful?
For example, if a process holds a lock, it doesnt need rcu version.
/proc/net/tcp comes to mind
Quoted 14 lines
Quoted 4 lines
include/linux/rculist_nulls.h
- mimics hlist part ofinclude/linux/rculist.h, derived tohlist_nullsvariantQuoted 8 lines
Only four helpers are declared for the moment :
hlist_nulls_del_init_rcu(),hlist_nulls_del_rcu(),hlist_nulls_add_head_rcu()andhlist_nulls_for_each_entry_rcu()
prefetches()were removed, since an end of list is not anymore NULL value.prefetches()could trigger useless (and possibly dangerous) memory transactions.
Quoted 8 lines
So by not using some memory barriers (would be nice to have it illustrated which ones), we can race and end up on the wrong chain, in case that happens we detect this by using this per-chain terminator and try again.
It would be really good to have it explained in the
rculist_nulls.hcomments what memory barriers are missing, what races they open, and how the this special terminator trick closes that race.
OK, maybe I should add a Documentation/RCU/rculist_nulls.txt file with appropriate examples and documentation.
(Say the lookup/insert algorithms, with standard hlist and memory barriers, and with hlist_nulls without those two memory barriers.
(These two memory barriers can be found in commits :
c37ccc0d4e2a4ee52f1a40cff1be0049f2104bba :
udp: add a missing smp_wmb() in udp_lib_get_port()
Corey Minyard spotted a missing memory barrier in udp_lib_get_port()
We need to make sure a reader cannot read the new 'sk->sk_next' value and previous value of 'sk->sk_hash'. Or else, an item could be deleted from a chain, and inserted into another chain. If new chain was empty before the move, 'next' pointer is NULL, and lockless reader can not detect it missed following items in original chain.
This patch is temporary, since we expect an upcoming patch to introduce another way of handling the problem.
And commit 96631ed16c514cf8b28fab991a076985ce378c26 :
udp: introduce sk_for_each_rcu_safenext()
Corey Minyard found a race added in commit 271b72c7fa82c2c7a795bc16896149933110672d (udp: RCU handling for Unicast packets.)
"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new sk_for_each_rcu_safenext() macro.
Quoted 5 lines
I'm sure most of us understand it now, but will we still in a few months? - how about new people?
Other than that, very cool stuff! :-)
Thanks Peter ;)
[PATCH 4/3] rcu: documents rculist_nulls
Quoted 10 lines · Eric Dumazet
Quoted 8 lines · Peter Zijlstra
So by not using some memory barriers (would be nice to have it illustrated which ones), we can race and end up on the wrong chain, in case that happens we detect this by using this per-chain terminator and try again.
It would be really good to have it explained in the
rculist_nulls.hcomments what memory barriers are missing, what races they open, and how the this special terminator trick closes that race.
Quoted 6 lines
OK, maybe I should add a
Documentation/RCU/rculist_nulls.txtfile with appropriate examples and documentation.(Say the lookup/insert algorithms, with standard hlist and memory barriers, and with
hlist_nullswithout those two memory barriers.
[PATCH 4/3] rcu: documents rculist_nulls
Adds Documentation/RCU/rculist_nulls.txt file to describe how 'nulls' end-of-list can help in some RCU algos.
---Documentation/RCU/rculist_nulls.txt | 167 ++++++++++++++++++++++++++
1 files changed, 167 insertions(+)
Documentation/RCU/rculist_nulls.txt new file +167 -0
@@ -0,0 +1,167 @@ +Using hlist_nulls to protect read-mostly linked lists and +objects using SLAB_DESTROY_BY_RCU allocations. + +Please read the basics in Documentation/RCU/listRCU.txt + +Using special makers (called 'nulls') is a convenient way +to solve following problem : + +A typical RCU linked list managing objects which are +allocated with SLAB_DESTROY_BY_RCU kmem_cache can +use following algos : + +1) Lookup algo +-------------- +rcu_read_lock() +begin: +obj = lockless_lookup(key); +if (obj) { + if (!try_get_ref(obj)) // might fail for free objects + goto begin; + /* + * Because a writer could delete object, and a writer could + * reuse these object before the RCU grace period, we + * must check key after geting the reference on object + */ + if (obj->key != key) { // not the object we expected + put_ref(obj); + goto begin; + } +} +rcu_read_unlock(); + +Beware that lockless_lookup(key) cannot use traditional hlist_for_each_entry_rcu() +but a version with an additional memory barrier (smp_rmb()) + +lockless_lookup(key) +{ + struct hlist_node *node, *next; + for (pos = rcu_dereference((head)->first); + pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); + pos = rcu_dereference(next)) + if (obj->key == key) + return obj; + return NULL; + +And note the traditional hlist_for_each_entry_rcu() misses this smp_rmb() : + + struct hlist_node *node; + for (pos = rcu_dereference((head)->first); + pos && ({ prefetch(pos->next); 1; }) && + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); + pos = rcu_dereference(pos->next)) + if (obj->key == key) + return obj; + return NULL; +} + +Quoting Corey Minyard : + +"If the object is moved from one list to another list in-between the + time the hash is calculated and the next field is accessed, and the + object has moved to the end of a new list, the traversal will not + complete properly on the list it should have, since the object will + be on the end of the new list and there's not a way to tell it's on a + new list and restart the list traversal. I think that this can be + solved by pre-fetching the "next" field (with proper barriers) before + checking the key." + +2) Insert algo : +---------------- + +We need to make sure a reader cannot read the new 'obj->obj_next' value +and previous value of 'obj->key'. Or else, an item could be deleted +from a chain, and inserted into another chain. If new chain was empty +before the move, 'next' pointer is NULL, and lockless reader can +not detect it missed following items in original chain. + +/* + * Please note that new inserts are done at the head of list, + * not in the middle or end. + */ +obj = kmem_cache_alloc(...); +lock_chain(); // typically a spin_lock() +obj->key = key; +atomic_inc(&obj->refcnt); +/* + * we need to make sure obj->key is updated before obj->next + */ +smp_wmb(); +hlist_add_head_rcu(&obj->obj_node, list); +unlock_chain(); // typically a spin_unlock() + + +3) Remove algo +-------------- +Nothing special here, we can use a standard RCU hlist deletion. +But thanks to SLAB_DESTROY_BY_RCU, beware a deleted object can be reused +very very fast (before the end of RCU grace period) + +if (put_last_reference_on(obj) { + lock_chain(); // typically a spin_lock() + hlist_del_init_rcu(&obj->obj_node); + unlock_chain(); // typically a spin_unlock() + kmem_cache_free(cachep, obj); +} + + + +-------------------------------------------------------------------------- +With hlist_nulls we can avoid extra smp_rmb() in lockless_lookup() +and extra smp_wmb() in insert function. + +For example, if we choose to store the slot number as the 'nulls' +end-of-list marker for each slot of the hash table, we can detect +a race (some writer did a delete and/or a move of an object +to another chain) checking the final 'nulls' value if +the lookup met the end of chain. If final 'nulls' value +is not the slot number, then we must restart the lookup at +the begining. If the object was moved to same chain, +then the reader doesnt care : It might eventually +scan the list again without harm. + + +1) lookup algo + + head = &table[slot]; + rcu_read_lock(); +begin: + hlist_nulls_for_each_entry_rcu(obj, node, head, member) { + if (obj->key == key) { + if (!try_get_ref(obj)) // might fail for free objects + goto begin; + if (obj->key != key) { // not the object we expected + put_ref(obj); + goto begin; + } + goto out; + } +/* + * if the nulls value we got at the end of this lookup is + * not the expected one, we must restart lookup. + * We probably met an item that was moved to another chain. + */ + if (get_nulls_value(node) != slot) + goto begin; + obj = NULL; + +out: + rcu_read_unlock(); + +2) Insert function : +-------------------- + +/* + * Please note that new inserts are done at the head of list, + * not in the middle or end. + */ +obj = kmem_cache_alloc(cachep); +lock_chain(); // typically a spin_lock() +obj->key = key; +atomic_set(&obj->refcnt, 1); +/* + * insert obj in RCU way (readers might be traversing chain) + */ +hlist_nulls_add_head_rcu(&obj->obj_node, list); +unlock_chain(); // typically a spin_unlock()
Peter Zijlstra
Acked-byRe: [PATCH 4/3] rcu: documents rculist_nulls
Quoted 1 line · Eric Dumazet
Quoted 17 lines · Eric Dumazet
Quoted 10 lines
Quoted 8 lines · Peter Zijlstra
So by not using some memory barriers (would be nice to have it illustrated which ones), we can race and end up on the wrong chain, in case that happens we detect this by using this per-chain terminator and try again.
It would be really good to have it explained in the
rculist_nulls.hcomments what memory barriers are missing, what races they open, and how the this special terminator trick closes that race.Quoted 6 lines
OK, maybe I should add a
Documentation/RCU/rculist_nulls.txtfile with appropriate examples and documentation.(Say the lookup/insert algorithms, with standard hlist and memory barriers, and with
hlist_nullswithout those two memory barriers.
Quoted 7 lines
[PATCH 4/3] rcu: documents
rculist_nullsAdds
Documentation/RCU/rculist_nulls.txtfile to describe how 'nulls' end-of-list can help in some RCU algos.Signed-off-by Eric Dumazet <[email protected]>
David Miller
maintainerRe: [PATCH 4/3] rcu: documents rculist_nulls
From: Peter Zijlstra <[email protected]>
Date: Fri, 14 Nov 2008 16:16:38 +0100
Quoted 22 lines · Eric Dumazet
Quoted 1 line
Quoted 19 lines · Eric Dumazet
Quoted 10 lines
Quoted 8 lines · Peter Zijlstra
So by not using some memory barriers (would be nice to have it illustrated which ones), we can race and end up on the wrong chain, in case that happens we detect this by using this per-chain terminator and try again.
It would be really good to have it explained in the
rculist_nulls.hcomments what memory barriers are missing, what races they open, and how the this special terminator trick closes that race.Quoted 6 lines
OK, maybe I should add a
Documentation/RCU/rculist_nulls.txtfile with appropriate examples and documentation.(Say the lookup/insert algorithms, with standard hlist and memory barriers, and with
hlist_nullswithout those two memory barriers.[PATCH 4/3] rcu: documents
rculist_nulls
Quoted 7 lines
Quoted 5 lines
Adds
Documentation/RCU/rculist_nulls.txtfile to describe how 'nulls' end-of-list can help in some RCU algos.Signed-off-by Eric Dumazet <[email protected]>Acked-by Peter Zijlstra <[email protected]>
Since there seems to be consensus for these changes I'm going to merge this stuff into net-next-2.6 so that I can add in the users that Eric has written.
Thanks everyone for the review and feedback.
Paul E. McKenney
Reviewed-byRe: [PATCH 4/3] rcu: documents rculist_nulls
In reply to Eric DumazetQuoted 10 lines · Eric Dumazet
Quoted 9 lines · Eric Dumazet
Quoted 8 lines · Peter Zijlstra
So by not using some memory barriers (would be nice to have it illustrated which ones), we can race and end up on the wrong chain, in case that happens we detect this by using this per-chain terminator and try again.
It would be really good to have it explained in the
rculist_nulls.hcomments what memory barriers are missing, what races they open, and how the this special terminator trick closes that race.
Quoted 7 lines
Quoted 5 lines
OK, maybe I should add a
Documentation/RCU/rculist_nulls.txtfile with appropriate examples and documentation.
(Say the lookup/insert algorithms, with standard hlist and memory barriers,
and withhlist_nullswithout those two memory barriers.[PATCH 4/3] rcu: documents
rculist_nulls
Very good -- only one small suggestion below.
Quoted 31 lines
Adds
Documentation/RCU/rculist_nulls.txtfile to describe how 'nulls' end-of-list can help in some RCU algos.Signed-off-by Eric Dumazet <[email protected]>---
Documentation/RCU/rculist_nulls.txt| 167 ++++++++++++++++++++++++++1 files changed, 167 insertions(+)Documentation/RCU/rculist_nulls.txt new file +16
-0@@ -0,0 +1,167 @@ +Using hlist_nulls to protect read-mostly linked lists and +objects using SLAB_DESTROY_BY_RCU allocations. + +Please read the basics in Documentation/RCU/listRCU.txt + +Using special makers (called 'nulls') is a convenient way +to solve following problem : + +A typical RCU linked list managing objects which are +allocated with SLAB_DESTROY_BY_RCU kmem_cache can +use following algos : + +1) Lookup algo +-------------- +rcu_read_lock() +begin:
Documentation/RCU/rculist_nulls.txt line 26
Quoted 10 lines
Documentation/RCU/rculist_nulls.txt new file +10
-0+obj = lockless_lookup(key); +if (obj) { + if (!try_get_ref(obj)) // might fail for free objects + goto begin; + /* + * Because a writer could delete object, and a writer could + * reuse these object before the RCU grace period, we + * must check key after geting the reference on object + */ + if (obj->key != key) { // not the object we expected
In some cases, a "generation number" will be needed. For example, there are algorithms that must detect when an object has been removed and then re-inserted with the same key. One increments the generation number on each free and sometimes also on each allocation.
Quoted 141 lines
Documentation/RCU/rculist_nulls.txt new file +141
-0+ put_ref(obj); + goto begin; + } +} +rcu_read_unlock(); + +Beware that lockless_lookup(key) cannot use traditional hlist_for_each_entry_rcu() +but a version with an additional memory barrier (smp_rmb()) + +lockless_lookup(key) +{ + struct hlist_node *node, *next; + for (pos = rcu_dereference((head)->first); + pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); + pos = rcu_dereference(next)) + if (obj->key == key) + return obj; + return NULL; + +And note the traditional hlist_for_each_entry_rcu() misses this smp_rmb() : + + struct hlist_node *node; + for (pos = rcu_dereference((head)->first); + pos && ({ prefetch(pos->next); 1; }) && + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); + pos = rcu_dereference(pos->next)) + if (obj->key == key) + return obj; + return NULL; +} + +Quoting Corey Minyard : + +"If the object is moved from one list to another list in-between the + time the hash is calculated and the next field is accessed, and the + object has moved to the end of a new list, the traversal will not + complete properly on the list it should have, since the object will + be on the end of the new list and there's not a way to tell it's on a + new list and restart the list traversal. I think that this can be + solved by pre-fetching the "next" field (with proper barriers) before + checking the key." + +2) Insert algo : +---------------- + +We need to make sure a reader cannot read the new 'obj->obj_next' value +and previous value of 'obj->key'. Or else, an item could be deleted +from a chain, and inserted into another chain. If new chain was empty +before the move, 'next' pointer is NULL, and lockless reader can +not detect it missed following items in original chain. + +/* + * Please note that new inserts are done at the head of list, + * not in the middle or end. + */ +obj = kmem_cache_alloc(...); +lock_chain(); // typically a spin_lock() +obj->key = key; +atomic_inc(&obj->refcnt); +/* + * we need to make sure obj->key is updated before obj->next + */ +smp_wmb(); +hlist_add_head_rcu(&obj->obj_node, list); +unlock_chain(); // typically a spin_unlock() + + +3) Remove algo +-------------- +Nothing special here, we can use a standard RCU hlist deletion. +But thanks to SLAB_DESTROY_BY_RCU, beware a deleted object can be reused +very very fast (before the end of RCU grace period) + +if (put_last_reference_on(obj) { + lock_chain(); // typically a spin_lock() + hlist_del_init_rcu(&obj->obj_node); + unlock_chain(); // typically a spin_unlock() + kmem_cache_free(cachep, obj); +} + + + +-------------------------------------------------------------------------- +With hlist_nulls we can avoid extra smp_rmb() in lockless_lookup() +and extra smp_wmb() in insert function. + +For example, if we choose to store the slot number as the 'nulls' +end-of-list marker for each slot of the hash table, we can detect +a race (some writer did a delete and/or a move of an object +to another chain) checking the final 'nulls' value if +the lookup met the end of chain. If final 'nulls' value +is not the slot number, then we must restart the lookup at +the begining. If the object was moved to same chain, +then the reader doesnt care : It might eventually +scan the list again without harm. + + +1) lookup algo + + head = &table[slot]; + rcu_read_lock(); +begin: + hlist_nulls_for_each_entry_rcu(obj, node, head, member) { + if (obj->key == key) { + if (!try_get_ref(obj)) // might fail for free objects + goto begin; + if (obj->key != key) { // not the object we expected + put_ref(obj); + goto begin; + } + goto out; + } +/* + * if the nulls value we got at the end of this lookup is + * not the expected one, we must restart lookup. + * We probably met an item that was moved to another chain. + */ + if (get_nulls_value(node) != slot) + goto begin; + obj = NULL; + +out: + rcu_read_unlock(); + +2) Insert function : +-------------------- + +/* + * Please note that new inserts are done at the head of list, + * not in the middle or end. + */ +obj = kmem_cache_alloc(cachep); +lock_chain(); // typically a spin_lock() +obj->key = key; +atomic_set(&obj->refcnt, 1); +/* + * insert obj in RCU way (readers might be traversing chain) + */ +hlist_nulls_add_head_rcu(&obj->obj_node, list); +unlock_chain(); // typically a spin_unlock()
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
In reply to Eric DumazetQuoted 65 lines · Eric Dumazet
hlist uses NULL value to finish a chain.
hlist_nullsvariant use the low order bit set to 1 to signal an end-of-list marker.This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
Two new files are added :
include/linux/list_nulls.h
- mimics hlist part ofinclude/linux/list.h, derived tohlist_nullsvariant
include/linux/rculist_nulls.h
- mimics hlist part ofinclude/linux/rculist.h, derived tohlist_nullsvariantOnly four helpers are declared for the moment :
hlist_nulls_del_init_rcu(),hlist_nulls_del_rcu(),hlist_nulls_add_head_rcu()andhlist_nulls_for_each_entry_rcu()
prefetches()were removed, since an end of list is not anymore NULL value.prefetches()could trigger useless (and possibly dangerous) memory transactions.Example of use (extracted from
__udp4_lib_lookup())struct sock *sk, *result; struct hlist_nulls_node *node; unsigned short hnum = ntohs(dport); unsigned int hash = udp_hashfn(net, hnum); struct udp_hslot *hslot = &udptable->hash[hash]; int score, badness; rcu_read_lock(); begin: result = NULL; badness = -1; sk_nulls_for_each_rcu(sk, node, &hslot->head) { score = compute_score(sk, net, saddr, hnum, sport, daddr, dport, dif); if (score > badness) { result = sk; badness = score; } } /* * if the nulls value we got at the end of this lookup is * not the expected one, we must restart lookup. * We probably met an item that was moved to another chain. */ if (get_nulls_value(node) != hash) goto begin; if (result) { if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt))) result = NULL; else if (unlikely(compute_score(result, net, saddr, hnum, sport, daddr, dport, dif) < badness)) { sock_put(result); goto begin; } } rcu_read_unlock(); return result;
Quoted 1 line
Signed-off-by Eric Dumazet <[email protected]>
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
In reply to Eric DumazetQuoted 61 lines · Eric Dumazet
hlist uses NULL value to finish a chain.
hlist_nullsvariant use the low order bit set to 1 to signal an end-of-list marker.This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
Two new files are added :
include/linux/list_nulls.h
- mimics hlist part ofinclude/linux/list.h, derived tohlist_nulls
variant
include/linux/rculist_nulls.h
- mimics hlist part ofinclude/linux/rculist.h, derived tohlist_nulls
variantOnly four helpers are declared for the moment :
hlist_nulls_del_init_rcu(),hlist_nulls_del_rcu(),hlist_nulls_add_head_rcu()andhlist_nulls_for_each_entry_rcu()
prefetches()were removed, since an end of list is not anymore NULL value.prefetches()could trigger useless (and possibly dangerous) memory transactions.Example of use (extracted from
__udp4_lib_lookup())struct sock *sk, *result; struct hlist_nulls_node *node; unsigned short hnum = ntohs(dport); unsigned int hash = udp_hashfn(net, hnum); struct udp_hslot *hslot = &udptable->hash[hash]; int score, badness; rcu_read_lock(); begin: result = NULL; badness = -1; sk_nulls_for_each_rcu(sk, node, &hslot->head) { score = compute_score(sk, net, saddr, hnum, sport, daddr, dport, dif); if (score > badness) { result = sk; badness = score; } } /* * if the nulls value we got at the end of this lookup is * not the expected one, we must restart lookup. * We probably met an item that was moved to another chain. */ if (get_nulls_value(node) != hash) goto begin; if (result) { if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt))) result = NULL; else if (unlikely(compute_score(result, net, saddr, hnum,
Quoted 8 lines
sport,
daddr, dport, dif) < badness)) { sock_put(result); goto begin; } } rcu_read_unlock(); return result;
Looks good, but a few questions and suggestions interspersed below.
Thanx, Paul
Quoted 87 lines
Signed-off-by Eric Dumazet <[email protected]>---
include/linux/list_nulls.h| 94 +++++++++++++++++++++++++++include/linux/rculist_nulls.h| 110 ++++++++++++++++++++++++++++++++2 files changed, 204 insertions(+)include/linux/list_nulls.h new file +75
-0@@ -0,0 +1,94 @@ +#ifndef _LINUX_LIST_NULLS_H +#define _LINUX_LIST_NULLS_H + +/* +* Special version of lists, where end of list is not a NULL pointer, +* but a 'nulls' marker, which can have many different values. +* (up to 2^31 different values guaranteed on all platforms) +* +* In the standard hlist, termination of a list is the NULL pointer. +* In this special 'nulls' variant, we use the fact that objects stored in +* a list are aligned on a word (4 or 8 bytes alignment). +* We therefore use the last significant bit of 'ptr' : +* Set to 1 : This is a 'nulls' end-of-list marker (ptr >> 1) +* Set to 0 : This is a pointer to some object (ptr) +*/ + +struct hlist_nulls_head { +struct hlist_nulls_node *first; +}; + +struct hlist_nulls_node { +struct hlist_nulls_node *next, **pprev; +}; +#define INIT_HLIST_NULLS_HEAD (ptr, nulls) \ + ((ptr)->first = (struct hlist_nulls_node *) (1UL | (((long)nulls) << 1))) + +#define hlist_nulls_entry (ptr, type, member) container_of(ptr,type,member) +/** +* ptr_is_a_nulls - Test if a ptr is a nulls +* @ptr: ptr to be tested +* +*/ +static inline int is_a_nulls (const struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr &1 ); +} + +/** +* get_nulls_value - Get the 'nulls' value of the end of chain +* @ptr: end of chain +* +* Should be called only if is_a_nulls(ptr); +*/ +static inline unsigned long get_nulls_value (const struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr) >>1 ; +} + +static inline int hlist_nulls_unhashed (const struct hlist_nulls_node *h) +{ +return !h->pprev; +} + +static inline int hlist_nulls_empty (const struct hlist_nulls_head *h) +{ +return is_a_nulls (h->first); +} + +static inline void __hlist_nulls_del (struct hlist_nulls_node *n) +{ +struct hlist_nulls_node *next = n->next; +struct hlist_nulls_node **pprev = n->pprev; + *pprev = next; +if (!is_a_nulls (next)) + next->pprev = pprev; +} + +/** +* hlist_nulls_for_each_entry - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* +*/
include/linux/list_nulls.h line 85
Quoted 10 lines
include/linux/list_nulls.h new file +10
-0+#define hlist_nulls_for_each_entry (tpos, pos, head, member) \ + for (pos = (head)->first; \ + (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** +* hlist_nulls_for_each_entry_from - iterate over a hlist continuing from current point +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor.
And @pos is the starting point, correct? Suggest something like:
@pos: the &struct hlist_node serving as starting point and cursor
Quoted 43 lines
include/linux/list_nulls.h new file +9
-0+* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry_from (tpos, pos, member) \ + for (; (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif include/linux/rculist_nulls.h new file +28
-0@@ -0,0 +1,110 @@ +#ifndef _LINUX_RCULIST_NULLS_H +#define _LINUX_RCULIST_NULLS_H + +#ifdef __KERNEL__ + +/* +* RCU-protected list version +*/ +#include <linux/list_nulls.h> +#include <linux/rcupdate.h> + +/** +* hlist_nulls_del_init_rcu - deletes entry from hash list with re-initialization +* @n: the element to delete from the hash list. +* +* Note: hlist_nulls_unhashed() on the node return true after this. It is +* useful for RCU based read lockfree traversal if the writer side +* must know if the list entry is still hashed or already unhashed. +* +* In particular, it means that we can not poison the forward pointers +* that may still be used for walking the hash list and we can only +* zero the pprev pointer so list_unhashed() will return true after +* this. +* +* The caller must take whatever precautions are necessary (such as +* holding appropriate locks) to avoid racing with another +* list-mutation primitive, such as hlist_nulls_add_head_rcu() or +* hlist_nulls_del_rcu(), running on this same list. However, it is
include/linux/rculist_nulls.h line 38
Quoted 10 lines
include/linux/rculist_nulls.h new file +10
-0+* perfectly legal to run concurrently with the _rcu list-traversal +* primitives, such as hlist_nulls_for_each_entry_rcu(). +*/ +static inline void hlist_nulls_del_init_rcu (struct hlist_nulls_node *n) +{ +if (!hlist_nulls_unhashed (n)) { +__hlist_nulls_del (n); + n->pprev =NULL ; + } +}
The point here is to allow an RCU reader to grab the update-side lock while holding a reference to an hlist_nulls_node, and then be able to blindly call hlist_nulls_del_init_rcu() without having to do any complex check to see if the element has already been deleted?
But this only works if each free operation waits for a grace period. If using SLAB_DESTROY_BY_RCU, the would-be deleter still needs to revalidate after grabbing the update-side lock, right? Hmmm...
Quoted 62 lines
include/linux/rculist_nulls.h new file +62
-0+ +/** +* hlist_nulls_del_rcu - deletes entry from hash list without re-initialization +* @n: the element to delete from the hash list. +* +* Note: hlist_nulls_unhashed() on entry does not return true after this, +* the entry is in an undefined state. It is useful for RCU based +* lockfree traversal. +* +* In particular, it means that we can not poison the forward +* pointers that may still be used for walking the hash list. +* +* The caller must take whatever precautions are necessary +* (such as holding appropriate locks) to avoid racing +* with another list-mutation primitive, such as hlist_nulls_add_head_rcu() +* or hlist_nulls_del_rcu(), running on this same list. +* However, it is perfectly legal to run concurrently with +* the _rcu list-traversal primitives, such as +* hlist_nulls_for_each_entry(). +*/ +static inline void hlist_nulls_del_rcu (struct hlist_nulls_node *n) +{ +__hlist_nulls_del (n); + n->pprev =LIST_POISON2 ; +} + +/** +* hlist_nulls_add_head_rcu +* @n: the element to add to the hash list. +* @h: the list to add to. +* +* Description: +* Adds the specified element to the specified hlist_nulls, +* while permitting racing traversals. +* +* The caller must take whatever precautions are necessary +* (such as holding appropriate locks) to avoid racing +* with another list-mutation primitive, such as hlist_nulls_add_head_rcu() +* or hlist_nulls_del_rcu(), running on this same list. +* However, it is perfectly legal to run concurrently with +* the _rcu list-traversal primitives, such as +* hlist_nulls_for_each_entry_rcu(), used to prevent memory-consistency +* problems on Alpha CPUs. Regardless of the type of CPU, the +* list-traversal primitive must be guarded by rcu_read_lock(). +*/ +static inline void hlist_nulls_add_head_rcu (struct hlist_nulls_node *n, +struct hlist_nulls_head *h) +{ +struct hlist_nulls_node *first = h->first; + + n->next = first; + n->pprev = &h->first; +rcu_assign_pointer (h->first, n); +if (!is_a_nulls (first)) + first->pprev = &n->next; +} +/** +* hlist_nulls_for_each_entry_rcu - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_nulls_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_nulls_node within the struct.
include/linux/rculist_nulls.h line 110
Quoted 10 lines
include/linux/rculist_nulls.h new file +10
-0+* +*/ +#define hlist_nulls_for_each_entry_rcu (tpos, pos, head, member) \ + for (pos = rcu_dereference((head)->first); \ + (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference(pos->next)) + +#endif +#endif
Any chance of using a trick like Oleg used to get rid of the "pos" argument? http://lkml.org/lkml/2008/3/12/47
The hlist_nulls_node must always be at an even address, correct? Couldn't this fact be used to allow testing for is_a_nulls() on tpos rather than on pos? Or is there a better way to approach this?
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
Quoted 173 lines · Paul E. McKenney
Quoted 59 lines · Eric Dumazet
hlist uses NULL value to finish a chain.
hlist_nullsvariant use the low order bit set to 1 to signal an end-of-list marker.This allows to store many different end markers, so that some RCU lockless algos (used in TCP/UDP stack for example) can save some memory barriers in fast paths.
Two new files are added :
include/linux/list_nulls.h
- mimics hlist part ofinclude/linux/list.h, derived tohlist_nulls
variant
include/linux/rculist_nulls.h
- mimics hlist part ofinclude/linux/rculist.h, derived tohlist_nulls
variantOnly four helpers are declared for the moment :
hlist_nulls_del_init_rcu(),hlist_nulls_del_rcu(),hlist_nulls_add_head_rcu()andhlist_nulls_for_each_entry_rcu()
prefetches()were removed, since an end of list is not anymore NULL value.prefetches()could trigger useless (and possibly dangerous) memory transactions.Example of use (extracted from
__udp4_lib_lookup())struct sock *sk, *result; struct hlist_nulls_node *node; unsigned short hnum = ntohs(dport); unsigned int hash = udp_hashfn(net, hnum); struct udp_hslot *hslot = &udptable->hash[hash]; int score, badness; rcu_read_lock(); begin: result = NULL; badness = -1; sk_nulls_for_each_rcu(sk, node, &hslot->head) { score = compute_score(sk, net, saddr, hnum, sport, daddr, dport, dif); if (score > badness) { result = sk; badness = score; } } /* * if the nulls value we got at the end of this lookup is * not the expected one, we must restart lookup. * We probably met an item that was moved to another chain. */ if (get_nulls_value(node) != hash) goto begin; if (result) { if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt))) result = NULL; else if (unlikely(compute_score(result, net, saddr, hnum,Quoted 8 lines
sport,
daddr, dport, dif) < badness)) { sock_put(result); goto begin; } } rcu_read_unlock(); return result;Looks good, but a few questions and suggestions interspersed below.
Thanx, Paul
Quoted 87 lines
Signed-off-by Eric Dumazet <[email protected]>---
include/linux/list_nulls.h| 94 +++++++++++++++++++++++++++include/linux/rculist_nulls.h| 110 ++++++++++++++++++++++++++++++++2 files changed, 204 insertions(+)include/linux/list_nulls.h new file +75
-0@@ -0,0 +1,94 @@ +#ifndef _LINUX_LIST_NULLS_H +#define _LINUX_LIST_NULLS_H + +/* +* Special version of lists, where end of list is not a NULL pointer, +* but a 'nulls' marker, which can have many different values. +* (up to 2^31 different values guaranteed on all platforms) +* +* In the standard hlist, termination of a list is the NULL pointer. +* In this special 'nulls' variant, we use the fact that objects stored in +* a list are aligned on a word (4 or 8 bytes alignment). +* We therefore use the last significant bit of 'ptr' : +* Set to 1 : This is a 'nulls' end-of-list marker (ptr >> 1) +* Set to 0 : This is a pointer to some object (ptr) +*/ + +struct hlist_nulls_head { +struct hlist_nulls_node *first; +}; + +struct hlist_nulls_node { +struct hlist_nulls_node *next, **pprev; +}; +#define INIT_HLIST_NULLS_HEAD (ptr, nulls) \ + ((ptr)->first = (struct hlist_nulls_node *) (1UL | (((long)nulls) << 1))) + +#define hlist_nulls_entry (ptr, type, member) container_of(ptr,type,member) +/** +* ptr_is_a_nulls - Test if a ptr is a nulls +* @ptr: ptr to be tested +* +*/ +static inline int is_a_nulls (const struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr &1 ); +} + +/** +* get_nulls_value - Get the 'nulls' value of the end of chain +* @ptr: end of chain +* +* Should be called only if is_a_nulls(ptr); +*/ +static inline unsigned long get_nulls_value (const struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr) >>1 ; +} + +static inline int hlist_nulls_unhashed (const struct hlist_nulls_node *h) +{ +return !h->pprev; +} + +static inline int hlist_nulls_empty (const struct hlist_nulls_head *h) +{ +return is_a_nulls (h->first); +} + +static inline void __hlist_nulls_del (struct hlist_nulls_node *n) +{ +struct hlist_nulls_node *next = n->next; +struct hlist_nulls_node **pprev = n->pprev; + *pprev = next; +if (!is_a_nulls (next)) + next->pprev = pprev; +} + +/** +* hlist_nulls_for_each_entry - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* +*/ include/linux/list_nulls.h line 85
Quoted 10 lines
include/linux/list_nulls.h new file +10
-0+#define hlist_nulls_for_each_entry (tpos, pos, head, member) \ + for (pos = (head)->first; \ + (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** +* hlist_nulls_for_each_entry_from - iterate over a hlist continuing from current point +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. And @pos is the starting point, correct? Suggest something like:
Quoted 1 line
@pos: the &struct
hlist_nodeserving as starting point and cursor
Yes, comment was copied from hlist_for_each_entry_from() comment, this one needs update too.
Quoted 60 lines
Quoted 44 lines
include/linux/list_nulls.h new file +9
-0+* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry_from (tpos, pos, member) \ + for (; (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif include/linux/rculist_nulls.h new file +28
-0@@ -0,0 +1,110 @@ +#ifndef _LINUX_RCULIST_NULLS_H +#define _LINUX_RCULIST_NULLS_H + +#ifdef __KERNEL__ + +/* +* RCU-protected list version +*/ +#include <linux/list_nulls.h> +#include <linux/rcupdate.h> + +/** +* hlist_nulls_del_init_rcu - deletes entry from hash list with re-initialization +* @n: the element to delete from the hash list. +* +* Note: hlist_nulls_unhashed() on the node return true after this. It is +* useful for RCU based read lockfree traversal if the writer side +* must know if the list entry is still hashed or already unhashed. +* +* In particular, it means that we can not poison the forward pointers +* that may still be used for walking the hash list and we can only +* zero the pprev pointer so list_unhashed() will return true after +* this. +* +* The caller must take whatever precautions are necessary (such as +* holding appropriate locks) to avoid racing with another +* list-mutation primitive, such as hlist_nulls_add_head_rcu() or +* hlist_nulls_del_rcu(), running on this same list. However, it is include/linux/rculist_nulls.h line 38
Quoted 10 lines
include/linux/rculist_nulls.h new file +10
-0+* perfectly legal to run concurrently with the _rcu list-traversal +* primitives, such as hlist_nulls_for_each_entry_rcu(). +*/ +static inline void hlist_nulls_del_init_rcu (struct hlist_nulls_node *n) +{ +if (!hlist_nulls_unhashed (n)) { +__hlist_nulls_del (n); + n->pprev =NULL ; + } +}The point here is to allow an RCU reader to grab the update-side lock while holding a reference to an
hlist_nulls_node, and then be able to blindly callhlist_nulls_del_init_rcu()without having to do any complex check to see if the element has already been deleted?
Quoted 4 lines
But this only works if each free operation waits for a grace period. If using
SLAB_DESTROY_BY_RCU, the would-be deleter still needs to revalidate after grabbing the update-side lock, right? Hmmm...
<start a brain refresh cycle>
<read again your questions>
Tilt...
hlist_nulls_del_init_rcu() is only used by a writer, exactly like hlist_del_init_rcu().
I see nothing special about SLAB_DESTROY_BY_RCU here.
static inline void hlist_del_init_rcu(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
n->pprev = NULL;
}
}Quoted 76 lines
Quoted 62 lines
include/linux/rculist_nulls.h new file +62
-0+ +/** +* hlist_nulls_del_rcu - deletes entry from hash list without re-initialization +* @n: the element to delete from the hash list. +* +* Note: hlist_nulls_unhashed() on entry does not return true after this, +* the entry is in an undefined state. It is useful for RCU based +* lockfree traversal. +* +* In particular, it means that we can not poison the forward +* pointers that may still be used for walking the hash list. +* +* The caller must take whatever precautions are necessary +* (such as holding appropriate locks) to avoid racing +* with another list-mutation primitive, such as hlist_nulls_add_head_rcu() +* or hlist_nulls_del_rcu(), running on this same list. +* However, it is perfectly legal to run concurrently with +* the _rcu list-traversal primitives, such as +* hlist_nulls_for_each_entry(). +*/ +static inline void hlist_nulls_del_rcu (struct hlist_nulls_node *n) +{ +__hlist_nulls_del (n); + n->pprev =LIST_POISON2 ; +} + +/** +* hlist_nulls_add_head_rcu +* @n: the element to add to the hash list. +* @h: the list to add to. +* +* Description: +* Adds the specified element to the specified hlist_nulls, +* while permitting racing traversals. +* +* The caller must take whatever precautions are necessary +* (such as holding appropriate locks) to avoid racing +* with another list-mutation primitive, such as hlist_nulls_add_head_rcu() +* or hlist_nulls_del_rcu(), running on this same list. +* However, it is perfectly legal to run concurrently with +* the _rcu list-traversal primitives, such as +* hlist_nulls_for_each_entry_rcu(), used to prevent memory-consistency +* problems on Alpha CPUs. Regardless of the type of CPU, the +* list-traversal primitive must be guarded by rcu_read_lock(). +*/ +static inline void hlist_nulls_add_head_rcu (struct hlist_nulls_node *n, +struct hlist_nulls_head *h) +{ +struct hlist_nulls_node *first = h->first; + + n->next = first; + n->pprev = &h->first; +rcu_assign_pointer (h->first, n); +if (!is_a_nulls (first)) + first->pprev = &n->next; +} +/** +* hlist_nulls_for_each_entry_rcu - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_nulls_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_nulls_node within the struct. include/linux/rculist_nulls.h line 110
Quoted 10 lines
include/linux/rculist_nulls.h new file +10
-0+* +*/ +#define hlist_nulls_for_each_entry_rcu (tpos, pos, head, member) \ + for (pos = rcu_dereference((head)->first); \ + (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference(pos->next)) + +#endif +#endif Any chance of using a trick like Oleg used to get rid of the "pos" argument? http://lkml.org/lkml/2008/3/12/47
Quoted 3 lines
The
hlist_nulls_nodemust always be at an even address, correct? Couldn't this fact be used to allow testing foris_a_nulls()on tpos rather than on pos? Or is there a better way to approach this?
#define sk_nulls_for_each_rcu(__sk, node, list) \
hlist_nulls_for_each_entry_rcu(__sk, node, list, sk_nulls_node)
1) __sk is the pointer to found item if any is found in the loop
2) node will contain the end value of chain, in case we find nothing in loop because we need to check it after the loop.
if (get_nulls_value(node) != hash)
goto begin;
I dont know, it seems quite complex to try to use only three args ?
This algo is not very easy to read as is already ...
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
Quoted 255 lines · Eric Dumazet
Quoted 170 lines · Paul E. McKenney
Quoted 64 lines · Eric Dumazet
hlist uses NULL value to finish a chain.
hlist_nullsvariant use the low order bit set to 1 to signal an end-of-list marker.This allows to store many different end markers, so that some RCU lockless
algos (used in TCP/UDP stack for example) can save some memory barriers in
fast paths.Two new files are added :
include/linux/list_nulls.h
- mimics hlist part ofinclude/linux/list.h, derived tohlist_nulls
variant
include/linux/rculist_nulls.h
- mimics hlist part ofinclude/linux/rculist.h, derived tohlist_nulls
variantOnly four helpers are declared for the moment :
hlist_nulls_del_init_rcu(),hlist_nulls_del_rcu(),hlist_nulls_add_head_rcu()andhlist_nulls_for_each_entry_rcu()
prefetches()were removed, since an end of list is not anymore NULL value.prefetches()could trigger useless (and possibly dangerous) memory transactions.Example of use (extracted from
__udp4_lib_lookup())struct sock *sk, *result; struct hlist_nulls_node *node; unsigned short hnum = ntohs(dport); unsigned int hash = udp_hashfn(net, hnum); struct udp_hslot *hslot = &udptable->hash[hash]; int score, badness; rcu_read_lock(); begin: result = NULL; badness = -1; sk_nulls_for_each_rcu(sk, node, &hslot->head) { score = compute_score(sk, net, saddr, hnum, sport, daddr, dport, dif); if (score > badness) { result = sk; badness = score; } } /* * if the nulls value we got at the end of this lookup is * not the expected one, we must restart lookup. * We probably met an item that was moved to another chain. */ if (get_nulls_value(node) != hash) goto begin; if (result) { if (unlikely(!atomic_inc_not_zero(&result->sk_refcnt))) result = NULL; else if (unlikely(compute_score(result, net, saddr, hnum,Quoted 8 lines
sport,
daddr, dport, dif) < badness)) { sock_put(result); goto begin; } } rcu_read_unlock(); return result;Looks good, but a few questions and suggestions interspersed below.
Thanx, PaulQuoted 95 lines
Signed-off-by Eric Dumazet <[email protected]>---
include/linux/list_nulls.h| 94 +++++++++++++++++++++++++++include/linux/rculist_nulls.h| 110 ++++++++++++++++++++++++++++++++2 files changed, 204 insertions(+)include/linux/list_nulls.h new file +10
-0@@ -0,0 +1,94 @@ +#ifndef _LINUX_LIST_NULLS_H +#define _LINUX_LIST_NULLS_H + +/* +* Special version of lists, where end of list is not a NULL pointer, +* but a 'nulls' marker, which can have many different values. +* (up to 2^31 different values guaranteed on all platforms) +* +* In the standard hlist, termination of a list is the NULL pointer. +* In this special 'nulls' variant, we use the fact that objects stored in
+ * a list are aligned on a word (4 or 8 bytes alignment).
+ * We therefore use the last significant bit of 'ptr' :
+ * Set to 1 : This is a 'nulls' end-of-list marker (ptr >> 1)
+ * Set to 0 : This is a pointer to some object (ptr)
+ */include/linux/list_nulls.h new file +10
-0+ +struct hlist_nulls_head { +struct hlist_nulls_node *first; +}; + +struct hlist_nulls_node { +struct hlist_nulls_node *next, **pprev; +}; +#define INIT_HLIST_NULLS_HEAD (ptr, nulls) \ + ((ptr)->first = (struct hlist_nulls_node *) (1UL | (((long)nulls) <<1)))
include/linux/list_nulls.h new file +2
-0+ +#define hlist_nulls_entry (ptr, type, member)
container_of(ptr,type,member)include/linux/list_nulls.h new file +17
-0+/** +* ptr_is_a_nulls - Test if a ptr is a nulls +* @ptr: ptr to be tested +* +*/ +static inline int is_a_nulls (const struct hlist_nulls_node *ptr) +{ +return ((unsigned long )ptr &1 ); +} + +/** +* get_nulls_value - Get the 'nulls' value of the end of chain +* @ptr: end of chain +* +* Should be called only if is_a_nulls(ptr); +*/ +static inline unsigned long get_nulls_value(const struct
hlist_nulls_node*ptr)include/linux/list_nulls.h new file +36
-0+{ +return ((unsigned long )ptr) >>1 ; +} + +static inline int hlist_nulls_unhashed (const struct hlist_nulls_node *h) +{ +return !h->pprev; +} + +static inline int hlist_nulls_empty (const struct hlist_nulls_head *h) +{ +return is_a_nulls (h->first); +} + +static inline void __hlist_nulls_del (struct hlist_nulls_node *n) +{ +struct hlist_nulls_node *next = n->next; +struct hlist_nulls_node **pprev = n->pprev; + *pprev = next; +if (!is_a_nulls (next)) + next->pprev = pprev; +} + +/** +* hlist_nulls_for_each_entry - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry (tpos, pos, head, member) \ + for (pos = (head)->first; \ + (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next)Quoted 8 lines
Quoted 6 lines
include/linux/list_nulls.h new file +3
-0+ +/** +* hlist_nulls_for_each_entry_from - iterate over a hlist continuing from current point
+ * @tpos: the type * to use as a loop cursor.
+ * @pos: the &structhlist_nodeto use as a loop cursor.And @pos is the starting point, correct? Suggest something like:
@pos: the &structhlist_nodeserving as starting point and cursorYes, comment was copied from
hlist_for_each_entry_from()comment, this one needs update too.Quoted 55 lines
Quoted 55 lines
+ * @member: the name of the
hlist_nodewithin the struct.
+ *
+ */include/linux/list_nulls.h new file +6
-0+#define hlist_nulls_for_each_entry_from (tpos, pos, member) \ + for (; (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +#endif include/linux/rculist_nulls.h new file +13
-0@@ -0,0 +1,110 @@ +#ifndef _LINUX_RCULIST_NULLS_H +#define _LINUX_RCULIST_NULLS_H + +#ifdef __KERNEL__ + +/* +* RCU-protected list version +*/ +#include <linux/list_nulls.h> +#include <linux/rcupdate.h> + +/** +* hlist_nulls_del_init_rcu - deletes entry from hash list with re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note:hlist_nulls_unhashed()on the node return true after this. It is
+ * useful for RCU based read lockfree traversal if the writer side
+ * must know if the list entry is still hashed or already unhashed.
+ *
+ * In particular, it means that we can not poison the forward pointers
+ * that may still be used for walking the hash list and we can only
+ * zero the pprev pointer solist_unhashed()will return true after
+ * this.
+ *
+ * The caller must take whatever precautions are necessary (such as
+ * holding appropriate locks) to avoid racing with another
+ * list-mutation primitive, such ashlist_nulls_add_head_rcu()or
+ *hlist_nulls_del_rcu(), running on this same list. However, it is
+ * perfectly legal to run concurrently with the_rculist-traversal
+ * primitives, such ashlist_nulls_for_each_entry_rcu().
+ */include/linux/rculist_nulls.h new file +6
-0+static inline void hlist_nulls_del_init_rcu (struct hlist_nulls_node *n) +{ +if (!hlist_nulls_unhashed (n)) { +__hlist_nulls_del (n); + n->pprev =NULL ; + }Quoted 8 lines
Quoted 1 line
+}
The point here is to allow an RCU reader to grab the update-side lock while holding a reference to an
hlist_nulls_node, and then be able to blindly callhlist_nulls_del_init_rcu()without having to do any complex check to see if the element has already been deleted?
But this only works if each free operation waits for a grace period. If usingSLAB_DESTROY_BY_RCU, the would-be deleter still needs to revalidate after grabbing the update-side lock, right? Hmmm...<start a brain refresh cycle>
<read again your questions>
Tilt...
hlist_nulls_del_init_rcu()is only used by a writer, exactly likehlist_del_init_rcu().
I see nothing special aboutSLAB_DESTROY_BY_RCUhere.
Quoted 7 lines
static inline void
hlist_del_init_rcu(struct hlist_node *n){ if (!hlist_unhashed(n)) { __hlist_del(n); n->pprev = NULL; } }
Not a problem, as you don't use it the way I was thinking.
For whatever it is worth, here is a more complete use case, on the off-chance that it becomes useful some time:
retry:
rcu_read_lock();
hlist_nulls_for_each_entry_rcu(tpos, pos, head, hn_node) {
if (!(curgen = still_valid(tpos)))
goto retry;
if (needs_deletion(tpos)) {
spin_lock(&update_side_lock);
if (still_valid(tpos) == curgen)
hlist_nulls_del_init_rcu(pos);
spin_unlock(&update_side_lock);
}
}
rcu_read_unlock();This approach requires that the key and a generation number be encoded into a single word, and that the generation number be changed on each allocation and on each free.
Quoted 91 lines
Quoted 73 lines
Quoted 73 lines
include/linux/rculist_nulls.h new file +3
-0+ +/** +* hlist_nulls_del_rcu - deletes entry from hash list without re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note:hlist_nulls_unhashed()on entry does not return true after this,
+ * the entry is in an undefined state. It is useful for RCU based
+ * lockfree traversal.
+ *
+ * In particular, it means that we can not poison the forward
+ * pointers that may still be used for walking the hash list.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such ashlist_nulls_add_head_rcu()
+ * orhlist_nulls_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the_rculist-traversal primitives, such as
+ *hlist_nulls_for_each_entry().
+ */include/linux/rculist_nulls.h new file +18
-0+static inline void hlist_nulls_del_rcu (struct hlist_nulls_node *n) +{ +__hlist_nulls_del (n); + n->pprev =LIST_POISON2 ; +} + +/** +* hlist_nulls_add_head_rcu +* @n: the element to add to the hash list. +* @h: the list to add to. +* +* Description: +* Adds the specified element to the specified hlist_nulls, +* while permitting racing traversals. +* +* The caller must take whatever precautions are necessary +* (such as holding appropriate locks) to avoid racing +* with another list-mutation primitive, such as
hlist_nulls_add_head_rcu()
+ * orhlist_nulls_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the_rculist-traversal primitives, such as
+ *hlist_nulls_for_each_entry_rcu(), used to prevent memory-consistency
+ * problems on Alpha CPUs. Regardless of the type of CPU, the
+ * list-traversal primitive must be guarded byrcu_read_lock().
+ */include/linux/rculist_nulls.h new file +24
-0+static inline void hlist_nulls_add_head_rcu (struct hlist_nulls_node *n, +struct hlist_nulls_head *h) +{ +struct hlist_nulls_node *first = h->first; + + n->next = first; + n->pprev = &h->first; +rcu_assign_pointer (h->first, n); +if (!is_a_nulls (first)) + first->pprev = &n->next; +} +/** +* hlist_nulls_for_each_entry_rcu - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_nulls_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_nulls_node within the struct. +* +*/ +#define hlist_nulls_for_each_entry_rcu (tpos, pos, head, member) \ + for (pos = rcu_dereference((head)->first); \ + (!is_a_nulls(pos)) && \ + ({ tpos = hlist_nulls_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference(pos->next))Quoted 8 lines
include/linux/rculist_nulls.h line 110
Quoted 3 lines
include/linux/rculist_nulls.h new file +3
-0+ +#endif +#endif Any chance of using a trick like Oleg used to get rid of the "pos" argument? http://lkml.org/lkml/2008/3/12/47
Thehlist_nulls_nodemust always be at an even address, correct? Couldn't this fact be used to allow testing foris_a_nulls()on tpos rather than on pos? Or is there a better way to approach this?#define
sk_nulls_for_each_rcu(__sk, node, list)\
hlist_nulls_for_each_entry_rcu(__sk, node, list, sk_nulls_node)1)
__skis the pointer to found item if any is found in the loop2) node will contain the end value of chain, in case we find nothing in loop
because we need to check it after the loop.
Quoted 6 lines
if (
get_nulls_value(node)!= hash)goto begin;I dont know, it seems quite complex to try to use only three args ?
This algo is not very easy to read as is already ...
One way around #2 would be for get_nulls_value() to undo the hlist_nulls_entry(). Not sure whether it is worth it, but a thought.
Thanx, Paul
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
Em Wed, Nov 19, 2008 at 10:46:24AM -0800, Paul E. McKenney escreveu:
Quoted 5 lines
For whatever it is worth, here is a more complete use case, on the off-chance that it becomes useful some time:
retry: rcu_read_lock();
retry: /* should be here, huh? */
Quoted 11 lines
hlist_nulls_for_each_entry_rcu(tpos, pos, head, hn_node) {
if (!(curgen = still_valid(tpos)))
goto retry;
if (needs_deletion(tpos)) {
spin_lock(&update_side_lock);
if (still_valid(tpos) == curgen)
hlist_nulls_del_init_rcu(pos);
spin_unlock(&update_side_lock);
}
}
rcu_read_unlock();- Arnaldo
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
Quoted 8 lines · Arnaldo Carvalho de Melo
Em Wed, Nov 19, 2008 at 10:46:24AM -0800, Paul E. McKenney escreveu:
Quoted 5 lines
For whatever it is worth, here is a more complete use case, on the off-chance that it becomes useful some time:
retry: rcu_read_lock();retry: /* should be here, huh? */
Indeed! Either that or have an rcu_read_unlock() before the goto retry.
Good catch!
Thanx, Paul
Quoted 13 lines
Quoted 11 lines
hlist_nulls_for_each_entry_rcu(tpos, pos, head, hn_node) { if (!(curgen = still_valid(tpos))) goto retry; if (needs_deletion(tpos)) { spin_lock(&update_side_lock); if (still_valid(tpos) == curgen) hlist_nulls_del_init_rcu(pos); spin_unlock(&update_side_lock); } } rcu_read_unlock();- Arnaldo
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
In reply to Paul E. McKenneyQuoted 74 lines · Paul E. McKenney
Quoted 46 lines · Eric Dumazet
Quoted 29 lines · Paul E. McKenney
Quoted 29 lines
include/linux/list_nulls.h new file +3
-0+ +/** +* hlist_nulls_del_init_rcu - deletes entry from hash list with re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note:hlist_nulls_unhashed()on the node return true after this. It is
+ * useful for RCU based read lockfree traversal if the writer side
+ * must know if the list entry is still hashed or already unhashed.
+ *
+ * In particular, it means that we can not poison the forward pointers
+ * that may still be used for walking the hash list and we can only
+ * zero the pprev pointer solist_unhashed()will return true after
+ * this.
+ *
+ * The caller must take whatever precautions are necessary (such as
+ * holding appropriate locks) to avoid racing with another
+ * list-mutation primitive, such ashlist_nulls_add_head_rcu()or
+ *hlist_nulls_del_rcu(), running on this same list. However, it is
+ * perfectly legal to run concurrently with the_rculist-traversal
+ * primitives, such ashlist_nulls_for_each_entry_rcu().
+ */include/linux/rculist_nulls.h new file +6
-0+static inline void hlist_nulls_del_init_rcu (struct hlist_nulls_node *n) +{ +if (!hlist_nulls_unhashed (n)) { +__hlist_nulls_del (n); + n->pprev =NULL ; + }Quoted 8 lines
Quoted 1 line
+}
The point here is to allow an RCU reader to grab the update-side lock while holding a reference to an
hlist_nulls_node, and then be able to blindly callhlist_nulls_del_init_rcu()without having to do any complex check to see if the element has already been deleted?
But this only works if each free operation waits for a grace period. If usingSLAB_DESTROY_BY_RCU, the would-be deleter still needs to revalidate after grabbing the update-side lock, right? Hmmm...<start a brain refresh cycle>
<read again your questions>
Tilt...
hlist_nulls_del_init_rcu()is only used by a writer, exactly likehlist_del_init_rcu().
I see nothing special aboutSLAB_DESTROY_BY_RCUhere.Quoted 7 lines
static inline void
hlist_del_init_rcu(struct hlist_node *n){ if (!hlist_unhashed(n)) { __hlist_del(n); n->pprev = NULL; } }Not a problem, as you don't use it the way I was thinking.
For whatever it is worth, here is a more complete use case, on the off-chance that it becomes useful some time:
retry: rcu_read_lock(); hlist_nulls_for_each_entry_rcu(tpos, pos, head, hn_node) { if (!(curgen = still_valid(tpos))) goto retry; if (needs_deletion(tpos)) { spin_lock(&update_side_lock); if (still_valid(tpos) == curgen) hlist_nulls_del_init_rcu(pos); spin_unlock(&update_side_lock); } } rcu_read_unlock();
Quoted 3 lines
This approach requires that the key and a generation number be encoded into a single word, and that the generation number be changed on each allocation and on each free.
Hum, we should add this template in Documentation/RCU I guess
Thanks
Re: [PATCH 1/3] rcu: Introduce hlist_nulls variant of hlist
Quoted 75 lines · Eric Dumazet
Quoted 70 lines · Paul E. McKenney
Quoted 46 lines · Eric Dumazet
Quoted 30 lines · Paul E. McKenney
Quoted 30 lines
include/linux/list_nulls.h new file +3
-0+ +/** +* hlist_nulls_del_init_rcu - deletes entry from hash list with re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note:hlist_nulls_unhashed()on the node return true after this. It is
+ * useful for RCU based read lockfree traversal if the writer side
+ * must know if the list entry is still hashed or already unhashed.
+ *
+ * In particular, it means that we can not poison the forward pointers
+ * that may still be used for walking the hash list and we can only
+ * zero the pprev pointer solist_unhashed()will return true after
+ * this.
+ *
+ * The caller must take whatever precautions are necessary (such as
+ * holding appropriate locks) to avoid racing with another
+ * list-mutation primitive, such ashlist_nulls_add_head_rcu()or
+ *hlist_nulls_del_rcu(), running on this same list. However, it is
+ * perfectly legal to run concurrently with the_rculist-traversal
+ * primitives, such ashlist_nulls_for_each_entry_rcu().
+ */
+static inline voidhlist_nulls_del_init_rcu(struct hlist_nulls_node *n)include/linux/rculist_nulls.h new file +5
-0+{ +if (!hlist_nulls_unhashed (n)) { +__hlist_nulls_del (n); + n->pprev =NULL ; + }Quoted 8 lines
Quoted 1 line
+}
The point here is to allow an RCU reader to grab the update-side lock while holding a reference to an
hlist_nulls_node, and then be able to blindly callhlist_nulls_del_init_rcu()without having to do any complex check to see if the element has already been deleted?
But this only works if each free operation waits for a grace period. If usingSLAB_DESTROY_BY_RCU, the would-be deleter still needs to revalidate after grabbing the update-side lock, right? Hmmm...<start a brain refresh cycle>
<read again your questions>
Tilt...hlist_nulls_del_init_rcu()is only used by a writer, exactly likehlist_del_init_rcu().
I see nothing special aboutSLAB_DESTROY_BY_RCUhere.Quoted 7 lines
static inline void
hlist_del_init_rcu(struct hlist_node *n){ if (!hlist_unhashed(n)) { __hlist_del(n); n->pprev = NULL; } }Not a problem, as you don't use it the way I was thinking. For whatever it is worth, here is a more complete use case, on the off-chance that it becomes useful some time:
retry: rcu_read_lock(); hlist_nulls_for_each_entry_rcu(tpos, pos, head, hn_node) { if (!(curgen = still_valid(tpos))) goto retry; if (needs_deletion(tpos)) { spin_lock(&update_side_lock); if (still_valid(tpos) == curgen) hlist_nulls_del_init_rcu(pos); spin_unlock(&update_side_lock); } } rcu_read_unlock();Quoted 3 lines
This approach requires that the key and a generation number be encoded into a single word, and that the generation number be changed on each allocation and on each free.
Quoted 1 line
Hum, we should add this template in
Documentation/RCUI guess
With Arnaldo's change -- probably should prototype and test to find the other inevitable bugs. :-/
Thanx, Paul
PATCH 2/3 udp: Use hlist_nulls in UDP RCU code
In reply to Eric DumazetThis is a straightforward patch, using hlist_nulls infrastructure.
RCUification already done on UDP two weeks ago.
Using hlist_nulls permits us to avoid some memory barriers, both at lookup time and delete time.
Patch is large because it adds new macros to include/net/sock.h. These macros will be used by TCP & DCCP in next patch.
---
include/linux/rculist.h | 17 ----------- include/net/sock.h | 57 ++++++++++++++++++++++++++++++-------- include/net/udp.h | 2 - net/ipv4/udp.c | 47 ++++++++++++++----------------- net/ipv6/udp.c | 26 +++++++++-------- 5 files changed, 83 insertions(+), 66 deletions(-)
include/linux/rculist.h +0 -17
@@ -383,22 +383,5 @@ static inline void hlist_add_after_rcu(struct hlist_node *prev, ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ; }); \ pos = rcu_dereference(pos->next)) -/** -* hlist_for_each_entry_rcu_safenext - iterate over rcu list of given type -* @tpos: the type * to use as a loop cursor. -* @pos: the &struct hlist_node to use as a loop cursor. -* @head: the head for your list. -* @member: the name of the hlist_node within the struct. -* @next: the &struct hlist_node to use as a next cursor -* -* Special version of hlist_for_each_entry_rcu that make sure -* each next pointer is fetched before each iteration. -*/ -#define hlist_for_each_entry_rcu_safenext (tpos, pos, head, member, next) \ - for (pos = rcu_dereference((head)->first); \ - pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && \ - ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ - pos = rcu_dereference(next)) -#endif /* __KERNEL__ */ #endif
include/net/sock.h +46 -11
139 lines not on this page · raw message
include/net/udp.h +1 -1
@@ -51,7 +51,7 @@ struct udp_skb_cb {#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb))struct udp_hslot { -struct hlist_head head; +struct hlist_nulls_head head;spinlock_t lock; } __attribute__((aligned (2 *sizeof (long ))));struct udp_table {
net/ipv4/udp.c +22 -25
125 lines not on this page · raw message
net/ipv6/udp.c +14 -12
@@ -98,7 +98,7 @@ static struct sock *__udp6_lib_lookup(struct net *net, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node, *next; +struct hlist_nulls_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];
@@ -108,19 +108,21 @@ static struct sock *__udp6_lib_lookup(struct net *net, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { -/* -* lockless reader, and SLAB_DESTROY_BY_RCU items: -* We must check this item was not moved to another chain -*/ -if (udp_hashfn (net, sk->sk_hash) != hash) - goto begin; +sk_nulls_for_each_rcu (sk, node, &hslot->head) { score =compute_score (sk, net, hnum, saddr, sport, daddr, dport, dif);if (score > badness) { result = sk; badness = score; } } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != hash) + goto begin; +if (result) {if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) result =NULL ;
@@ -374,11 +376,11 @@ static struct sock *udp_v6_mcast_next(struct net *net, struct sock *sk, __be16 rmt_port, struct in6_addr *rmt_addr, int dif) { - struct hlist_node *node; + struct hlist_nulls_node *node;struct sock *s = sk;unsigned short num =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_nulls_for_each_from (s, node) {struct inet_sock *inet =inet_sk (s);if (!net_eq (sock_net (s), net))
@@ -423,7 +425,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,int dif;spin_lock (&hslot->lock); - sk =sk_head (&hslot->head); + sk =sk_nulls_head (&hslot->head); dif =inet6_iif (skb); sk =udp_v6_mcast_next (net, sk, uh->dest, daddr, uh->source, saddr, dif);if (!sk) {
@@ -432,7 +434,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } sk2 = sk; -while ((sk2 =udp_v6_mcast_next (net,sk_next (sk2), uh->dest, daddr, +while ((sk2 =udp_v6_mcast_next (net,sk_nulls_next (sk2), uh->dest, daddr, uh->source, saddr, dif))) {struct sk_buff *buff =skb_clone (skb,GFP_ATOMIC );if (buff) {
Re: [PATCH 2/3] udp: Use hlist_nulls in UDP RCU code
Quoted 9 lines · Eric Dumazet
This is a straightforward patch, using
hlist_nullsinfrastructure.RCUification already done on UDP two weeks ago.
Using
hlist_nullspermits us to avoid some memory barriers, both at lookup time and delete time.Patch is large because it adds new macros to
include/net/sock.h. These macros will be used by TCP & DCCP in next patch.
Looks good, one question below about the lockless searches. If the answer is that the search must complete undisturbed by deletions and additions, then:
Quoted 25 lines
Signed-off-by Eric Dumazet <[email protected]>---
include/linux/rculist.h | 17 ----------- include/net/sock.h | 57 ++++++++++++++++++++++++++++++-------- include/net/udp.h | 2 - net/ipv4/udp.c | 47 ++++++++++++++----------------- net/ipv6/udp.c | 26 +++++++++-------- 5 files changed, 83 insertions(+), 66 deletions(-)include/linux/rculist.h +0
-7@@ -383,22 +383,5 @@ static inline void hlist_add_after_rcu(struct hlist_node *prev, ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ; }); \ pos = rcu_dereference(pos->next)) -/** -* hlist_for_each_entry_rcu_safenext - iterate over rcu list of given type -* @tpos: the type * to use as a loop cursor. -* @pos: the &struct hlist_node to use as a loop cursor. -* @head: the head for your list. -* @member: the name of the hlist_node within the struct. -* @next: the &struct hlist_node to use as a next cursor
include/linux/rculist.h static inline void hlist_add_after_rcu(struct hlist_node *prev, · line 402
Quoted 10 lines
include/linux/rculist.h +0
-10-* -* Special version of hlist_for_each_entry_rcu that make sure -* each next pointer is fetched before each iteration. -*/ -#define hlist_for_each_entry_rcu_safenext (tpos, pos, head, member, next) \ - for (pos = rcu_dereference((head)->first); \ - pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && \ - ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ - pos = rcu_dereference(next)) -
Bonus points for getting rid of a list primitive!!! ;-)
Quoted 224 lines
include/linux/rculist.h +0
-0#endif /* __KERNEL__ */ #endif include/net/sock.h +46
-11@@ -42,6 +42,7 @@#include <linux/kernel.h> #include <linux/list.h> +#include <linux/list_nulls.h> #include <linux/timer.h> #include <linux/cache.h> #include <linux/module.h> @@ -52,6 +53,7 @@#include <linux/security.h> #include <linux/filter.h> +#include <linux/rculist_nulls.h> #include <asm/atomic.h> #include <net/dst.h> @@ -106,6 +108,7 @@ struct net;* @skc_reuse: %SO_REUSEADDR setting * @skc_bound_dev_if: bound device index if != 0 * @skc_node: main hash linkage for various protocol lookup tables +* @skc_nulls_node: main hash linkage for UDP/UDP-Lite protocol * @skc_bind_node: bind hash linkage for various protocol lookup tables * @skc_refcnt: reference count * @skc_hash: hash value used with various protocol lookup tables @@ -120,7 +123,10 @@ struct sock_common {volatile unsigned char skc_state;unsigned char skc_reuse;int skc_bound_dev_if; -struct hlist_node skc_node; +union { +struct hlist_node skc_node; +struct hlist_nulls_node skc_nulls_node; + };struct hlist_node skc_bind_node;atomic_t skc_refcnt;unsigned int skc_hash;@@ -206,6 +212,7 @@ struct sock {#define sk_reuse __sk_common.skc_reuse#define sk_bound_dev_if __sk_common.skc_bound_dev_if#define sk_node __sk_common.skc_node +#define sk_nulls_node __sk_common.skc_nulls_node#define sk_bind_node __sk_common.skc_bind_node#define sk_refcnt __sk_common.skc_refcnt#define sk_hash __sk_common.skc_hash@@ -300,12 +307,30 @@ static inline struct sock *sk_head(const struct hlist_head *head)return hlist_empty (head) ?NULL :__sk_head (head); } +static inline struct sock *__sk_nulls_head (const struct hlist_nulls_head *head) +{ + return hlist_nulls_entry(head->first, struct sock, sk_nulls_node); +} + +static inline struct sock *sk_nulls_head (const struct hlist_nulls_head *head) +{ +return hlist_nulls_empty (head) ?NULL :__sk_nulls_head (head); +} +static inline struct sock *sk_next (const struct sock *sk) {return sk->sk_node.next ? hlist_entry(sk->sk_node.next, struct sock, sk_node) : NULL; } +static inline struct sock *sk_nulls_next (const struct sock *sk) +{ +return (!is_a_nulls (sk->sk_nulls_node.next)) ? +hlist_nulls_entry (sk->sk_nulls_node.next, + struct sock, sk_nulls_node) : +NULL ; +} +static inline int sk_unhashed (const struct sock *sk) {return hlist_unhashed (&sk->sk_node);@@ -321,6 +346,11 @@ static __inline__ void sk_node_init(struct hlist_node *node) node->pprev =NULL ; } +static __inline__void sk_nulls_node_init (struct hlist_nulls_node *node) +{ + node->pprev =NULL ; +} +static __inline__void __sk_del_node (struct sock *sk) {__hlist_del (&sk->sk_node);@@ -367,18 +397,18 @@ static __inline__ int sk_del_node_init(struct sock *sk)return rc; } -static __inline__int __sk_del_node_init_rcu (struct sock *sk) +static __inline__int __sk_nulls_del_node_init_rcu (struct sock *sk) {if (sk_hashed (sk)) { -hlist_del_init_rcu (&sk->sk_node); +hlist_nulls_del_init_rcu (&sk->sk_nulls_node);return 1 ; }return 0 ; } -static __inline__int sk_del_node_init_rcu (struct sock *sk) +static __inline__int sk_nulls_del_node_init_rcu (struct sock *sk) { -int rc =__sk_del_node_init_rcu (sk); +int rc =__sk_nulls_del_node_init_rcu (sk);if (rc) {/* paranoid for a while -acme */ @@ -399,15 +429,15 @@ static __inline__ void sk_add_node(struct sock *sk, struct hlist_head *list)__sk_add_node (sk, list); } -static __inline__void __sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +static __inline__void __sk_nulls_add_node_rcu (struct sock *sk,struct hlist_nulls_head *list) { -hlist_add_head_rcu (&sk->sk_node, list); +hlist_nulls_add_head_rcu (&sk->sk_nulls_node, list); } -static __inline__void sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +static __inline__void sk_nulls_add_node_rcu (struct sock *sk,struct hlist_nulls_head *list) {sock_hold (sk); -__sk_add_node_rcu (sk, list); +__sk_nulls_add_node_rcu (sk, list); }static __inline__void __sk_del_bind_node (struct sock *sk)@@ -423,11 +453,16 @@ static __inline__ void sk_add_bind_node(struct sock *sk,#define sk_for_each (__sk, node, list) \ hlist_for_each_entry(__sk, node, list, sk_node) -#define sk_for_each_rcu_safenext (__sk, node, list, next) \ - hlist_for_each_entry_rcu_safenext(__sk, node, list, sk_node, next) +#define sk_nulls_for_each (__sk, node, list) \ + hlist_nulls_for_each_entry(__sk, node, list, sk_nulls_node) +#define sk_nulls_for_each_rcu (__sk, node, list) \ + hlist_nulls_for_each_entry_rcu(__sk, node, list, sk_nulls_node)#define sk_for_each_from (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_from(__sk, node, sk_node) +#define sk_nulls_for_each_from (__sk, node) \ + if (__sk && ({ node = &(__sk)->sk_nulls_node; 1; })) \ + hlist_nulls_for_each_entry_from(__sk, node, sk_nulls_node)#define sk_for_each_continue (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_continue(__sk, node, sk_node)include/net/udp.h +1
-1@@ -51,7 +51,7 @@ struct udp_skb_cb {#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb))struct udp_hslot { -struct hlist_head head; +struct hlist_nulls_head head;spinlock_t lock; } __attribute__((aligned (2 *sizeof (long ))));struct udp_table {net/ipv4/udp.c +5
-16@@ -127,9 +127,9 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num, const struct sock *sk2)) {struct sock *sk2; -struct hlist_node *node; +struct hlist_nulls_node *node; -sk_for_each (sk2, node, &hslot->head) +sk_nulls_for_each (sk2, node, &hslot->head) if (net_eq(sock_net(sk2), net) && sk2 != sk && sk2->sk_hash == num &&@@ -189,12 +189,7 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,inet_sk (sk)->num = snum; sk->sk_hash = snum;if (sk_unhashed (sk)) { -/* -* We need that previous write to sk->sk_hash committed -* before write to sk->next done in following add_node() variant -*/ -smp_wmb (); -sk_add_node_rcu (sk, &hslot->head); +sk_nulls_add_node_rcu (sk, &hslot->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 ); } error =0 ;@@ -261,7 +256,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node, *next; +struct hlist_nulls_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];@@ -271,13 +266,7 @@ static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { -/* -* lockless reader, and SLAB_DESTROY_BY_RCU items: -* We must check this item was not moved to another chain -*/ -if (udp_hashfn (net, sk->sk_hash) != hash) - goto begin; +sk_nulls_for_each_rcu (sk, node, &hslot->head) { score =compute_score (sk, net, saddr, hnum, sport, daddr, dport, dif);if (score > badness) {@@ -285,6 +274,14 @@ begin: badness = score;
net/ipv4/udp.c begin: · line 284
Quoted 10 lines
net/ipv4/udp.c +8
-0} } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != hash) + goto begin; +
Shouldn't this check go -after- the check for "result"? Or is this a case where the readers absolutely must have traversed a chain without modification to be guaranteed of finding the correct result?
Quoted 104 lines
net/ipv4/udp.c +9
-9if (result) {if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) result =NULL ;@@ -325,11 +322,11 @@ static inline struct sock *udp_v4_mcast_next(struct net *net, struct sock *sk, __be16 rmt_port, __be32 rmt_addr, int dif) { - struct hlist_node *node; + struct hlist_nulls_node *node;struct sock *s = sk;unsigned short hnum =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_nulls_for_each_from (s, node) {struct inet_sock *inet =inet_sk (s); if (!net_eq(sock_net(s), net) ||@@ -977,7 +974,7 @@ void udp_lib_unhash(struct sock *sk)struct udp_hslot *hslot = &udptable->hash[hash];spin_lock_bh (&hslot->lock); -if (sk_del_node_init_rcu (sk)) { +if (sk_nulls_del_node_init_rcu (sk)) {inet_sk (sk)->num =0 ;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); }@@ -1130,7 +1127,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,int dif;spin_lock (&hslot->lock); - sk =sk_head (&hslot->head); + sk =sk_nulls_head (&hslot->head); dif = skb->dev->ifindex; sk =udp_v4_mcast_next (net, sk, uh->dest, daddr, uh->source, saddr, dif);if (sk) {@@ -1139,7 +1136,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, do { struct sk_buff *skb1 = skb; - sknext = udp_v4_mcast_next(net, sk_next(sk), uh->dest, + sknext = udp_v4_mcast_next(net, sk_nulls_next(sk), uh->dest, daddr, uh->source, saddr, dif); if (sknext)@@ -1560,10 +1557,10 @@ static struct sock *udp_get_first(struct seq_file *seq, int start)struct net *net =seq_file_net (seq);for (state->bucket = start; state->bucket <UDP_HTABLE_SIZE ; ++state->bucket) { -struct hlist_node *node; +struct hlist_nulls_node *node;struct udp_hslot *hslot = &state->udp_table->hash[state->bucket];spin_lock_bh (&hslot->lock); -sk_for_each (sk, node, &hslot->head) { +sk_nulls_for_each (sk, node, &hslot->head) {if (!net_eq (sock_net (sk), net))continue ;if (sk->sk_family == state->family)@@ -1582,7 +1579,7 @@ static struct sock *udp_get_next(struct seq_file *seq, struct sock *sk)struct net *net =seq_file_net (seq);do { - sk =sk_next (sk); + sk =sk_nulls_next (sk); }while (sk && (!net_eq (sock_net (sk), net) || sk->sk_family != state->family));if (!sk) {@@ -1753,7 +1750,7 @@ void __init udp_table_init(struct udp_table *table)int i;for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { -INIT_HLIST_HEAD (&table->hash[i].head); +INIT_HLIST_NULLS_HEAD (&table->hash[i].head, i);spin_lock_init (&table->hash[i].lock); } }net/ipv6/udp.c +2
-8@@ -98,7 +98,7 @@ static struct sock *__udp6_lib_lookup(struct net *net, int dif, struct udp_table *udptable) { struct sock *sk, *result; -struct hlist_node *node, *next; +struct hlist_nulls_node *node;unsigned short hnum =ntohs (dport);unsigned int hash =udp_hashfn (net, hnum);struct udp_hslot *hslot = &udptable->hash[hash];@@ -108,19 +108,21 @@ static struct sock *__udp6_lib_lookup(struct net *net, begin: result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { -/* -* lockless reader, and SLAB_DESTROY_BY_RCU items: -* We must check this item was not moved to another chain -*/ -if (udp_hashfn (net, sk->sk_hash) != hash) - goto begin; +sk_nulls_for_each_rcu (sk, node, &hslot->head) { score =compute_score (sk, net, hnum, saddr, sport, daddr, dport, dif);if (score > badness) { result = sk; badness = score;
net/ipv6/udp.c static struct sock *__udp6_lib_lookup(struct net *net, · line 125
Quoted 10 lines
net/ipv6/udp.c +8
-0} } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != hash) + goto begin; +
Same question as before...
Quoted 35 lines
net/ipv6/udp.c +4
-4if (result) {if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) result =NULL ;@@ -374,11 +376,11 @@ static struct sock *udp_v6_mcast_next(struct net *net, struct sock *sk, __be16 rmt_port, struct in6_addr *rmt_addr, int dif) { - struct hlist_node *node; + struct hlist_nulls_node *node;struct sock *s = sk;unsigned short num =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_nulls_for_each_from (s, node) {struct inet_sock *inet =inet_sk (s);if (!net_eq (sock_net (s), net))@@ -423,7 +425,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,int dif;spin_lock (&hslot->lock); - sk =sk_head (&hslot->head); + sk =sk_nulls_head (&hslot->head); dif =inet6_iif (skb); sk =udp_v6_mcast_next (net, sk, uh->dest, daddr, uh->source, saddr, dif);if (!sk) {@@ -432,7 +434,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, } sk2 = sk; -while ((sk2 =udp_v6_mcast_next (net,sk_next (sk2), uh->dest, daddr, +while ((sk2 =udp_v6_mcast_next (net,sk_nulls_next (sk2), uh->dest, daddr, uh->source, saddr, dif))) {struct sk_buff *buff =skb_clone (skb,GFP_ATOMIC );if (buff) {
Re: [PATCH 2/3] udp: Use hlist_nulls in UDP RCU code
Quoted 27 lines · Paul E. McKenney
Quoted 9 lines · Eric Dumazet
This is a straightforward patch, using
hlist_nullsinfrastructure.RCUification already done on UDP two weeks ago.
Using
hlist_nullspermits us to avoid some memory barriers, both at lookup time and delete time.Patch is large because it adds new macros to
include/net/sock.h. These macros will be used by TCP & DCCP in next patch.Looks good, one question below about the lockless searches. If the answer is that the search must complete undisturbed by deletions and additions, then:
Reviewed-by Paul E. McKenney <[email protected]>Quoted 9 lines
Signed-off-by Eric Dumazet <[email protected]>---
include/linux/rculist.h | 17 ----------- include/net/sock.h | 57 ++++++++++++++++++++++++++++++-------- include/net/udp.h | 2 - net/ipv4/udp.c | 47 ++++++++++++++----------------- net/ipv6/udp.c | 26 +++++++++-------- 5 files changed, 83 insertions(+), 66 deletions(-)
...
Quoted 21 lines
Quoted 21 lines
net/ipv4/udp.c +5
-7result =NULL ; badness =-1 ; -sk_for_each_rcu_safenext (sk, node, &hslot->head, next) { -/* -* lockless reader, and SLAB_DESTROY_BY_RCU items: -* We must check this item was not moved to another chain -*/ -if (udp_hashfn (net, sk->sk_hash) != hash) - goto begin; +sk_nulls_for_each_rcu (sk, node, &hslot->head) { score =compute_score (sk, net, saddr, hnum, sport, daddr, dport, dif);if (score > badness) {@@ -285,6 +274,14 @@ begin: badness = score; } } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain.
Quoted 8 lines
net/ipv4/udp.c begin: · line 284
Quoted 4 lines
net/ipv4/udp.c +4
-0+*/ +if (get_nulls_value (node) != hash) + goto begin; +Shouldn't this check go -after- the check for "result"? Or is this a case where the readers absolutely must have traversed a chain without modification to be guaranteed of finding the correct result?
Very good question
Yes, we really have to look at all the sockets, to find the one with higher score, not one with a low score, and the one with higher score being ignored because we didnt examined it.
So we really must check we finished our lookup on the right chain end, not an alien one.
(Previous UDP code had a shortcut if we found the socket with the maximal possible score, I deleted this test as it had basically 0.0001 % of chance being hit)
Thanks a lot for your patient review Paul.
PATCH 3/3 net: Convert TCP & DCCP hash tables to use RCU / hlist_nulls
In reply to Eric DumazetRCU was added to UDP lookups, using a fast infrastructure :
- sockets kmem_cache use SLAB_DESTROY_BY_RCU and dont pay the price of call_rcu() at freeing time.
- hlist_nulls permits to use few memory barriers.
This patch uses same infrastructure for TCP/DCCP established and timewait sockets.
Thanks to SLAB_DESTROY_BY_RCU, no slowdown for applications using short lived TCP connections. A followup patch, converting rwlocks to spinlocks will even speedup this case.
__inet_lookup_established() is pretty fast now we dont have to dirty a contended cache line (read_lock/read_unlock)
Only established and timewait hashtable are converted to RCU (bind table and listen table are still using traditional locking)
---
include/net/inet_hashtables.h | 4 - include/net/inet_timewait_sock.h | 10 +-- net/core/sock.c | 4 + net/dccp/ipv4.c | 1 net/dccp/ipv6.c | 1 net/dccp/proto.c | 4 - net/ipv4/inet_diag.c | 6 +- net/ipv4/inet_hashtables.c | 78 ++++++++++++++++++++--------- net/ipv4/inet_timewait_sock.c | 26 +++++---- net/ipv4/tcp.c | 4 - net/ipv4/tcp_ipv4.c | 25 ++++----- net/ipv6/inet6_hashtables.c | 70 +++++++++++++++++--------- net/ipv6/tcp_ipv6.c | 1 13 files changed, 150 insertions(+), 84 deletions(-)
include/net/inet_hashtables.h +2 -2
@@ -41,8 +41,8 @@* I'll experiment with dynamic table growth later. */ struct inet_ehash_bucket { -struct hlist_head chain; -struct hlist_head twchain; +struct hlist_nulls_head chain; +struct hlist_nulls_head twchain; };/* There are a few simple rules, which allow for local port reuse by
include/net/inet_timewait_sock.h +5 -5
@@ -110,7 +110,7 @@ struct inet_timewait_sock {#define tw_state __tw_common.skc_state#define tw_reuse __tw_common.skc_reuse#define tw_bound_dev_if __tw_common.skc_bound_dev_if -#define tw_node __tw_common.skc_node +#define tw_node __tw_common.skc_nulls_node#define tw_bind_node __tw_common.skc_bind_node#define tw_refcnt __tw_common.skc_refcnt#define tw_hash __tw_common.skc_hash
@@ -137,10 +137,10 @@ struct inet_timewait_sock {struct hlist_node tw_death_node; }; -static inline void inet_twsk_add_node (struct inet_timewait_sock *tw, -struct hlist_head *list) +static inline void inet_twsk_add_node_rcu (struct inet_timewait_sock *tw, +struct hlist_nulls_head *list) { -hlist_add_head (&tw->tw_node, list); +hlist_nulls_add_head_rcu (&tw->tw_node, list); } static inline void inet_twsk_add_bind_node(struct inet_timewait_sock *tw,
@@ -175,7 +175,7 @@ static inline int inet_twsk_del_dead_node(struct inet_timewait_sock *tw) }#define inet_twsk_for_each (tw, node, head) \ - hlist_for_each_entry(tw, node, head, tw_node) + hlist_nulls_for_each_entry(tw, node, head, tw_node)#define inet_twsk_for_each_inmate (tw, node, jail) \ hlist_for_each_entry(tw, node, jail, tw_death_node)
net/core/sock.c +3 -1
@@ -2082,7 +2082,9 @@ int proto_register(struct proto *prot, int alloc_slab) prot->twsk_prot->twsk_slab =kmem_cache_create (timewait_sock_slab_name, prot->twsk_prot->twsk_obj_size, -0 ,SLAB_HWCACHE_ALIGN , +0 , +SLAB_HWCACHE_ALIGN | + prot->slab_flags,NULL );if (prot->twsk_prot->twsk_slab ==NULL ) goto out_free_timewait_sock_slab_name;
net/dccp/ipv4.c +1 -0
@@ -938,6 +938,7 @@ static struct proto dccp_v4_prot = { .orphan_count = &dccp_orphan_count, .max_header = MAX_DCCP_HEADER, .obj_size = sizeof(struct dccp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .rsk_prot = &dccp_request_sock_ops, .twsk_prot = &dccp_timewait_sock_ops, .h.hashinfo = &dccp_hashinfo,
net/dccp/ipv6.c +1 -0
@@ -1140,6 +1140,7 @@ static struct proto dccp_v6_prot = { .orphan_count = &dccp_orphan_count, .max_header = MAX_DCCP_HEADER, .obj_size = sizeof(struct dccp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .rsk_prot = &dccp6_request_sock_ops, .twsk_prot = &dccp6_timewait_sock_ops, .h.hashinfo = &dccp_hashinfo,
net/dccp/proto.c +2 -2
@@ -1090,8 +1090,8 @@ static int __init dccp_init(void) }for (i =0 ; i < dccp_hashinfo.ehash_size; i++) { -INIT_HLIST_HEAD (&dccp_hashinfo.ehash[i].chain); -INIT_HLIST_HEAD (&dccp_hashinfo.ehash[i].twchain); +INIT_HLIST_NULLS_HEAD (&dccp_hashinfo.ehash[i].chain, i); +INIT_HLIST_NULLS_HEAD (&dccp_hashinfo.ehash[i].twchain, i); }if (inet_ehash_locks_alloc (&dccp_hashinfo))
net/ipv4/inet_diag.c +4 -3
@@ -778,18 +778,19 @@ skip_listen_ht:struct inet_ehash_bucket *head = &hashinfo->ehash[i];rwlock_t *lock =inet_ehash_lockp (hashinfo, i);struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node; num =0 ; -if (hlist_empty (&head->chain) &&hlist_empty (&head->twchain)) +if (hlist_nulls_empty (&head->chain) && +hlist_nulls_empty (&head->twchain))continue ;if (i > s_i) s_num =0 ;read_lock_bh (lock); -sk_for_each (sk, node, &head->chain) { +sk_nulls_for_each (sk, node, &head->chain) {struct inet_sock *inet =inet_sk (sk);if (num < s_num)
net/ipv4/inet_hashtables.c +55 -23
145 lines not on this page · raw message
net/ipv4/inet_timewait_sock.c +15 -11
@@ -23,12 +23,12 @@ static void __inet_twsk_kill(struct inet_timewait_sock *tw,rwlock_t *lock =inet_ehash_lockp (hashinfo, tw->tw_hash);write_lock (lock); -if (hlist_unhashed (&tw->tw_node)) { +if (hlist_nulls_unhashed (&tw->tw_node)) {write_unlock (lock);return ; } -__hlist_del (&tw->tw_node); -sk_node_init (&tw->tw_node); +hlist_nulls_del_rcu (&tw->tw_node); +sk_nulls_node_init (&tw->tw_node);write_unlock (lock);/* Disassociate with bind bucket. */
@@ -92,13 +92,17 @@ void __inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,write_lock (lock); -/* Step 2: Remove SK from established hash. */ -if (__sk_del_node_init (sk)) -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); - -/* Step 3: Hash TW into TIMEWAIT chain. */ -inet_twsk_add_node (tw, &ehead->twchain); +/* +* Step 2: Hash TW into TIMEWAIT chain. +* Should be done before removing sk from established chain +* because readers are lockless and search established first. +*/ atomic_inc (&tw->tw_refcnt); +inet_twsk_add_node_rcu (tw, &ehead->twchain); + +/* Step 3: Remove SK from established hash. */ +if (__sk_nulls_del_node_init_rcu (sk)) +sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 );write_unlock (lock); }
@@ -416,7 +420,7 @@ void inet_twsk_purge(struct net *net, struct inet_hashinfo *hashinfo, {struct inet_timewait_sock *tw;struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node;int h;local_bh_disable ();
@@ -426,7 +430,7 @@ void inet_twsk_purge(struct net *net, struct inet_hashinfo *hashinfo,rwlock_t *lock =inet_ehash_lockp (hashinfo, h); restart:write_lock (lock); -sk_for_each (sk, node, &head->twchain) { +sk_nulls_for_each (sk, node, &head->twchain) { tw =inet_twsk (sk); if (!net_eq(twsk_net(tw), net) ||
net/ipv4/tcp.c +2 -2
@@ -2707,8 +2707,8 @@ void __init tcp_init(void) thash_entries ? 0 : 512 * 1024); tcp_hashinfo.ehash_size =1 << tcp_hashinfo.ehash_size;for (i =0 ; i < tcp_hashinfo.ehash_size; i++) { -INIT_HLIST_HEAD (&tcp_hashinfo.ehash[i].chain); -INIT_HLIST_HEAD (&tcp_hashinfo.ehash[i].twchain); +INIT_HLIST_NULLS_HEAD (&tcp_hashinfo.ehash[i].chain, i); +INIT_HLIST_NULLS_HEAD (&tcp_hashinfo.ehash[i].twchain, i); }if (inet_ehash_locks_alloc (&tcp_hashinfo))panic ("TCP: failed to alloc ehash_locks" );
net/ipv4/tcp_ipv4.c +13 -12
@@ -1857,16 +1857,16 @@ EXPORT_SYMBOL(tcp_v4_destroy_sock);#ifdef CONFIG_PROC_FS /* Proc filesystem TCP sock list dumping. */ -static inline struct inet_timewait_sock *tw_head (struct hlist_head *head) +static inline struct inet_timewait_sock *tw_head (struct hlist_nulls_head *head) { -return hlist_empty (head) ?NULL : +return hlist_nulls_empty (head) ?NULL : list_entry(head->first, struct inet_timewait_sock, tw_node); }static inline struct inet_timewait_sock *tw_next (struct inet_timewait_sock *tw) { -return tw->tw_node.next ? -hlist_entry (tw->tw_node.next,typeof (*tw), tw_node) :NULL ; +return !is_a_nulls (tw->tw_node.next) ? +hlist_nulls_entry (tw->tw_node.next,typeof (*tw), tw_node) :NULL ; }static void *listening_get_next (struct seq_file *seq,void *cur)
@@ -1954,8 +1954,8 @@ static void *listening_get_idx(struct seq_file *seq, loff_t *pos)static inline int empty_bucket (struct tcp_iter_state *st) { -return hlist_empty (&tcp_hashinfo.ehash[st->bucket].chain) && -hlist_empty (&tcp_hashinfo.ehash[st->bucket].twchain); +return hlist_nulls_empty (&tcp_hashinfo.ehash[st->bucket].chain) && +hlist_nulls_empty (&tcp_hashinfo.ehash[st->bucket].twchain); }static void *established_get_first (struct seq_file *seq)
@@ -1966,7 +1966,7 @@ static void *established_get_first(struct seq_file *seq)for (st->bucket =0 ; st->bucket < tcp_hashinfo.ehash_size; ++st->bucket) {struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node;struct inet_timewait_sock *tw;rwlock_t *lock =inet_ehash_lockp (&tcp_hashinfo, st->bucket);
@@ -1975,7 +1975,7 @@ static void *established_get_first(struct seq_file *seq)continue ;read_lock_bh (lock); -sk_for_each (sk, node, &tcp_hashinfo.ehash[st->bucket].chain) { +sk_nulls_for_each (sk, node, &tcp_hashinfo.ehash[st->bucket].chain) {if (sk->sk_family != st->family || !net_eq (sock_net (sk), net)) {continue ;
@@ -2004,7 +2004,7 @@ static void *established_get_next(struct seq_file *seq, void *cur) {struct sock *sk = cur;struct inet_timewait_sock *tw; -struct hlist_node *node; +struct hlist_nulls_node *node;struct tcp_iter_state *st = seq->private;struct net *net =seq_file_net (seq);
@@ -2032,11 +2032,11 @@ get_tw:return NULL ;read_lock_bh (inet_ehash_lockp (&tcp_hashinfo, st->bucket)); - sk =sk_head (&tcp_hashinfo.ehash[st->bucket].chain); + sk =sk_nulls_head (&tcp_hashinfo.ehash[st->bucket].chain); }else - sk =sk_next (sk); + sk =sk_nulls_next (sk); -sk_for_each_from (sk, node) { +sk_nulls_for_each_from (sk, node) {if (sk->sk_family == st->family &&net_eq (sock_net (sk), net)) goto found; }
@@ -2375,6 +2375,7 @@ struct proto tcp_prot = { .sysctl_rmem = sysctl_tcp_rmem, .max_header = MAX_TCP_HEADER, .obj_size = sizeof(struct tcp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .twsk_prot = &tcp_timewait_sock_ops, .rsk_prot = &tcp_request_sock_ops, .h.hashinfo = &tcp_hashinfo,
net/ipv6/inet6_hashtables.c +47 -23
132 lines not on this page · raw message
net/ipv6/tcp_ipv6.c +1 -0
@@ -2043,6 +2043,7 @@ struct proto tcpv6_prot = { .sysctl_rmem = sysctl_tcp_rmem, .max_header = MAX_TCP_HEADER, .obj_size = sizeof(struct tcp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .twsk_prot = &tcp6_timewait_sock_ops, .rsk_prot = &tcp6_request_sock_ops, .h.hashinfo = &tcp_hashinfo,
Re: [PATCH 3/3] net: Convert TCP & DCCP hash tables to use RCU / hlist_nulls
net/ipv4/inet_hashtables.c struct sock * __inet_lookup_established(struct net *net, · line 245
Quoted 11 lines · Eric Dumazet
net/ipv4/inet_hashtables.c +10
-0+begin: +sk_nulls_for_each_rcu (sk, node, &head->chain) {if (INET_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif)) { +if (unlikely (!atomic_inc_not_zero (&sk->sk_refcnt))) + goto begintw; +if (unlikely (!INET_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif))) { +sock_put (sk); + goto begin; + }
This is the validation step that verifies the race opened by using SLAB_DESTROY_BY_RCU, right?
Does it make sense to add a little comment to these validation steps to keep people on their toes and aware of the trickery?
Quoted 11 lines
net/ipv4/inet_hashtables.c +9
-0+ goto out; + } } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != slot) + goto begin;
Re: [PATCH 3/3] net: Convert TCP & DCCP hash tables to use RCU / hlist_nulls
Quoted 13 lines · Peter Zijlstra
net/ipv4/inet_hashtables.c struct sock * __inet_lookup_established(struct net *net, · line 245
Quoted 11 lines · Eric Dumazet
net/ipv4/inet_hashtables.c +10
-0+begin: +sk_nulls_for_each_rcu (sk, node, &head->chain) {if (INET_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif)) { +if (unlikely (!atomic_inc_not_zero (&sk->sk_refcnt))) + goto begintw; +if (unlikely (!INET_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif))) { +sock_put (sk); + goto begin; + }
Quoted 2 lines
This is the validation step that verifies the race opened by using
SLAB_DESTROY_BY_RCU, right?
The atomic_inc_not_zero() is not related to SLAB_DESTROY_BY_RCU but classic RCU lookup. A writer can delete the item right before we try to use it.
Next step is necessary in case the deleted item was re-allocated and inserted in a hash chain (this one or another one, it doesnt matter). In this case, previous atomic_inc_not_zero test will succeed. So we must check again the item we selected (and refcounted) is the one we were searching.
So yes, this bit should be documented, since SLAB_DESTROY_BY_RCU is not really used in linux kernel at this moment.
Quoted 3 lines
Does it make sense to add a little comment to these validation steps to keep people on their toes and aware of the trickery?
Yes, you are right.
Thanks
Re: [PATCH 3/3] net: Convert TCP & DCCP hash tables to use RCU / hlist_nulls
Quoted 2 lines · Eric Dumazet
So yes, this bit should be documented, since
SLAB_DESTROY_BY_RCUis not really used in linux kernel at this moment.
It is used for the anonymous vmas. That is the purpose that Hugh introduced it for since he saw regression if he would use straight rcu freeing. See mm/rmap.c. SLAB_DESTROY_BY_RCU a pretty strange way of using RCU and slab so it should always be documented in detail.
Re: [PATCH 3/3] net: Convert TCP & DCCP hash tables to use RCU / hlist_nulls
In reply to Eric DumazetQuoted 14 lines · Eric Dumazet
Quoted 13 lines · Peter Zijlstra
Quoted 11 lines · Eric Dumazet
net/ipv4/inet_hashtables.c +10
-0+begin: +sk_nulls_for_each_rcu (sk, node, &head->chain) {if (INET_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif)) { +if (unlikely (!atomic_inc_not_zero (&sk->sk_refcnt))) + goto begintw; +if (unlikely (!INET_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif))) { +sock_put (sk); + goto begin; + }
Quoted 5 lines
Quoted 2 lines
This is the validation step that verifies the race opened by using
SLAB_DESTROY_BY_RCU, right?The
atomic_inc_not_zero()is not related toSLAB_DESTROY_BY_RCUbut classic RCU lookup. A writer can delete the item right before we try to use it.
Yeah, its to stabilize the current situation.
Quoted 7 lines
Next step is necessary in case the deleted item was re-allocated and inserted in a hash chain (this one or another one, it doesnt matter). In this case, previous
atomic_inc_not_zerotest will succeed. So we must check again the item we selected (and refcounted) is the one we were searching.So yes, this bit should be documented, since
SLAB_DESTROY_BY_RCUis not really used in linux kernel at this moment.
We have one user, anon_vma, and one thing that is very nearly identical the lockless pagecache.
See page_cache_get_speculative() and find_get_page(). The pagecache gets away with this due to the simple fact that the page frames are never freed.
Hmm, I once wrote a comment to go with SLAB_DESTROY_BY_RCU, which seems to have gotten lost... /me goes dig.
Found it: http://lkml.org/lkml/2008/4/2/143
I guess I'd better re-submit that..
Re: [PATCH 3/3] net: Convert TCP & DCCP hash tables to use RCU / hlist_nulls
Quoted 3 lines · Peter Zijlstra
Found it: http://lkml.org/lkml/2008/4/2/143
I guess I'd better re-submit that..
Looks good. CC Pekka and he will merge it.
Re: [PATCH 3/3] net: Convert TCP & DCCP hash tables to use RCU / hlist_nulls
In reply to Eric DumazetQuoted 12 lines · Eric Dumazet
RCU was added to UDP lookups, using a fast infrastructure :
- socketskmem_cacheuseSLAB_DESTROY_BY_RCUand dont pay the price ofcall_rcu()at freeing time.
-hlist_nullspermits to use few memory barriers.This patch uses same infrastructure for TCP/DCCP established and timewait sockets.
Thanks to
SLAB_DESTROY_BY_RCU, no slowdown for applications using short lived TCP connections. A followup patch, converting rwlocks to spinlocks will even speedup this case.
Quoted 5 lines
__inet_lookup_established()is pretty fast now we dont have to dirty a contended cache line (read_lock/read_unlock)Only established and timewait hashtable are converted to RCU (bind table and listen table are still using traditional locking)
Looks good to me!
Quoted 621 lines
Signed-off-by Eric Dumazet <[email protected]>---
include/net/inet_hashtables.h | 4 - include/net/inet_timewait_sock.h | 10 +-- net/core/sock.c | 4 + net/dccp/ipv4.c | 1 net/dccp/ipv6.c | 1 net/dccp/proto.c | 4 - net/ipv4/inet_diag.c | 6 +- net/ipv4/inet_hashtables.c | 78 ++++++++++++++++++++--------- net/ipv4/inet_timewait_sock.c | 26 +++++---- net/ipv4/tcp.c | 4 - net/ipv4/tcp_ipv4.c | 25 ++++----- net/ipv6/inet6_hashtables.c | 70 +++++++++++++++++--------- net/ipv6/tcp_ipv6.c | 1 13 files changed, 150 insertions(+), 84 deletions(-)include/net/inet_hashtables.h +2
-2@@ -41,8 +41,8 @@* I'll experiment with dynamic table growth later. */ struct inet_ehash_bucket { -struct hlist_head chain; -struct hlist_head twchain; +struct hlist_nulls_head chain; +struct hlist_nulls_head twchain; };/* There are a few simple rules, which allow for local port reuse by include/net/inet_timewait_sock.h +5
-5@@ -110,7 +110,7 @@ struct inet_timewait_sock {#define tw_state __tw_common.skc_state#define tw_reuse __tw_common.skc_reuse#define tw_bound_dev_if __tw_common.skc_bound_dev_if -#define tw_node __tw_common.skc_node +#define tw_node __tw_common.skc_nulls_node#define tw_bind_node __tw_common.skc_bind_node#define tw_refcnt __tw_common.skc_refcnt#define tw_hash __tw_common.skc_hash@@ -137,10 +137,10 @@ struct inet_timewait_sock {struct hlist_node tw_death_node; }; -static inline void inet_twsk_add_node (struct inet_timewait_sock *tw, -struct hlist_head *list) +static inline void inet_twsk_add_node_rcu (struct inet_timewait_sock *tw, +struct hlist_nulls_head *list) { -hlist_add_head (&tw->tw_node, list); +hlist_nulls_add_head_rcu (&tw->tw_node, list); } static inline void inet_twsk_add_bind_node(struct inet_timewait_sock *tw,@@ -175,7 +175,7 @@ static inline int inet_twsk_del_dead_node(struct inet_timewait_sock *tw) }#define inet_twsk_for_each (tw, node, head) \ - hlist_for_each_entry(tw, node, head, tw_node) + hlist_nulls_for_each_entry(tw, node, head, tw_node)#define inet_twsk_for_each_inmate (tw, node, jail) \ hlist_for_each_entry(tw, node, jail, tw_death_node)net/core/sock.c +3
-1@@ -2082,7 +2082,9 @@ int proto_register(struct proto *prot, int alloc_slab) prot->twsk_prot->twsk_slab =kmem_cache_create (timewait_sock_slab_name, prot->twsk_prot->twsk_obj_size, -0 ,SLAB_HWCACHE_ALIGN , +0 , +SLAB_HWCACHE_ALIGN | + prot->slab_flags,NULL );if (prot->twsk_prot->twsk_slab ==NULL ) goto out_free_timewait_sock_slab_name;net/dccp/ipv4.c +1
-0@@ -938,6 +938,7 @@ static struct proto dccp_v4_prot = { .orphan_count = &dccp_orphan_count, .max_header = MAX_DCCP_HEADER, .obj_size = sizeof(struct dccp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .rsk_prot = &dccp_request_sock_ops, .twsk_prot = &dccp_timewait_sock_ops, .h.hashinfo = &dccp_hashinfo,net/dccp/ipv6.c +1
-0@@ -1140,6 +1140,7 @@ static struct proto dccp_v6_prot = { .orphan_count = &dccp_orphan_count, .max_header = MAX_DCCP_HEADER, .obj_size = sizeof(struct dccp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .rsk_prot = &dccp6_request_sock_ops, .twsk_prot = &dccp6_timewait_sock_ops, .h.hashinfo = &dccp_hashinfo,net/dccp/proto.c +2
-2@@ -1090,8 +1090,8 @@ static int __init dccp_init(void) }for (i =0 ; i < dccp_hashinfo.ehash_size; i++) { -INIT_HLIST_HEAD (&dccp_hashinfo.ehash[i].chain); -INIT_HLIST_HEAD (&dccp_hashinfo.ehash[i].twchain); +INIT_HLIST_NULLS_HEAD (&dccp_hashinfo.ehash[i].chain, i); +INIT_HLIST_NULLS_HEAD (&dccp_hashinfo.ehash[i].twchain, i); }if (inet_ehash_locks_alloc (&dccp_hashinfo))net/ipv4/inet_diag.c +4
-3@@ -778,18 +778,19 @@ skip_listen_ht:struct inet_ehash_bucket *head = &hashinfo->ehash[i];rwlock_t *lock =inet_ehash_lockp (hashinfo, i);struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node; num =0 ; -if (hlist_empty (&head->chain) &&hlist_empty (&head->twchain)) +if (hlist_nulls_empty (&head->chain) && +hlist_nulls_empty (&head->twchain))continue ;if (i > s_i) s_num =0 ;read_lock_bh (lock); -sk_for_each (sk, node, &head->chain) { +sk_nulls_for_each (sk, node, &head->chain) {struct inet_sock *inet =inet_sk (sk);if (num < s_num)net/ipv4/inet_hashtables.c +55
-23@@ -223,35 +223,65 @@ struct sock * __inet_lookup_established(struct net *net, INET_ADDR_COOKIE(acookie, saddr, daddr)const __portpair ports =INET_COMBINED_PORTS (sport, hnum);struct sock *sk; -const struct hlist_node *node; +const struct hlist_nulls_node *node;/* Optimize here for direct hit, only listening connections can * have wildcards anyways. */ unsigned int hash =inet_ehashfn (net, daddr, hnum, saddr, sport); -struct inet_ehash_bucket *head =inet_ehash_bucket (hashinfo, hash); -rwlock_t *lock =inet_ehash_lockp (hashinfo, hash); +unsigned int slot = hash & (hashinfo->ehash_size -1 ); +struct inet_ehash_bucket *head = &hashinfo->ehash[slot]; -prefetch (head->chain.first); -read_lock (lock); -sk_for_each (sk, node, &head->chain) { +rcu_read_lock (); +begin: +sk_nulls_for_each_rcu (sk, node, &head->chain) {if (INET_MATCH (sk, net, hash, acookie, - saddr, daddr, ports, dif)) - goto hit;/* You sunk my battleship! */ + saddr, daddr, ports, dif)) { +if (unlikely (!atomic_inc_not_zero (&sk->sk_refcnt))) + goto begintw; +if (unlikely (!INET_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif))) { +sock_put (sk); + goto begin; + } + goto out; + } } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != slot) + goto begin; +begintw:/* Must check for a TIME_WAIT'er before going to listener hash. */ -sk_for_each (sk, node, &head->twchain) { +sk_nulls_for_each_rcu (sk, node, &head->twchain) {if (INET_TW_MATCH (sk, net, hash, acookie, - saddr, daddr, ports, dif)) - goto hit; + saddr, daddr, ports, dif)) { +if (unlikely (!atomic_inc_not_zero (&sk->sk_refcnt))) { + sk =NULL ; + goto out; + } +if (unlikely (!INET_TW_MATCH (sk, net, hash, acookie, + saddr, daddr, ports, dif))) { +sock_put (sk); + goto begintw; + } + goto out; + } } +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != slot) + goto begintw; sk =NULL ; out: -read_unlock (lock); +rcu_read_unlock ();return sk; -hit: -sock_hold (sk); - goto out; }EXPORT_SYMBOL_GPL (__inet_lookup_established);@@ -272,14 +302,14 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row,struct inet_ehash_bucket *head =inet_ehash_bucket (hinfo, hash);rwlock_t *lock =inet_ehash_lockp (hinfo, hash);struct sock *sk2; -const struct hlist_node *node; +const struct hlist_nulls_node *node;struct inet_timewait_sock *tw;prefetch (head->chain.first);write_lock (lock);/* Check TIME-WAIT sockets first. */ -sk_for_each (sk2, node, &head->twchain) { +sk_nulls_for_each (sk2, node, &head->twchain) { tw =inet_twsk (sk2); if (INET_TW_MATCH(sk2, net, hash, acookie,@@ -293,7 +323,7 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row, tw =NULL ;/* And established part... */ -sk_for_each (sk2, node, &head->chain) { +sk_nulls_for_each (sk2, node, &head->chain) {if (INET_MATCH (sk2, net, hash, acookie, saddr, daddr, ports, dif)) goto not_unique;@@ -306,7 +336,7 @@ unique: inet->sport =htons (lport); sk->sk_hash = hash;WARN_ON (!sk_unhashed (sk)); -__sk_add_node (sk, &head->chain); +__sk_nulls_add_node_rcu (sk, &head->chain);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 );write_unlock (lock);@@ -338,7 +368,7 @@ static inline u32 inet_sk_port_offset(const struct sock *sk)void __inet_hash_nolisten (struct sock *sk) {struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; -struct hlist_head *list; +struct hlist_nulls_head *list;rwlock_t *lock;struct inet_ehash_bucket *head;@@ -350,7 +380,7 @@ void __inet_hash_nolisten(struct sock *sk) lock =inet_ehash_lockp (hashinfo, sk->sk_hash);write_lock (lock); -__sk_add_node (sk, list); +__sk_nulls_add_node_rcu (sk, list);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 );write_unlock (lock); }@@ -400,13 +430,15 @@ void inet_unhash(struct sock *sk)local_bh_disable ();inet_listen_wlock (hashinfo); lock = &hashinfo->lhash_lock; +if (__sk_del_node_init (sk)) +sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); }else { lock =inet_ehash_lockp (hashinfo, sk->sk_hash);write_lock_bh (lock); +if (__sk_nulls_del_node_init_rcu (sk)) +sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); } -if (__sk_del_node_init (sk)) -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 );write_unlock_bh (lock); out:if (sk->sk_state ==TCP_LISTEN )net/ipv4/inet_timewait_sock.c +15
-11@@ -23,12 +23,12 @@ static void __inet_twsk_kill(struct inet_timewait_sock *tw,rwlock_t *lock =inet_ehash_lockp (hashinfo, tw->tw_hash);write_lock (lock); -if (hlist_unhashed (&tw->tw_node)) { +if (hlist_nulls_unhashed (&tw->tw_node)) {write_unlock (lock);return ; } -__hlist_del (&tw->tw_node); -sk_node_init (&tw->tw_node); +hlist_nulls_del_rcu (&tw->tw_node); +sk_nulls_node_init (&tw->tw_node);write_unlock (lock);/* Disassociate with bind bucket. */ @@ -92,13 +92,17 @@ void __inet_twsk_hashdance(struct inet_timewait_sock *tw, struct sock *sk,write_lock (lock); -/* Step 2: Remove SK from established hash. */ -if (__sk_del_node_init (sk)) -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); - -/* Step 3: Hash TW into TIMEWAIT chain. */ -inet_twsk_add_node (tw, &ehead->twchain); +/* +* Step 2: Hash TW into TIMEWAIT chain. +* Should be done before removing sk from established chain +* because readers are lockless and search established first. +*/ atomic_inc (&tw->tw_refcnt); +inet_twsk_add_node_rcu (tw, &ehead->twchain); + +/* Step 3: Remove SK from established hash. */ +if (__sk_nulls_del_node_init_rcu (sk)) +sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 );write_unlock (lock); }@@ -416,7 +420,7 @@ void inet_twsk_purge(struct net *net, struct inet_hashinfo *hashinfo, {struct inet_timewait_sock *tw;struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node;int h;local_bh_disable ();@@ -426,7 +430,7 @@ void inet_twsk_purge(struct net *net, struct inet_hashinfo *hashinfo,rwlock_t *lock =inet_ehash_lockp (hashinfo, h); restart:write_lock (lock); -sk_for_each (sk, node, &head->twchain) { +sk_nulls_for_each (sk, node, &head->twchain) { tw =inet_twsk (sk); if (!net_eq(twsk_net(tw), net) ||net/ipv4/tcp.c +2
-2@@ -2707,8 +2707,8 @@ void __init tcp_init(void) thash_entries ? 0 : 512 * 1024); tcp_hashinfo.ehash_size =1 << tcp_hashinfo.ehash_size;for (i =0 ; i < tcp_hashinfo.ehash_size; i++) { -INIT_HLIST_HEAD (&tcp_hashinfo.ehash[i].chain); -INIT_HLIST_HEAD (&tcp_hashinfo.ehash[i].twchain); +INIT_HLIST_NULLS_HEAD (&tcp_hashinfo.ehash[i].chain, i); +INIT_HLIST_NULLS_HEAD (&tcp_hashinfo.ehash[i].twchain, i); }if (inet_ehash_locks_alloc (&tcp_hashinfo))panic ("TCP: failed to alloc ehash_locks" );net/ipv4/tcp_ipv4.c +13
-12@@ -1857,16 +1857,16 @@ EXPORT_SYMBOL(tcp_v4_destroy_sock);#ifdef CONFIG_PROC_FS /* Proc filesystem TCP sock list dumping. */ -static inline struct inet_timewait_sock *tw_head (struct hlist_head *head) +static inline struct inet_timewait_sock *tw_head (struct hlist_nulls_head *head) { -return hlist_empty (head) ?NULL : +return hlist_nulls_empty (head) ?NULL : list_entry(head->first, struct inet_timewait_sock, tw_node); }static inline struct inet_timewait_sock *tw_next (struct inet_timewait_sock *tw) { -return tw->tw_node.next ? -hlist_entry (tw->tw_node.next,typeof (*tw), tw_node) :NULL ; +return !is_a_nulls (tw->tw_node.next) ? +hlist_nulls_entry (tw->tw_node.next,typeof (*tw), tw_node) :NULL ; }static void *listening_get_next (struct seq_file *seq,void *cur)@@ -1954,8 +1954,8 @@ static void *listening_get_idx(struct seq_file *seq, loff_t *pos)static inline int empty_bucket (struct tcp_iter_state *st) { -return hlist_empty (&tcp_hashinfo.ehash[st->bucket].chain) && -hlist_empty (&tcp_hashinfo.ehash[st->bucket].twchain); +return hlist_nulls_empty (&tcp_hashinfo.ehash[st->bucket].chain) && +hlist_nulls_empty (&tcp_hashinfo.ehash[st->bucket].twchain); }static void *established_get_first (struct seq_file *seq)@@ -1966,7 +1966,7 @@ static void *established_get_first(struct seq_file *seq)for (st->bucket =0 ; st->bucket < tcp_hashinfo.ehash_size; ++st->bucket) {struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node;struct inet_timewait_sock *tw;rwlock_t *lock =inet_ehash_lockp (&tcp_hashinfo, st->bucket);@@ -1975,7 +1975,7 @@ static void *established_get_first(struct seq_file *seq)continue ;read_lock_bh (lock); -sk_for_each (sk, node, &tcp_hashinfo.ehash[st->bucket].chain) { +sk_nulls_for_each (sk, node, &tcp_hashinfo.ehash[st->bucket].chain) {if (sk->sk_family != st->family || !net_eq (sock_net (sk), net)) {continue ;@@ -2004,7 +2004,7 @@ static void *established_get_next(struct seq_file *seq, void *cur) {struct sock *sk = cur;struct inet_timewait_sock *tw; -struct hlist_node *node; +struct hlist_nulls_node *node;struct tcp_iter_state *st = seq->private;struct net *net =seq_file_net (seq);@@ -2032,11 +2032,11 @@ get_tw:return NULL ;read_lock_bh (inet_ehash_lockp (&tcp_hashinfo, st->bucket)); - sk =sk_head (&tcp_hashinfo.ehash[st->bucket].chain); + sk =sk_nulls_head (&tcp_hashinfo.ehash[st->bucket].chain); }else - sk =sk_next (sk); + sk =sk_nulls_next (sk); -sk_for_each_from (sk, node) { +sk_nulls_for_each_from (sk, node) {if (sk->sk_family == st->family &&net_eq (sock_net (sk), net)) goto found; }@@ -2375,6 +2375,7 @@ struct proto tcp_prot = { .sysctl_rmem = sysctl_tcp_rmem, .max_header = MAX_TCP_HEADER, .obj_size = sizeof(struct tcp_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .twsk_prot = &tcp_timewait_sock_ops, .rsk_prot = &tcp_request_sock_ops, .h.hashinfo = &tcp_hashinfo,net/ipv6/inet6_hashtables.c +47
-23@@ -25,24 +25,28 @@void __inet6_hash (struct sock *sk) {struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; -struct hlist_head *list;rwlock_t *lock;WARN_ON (!sk_unhashed (sk));if (sk->sk_state ==TCP_LISTEN ) { +struct hlist_head *list; + list = &hashinfo->listening_hash[inet_sk_listen_hashfn (sk)]; lock = &hashinfo->lhash_lock;inet_listen_wlock (hashinfo); +__sk_add_node (sk, list); }else {unsigned int hash; +struct hlist_nulls_head *list; + sk->sk_hash = hash =inet6_sk_ehashfn (sk); list = &inet_ehash_bucket (hashinfo, hash)->chain; lock =inet_ehash_lockp (hashinfo, hash);write_lock (lock); +__sk_nulls_add_node_rcu (sk, list); } -__sk_add_node (sk, list);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 );write_unlock (lock); }@@ -63,33 +67,53 @@ struct sock *__inet6_lookup_established(struct net *net, const int dif) {struct sock *sk; -const struct hlist_node *node; +const struct hlist_nulls_node *node;const __portpair ports =INET_COMBINED_PORTS (sport, hnum);/* Optimize here for direct hit, only listening connections can * have wildcards anyways. */ unsigned int hash =inet6_ehashfn (net, daddr, hnum, saddr, sport); -struct inet_ehash_bucket *head =inet_ehash_bucket (hashinfo, hash); -rwlock_t *lock =inet_ehash_lockp (hashinfo, hash); +unsigned int slot = hash & (hashinfo->ehash_size -1 ); +struct inet_ehash_bucket *head = &hashinfo->ehash[slot]; -prefetch (head->chain.first); -read_lock (lock); -sk_for_each (sk, node, &head->chain) { + +rcu_read_lock (); +begin: +sk_nulls_for_each_rcu (sk, node, &head->chain) {/* For IPV6 do the cheaper port and family tests first. */ -if (INET6_MATCH (sk, net, hash, saddr, daddr, ports, dif)) - goto hit;/* You sunk my battleship! */ +if (INET6_MATCH (sk, net, hash, saddr, daddr, ports, dif)) { +if (unlikely (!atomic_inc_not_zero (&sk->sk_refcnt))) + goto begintw; +if (!INET6_MATCH (sk, net, hash, saddr, daddr, ports, dif)) { +sock_put (sk); + goto begin; + } + goto out; + } } +if (get_nulls_value (node) != slot) + goto begin; + +begintw:/* Must check for a TIME_WAIT'er before going to listener hash. */ -sk_for_each (sk, node, &head->twchain) { -if (INET6_TW_MATCH (sk, net, hash, saddr, daddr, ports, dif)) - goto hit; +sk_nulls_for_each_rcu (sk, node, &head->twchain) { +if (INET6_TW_MATCH (sk, net, hash, saddr, daddr, ports, dif)) { +if (unlikely (!atomic_inc_not_zero (&sk->sk_refcnt))) { + sk =NULL ; + goto out; + } +if (!INET6_TW_MATCH (sk, net, hash, saddr, daddr, ports, dif)) { +sock_put (sk); + goto begintw; + } + goto out; + } } -read_unlock (lock); -return NULL ; - -hit: -sock_hold (sk); -read_unlock (lock); +if (get_nulls_value (node) != slot) + goto begintw; + sk =NULL ; +out: +rcu_read_unlock ();return sk; }EXPORT_SYMBOL (__inet6_lookup_established);@@ -172,14 +196,14 @@ static int __inet6_check_established(struct inet_timewait_death_row *death_row,struct inet_ehash_bucket *head =inet_ehash_bucket (hinfo, hash);rwlock_t *lock =inet_ehash_lockp (hinfo, hash);struct sock *sk2; -const struct hlist_node *node; +const struct hlist_nulls_node *node;struct inet_timewait_sock *tw;prefetch (head->chain.first);write_lock (lock);/* Check TIME-WAIT sockets first. */ -sk_for_each (sk2, node, &head->twchain) { +sk_nulls_for_each (sk2, node, &head->twchain) { tw =inet_twsk (sk2);if (INET6_TW_MATCH (sk2, net, hash, saddr, daddr, ports, dif)) {@@ -192,7 +216,7 @@ static int __inet6_check_established(struct inet_timewait_death_row *death_row, tw =NULL ;/* And established part... */ -sk_for_each (sk2, node, &head->chain) { +sk_nulls_for_each (sk2, node, &head->chain) {if (INET6_MATCH (sk2, net, hash, saddr, daddr, ports, dif)) goto not_unique; }@@ -203,7 +227,7 @@ unique: inet->num = lport; inet->sport =htons (lport);WARN_ON (!sk_unhashed (sk)); -__sk_add_node (sk, &head->chain); +__sk_nulls_add_node_rcu (sk, &head->chain); sk->sk_hash = hash;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 );write_unlock (lock);net/ipv6/tcp_ipv6.c +1
-0@@ -2043,6 +2043,7 @@ struct proto tcpv6_prot = { .sysctl_rmem = sysctl_tcp_rmem, .max_header = MAX_TCP_HEADER, .obj_size = sizeof(struct tcp6_sock), + .slab_flags = SLAB_DESTROY_BY_RCU, .twsk_prot = &tcp6_timewait_sock_ops, .rsk_prot = &tcp6_request_sock_ops, .h.hashinfo = &tcp_hashinfo,
PATCH 1/1 net: Convert TCP/DCCP listening hash tables to use RCU
In reply to Eric DumazetHi David
Please find patch to convert TCP/DCCP listening hash tables to RCU.
A followup patch will cleanup all sk_node fields and macros that are not used anymore.
Thanks
[PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
This is the last step to be able to perform full RCU lookups in __inet_lookup() : After established/timewait tables, we add RCU lookups to listening hash table.
The only trick here is that a socket of a given type (TCP ipv4, TCP ipv6, ...) can now flight between two different tables (established and listening) during a RCU grace period, so we must use different 'nulls' end-of-chain values for two tables.
We define a large value :
#define LISTENING_NULLS_BASE (1U << 29)
So that slots in listening table are guaranteed to have different end-of-chain values than slots in established table. A reader can still detect it finished its lookup in the right chain.
---
include/net/inet_hashtables.h | 9 + net/ipv4/inet_diag.c | 4 net/ipv4/inet_hashtables.c | 148 ++++++++++++++++---------------- net/ipv4/tcp_ipv4.c | 8 - net/ipv6/inet6_hashtables.c | 94 ++++++++++++-------- 5 files changed, 147 insertions(+), 116 deletions(-)
include/net/inet_hashtables.h +8 -1
@@ -99,9 +99,16 @@ struct inet_bind_hashbucket {struct hlist_head chain; }; +/* +* Sockets can be hashed in established or listening table +* We must use different 'nulls' end-of-chain value for listening +* hash table, or we might find a socket that was closed and +* reallocated/inserted into established hash table +*/ +#define LISTENING_NULLS_BASE (1U << 29)struct inet_listen_hashbucket {spinlock_t lock; -struct hlist_head head; +struct hlist_nulls_head head; };/* This is for listening sockets, thus all sockets which possess wildcards. */
net/ipv4/inet_diag.c +2 -2
@@ -720,13 +720,13 @@ static int inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)for (i = s_i; i <INET_LHTABLE_SIZE ; i++) {struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node;struct inet_listen_hashbucket *ilb; num =0 ; ilb = &hashinfo->listening_hash[i];spin_lock_bh (&ilb->lock); -sk_for_each (sk, node, &ilb->head) { +sk_nulls_for_each (sk, node, &ilb->head) {struct inet_sock *inet =inet_sk (sk);if (num < s_num) {
net/ipv4/inet_hashtables.c +74 -74
193 lines not on this page · raw message
net/ipv4/tcp_ipv4.c +4 -4
@@ -1868,7 +1868,7 @@ static inline struct inet_timewait_sock *tw_next(struct inet_timewait_sock *tw)static void *listening_get_next (struct seq_file *seq,void *cur) {struct inet_connection_sock *icsk; -struct hlist_node *node; +struct hlist_nulls_node *node;struct sock *sk = cur;struct inet_listen_hashbucket *ilb;struct tcp_iter_state *st = seq->private;
@@ -1878,7 +1878,7 @@ static void *listening_get_next(struct seq_file *seq, void *cur) st->bucket =0 ; ilb = &tcp_hashinfo.listening_hash[0 ];spin_lock_bh (&ilb->lock); - sk =sk_head (&ilb->head); + sk =sk_nulls_head (&ilb->head); goto get_sk; } ilb = &tcp_hashinfo.listening_hash[st->bucket];
@@ -1914,7 +1914,7 @@ get_req: sk =sk_next (sk); } get_sk: -sk_for_each_from (sk, node) { +sk_nulls_for_each_from (sk, node) {if (sk->sk_family == st->family &&net_eq (sock_net (sk), net)) { cur = sk; goto out;
@@ -1935,7 +1935,7 @@ start_req:if (++st->bucket <INET_LHTABLE_SIZE ) { ilb = &tcp_hashinfo.listening_hash[st->bucket];spin_lock_bh (&ilb->lock); - sk =sk_head (&ilb->head); + sk =sk_nulls_head (&ilb->head); goto get_sk; } cur =NULL ;
net/ipv6/inet6_hashtables.c +59 -35
113 lines not on this page · raw message
Re: [PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
Quoted 16 lines · Eric Dumazet
Hi David
Please find patch to convert TCP/DCCP listening hash tables to RCU.
A followup patch will cleanup all
sk_nodefields and macros that are not used anymore.Thanks
[PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
This is the last step to be able to perform full RCU lookups in
__inet_lookup(): After established/timewait tables, we add RCU lookups to listening hash table.
Quoted 8 lines
The only trick here is that a socket of a given type (TCP ipv4, TCP ipv6, ...) can now flight between two different tables (established and listening) during a RCU grace period, so we must use different 'nulls' end-of-chain values for two tables.
We define a large value :
#define
LISTENING_NULLS_BASE(1U << 29)
I do like this use of the full set up upper bits! However, wouldn't it be a good idea to use a larger base value for 64-bit systems, perhaps using CONFIG_64BIT to choose? 500M entries might not seem like that many in a few years time...
Thanx, Paul
Quoted 415 lines
So that slots in listening table are guaranteed to have different end-of-chain values than slots in established table. A reader can still detect it finished its lookup in the right chain.
Signed-off-by Eric Dumazet <[email protected]>---
include/net/inet_hashtables.h | 9 + net/ipv4/inet_diag.c | 4 net/ipv4/inet_hashtables.c | 148 ++++++++++++++++---------------- net/ipv4/tcp_ipv4.c | 8 - net/ipv6/inet6_hashtables.c | 94 ++++++++++++-------- 5 files changed, 147 insertions(+), 116 deletions(-)include/net/inet_hashtables.h +8
-1@@ -99,9 +99,16 @@ struct inet_bind_hashbucket {struct hlist_head chain; }; +/* +* Sockets can be hashed in established or listening table +* We must use different 'nulls' end-of-chain value for listening +* hash table, or we might find a socket that was closed and +* reallocated/inserted into established hash table +*/ +#define LISTENING_NULLS_BASE (1U << 29)struct inet_listen_hashbucket {spinlock_t lock; -struct hlist_head head; +struct hlist_nulls_head head; };/* This is for listening sockets, thus all sockets which possess wildcards. */ net/ipv4/inet_diag.c +2
-2@@ -720,13 +720,13 @@ static int inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)for (i = s_i; i <INET_LHTABLE_SIZE ; i++) {struct sock *sk; -struct hlist_node *node; +struct hlist_nulls_node *node;struct inet_listen_hashbucket *ilb; num =0 ; ilb = &hashinfo->listening_hash[i];spin_lock_bh (&ilb->lock); -sk_for_each (sk, node, &ilb->head) { +sk_nulls_for_each (sk, node, &ilb->head) {struct inet_sock *inet =inet_sk (sk);if (num < s_num) {net/ipv4/inet_hashtables.c +74
-74@@ -110,78 +110,79 @@ void __inet_inherit_port(struct sock *sk, struct sock *child)EXPORT_SYMBOL_GPL (__inet_inherit_port); +static inline int compute_score (struct sock *sk,struct net *net, +const unsigned short hnum,const __be32 daddr, +const int dif) +{ +int score =-1 ; +struct inet_sock *inet =inet_sk (sk); + +if (net_eq (sock_net (sk), net) && inet->num == hnum && + !ipv6_only_sock (sk)) { +__be32 rcv_saddr = inet->rcv_saddr; + score = sk->sk_family ==PF_INET ?1 :0 ; +if (rcv_saddr) { +if (rcv_saddr != daddr) +return -1 ; + score +=2 ; + } +if (sk->sk_bound_dev_if) { +if (sk->sk_bound_dev_if != dif) +return -1 ; + score +=2 ; + } + } +return score; +} +/* * Don't inline this cruft. Here are some nice properties to exploit here. The * BSD API does not allow a listening sock to specify the remote port nor the * remote address for the connection. So always assume those are both * wildcarded during the search since they can never be otherwise. */ -static struct sock *inet_lookup_listener_slow (struct net *net, -const struct hlist_head *head, -const __be32 daddr, -const unsigned short hnum, -const int dif) -{ -struct sock *result =NULL , *sk; -const struct hlist_node *node; -int hiscore =-1 ; - -sk_for_each (sk, node, head) { -const struct inet_sock *inet =inet_sk (sk); - -if (net_eq (sock_net (sk), net) && inet->num == hnum && - !ipv6_only_sock (sk)) { -const __be32 rcv_saddr = inet->rcv_saddr; -int score = sk->sk_family ==PF_INET ?1 :0 ; - -if (rcv_saddr) { -if (rcv_saddr != daddr) -continue ; - score +=2 ; - } -if (sk->sk_bound_dev_if) { -if (sk->sk_bound_dev_if != dif) -continue ; - score +=2 ; - } -if (score ==5 ) -return sk; -if (score > hiscore) { - hiscore = score; - result = sk; - } - } - } -return result; -} -/* Optimize the common listener case. */ +struct sock *__inet_lookup_listener (struct net *net,struct inet_hashinfo *hashinfo,const __be32 daddr,const unsigned short hnum,const int dif) { -struct sock *sk =NULL ; -struct inet_listen_hashbucket *ilb; +struct sock *sk, *result; +struct hlist_nulls_node *node; +unsigned int hash =inet_lhashfn (net, hnum); +struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash]; +int score, hiscore; - ilb = &hashinfo->listening_hash[inet_lhashfn (net, hnum)]; -spin_lock (&ilb->lock); -if (!hlist_empty (&ilb->head)) { -const struct inet_sock *inet =inet_sk ((sk =__sk_head (&ilb->head))); - -if (inet->num == hnum && !sk->sk_node.next && - (!inet->rcv_saddr || inet->rcv_saddr == daddr) && - (sk->sk_family ==PF_INET || !ipv6_only_sock (sk)) && - !sk->sk_bound_dev_if &&net_eq (sock_net (sk), net)) - goto sherry_cache; - sk =inet_lookup_listener_slow (net, &ilb->head, daddr, hnum, dif); +rcu_read_lock (); +begin: + result =NULL ; + hiscore =-1 ; +sk_nulls_for_each_rcu (sk, node, &ilb->head) { + score =compute_score (sk, net, hnum, daddr, dif); +if (score > hiscore) { + result = sk; + hiscore = score; + } } -if (sk) { -sherry_cache: -sock_hold (sk); +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != hash +LISTENING_NULLS_BASE ) + goto begin; +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, hnum, daddr, + dif) < hiscore)) { +sock_put (result); + goto begin; + } } -spin_unlock (&ilb->lock); -return sk; +rcu_read_unlock (); +return result; }EXPORT_SYMBOL_GPL (__inet_lookup_listener);@@ -370,7 +371,7 @@ static void __inet_hash(struct sock *sk) ilb = &hashinfo->listening_hash[inet_sk_listen_hashfn (sk)];spin_lock (&ilb->lock); -__sk_add_node (sk, &ilb->head); +__sk_nulls_add_node_rcu (sk, &ilb->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 );spin_unlock (&ilb->lock); }@@ -388,26 +389,22 @@ EXPORT_SYMBOL_GPL(inet_hash);void inet_unhash (struct sock *sk) {struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; +spinlock_t *lock; +int done;if (sk_unhashed (sk))return ; -if (sk->sk_state ==TCP_LISTEN ) { -struct inet_listen_hashbucket *ilb; +if (sk->sk_state ==TCP_LISTEN ) + lock = &hashinfo->listening_hash[inet_sk_listen_hashfn (sk)].lock; +else + lock =inet_ehash_lockp (hashinfo, sk->sk_hash); - ilb = &hashinfo->listening_hash[inet_sk_listen_hashfn (sk)]; -spin_lock_bh (&ilb->lock); -if (__sk_del_node_init (sk)) -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); -spin_unlock_bh (&ilb->lock); - }else { -spinlock_t *lock =inet_ehash_lockp (hashinfo, sk->sk_hash); - -spin_lock_bh (lock); -if (__sk_nulls_del_node_init_rcu (sk)) -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); -spin_unlock_bh (lock); - } +spin_lock_bh (lock); + done =__sk_nulls_del_node_init_rcu (sk); +spin_unlock_bh (lock); +if (done) +sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); }EXPORT_SYMBOL_GPL (inet_unhash);@@ -526,8 +523,11 @@ void inet_hashinfo_init(struct inet_hashinfo *h) {int i; -for (i =0 ; i <INET_LHTABLE_SIZE ; i++) +for (i =0 ; i <INET_LHTABLE_SIZE ; i++) {spin_lock_init (&h->listening_hash[i].lock); +INIT_HLIST_NULLS_HEAD (&h->listening_hash[i].head, + i +LISTENING_NULLS_BASE ); + } }EXPORT_SYMBOL_GPL (inet_hashinfo_init);net/ipv4/tcp_ipv4.c +4
-4@@ -1868,7 +1868,7 @@ static inline struct inet_timewait_sock *tw_next(struct inet_timewait_sock *tw)static void *listening_get_next (struct seq_file *seq,void *cur) {struct inet_connection_sock *icsk; -struct hlist_node *node; +struct hlist_nulls_node *node;struct sock *sk = cur;struct inet_listen_hashbucket *ilb;struct tcp_iter_state *st = seq->private;@@ -1878,7 +1878,7 @@ static void *listening_get_next(struct seq_file *seq, void *cur) st->bucket =0 ; ilb = &tcp_hashinfo.listening_hash[0 ];spin_lock_bh (&ilb->lock); - sk =sk_head (&ilb->head); + sk =sk_nulls_head (&ilb->head); goto get_sk; } ilb = &tcp_hashinfo.listening_hash[st->bucket];@@ -1914,7 +1914,7 @@ get_req: sk =sk_next (sk); } get_sk: -sk_for_each_from (sk, node) { +sk_nulls_for_each_from (sk, node) {if (sk->sk_family == st->family &&net_eq (sock_net (sk), net)) { cur = sk; goto out;@@ -1935,7 +1935,7 @@ start_req:if (++st->bucket <INET_LHTABLE_SIZE ) { ilb = &tcp_hashinfo.listening_hash[st->bucket];spin_lock_bh (&ilb->lock); - sk =sk_head (&ilb->head); + sk =sk_nulls_head (&ilb->head); goto get_sk; } cur =NULL ;net/ipv6/inet6_hashtables.c +59
-35@@ -33,7 +33,7 @@ void __inet6_hash(struct sock *sk) ilb = &hashinfo->listening_hash[inet_sk_listen_hashfn (sk)];spin_lock (&ilb->lock); -__sk_add_node (sk, &ilb->head); +__sk_nulls_add_node_rcu (sk, &ilb->head);spin_unlock (&ilb->lock); }else {unsigned int hash;@@ -118,47 +118,71 @@ out: }EXPORT_SYMBOL (__inet6_lookup_established); +static int inline compute_score (struct sock *sk,struct net *net, +const unsigned short hnum, +const struct in6_addr *daddr, +const int dif) +{ +int score =-1 ; + +if (net_eq (sock_net (sk), net) &&inet_sk (sk)->num == hnum && + sk->sk_family ==PF_INET6 ) { +const struct ipv6_pinfo *np =inet6_sk (sk); + + score =1 ; +if (!ipv6_addr_any (&np->rcv_saddr)) { +if (!ipv6_addr_equal (&np->rcv_saddr, daddr)) +return -1 ; + score++; + } +if (sk->sk_bound_dev_if) { +if (sk->sk_bound_dev_if != dif) +return -1 ; + score++; + } + } +return score; +} +struct sock *inet6_lookup_listener (struct net *net,struct inet_hashinfo *hashinfo,const struct in6_addr *daddr,const unsigned short hnum,const int dif) {struct sock *sk; -const struct hlist_node *node; -struct sock *result =NULL ; -int score, hiscore =0 ; -struct inet_listen_hashbucket *ilb; - - ilb = &hashinfo->listening_hash[inet_lhashfn (net, hnum)]; -spin_lock (&ilb->lock); -sk_for_each (sk, node, &ilb->head) { -if (net_eq (sock_net (sk), net) &&inet_sk (sk)->num == hnum && - sk->sk_family ==PF_INET6 ) { -const struct ipv6_pinfo *np =inet6_sk (sk); - - score =1 ; -if (!ipv6_addr_any (&np->rcv_saddr)) { -if (!ipv6_addr_equal (&np->rcv_saddr, daddr)) -continue ; - score++; - } -if (sk->sk_bound_dev_if) { -if (sk->sk_bound_dev_if != dif) -continue ; - score++; - } -if (score ==3 ) { - result = sk; -break ; - } -if (score > hiscore) { - hiscore = score; - result = sk; - } +const struct hlist_nulls_node *node; +struct sock *result; +int score, hiscore; +unsigned int hash =inet_lhashfn (net, hnum); +struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash]; + +rcu_read_lock (); +begin: + result =NULL ; + hiscore =-1 ; +sk_nulls_for_each (sk, node, &ilb->head) { + score =compute_score (sk, net, hnum, daddr, dif); +if (score > hiscore) { + hiscore = score; + result = sk; } } -if (result) -sock_hold (result); -spin_unlock (&ilb->lock); +/* +* if the nulls value we got at the end of this lookup is +* not the expected one, we must restart lookup. +* We probably met an item that was moved to another chain. +*/ +if (get_nulls_value (node) != hash +LISTENING_NULLS_BASE ) + goto begin; +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, hnum, daddr, + dif) < hiscore)) { +sock_put (result); + goto begin; + } + } +rcu_read_unlock ();return result; }
Re: [PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
Quoted 22 lines · Paul E. McKenney
Quoted 21 lines · Eric Dumazet
Hi David
Please find patch to convert TCP/DCCP listening hash tables to RCU.
A followup patch will cleanup all
sk_nodefields and macros that are not used anymore.Thanks
[PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
This is the last step to be able to perform full RCU lookups in
__inet_lookup(): After established/timewait tables, we add RCU lookups to listening hash table.The only trick here is that a socket of a given type (TCP ipv4, TCP ipv6, ...) can now flight between two different tables (established and listening) during a RCU grace period, so we must use different 'nulls' end-of-chain values for two tables.
Quoted 9 lines
Quoted 3 lines
We define a large value :
#define
LISTENING_NULLS_BASE(1U << 29)I do like this use of the full set up upper bits! However, wouldn't it be a good idea to use a larger base value for 64-bit systems, perhaps using
CONFIG_64BITto choose? 500M entries might not seem like that many in a few years time...
Well, this value is correct up to 2^29 slots, and a hash table of 2^32 bytes (8 bytes per pointer)
A TCP socket uses about 1472 bytes on 64bit arches, so 2^29 sessions would need 800 GB of ram, not counting dentries, inodes, ...
I really doubt a machine, even with 4096 cpus should/can handle so many tcp sessions :)
Re: [PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
Quoted 35 lines · Eric Dumazet
Quoted 22 lines · Paul E. McKenney
Quoted 21 lines · Eric Dumazet
Hi David
Please find patch to convert TCP/DCCP listening hash tables to RCU.
A followup patch will cleanup all
sk_nodefields and macros that are not used anymore.Thanks
[PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
This is the last step to be able to perform full RCU lookups in
__inet_lookup(): After established/timewait tables, we add RCU lookups to listening hash table.The only trick here is that a socket of a given type (TCP ipv4, TCP ipv6, ...) can now flight between two different tables (established and listening) during a RCU grace period, so we must use different 'nulls' end-of-chain values for two tables.
Quoted 7 lines
Quoted 3 lines
We define a large value :
#define
LISTENING_NULLS_BASE(1U << 29)I do like this use of the full set up upper bits! However, wouldn't it be a good idea to use a larger base value for 64-bit systems, perhaps using
CONFIG_64BITto choose? 500M entries might not seem like that many in a few years time...Well, this value is correct up to 2^29 slots, and a hash table of 2^32 bytes
(8 bytes per pointer)
Quoted 5 lines
A TCP socket uses about 1472 bytes on 64bit arches, so 2^29 sessions would need 800 GB of ram, not counting dentries, inodes, ...
I really doubt a machine, even with 4096 cpus should/can handle so many tcp sessions :)
200MB per CPU, right?
But yes, now that you mention it, 800GB of memory dedicated to TCP connections sounds almost as ridiculous as did 640K of memory in the late 1970s. ;-)
Nevertheless, I don't have an overwhelming objection to the current code. Easy enough to change should it become a problem, right?
Thanx, Paul
Re: [PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
Quoted 37 lines · Paul E. McKenney
Quoted 36 lines · Eric Dumazet
Quoted 22 lines · Paul E. McKenney
Quoted 21 lines · Eric Dumazet
Hi David
Please find patch to convert TCP/DCCP listening hash tables to RCU.
A followup patch will cleanup all
sk_nodefields and macros that are not used anymore.Thanks
[PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
This is the last step to be able to perform full RCU lookups in
__inet_lookup(): After established/timewait tables, we add RCU lookups to listening hash table.The only trick here is that a socket of a given type (TCP ipv4, TCP ipv6, ...) can now flight between two different tables (established and listening) during a RCU grace period, so we must use different 'nulls' end-of-chain values for two tables.
Quoted 7 lines
Quoted 3 lines
We define a large value :
#define
LISTENING_NULLS_BASE(1U << 29)I do like this use of the full set up upper bits! However, wouldn't it be a good idea to use a larger base value for 64-bit systems, perhaps using
CONFIG_64BITto choose? 500M entries might not seem like that many in a few years time...Well, this value is correct up to 2^29 slots, and a hash table of 2^32 bytes (8 bytes per pointer)
A TCP socket uses about 1472 bytes on 64bit arches, so 2^29 sessions would need 800 GB of ram, not counting dentries, inodes, ...
Quoted 8 lines
Quoted 2 lines
I really doubt a machine, even with 4096 cpus should/can handle so many tcp sessions :)
200MB per CPU, right?
But yes, now that you mention it, 800GB of memory dedicated to TCP connections sounds almost as ridiculous as did 640K of memory in the late 1970s. ;-)
;)
Quoted 3 lines
Nevertheless, I don't have an overwhelming objection to the current code. Easy enough to change should it become a problem, right?
Sure. By that time, cpus might be 128 bits or 256 bits anyway :)
Re: [PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
Quoted 46 lines · Eric Dumazet
Quoted 38 lines · Paul E. McKenney
Quoted 37 lines · Eric Dumazet
Quoted 22 lines · Paul E. McKenney
Quoted 21 lines · Eric Dumazet
Hi David
Please find patch to convert TCP/DCCP listening hash tables to RCU.
A followup patch will cleanup all
sk_nodefields and macros that are not used anymore.Thanks
[PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
This is the last step to be able to perform full RCU lookups in
__inet_lookup(): After established/timewait tables, we add RCU lookups to listening hash table.The only trick here is that a socket of a given type (TCP ipv4, TCP ipv6, ...) can now flight between two different tables (established and listening) during a RCU grace period, so we must use different 'nulls' end-of-chain values for two tables.
Quoted 7 lines
Quoted 3 lines
We define a large value :
#define
LISTENING_NULLS_BASE(1U << 29)I do like this use of the full set up upper bits! However, wouldn't it be a good idea to use a larger base value for 64-bit systems, perhaps using
CONFIG_64BITto choose? 500M entries might not seem like that many in a few years time...Well, this value is correct up to 2^29 slots, and a hash table of 2^32 bytes
(8 bytes per pointer)A TCP socket uses about 1472 bytes on 64bit arches, so 2^29 sessions would need 800 GB of ram, not counting dentries, inodes, ...
Quoted 6 lines
Quoted 2 lines
I really doubt a machine, even with 4096 cpus should/can handle so many tcp sessions :)
200MB per CPU, right?
But yes, now that you mention it, 800GB of memory dedicated to TCP connections sounds almost as ridiculous as did 640K of memory in the late 1970s. ;-)
Quoted 6 lines
;)
Quoted 2 lines
Nevertheless, I don't have an overwhelming objection to the current code. Easy enough to change should it become a problem, right?
Sure. By that time, cpus might be 128 bits or 256 bits anyway :)
Or even 640K bits. ;-)
Thanx, Paul
Re: [PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
In reply to Eric DumazetFrom: Eric Dumazet <[email protected]>
Date: Sun, 23 Nov 2008 10:33:28 +0100
Quoted 1 line
[PATCH] net: Convert TCP/DCCP listening hash tables to use RCU
Applied, thanks Eric.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Eric DumazetQuoted 6 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Why not use the bucket pointer as terminating condition?
Because all you really need is a pointer that is specific per bucket, and not a valid element, right?
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 5 lines · Peter Zijlstra
Quoted 3 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Quoted 7 lines
Quoted 2 lines
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it can be the 128 values : [ (void*)0 .. (void *)127 ]
Why not use the bucket pointer as terminating condition?
Because all you really need is a pointer that is specific per bucket, and not a valid element, right?
Yes, but it forces compiler to keep around the bucket pointer.
Big chance this value will be stored on stack.
Next patch will use the least significant bit to distinguish a valid pointer from a "NULL pointer"
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 12 lines · Eric Dumazet
Quoted 10 lines · Peter Zijlstra
Quoted 6 lines · Eric Dumazet
Hum... Another way of handling all those cases and avoid memory barriers would be to have different "NULL" pointers.
Each hash chain should have a unique "NULL" pointer (in the case of UDP, it
can be the 128 values : [ (void*)0 .. (void *)127 ]Why not use the bucket pointer as terminating condition? Because all you really need is a pointer that is specific per bucket, and not a valid element, right?
Quoted 6 lines
Yes, but it forces compiler to keep around the bucket pointer.
Big chance this value will be stored on stack.
Next patch will use the least significant bit to distinguish a valid pointer from a "NULL pointer"
As you might guess, I do like that idea. ;-)
On my plane trip, so am reviewing what I believe to be your current combined patchset consisting of six patches transmitted in this thread:
Message-ID: <[email protected]>
Message-ID: <[email protected]>
Message-ID: <[email protected]>
Message-ID: <[email protected]>
Message-ID: <[email protected]>
Message-ID: <[email protected]>
This probably won't be your latest and greatest by the time you receive this, but it appears to be the latest and greatest that I have. ;-)
A few comments, search for blank lines.
Thanx, Paul
Quoted 14 lines
include/linux/list.h +6
-0@@ -654,6 +654,22 @@ static inline void hlist_move_list(struct hlist_head *old, pos && ({prefetch (pos->next);1 ;}) && \ ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ;}); \ pos = pos->next) +/** +* hlist_for_each_entry_nulls - iterate over list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct.
include/linux/list.h static inline void hlist_move_list(struct hlist_head *old, · line 672
Quoted 10 lines
include/linux/list.h +10
-0+* @nullval: the iteration should stop if a pointer is < nullval +* +* Special version of hlist_for_each_entry where the end pointer +* can be NULL but also any value < nullval. +*/ +#define hlist_for_each_entry_nulls (tpos, pos, head, member, nullval) \ + for (pos = (head)->first; \ + ((unsigned long)pos >= nullval) && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next)
It might be good to take a predicate macro/function instead of nullval, in order to allow this primitive to be used for a number of different pseudo-NULL-pointer schemes, and ditto for the similar macros you define below. Might be too early to know exactly how such a primitive should look, though.
So just a random thought at this point.
In any case, this macro cannot be used in read-side critical sections.
Used by sk_for_each_nulls(), which is called by udp_lib_lport_inuse() and udp_get_first().
udp_lib_lport_inuse() is called by udp_lib_get_port(), which holds the hslot->lock, so should be OK.
In udp_get_first(), this same lock is held, so again should be OK.
Quoted 10 lines
include/linux/list.h +4
-0/** * hlist_for_each_entry_continue - iterate over a hlist continuing after current point @@ -679,6 +695,22 @@ static inline void hlist_move_list(struct hlist_head *old, pos = pos->next)/** +* hlist_for_each_entry_from_nulls - iterate over a hlist continuing from current point +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @member: the name of the hlist_node within the struct.
include/linux/list.h static inline void hlist_move_list(struct hlist_head *old, · line 711
Quoted 10 lines
include/linux/list.h +10
-0+* @nullval: the iteration should stop if a pointer is < nullval +* +* Special version of hlist_for_each_entry_from where the end pointer +* can be NULL but also any value < nullval. +*/ +#define hlist_for_each_entry_from_nulls (tpos, pos, member, nullval) \ + for (; ((unsigned long)pos >= nullval) && \ + ({ prefetch(pos->next); 1;}) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next)
This also must be called with the update-side lock held. It is wrappered by sk_for_each_from_nulls(), which in turn is called from udp_v4_mcast_next() and udp_v6_mcast_next(). According to messages earlier in this thread, that is the case.
udp_v4_mcast_next() is called from __udp4_lib_mcast_deliver(), which does hold hslot->lock, so OK. Ditto for the calls from udp_v6_mcast_next().
Interestingly enough, these two functions use sk_head(), which calls __sk_head(), which do not do rcu_dereference(). Which is another reason that this cannot be called from an RCU read-side critical section.
Quoted 19 lines
include/linux/list.h +1
-0+/** * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @tpos: the type * to use as a loop cursor. * @pos: the &struct hlist_node to use as a loop cursor. include/linux/rculist.h +7
-0@@ -383,5 +383,23 @@ static inline void hlist_add_after_rcu(struct hlist_node *prev, ({ tpos =hlist_entry (pos,typeof (*tpos), member);1 ; }); \ pos = rcu_dereference(pos->next)) +/** +* hlist_for_each_entry_rcu_nulls - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* @nullval: the iteration should stop if a pointer is < nullval
include/linux/rculist.h static inline void hlist_add_after_rcu(struct hlist_node *prev, · line 402
Quoted 10 lines
include/linux/rculist.h +10
-0+* +* Special version of hlist_for_each_entry_rcu where the end pointer +* can be NULL but also any value < nullval. +*/ +#define hlist_for_each_entry_rcu_nulls (tpos, pos, head, member, nullval) \ + for (pos = rcu_dereference((head)->first); \ + ((unsigned long)pos >= nullval) && \ + ({ prefetch(pos->next); 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference(pos->next))
Looks good. It should be possible to get rid of the "pos" argument in the upcoming version, as all of the offsets should be even, so the bottom bit would come through unchanged. In fact, it should be possible in this variant as well:
((unsigned long)pos - offsetof(typeof(*tpos)) >= nullval)
Or am I missing something?
Quoted 7 lines
include/linux/rculist.h +0
-0#endif /* __KERNEL__ */ #endif include/net/sock.h +0
-0
include/net/sock.h static __inline__ int sk_del_node_init(struct sock *sk) · line 367
Quoted 7 lines
include/net/sock.h +4
-0@@ -361,6 +361,27 @@ static __inline__ int sk_del_node_init(struct sock *sk)return rc; } +static __inline__int __sk_del_node_init_rcu (struct sock *sk) +{ +if (sk_hashed (sk)) { +hlist_del_init_rcu (&sk->sk_node);
Won't your pseudo-NULL pointers mess up __hlist_del(), which is called from hlist_del_init_rcu(), and which expects real NULL pointers?
Seems like you need _nulls variants of these list primitives as well.
Quoted 21 lines
include/net/sock.h +17
-0+return 1 ; + } +return 0 ; +} + +static __inline__int sk_del_node_init_rcu (struct sock *sk) +{ +int rc =__sk_del_node_init_rcu (sk); + +if (rc) { +/* paranoid for a while -acme */ +WARN_ON (atomic_read (&sk->sk_refcnt) ==1 ); +__sock_put (sk); + } +return rc; +} +static __inline__void __sk_add_node (struct sock *sk,struct hlist_head *list) {hlist_add_head (&sk->sk_node, list);
include/net/sock.h static __inline__ void sk_add_node(struct sock *sk, struct hlist_head *list) · line 398
Quoted 6 lines
include/net/sock.h +3
-0@@ -372,6 +393,17 @@ static __inline__ void sk_add_node(struct sock *sk, struct hlist_head *list)__sk_add_node (sk, list); } +static __inline__void __sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +{ +hlist_add_head_rcu (&sk->sk_node, list);
Same here -- hlist_add_head_rcu() expects real NULL pointers.
Or are these different lists? I believe that these are the same lists, given that the ->sk_node field is used in both cases.
So I believe that you need parallel _nulls primitives for this one also.
net/ipv4/udp.c int udp_lib_get_port(struct sock *sk, unsigned short snum,
Quoted 185 lines
include/net/sock.h +18
-2+} + +static __inline__void sk_add_node_rcu (struct sock *sk,struct hlist_head *list) +{ +sock_hold (sk); +__sk_add_node_rcu (sk, list); +} +static __inline__void __sk_del_bind_node (struct sock *sk) {__hlist_del (&sk->sk_bind_node);@@ -385,9 +417,16 @@ static __inline__ void sk_add_bind_node(struct sock *sk,#define sk_for_each (__sk, node, list) \ hlist_for_each_entry(__sk, node, list, sk_node) +#define sk_for_each_nulls (__sk, node, list, nullval) \ + hlist_for_each_entry_nulls(__sk, node, list, sk_node, nullval) +#define sk_for_each_rcu_nulls (__sk, node, list, nullval) \ + hlist_for_each_entry_rcu_nulls(__sk, node, list, sk_node, nullval)#define sk_for_each_from (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_from(__sk, node, sk_node) +#define sk_for_each_from_nulls (__sk, node, nullval) \ + if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ + hlist_for_each_entry_from_nulls(__sk, node, sk_node, nullval)#define sk_for_each_continue (__sk, node) \ if (__sk && ({ node = &(__sk)->sk_node; 1; })) \ hlist_for_each_entry_continue(__sk, node, sk_node)@@ -587,8 +626,9 @@ struct proto {int *sysctl_rmem;int max_header; -struct kmem_cache *slab; +struct kmem_cache *slab;unsigned int obj_size; +int slab_flags;atomic_t *orphan_count;@@ -597,7 +637,7 @@ struct proto {union {struct inet_hashinfo *hashinfo; -struct hlist_head *udp_hash; +struct udp_table *udp_table;struct raw_hashinfo *raw_hash; } h;include/net/udp.h +12
-13@@ -50,8 +50,15 @@ struct udp_skb_cb { };#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb)) -extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +struct udp_hslot { +struct hlist_head head; +spinlock_t lock; +} __attribute__((aligned (2 *sizeof (long )))); +struct udp_table { +struct udp_hslot hash[UDP_HTABLE_SIZE ]; +}; +extern struct udp_table udp_table; +extern void udp_table_init (struct udp_table *);/* Note: this must match 'valbool' in sock_setsockopt */ @@ -110,15 +117,7 @@ static inline void udp_lib_hash(struct sock *sk)BUG (); } -static inline void udp_lib_unhash (struct sock *sk) -{ -write_lock_bh (&udp_hash_lock); -if (sk_del_node_init (sk)) { -inet_sk (sk)->num =0 ; -sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); - } -write_unlock_bh (&udp_hash_lock); -} +extern void udp_lib_unhash (struct sock *sk);static inline void udp_lib_close (struct sock *sk,long timeout) {@@ -187,7 +186,7 @@ extern struct sock *udp4_lib_lookup(struct net *net, __be32 saddr, __be16 sport,struct udp_seq_afinfo {char *name;sa_family_t family; -struct hlist_head *hashtable; +struct udp_table *udp_table;struct file_operations seq_fops;struct seq_operations seq_ops; };@@ -196,7 +195,7 @@ struct udp_iter_state {struct seq_net_private p;sa_family_t family;int bucket; -struct hlist_head *hashtable; +struct udp_table *udp_table; };#ifdef CONFIG_PROC_FS include/net/udplite.h +1
-1@@ -11,7 +11,7 @@#define UDPLITE_RECV_CSCOV 11/* receiver partial coverage (threshold ) */ extern struct proto udplite_prot; -extern struct hlist_head udplite_hash[UDP_HTABLE_SIZE ]; +extern struct udp_table udplite_table;/* * Checksum computation is all in software, hence simpler getfrag. net/core/sock.c +2
-1@@ -2042,7 +2042,8 @@ int proto_register(struct proto *prot, int alloc_slab)if (alloc_slab) { prot->slab =kmem_cache_create (prot->name, prot->obj_size,0 , -SLAB_HWCACHE_ALIGN ,NULL ); +SLAB_HWCACHE_ALIGN | prot->slab_flags, +NULL );if (prot->slab ==NULL ) { printk(KERN_CRIT "%s: Can't create sock SLAB cache!\n",net/ipv4/udp.c +6
-11@@ -104,12 +104,8 @@#include <net/xfrm.h> #include "udp_impl.h" -/* -* Snmp MIB for the UDP layer -*/ - -struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -DEFINE_RWLOCK (udp_hash_lock); +struct udp_table udp_table; +EXPORT_SYMBOL (udp_table);int sysctl_udp_mem[3 ]__read_mostly ;int sysctl_udp_rmem_min__read_mostly ;@@ -123,7 +119,7 @@ atomic_t udp_memory_allocated;EXPORT_SYMBOL (udp_memory_allocated);static int udp_lib_lport_inuse (struct net *net,__u16 num, -const struct hlist_head udptable[], +const struct udp_hslot *hslot,struct sock *sk,int (*saddr_comp)(const struct sock *sk1,const struct sock *sk2))@@ -131,7 +127,7 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num,struct sock *sk2;struct hlist_node *node; -sk_for_each (sk2, node, &udptable[udp_hashfn (net, num)]) +sk_for_each_nulls (sk2, node, &hslot->head,UDP_HTABLE_SIZE ) if (net_eq(sock_net(sk2), net) && sk2 != sk && sk2->sk_hash == num &&@@ -154,12 +150,11 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,int (*saddr_comp)(const struct sock *sk1, const struct sock *sk2 ) ) { -struct hlist_head *udptable = sk->sk_prot->h.udp_hash; +struct udp_hslot *hslot; +struct udp_table *udptable = sk->sk_prot->h.udp_table;int error =1 ;struct net *net =sock_net (sk); -write_lock_bh (&udp_hash_lock); -if (!snum) {int low, high, remaining;unsigned rand;@@ -171,26 +166,39 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,
inet_get_local_port_range() used to be under the write_lock_bh(), and no longer is. So, how do we now protect against concurrent port range changes?
Quoted 4 lines
net/ipv4/udp.c +0
-1rand =net_random (); snum = first = rand % remaining + low; rand |=1 ; - while (udp_lib_lport_inuse(net, snum, udptable, sk,
net/ipv4/udp.c int udp_lib_get_port(struct sock *sk, unsigned short snum, · line 177
Quoted 10 lines
net/ipv4/udp.c +6
-1- saddr_comp)) { +for (;;) { + hslot = &udptable->hash[udp_hashfn (net, snum)]; +spin_lock_bh (&hslot->lock); +if (!udp_lib_lport_inuse (net, snum, hslot, sk, saddr_comp)) +break ; +spin_unlock_bh (&hslot->lock);do { snum = snum + rand; }while (snum < low || snum > high);
The above -really- confuses me, but not part of this patch. If we are out of range, keep going? Well, I guess that since it is a short, we cannot go very far...
net/ipv4/udp.c int udp_lib_get_port(struct sock *sk, unsigned short snum, · line 179
Quoted 2 lines
net/ipv4/udp.c +0
-0if (snum == first) goto fail;
And I don't understand how we are guaranteed to have scanned all the possible ports upon failure, but happy to leave that to you guys.
Quoted 773 lines
net/ipv4/udp.c +144
-75} - }else if (udp_lib_lport_inuse (net, snum, udptable, sk, saddr_comp)) - goto fail; - + }else { + hslot = &udptable->hash[udp_hashfn (net, snum)]; +spin_lock_bh (&hslot->lock); +if (udp_lib_lport_inuse (net, snum, hslot, sk, saddr_comp)) + goto fail_unlock; + }inet_sk (sk)->num = snum; sk->sk_hash = snum;if (sk_unhashed (sk)) { -sk_add_node (sk, &udptable[udp_hashfn (net, snum)]); +/* +* We need that previous write to sk->sk_hash committed +* before write to sk->next done in following add_node() variant +*/ +smp_wmb (); +sk_add_node_rcu (sk, &hslot->head);sock_prot_inuse_add (sock_net (sk), sk->sk_prot,1 ); } error =0 ; +fail_unlock: +spin_unlock_bh (&hslot->lock); fail: -write_unlock_bh (&udp_hash_lock);return error; }@@ -208,63 +216,96 @@ int udp_v4_get_port(struct sock *sk, unsigned short snum)return udp_lib_get_port (sk, snum, ipv4_rcv_saddr_equal); } +static inline int compute_score (struct sock *sk,struct net *net,__be32 saddr, +unsigned short hnum, +__be16 sport,__be32 daddr,__be16 dport,int dif) +{ +int score =-1 ; + +if (net_eq (sock_net (sk), net) && sk->sk_hash == hnum && + !ipv6_only_sock (sk)) { +struct inet_sock *inet =inet_sk (sk); + + score = (sk->sk_family ==PF_INET ?1 :0 ); +if (inet->rcv_saddr) { +if (inet->rcv_saddr != daddr) +return -1 ; + score +=2 ; + } +if (inet->daddr) { +if (inet->daddr != saddr) +return -1 ; + score +=2 ; + } +if (inet->dport) { +if (inet->dport != sport) +return -1 ; + score +=2 ; + } +if (sk->sk_bound_dev_if) { +if (sk->sk_bound_dev_if != dif) +return -1 ; + score +=2 ; + } + } +return score; +} +/* UDP is nearly always wildcards out the wazoo, it makes no sense to try * harder than this. -DaveM */ static struct sock *__udp4_lib_lookup (struct net *net,__be32 saddr,__be16 sport,__be32 daddr,__be16 dport, -int dif,struct hlist_head udptable[]) +int dif,struct udp_table *udptable) { -struct sock *sk, *result =NULL ; +struct sock *sk, *result;struct hlist_node *node;unsigned short hnum =ntohs (dport); -int badness =-1 ; - -read_lock (&udp_hash_lock); -sk_for_each (sk, node, &udptable[udp_hashfn (net, hnum)]) { -struct inet_sock *inet =inet_sk (sk); - -if (net_eq (sock_net (sk), net) && sk->sk_hash == hnum && - !ipv6_only_sock (sk)) { -int score = (sk->sk_family ==PF_INET ?1 :0 ); -if (inet->rcv_saddr) { -if (inet->rcv_saddr != daddr) -continue ; - score+=2 ; - } -if (inet->daddr) { -if (inet->daddr != saddr) -continue ; - score+=2 ; - } -if (inet->dport) { -if (inet->dport != sport) -continue ; - score+=2 ; - } -if (sk->sk_bound_dev_if) { -if (sk->sk_bound_dev_if != dif) -continue ; - score+=2 ; - } -if (score ==9 ) { - result = sk; -break ; - }else if (score > badness) { - result = sk; - badness = score; - } +unsigned int hash =udp_hashfn (net, hnum); +struct udp_hslot *hslot = &udptable->hash[hash]; +int score, badness; + +rcu_read_lock (); +begin: + result =NULL ; + badness =-1 ; +sk_for_each_rcu_nulls (sk, node, &hslot->head,UDP_HTABLE_SIZE ) { +/* +* lockless reader, and SLAB_DESTROY_BY_RCU items: +* We must check this item was not moved to another chain +*/ +if (udp_hashfn (net, sk->sk_hash) != hash) + goto begin; + score =compute_score (sk, net, saddr, hnum, sport, + daddr, dport, dif); +if (score > badness) { + result = sk; + badness = score; + } + } +/* +* if the 'NULL' pointer we got at the end of this lookup is +* not the expected one, we must restart lookup. +*/ +if ((unsigned long )node != hash) + goto begin; + +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, saddr, hnum, sport, + daddr, dport, dif) < badness)) { +sock_put (result); + goto begin; } } -if (result) -sock_hold (result); -read_unlock (&udp_hash_lock); +rcu_read_unlock ();return result; }static inline struct sock *__udp4_lib_lookup_skb (struct sk_buff *skb,__be16 sport,__be16 dport, -struct hlist_head udptable[]) +struct udp_table *udptable) {struct sock *sk;const struct iphdr *iph =ip_hdr (skb);@@ -280,7 +321,7 @@ static inline struct sock *__udp4_lib_lookup_skb(struct sk_buff *skb,struct sock *udp4_lib_lookup (struct net *net,__be32 saddr,__be16 sport,__be32 daddr,__be16 dport,int dif) { -return __udp4_lib_lookup (net, saddr, sport, daddr, dport, dif, udp_hash); +return __udp4_lib_lookup (net, saddr, sport, daddr, dport, dif, &udp_table); }EXPORT_SYMBOL_GPL (udp4_lib_lookup);@@ -293,7 +334,7 @@ static inline struct sock *udp_v4_mcast_next(struct sock *sk,struct sock *s = sk;unsigned short hnum =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_for_each_from_nulls (s, node,UDP_HTABLE_SIZE ) {struct inet_sock *inet =inet_sk (s); if (s->sk_hash != hnum ||@@ -323,7 +364,7 @@ found:* to find the appropriate port. */ -void __udp4_lib_err (struct sk_buff *skb,u32 info,struct hlist_head udptable[]) +void __udp4_lib_err (struct sk_buff *skb,u32 info,struct udp_table *udptable) {struct inet_sock *inet;struct iphdr *iph = (struct iphdr *)skb->data;@@ -392,7 +433,7 @@ out:void udp_err (struct sk_buff *skb,u32 info) { -__udp4_lib_err (skb, info, udp_hash); +__udp4_lib_err (skb, info, &udp_table); }/* @@ -933,6 +974,21 @@ int udp_disconnect(struct sock *sk, int flags)return 0 ; } +void udp_lib_unhash (struct sock *sk) +{ +struct udp_table *udptable = sk->sk_prot->h.udp_table; +unsigned int hash =udp_hashfn (sock_net (sk), sk->sk_hash); +struct udp_hslot *hslot = &udptable->hash[hash]; + +spin_lock (&hslot->lock); +if (sk_del_node_init_rcu (sk)) { +inet_sk (sk)->num =0 ; +sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); + } +spin_unlock (&hslot->lock); +} +EXPORT_SYMBOL (udp_lib_unhash); +static int __udp_queue_rcv_skb (struct sock *sk,struct sk_buff *skb) {int is_udplite =IS_UDPLITE (sk);@@ -1071,13 +1127,14 @@ drop:static int __udp4_lib_mcast_deliver (struct net *net,struct sk_buff *skb,struct udphdr *uh,__be32 saddr,__be32 daddr, -struct hlist_head udptable[]) +struct udp_table *udptable) {struct sock *sk; +struct udp_hslot *hslot = &udptable->hash[udp_hashfn (net,ntohs (uh->dest))];int dif; -read_lock (&udp_hash_lock); - sk =sk_head (&udptable[udp_hashfn (net,ntohs (uh->dest))]); +spin_lock (&hslot->lock); + sk =sk_head (&hslot->head); dif = skb->dev->ifindex; sk =udp_v4_mcast_next (sk, uh->dest, daddr, uh->source, saddr, dif);if (sk) {@@ -1102,7 +1159,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, }while (sknext); }else kfree_skb (skb); -read_unlock (&udp_hash_lock); +spin_unlock (&hslot->lock);return 0 ; }@@ -1148,7 +1205,7 @@ static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh,* All we need to do is get the socket, and then do a checksum. */ -int __udp4_lib_rcv (struct sk_buff *skb,struct hlist_head udptable[], +int __udp4_lib_rcv (struct sk_buff *skb,struct udp_table *udptable,int proto) {struct sock *sk;@@ -1246,7 +1303,7 @@ drop:int udp_rcv (struct sk_buff *skb) { -return __udp4_lib_rcv (skb, udp_hash,IPPROTO_UDP ); +return __udp4_lib_rcv (skb, &udp_table,IPPROTO_UDP ); }void udp_destroy_sock (struct sock *sk)@@ -1488,7 +1545,8 @@ struct proto udp_prot = { .sysctl_wmem = &sysctl_udp_wmem_min, .sysctl_rmem = &sysctl_udp_rmem_min, .obj_size = sizeof(struct udp_sock), - .h.udp_hash = udp_hash, + .slab_flags = SLAB_DESTROY_BY_RCU, + .h.udp_table = &udp_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt, .compat_getsockopt = compat_udp_getsockopt,@@ -1498,20 +1556,23 @@ struct proto udp_prot = {/* ------------------------------------------------------------------------ */ #ifdef CONFIG_PROC_FS -static struct sock *udp_get_first (struct seq_file *seq) +static struct sock *udp_get_first (struct seq_file *seq,int start) {struct sock *sk;struct udp_iter_state *state = seq->private;struct net *net =seq_file_net (seq); -for (state->bucket =0 ; state->bucket <UDP_HTABLE_SIZE ; ++state->bucket) { +for (state->bucket = start; state->bucket <UDP_HTABLE_SIZE ; ++state->bucket) {struct hlist_node *node; -sk_for_each (sk, node, state->hashtable + state->bucket) { +struct udp_hslot *hslot = &state->udp_table->hash[state->bucket]; +spin_lock_bh (&hslot->lock); +sk_for_each_nulls (sk, node, &hslot->head,UDP_HTABLE_SIZE ) {if (!net_eq (sock_net (sk), net))continue ;if (sk->sk_family == state->family) goto found; } +spin_unlock_bh (&hslot->lock); } sk =NULL ; found:@@ -1525,20 +1586,18 @@ static struct sock *udp_get_next(struct seq_file *seq, struct sock *sk)do { sk =sk_next (sk); -try_again: - ; }while (sk && (!net_eq (sock_net (sk), net) || sk->sk_family != state->family)); -if (!sk && ++state->bucket <UDP_HTABLE_SIZE ) { - sk =sk_head (state->hashtable + state->bucket); - goto try_again; +if (!sk) { +spin_unlock_bh (&state->udp_table->hash[state->bucket].lock); +return udp_get_first (seq, state->bucket +1 ); }return sk; }static struct sock *udp_get_idx (struct seq_file *seq,loff_t pos) { -struct sock *sk =udp_get_first (seq); +struct sock *sk =udp_get_first (seq,0 );if (sk)while (pos && (sk =udp_get_next (seq, sk)) !=NULL )@@ -1547,9 +1606,7 @@ static struct sock *udp_get_idx(struct seq_file *seq, loff_t pos) }static void *udp_seq_start (struct seq_file *seq,loff_t *pos) -__acquires (udp_hash_lock) { -read_lock (&udp_hash_lock);return *pos ?udp_get_idx (seq, *pos-1 ) :SEQ_START_TOKEN ; }@@ -1567,9 +1624,11 @@ static void *udp_seq_next(struct seq_file *seq, void *v, loff_t *pos) }static void udp_seq_stop (struct seq_file *seq,void *v) -__releases (udp_hash_lock) { -read_unlock (&udp_hash_lock); +struct udp_iter_state *state = seq->private; + +if (state->bucket <UDP_HTABLE_SIZE ) +spin_unlock_bh (&state->udp_table->hash[state->bucket].lock); }static int udp_seq_open (struct inode *inode,struct file *file)@@ -1585,7 +1644,7 @@ static int udp_seq_open(struct inode *inode, struct file *file) s = ((struct seq_file *)file->private_data)->private; s->family = afinfo->family; - s->hashtable = afinfo->hashtable; + s->udp_table = afinfo->udp_table;return err; }@@ -1657,7 +1716,7 @@ int udp4_seq_show(struct seq_file *seq, void *v)static struct udp_seq_afinfo udp4_seq_afinfo = { .name ="udp" , .family =AF_INET , - .hashtable = udp_hash, + .udp_table = &udp_table, .seq_fops = { .owner =THIS_MODULE , },@@ -1692,10 +1751,21 @@ void udp4_proc_exit(void) }#endif /* CONFIG_PROC_FS */ +void __init udp_table_init (struct udp_table *table) +{ +int i; + +for (i =0 ; i <UDP_HTABLE_SIZE ; i++) { + table->hash[i].head.first = (struct hlist_node *)i; +spin_lock_init (&table->hash[i].lock); + } +} +void __init udp_init (void ) {unsigned long limit; +udp_table_init (&udp_table);/* Set the pressure threshold up by the same strategy of TCP. It is a * fraction of global memory that is up to 1/2 at 256 MB, decreasing * toward zero with the amount of memory, with a floor of 128 pages. @@ -1712,8 +1782,6 @@ void __init udp_init(void) }EXPORT_SYMBOL (udp_disconnect); -EXPORT_SYMBOL (udp_hash); -EXPORT_SYMBOL (udp_hash_lock);EXPORT_SYMBOL (udp_ioctl);EXPORT_SYMBOL (udp_prot);EXPORT_SYMBOL (udp_sendmsg);net/ipv4/udp_impl.h +2
-2@@ -5,8 +5,8 @@#include <net/protocol.h> #include <net/inet_common.h> -extern int __udp4_lib_rcv (struct sk_buff *,struct hlist_head [],int ); -extern void __udp4_lib_err (struct sk_buff *,u32 ,struct hlist_head []); +extern int __udp4_lib_rcv (struct sk_buff *,struct udp_table *,int ); +extern void __udp4_lib_err (struct sk_buff *,u32 ,struct udp_table *);extern int udp_v4_get_port (struct sock *sk,unsigned short snum);net/ipv4/udplite.c +8
-6@@ -12,16 +12,17 @@*/ #include "udp_impl.h" -struct hlist_head udplite_hash[UDP_HTABLE_SIZE ]; +struct udp_table udplite_table; +EXPORT_SYMBOL (udplite_table);static int udplite_rcv (struct sk_buff *skb) { -return __udp4_lib_rcv (skb, udplite_hash,IPPROTO_UDPLITE ); +return __udp4_lib_rcv (skb, &udplite_table,IPPROTO_UDPLITE ); }static void udplite_err (struct sk_buff *skb,u32 info) { -__udp4_lib_err (skb, info, udplite_hash); +__udp4_lib_err (skb, info, &udplite_table); }static struct net_protocol udplite_protocol = {@@ -50,7 +51,8 @@ struct proto udplite_prot = { .unhash = udp_lib_unhash, .get_port = udp_v4_get_port, .obj_size = sizeof(struct udp_sock), - .h.udp_hash = udplite_hash, + .slab_flags = SLAB_DESTROY_BY_RCU, + .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udp_setsockopt, .compat_getsockopt = compat_udp_getsockopt,@@ -71,7 +73,7 @@ static struct inet_protosw udplite4_protosw = {static struct udp_seq_afinfo udplite4_seq_afinfo = { .name ="udplite" , .family =AF_INET , - .hashtable = udplite_hash, + .udp_table = &udplite_table, .seq_fops = { .owner =THIS_MODULE , },@@ -108,6 +110,7 @@ static inline int udplite4_proc_init(void)void __init udplite4_register (void ) { +udp_table_init (&udplite_table);if (proto_register (&udplite_prot,1 )) goto out_register_err;@@ -126,5 +129,4 @@ out_register_err:printk (KERN_CRIT "%s: Cannot add UDP-Lite protocol.\n" , __func__); } -EXPORT_SYMBOL (udplite_hash);EXPORT_SYMBOL (udplite_prot);net/ipv6/udp.c +91
-54@@ -54,62 +54,97 @@ int udp_v6_get_port(struct sock *sk, unsigned short snum)return udp_lib_get_port (sk, snum, ipv6_rcv_saddr_equal); } +static inline int compute_score (struct sock *sk,struct net *net, +unsigned short hnum, +struct in6_addr *saddr,__be16 sport, +struct in6_addr *daddr,__be16 dport, +int dif) +{ +int score =-1 ; + +if (net_eq (sock_net (sk), net) && sk->sk_hash == hnum && + sk->sk_family ==PF_INET6 ) { +struct ipv6_pinfo *np =inet6_sk (sk); +struct inet_sock *inet =inet_sk (sk); + + score =0 ; +if (inet->dport) { +if (inet->dport != sport) +return -1 ; + score++; + } +if (!ipv6_addr_any (&np->rcv_saddr)) { +if (!ipv6_addr_equal (&np->rcv_saddr, daddr)) +return -1 ; + score++; + } +if (!ipv6_addr_any (&np->daddr)) { +if (!ipv6_addr_equal (&np->daddr, saddr)) +return -1 ; + score++; + } +if (sk->sk_bound_dev_if) { +if (sk->sk_bound_dev_if != dif) +return -1 ; + score++; + } + } +return score; +} +static struct sock *__udp6_lib_lookup (struct net *net,struct in6_addr *saddr,__be16 sport,struct in6_addr *daddr,__be16 dport, -int dif,struct hlist_head udptable[]) +int dif,struct udp_table *udptable) { -struct sock *sk, *result =NULL ; +struct sock *sk, *result;struct hlist_node *node;unsigned short hnum =ntohs (dport); -int badness =-1 ; - -read_lock (&udp_hash_lock); -sk_for_each (sk, node, &udptable[udp_hashfn (net, hnum)]) { -struct inet_sock *inet =inet_sk (sk); - -if (net_eq (sock_net (sk), net) && sk->sk_hash == hnum && - sk->sk_family ==PF_INET6 ) { -struct ipv6_pinfo *np =inet6_sk (sk); -int score =0 ; -if (inet->dport) { -if (inet->dport != sport) -continue ; - score++; - } -if (!ipv6_addr_any (&np->rcv_saddr)) { -if (!ipv6_addr_equal (&np->rcv_saddr, daddr)) -continue ; - score++; - } -if (!ipv6_addr_any (&np->daddr)) { -if (!ipv6_addr_equal (&np->daddr, saddr)) -continue ; - score++; - } -if (sk->sk_bound_dev_if) { -if (sk->sk_bound_dev_if != dif) -continue ; - score++; - } -if (score ==4 ) { - result = sk; -break ; - }else if (score > badness) { - result = sk; - badness = score; - } +unsigned int hash =udp_hashfn (net, hnum); +struct udp_hslot *hslot = &udptable->hash[hash]; +int score, badness; + +rcu_read_lock (); +begin: + result =NULL ; + badness =-1 ; +sk_for_each_rcu_nulls (sk, node, &hslot->head,UDP_HTABLE_SIZE ) { +/* +* lockless reader, and SLAB_DESTROY_BY_RCU items: +* We must check this item was not moved to another chain +*/ +if (udp_hashfn (net, sk->sk_hash) != hash) + goto begin; + score =compute_score (sk, net, hnum, saddr, sport, + daddr, dport, dif); +if (score > badness) { + result = sk; + badness = score; } } -if (result) -sock_hold (result); -read_unlock (&udp_hash_lock); +/* +* if the 'NULL' pointer we got at the end of this lookup is +* not the expected one, we must restart lookup. +*/ +if ((unsigned long )node != hash) + goto begin; + +if (result) { +if (unlikely (!atomic_inc_not_zero (&result->sk_refcnt))) + result =NULL ; +else if (unlikely (compute_score (result, net, hnum, saddr, sport, + daddr, dport, dif) < badness)) { +sock_put (result); + goto begin; + } + } +rcu_read_unlock ();return result; }static struct sock *__udp6_lib_lookup_skb (struct sk_buff *skb,__be16 sport,__be16 dport, -struct hlist_head udptable[]) +struct udp_table *udptable) {struct sock *sk;struct ipv6hdr *iph =ipv6_hdr (skb);@@ -239,7 +274,7 @@ csum_copy_err:void __udp6_lib_err (struct sk_buff *skb,struct inet6_skb_parm *opt,int type,int code,int offset,__be32 info, -struct hlist_head udptable[] ) +struct udp_table *udptable) {struct ipv6_pinfo *np;struct ipv6hdr *hdr = (struct ipv6hdr *)skb->data;@@ -275,7 +310,7 @@ static __inline__ void udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, int type, int code, int offset, __be32 info ) { -__udp6_lib_err (skb ,opt ,type ,code ,offset ,info ,udp_hash ); + __udp6_lib_err(skb, opt, type, code, offset, info, &udp_table); }int udpv6_queue_rcv_skb (struct sock * sk,struct sk_buff *skb)@@ -337,7 +372,7 @@ static struct sock *udp_v6_mcast_next(struct sock *sk,struct sock *s = sk;unsigned short num =ntohs (loc_port); -sk_for_each_from (s, node) { +sk_for_each_from_nulls (s, node,UDP_HTABLE_SIZE ) {struct inet_sock *inet =inet_sk (s);if (sock_net (s) !=sock_net (sk))@@ -374,14 +409,15 @@ static struct sock *udp_v6_mcast_next(struct sock *sk,*/ static int __udp6_lib_mcast_deliver (struct net *net,struct sk_buff *skb,struct in6_addr *saddr,struct in6_addr *daddr, -struct hlist_head udptable[]) +struct udp_table *udptable) {struct sock *sk, *sk2;const struct udphdr *uh =udp_hdr (skb); +struct udp_hslot *hslot = &udptable->hash[udp_hashfn (net,ntohs (uh->dest))];int dif; -read_lock (&udp_hash_lock); - sk =sk_head (&udptable[udp_hashfn (net,ntohs (uh->dest))]); +spin_lock (&hslot->lock); + sk =sk_head (&hslot->head); dif =inet6_iif (skb); sk =udp_v6_mcast_next (sk, uh->dest, daddr, uh->source, saddr, dif);if (!sk) {@@ -409,7 +445,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,sk_add_backlog (sk, skb);bh_unlock_sock (sk); out: -read_unlock (&udp_hash_lock); +spin_unlock (&hslot->lock);return 0 ; }@@ -447,7 +483,7 @@ static inline int udp6_csum_init(struct sk_buff *skb, struct udphdr *uh,return 0 ; } -int __udp6_lib_rcv (struct sk_buff *skb,struct hlist_head udptable[], +int __udp6_lib_rcv (struct sk_buff *skb,struct udp_table *udptable,int proto) {struct sock *sk;@@ -544,7 +580,7 @@ discard:static __inline__int udpv6_rcv (struct sk_buff *skb) { -return __udp6_lib_rcv (skb, udp_hash,IPPROTO_UDP ); +return __udp6_lib_rcv (skb, &udp_table,IPPROTO_UDP ); }/* @@ -1008,7 +1044,7 @@ int udp6_seq_show(struct seq_file *seq, void *v)static struct udp_seq_afinfo udp6_seq_afinfo = { .name ="udp6" , .family =AF_INET6 , - .hashtable = udp_hash, + .udp_table = &udp_table, .seq_fops = { .owner =THIS_MODULE , },@@ -1050,7 +1086,8 @@ struct proto udpv6_prot = { .sysctl_wmem = &sysctl_udp_wmem_min, .sysctl_rmem = &sysctl_udp_rmem_min, .obj_size = sizeof(struct udp6_sock), - .h.udp_hash = udp_hash, + .slab_flags = SLAB_DESTROY_BY_RCU, + .h.udp_table = &udp_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt, .compat_getsockopt = compat_udpv6_getsockopt,net/ipv6/udp_impl.h +2
-2@@ -7,9 +7,9 @@#include <net/inet_common.h> #include <net/transp_v6.h> -extern int __udp6_lib_rcv (struct sk_buff *,struct hlist_head [],int ); +extern int __udp6_lib_rcv (struct sk_buff *,struct udp_table *,int );extern void __udp6_lib_err (struct sk_buff *,struct inet6_skb_parm *, -int ,int ,int ,__be32 ,struct hlist_head []); +int ,int ,int ,__be32 ,struct udp_table *);extern int udp_v6_get_port (struct sock *sk,unsigned short snum);net/ipv6/udplite.c +5
-4@@ -15,14 +15,14 @@static int udplitev6_rcv (struct sk_buff *skb) { -return __udp6_lib_rcv (skb, udplite_hash,IPPROTO_UDPLITE ); +return __udp6_lib_rcv (skb, &udplite_table,IPPROTO_UDPLITE ); }static void udplitev6_err (struct sk_buff *skb,struct inet6_skb_parm *opt,int type,int code,int offset,__be32 info) { -__udp6_lib_err (skb, opt, type, code, offset, info, udplite_hash); +__udp6_lib_err (skb, opt, type, code, offset, info, &udplite_table); }static struct inet6_protocol udplitev6_protocol = {@@ -49,7 +49,8 @@ struct proto udplitev6_prot = { .unhash = udp_lib_unhash, .get_port = udp_v6_get_port, .obj_size = sizeof(struct udp6_sock), - .h.udp_hash = udplite_hash, + .slab_flags = SLAB_DESTROY_BY_RCU, + .h.udp_table = &udplite_table, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_udpv6_setsockopt, .compat_getsockopt = compat_udpv6_getsockopt,@@ -95,7 +96,7 @@ void udplitev6_exit(void)static struct udp_seq_afinfo udplite6_seq_afinfo = { .name ="udplite6" , .family =AF_INET6 , - .hashtable = udplite_hash, + .udp_table = &udplite_table, .seq_fops = { .owner =THIS_MODULE , },
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 17 lines · Paul E. McKenney
Review
Quoted 11 lines · Eric Dumazet
(quoted diff) +6
-2- while (udp_lib_lport_inuse(net, snum, udptable, sk, - saddr_comp)) { + for (;;) { + hslot = &udptable->hash[udp_hashfn(net, snum)]; + spin_lock_bh(&hslot->lock); + if (!udp_lib_lport_inuse(net, snum, hslot, sk, saddr_comp)) + break; + spin_unlock_bh(&hslot->lock); do { snum = snum + rand; } while (snum < low || snum > high);The above -really- confuses me, but not part of this patch. If we are out of range, keep going? Well, I guess that since it is a short, we cannot go very far...
Quoted 5 lines
Review
Quoted 2 lines
(quoted diff) +0
-0if (snum == first) goto fail;And I don't understand how we are guaranteed to have scanned all the possible ports upon failure, but happy to leave that to you guys.
Well, we have 65536(=2^16) possible port values, and while 'rand' is random, it has the interesting property/bias of being odd.
We know (thanks modular arithmetic / congruence relation) we will hit all 65356 values exactly once, after exactly 65536 iterations.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 21 lines · Eric Dumazet
Quoted 15 lines · Paul E. McKenney
Review
Quoted 11 lines · Eric Dumazet
(quoted diff) +6
-2- while (udp_lib_lport_inuse(net, snum, udptable, sk, - saddr_comp)) { + for (;;) { + hslot = &udptable->hash[udp_hashfn(net, snum)]; + spin_lock_bh(&hslot->lock); + if (!udp_lib_lport_inuse(net, snum, hslot, sk, saddr_comp)) + break; + spin_unlock_bh(&hslot->lock); do { snum = snum + rand; } while (snum < low || snum > high);The above -really- confuses me, but not part of this patch. If we are out of range, keep going? Well, I guess that since it is a short, we cannot go very far...
Quoted 4 lines
Review
Quoted 2 lines
(quoted diff) +0
-0if (snum == first) goto fail;And I don't understand how we are guaranteed to have scanned all the possible ports upon failure, but happy to leave that to you guys.
Quoted 6 lines
Well, we have 65536(=2^16) possible port values, and while 'rand' is random,
it has the interesting property/bias of being odd.We know (thanks modular arithmetic / congruence relation) we will hit all 65356 values exactly once, after exactly 65536 iterations.
Ah, got it! Thank you for the explanation!
I was fixating on the low..high interval. ;-)
Thanx, Paul
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Paul E. McKenneyQuoted 26 lines · Paul E. McKenney
Quoted 16 lines · Eric Dumazet
Quoted 11 lines · Corey Minyard
Quoted 9 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time
the hash is calculated and the next field is accessed, and the socket
has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :Quoted 8 lines
1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader will redo its scan2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.
Quoted 3 lines
3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.
yes, but 'new position' is 'before any not yet examined objects', since we insert objects only at chain head.
Quoted 6 lines
You might well cover this (will examine your code in detail on my plane flight starting about 20 hours from now), but thought I should point it out. ;-)
Yes, I'll double check too, this seems tricky :)
About SLAB_DESTROY_BY_RCU effect, we now have two different kmem_cache for "UDP-Lite" and "UDP".
This is expected, but we could avoid that and alias these caches, since these objects have the same *type* . (The fields used for the RCU lookups, deletes and inserts are the same)
Maybe a hack in net/ipv4/udplite.c before calling proto_register(), to copy the kmem_cache from UDP.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 37 lines · Eric Dumazet
Quoted 36 lines · Paul E. McKenney
Quoted 35 lines · Eric Dumazet
Quoted 14 lines · Corey Minyard
Quoted 13 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d
(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
Quoted 6 lines
Quoted 3 lines
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()
macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader
will redo its scan2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.
Quoted 6 lines
Quoted 3 lines
3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.yes, but 'new position' is 'before any not yet examined objects', since we insert objects only at chain head.
OK. However, this reasoning assumes that a socket with a given udp_hashfn() value will appear on one and only one list. There are no side lists for sockets in other states? (listen, &c)
Quoted 5 lines
Quoted 3 lines
You might well cover this (will examine your code in detail on my plane flight starting about 20 hours from now), but thought I should point it out. ;-)
Yes, I'll double check too, this seems tricky :)
;-)
Quoted 10 lines
About
SLAB_DESTROY_BY_RCUeffect, we now have two differentkmem_cachefor "UDP-Lite"
and "UDP".This is expected, but we could avoid that and alias these caches, since these objects have the same *type* . (The fields used for the RCU lookups, deletes and inserts are the same)
Maybe a hack in
net/ipv4/udplite.cbefore callingproto_register(), to copy thekmem_cachefrom UDP.
As long as this preserves the aforementioned assumption that a socket with a given hash can appear on one and only one list. ;-)
Thanx, Paul
David Miller
maintainerRe: [PATCH 2/2] udp: RCU handling for Unicast packets.
From: "Paul E. McKenney" <[email protected]>
Date: Wed, 29 Oct 2008 11:11:14 -0700
Quoted 3 lines
OK. However, this reasoning assumes that a socket with a given
udp_hashfn()value will appear on one and only one list. There are no side lists for sockets in other states? (listen, &c)
Nope, with UDP things are very simple, just one hash table.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 8 lines · David Miller
From: "Paul E. McKenney" <[email protected]>
Date: Wed, 29 Oct 2008 11:11:14 -0700Quoted 3 lines
OK. However, this reasoning assumes that a socket with a given
udp_hashfn()value will appear on one and only one list. There are no side lists for sockets in other states? (listen, &c)Nope, with UDP things are very simple, just one hash table.
Cool! That does make things easier. ;-)
Thanx, Paul
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Paul E. McKenneyQuoted 49 lines · Paul E. McKenney
Quoted 24 lines · Eric Dumazet
Quoted 23 lines · Paul E. McKenney
Quoted 22 lines · Eric Dumazet
Quoted 9 lines · Corey Minyard
Quoted 7 lines · Eric Dumazet
Corey Minyard found a race added in commit
271b72c7fa82c2c7a795bc16896149933110672d(udp: RCU handling for Unicast packets.)"If the socket is moved from one list to another list in-between the time the hash is calculated and the next field is accessed, and the socket has moved to the end of the new list, the traversal will not complete properly on the list it should have, since the socket will be on the end of the new list and there's not a way to tell it's on a new list and restart the list traversal. I think that this can be solved by pre-fetching the "next" field (with proper barriers) before checking the hash."
This patch corrects this problem, introducing a new
sk_for_each_rcu_safenext()macro.You also need the appropriate
smp_wmb()inudp_lib_get_port()aftersk_hashis set, I think, so the next field is guaranteed to be changed after the hash value is changed.Not sure about this one Corey.
If a reader catches previous value of
item->sk_hash, two cases are to be taken into :1) its
udp_hashfn(net, sk->sk_hash)is != hash -> goto begin : Reader will redo its scan2) its
udp_hashfn(net, sk->sk_hash)is == hash
-> next pointer is good enough : it points to next item in same hash
chain.
No need to rescan the chain at this point.
Yes we could miss the fact that a new port was bound and this UDP
message could be lost.Quoted 5 lines
Quoted 3 lines
3) its
udp_hashfn(net, sk-sk_hash)is == hash, but only because it was removed, freed, reallocated, and then readded with the same hash value, possibly carrying the reader to a new position in the same list.yes, but 'new position' is 'before any not yet examined objects', since we insert objects only at chain head.
OK. However, this reasoning assumes that a socket with a given
udp_hashfn()value will appear on one and only one list. There are no side lists for sockets in other states? (listen, &c)Quoted 4 lines
Quoted 3 lines
You might well cover this (will examine your code in detail on my plane flight starting about 20 hours from now), but thought I should point it out. ;-)
Yes, I'll double check too, this seems tricky :)
;-)
Quoted 7 lines
About
SLAB_DESTROY_BY_RCUeffect, we now have two differentkmem_cachefor "UDP-Lite" and "UDP".This is expected, but we could avoid that and alias these caches, since these objects have the same *type* . (The fields used for the RCU lookups, deletes and inserts are the same)
Quoted 6 lines
Quoted 2 lines
Maybe a hack in
net/ipv4/udplite.cbefore callingproto_register(), to copy thekmem_cachefrom UDP.As long as this preserves the aforementioned assumption that a socket with a given hash can appear on one and only one list. ;-)
Ouch, thanks Paul, that is indeed the point, well sort of.
If a UDP socket is freed, and re-allocated as an UDP-Lite socket, inserted on the udplite_table, then we would have a problem with current implementation.
A reader could be directed to the chain of the other hash table, without noticing it should restart its lookup...
Not worth adding a check to detect such a scenario, we can live with two different kmem_cache after all, they are not that expensive.
David Miller
maintainerRe: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Eric DumazetFrom: Eric Dumazet <[email protected]>
Date: Wed, 29 Oct 2008 15:36:34 +0100
Quoted 1 line
[PATCH] udp: introduce
sk_for_each_rcu_safenext()
Also applied, thanks Eric.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
In reply to Eric DumazetQuoted 7 lines · Eric Dumazet
include/linux/rculist.h +7
-0+/** +* hlist_for_each_entry_rcu_safenext - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* @next: the &struct hlist_node to use as a next cursor
include/linux/rculist.h static inline void hlist_add_after_rcu(struct hlist_node *prev, · line 402
Quoted 10 lines
include/linux/rculist.h +10
-0+* +* Special version of hlist_for_each_entry_rcu that make sure +* each next pointer is fetched before each iteration. +*/ +#define hlist_for_each_entry_rcu_safenext (tpos, pos, head, member, next) \ + for (pos = rcu_dereference((head)->first); \ + pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference(next)) +
why _safenext and not _safe like hlist_for_each_entry_safe() which also keeps a next pointer?
Also note the difference in argument order between these two.
Re: [PATCH 2/2] udp: RCU handling for Unicast packets.
Quoted 22 lines · Peter Zijlstra
Quoted 7 lines · Eric Dumazet
include/linux/rculist.h +7
-0+/** +* hlist_for_each_entry_rcu_safenext - iterate over rcu list of given type +* @tpos: the type * to use as a loop cursor. +* @pos: the &struct hlist_node to use as a loop cursor. +* @head: the head for your list. +* @member: the name of the hlist_node within the struct. +* @next: the &struct hlist_node to use as a next cursor include/linux/rculist.h static inline void hlist_add_after_rcu(struct hlist_node *prev, · line 402
Quoted 10 lines
include/linux/rculist.h +10
-0+* +* Special version of hlist_for_each_entry_rcu that make sure +* each next pointer is fetched before each iteration. +*/ +#define hlist_for_each_entry_rcu_safenext (tpos, pos, head, member, next) \ + for (pos = rcu_dereference((head)->first); \ + pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \ + pos = rcu_dereference(next)) +why
_safenextand not_safelikehlist_for_each_entry_safe()which also keeps a next pointer?
Quoted 2 lines
Also note the difference in argument order between these two.
Yes, this one is going to vanish soon.
cover udp: Convert the UDP hash lock to RCU
In reply to Eric DumazetUDP sockets are hashed in a 128 slots hash table.
This hash table is protected by *one* rwlock.
This rwlock is readlocked each time an incoming UDP message is handled.
This rwlock is writelocked each time a socket must be inserted in hash table (bind time), or deleted from this table (unbind time)
This is not scalable on SMP machines :
1) Even in read mode, lock() and unlock() are atomic operations and must dirty a contended cache line, shared by all cpus.
2) A writer might be starved if many readers are 'in flight'. This can happen on a machine with some NIC receiving many UDP messages. User process can be delayed a long time at socket creation/dismantle time.
What Corey and I propose is to use RCU to protect this hash table.
Goals are :
1) Optimizing handling of incoming Unicast UDP frames, so that no memory writes should happen in the fast path. Using an array of rwlocks (one per slot for example is not an option in this regard)
Note: Multicasts and broadcasts still will need to take a lock, because doing a full lockless lookup in this case is difficult.
2) No expensive operations in the socket bind/unhash phases :
- No expensive synchronize_rcu() calls.
- No added rcu_head in socket structure, increasing memory needs, but more important, forcing us to use call_rcu() calls, that have the bad property of making sockets structure cold. (rcu grace period between socket freeing and its potential reuse make this socket being cold in CPU cache).
David did a previous patch using call_rcu() and noticed a 20% impact on TCP connection rates.
Quoting Cristopher Lameter :
"Right. That results in cacheline cooldown. You'd want to recycle
the object as they are cache hot on a per cpu basis. That is screwed up by the delayed regular rcu processing. We have seen multiple regressions due to cacheline cooldown.
The only choice in cacheline hot sensitive areas is to deal with the complexity that comes with SLAB_DESTROY_BY_RCU or give up on RCU."
- Because udp sockets are allocated from dedicated kmem_cache, use of SLAB_DESTROY_BY_RCU can help here.
Theory of operation :
---------------------
As the lookup is lockfree (using rcu_read_lock()/rcu_read_unlock()), special attention must be taken by readers and writers.
Use of SLAB_DESTROY_BY_RCU is tricky too, because a socket can be freed, reused, inserted in a different chain or in worst case in the same chain while readers could do lookups in the same time.
In order to avoid loops, a reader must check each socket found in a chain really belongs to the chain the reader was traversing. If it finds a mismatch, lookup must start again at the begining. This *restart* loop is the reason we had to use rdlock for the multicast case, because we dont want to send same message several times to the same socket.
We use RCU only for fast path. Thus, /proc/net/udp still take rdlocks.
Work splited on two patches.
[PATCH 1/2] udp: introduce struct udp_table and multiple rwlocks
Introduction 'struct udp_table and struct udp_hslot', with one rwlock per chain, instead of a global one. Some cleanups were done to ease review of next patch.
[PATCH 2/2] udp: RCU handling for Unicast packets.
Tests done on a dual quad core machine (8 cpus) with IPV4 only were pretty good, since some microbenches ran ten times faster.
Many thanks to all contributors (David Miller, Christoph Lameter, Peter Zijlstra, Stephen Hemminger, Paul E. McKenney, Evgeniy Polyakov) for their review/comments on initial Corey work.
Re: [PATCH 0/2] udp: Convert the UDP hash lock to RCU
Quoted 62 lines · On Tue, 28 Oct 2008 21:37:15 +0100 Eric Dumazet <[email protected]> wrote:
UDP sockets are hashed in a 128 slots hash table.
This hash table is protected by *one* rwlock.
This rwlock is readlocked each time an incoming UDP message is handled.
This rwlock is writelocked each time a socket must be inserted in hash table (bind time), or deleted from this table (unbind time)
This is not scalable on SMP machines :
1) Even in read mode,
lock()andunlock()are atomic operations and must dirty a contended cache line, shared by all cpus.2) A writer might be starved if many readers are 'in flight'. This can happen on a machine with some NIC receiving many UDP messages. User process can be delayed a long time at socket creation/dismantle time.
What Corey and I propose is to use RCU to protect this hash table.
Goals are :
1) Optimizing handling of incoming Unicast UDP frames, so that no memory writes should happen in the fast path. Using an array of rwlocks (one per slot for example is not an option in this regard)
Note: Multicasts and broadcasts still will need to take a lock, because doing a full lockless lookup in this case is difficult.
2) No expensive operations in the socket bind/unhash phases :
- No expensivesynchronize_rcu()calls.- No added
rcu_headin socket structure, increasing memory needs, but more important, forcing us to usecall_rcu()calls, that have the bad property of making sockets structure cold. (rcu grace period between socket freeing and its potential reuse make this socket being cold in CPU cache).
David did a previous patch usingcall_rcu()and noticed a 20% impact on TCP connection rates.Quoting Cristopher Lameter :
"Right. That results in cacheline cooldown. You'd want to recycle
the object as they are cache hot on a per cpu basis. That is screwed up by the delayed regular rcu processing. We have seen multiple regressions due to cacheline cooldown.
The only choice in cacheline hot sensitive areas is to deal with the complexity that comes withSLAB_DESTROY_BY_RCUor give up on RCU."- Because udp sockets are allocated from dedicated
kmem_cache, use ofSLAB_DESTROY_BY_RCUcan help here.Theory of operation :
---------------------As the lookup is lockfree (using rcu_read_lock()/rcu_read_unlock()), special attention must be taken by readers and writers.
Use of
SLAB_DESTROY_BY_RCUis tricky too, because a socket can be freed, reused, inserted in a different chain or in worst case in the same chain while readers could do lookups in the same time.
Quoted 7 lines
In order to avoid loops, a reader must check each socket found in a chain really belongs to the chain the reader was traversing. If it finds a mismatch, lookup must start again at the begining. This *restart* loop is the reason we had to use rdlock for the multicast case, because we dont want to send same message several times to the same socket.
We use RCU only for fast path. Thus,
/proc/net/udpstill take rdlocks.
We should just make it a spin_lock later and speed up udp socket creation.
Re: [PATCH 0/2] udp: Convert the UDP hash lock to RCU
Quoted 5 lines · Stephen Hemminger
Quoted 1 line · On Tue, 28 Oct 2008 21:37:15 +0100 Eric Dumazet <[email protected]> wrote:
We use RCU only for fast path. Thus,
/proc/net/udpstill take rdlocks.We should just make it a
spin_locklater and speed up udp socket creation.
Indeed
Quoted 3 lines · Eric Dumazet
(2 weeks ago, Corey mentioned a 10x increase on UDP throughput on a 16-way machine, that sounds promising)
Just to be clear, that was 10x with preempt RT, which converts rwlocks into PI mutexes. So 16 processors going for the same lock is pretty ugly.
Under heavy loads there is also a writer starvation problem, I believe in non-RT. You can't actually create or destroy a UDP socket when the load is high because there's always a reader holding the lock. RCU also solves that problem.
-corey
David Miller
maintainerFrom: Eric Dumazet <[email protected]>
Date: Tue, 07 Oct 2008 07:24:45 +0200
Quoted 3 lines
Most UDP sockets are setup for long periods (RTP trafic), or if an application really wants to {open/send or receive one UDP frame/close} many sockets, it already hits RCU handling of its file structures and should not be slowed down that much.
As stated, I added RCU destruction generically for socket objects, and it showed up clearly.
So "not be slowed down that much" has been disproven, at least to me, already :-)
Quoted 7 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Tue, 07 Oct 2008 07:24:45 +0200Quoted 3 lines
Most UDP sockets are setup for long periods (RTP trafic), or if an application really wants to {open/send or receive one UDP frame/close} many sockets, it already hits RCU handling of its file structures and should not be slowed down that much.
Quoted 7 lines
As stated, I added RCU destruction generically for socket objects, and it showed up clearly.
So "not be slowed down that much" has been disproven, at least to me, already :-)
RCU in hash table is ok for managing read mostly data, since only during the read access you avoid to dirty a rwlock...
If we have a workload that insert/delete sockets as hell but receive few frames (that hit the hash table in a read only way), then you defeat the purpose of RCU, and pay the price of throwing away (in rcu queue) hot data that will become cold before reuse...
BTW is there any chance your results were obtained before October 2005 ?
At that time, RCU was able to queue an unlimited number of events. a single loop doing close(open("/dev/null",0)) could exhaust RAM...
Refs: commit 5ee832dbc6770135ec8d63296af0a4374557bb79 and many others...
Anyway we can probably code something without call_rcu() cache blower for UDP, if time permits :)
David Miller
maintainerFrom: Eric Dumazet <[email protected]>
Date: Wed, 08 Oct 2008 10:35:07 +0200
Quoted 1 line
BTW is there any chance your results were obtained before October 2005 ?
No, the timestamp on the saved patch file I have is June 16, 2008 :-)
Quoted 7 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Mon, 06 Oct 2008 23:22:31 +0200Quoted 3 lines
Me wondering what impact this
synchronize_rcu()can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?
Quoted 5 lines
The cost is enormous, we really can't use it here.
I have a patch that did top-level socket destruction using RCU, and that didn't use
synchronize_rcu(), and that killed connection rates by up to %20.
Did you ever figure out why you lost those 20% ?
Quoted 2 lines
I can only imagine what the cost would be if I had to add such a call in there.
Yeah, sync_rcu() is rediculously expensive, at best 3 jiffies IIRC.
Quoted 14 lines · Peter Zijlstra
Quoted 7 lines · David Miller
From: Eric Dumazet <[email protected]>
Date: Mon, 06 Oct 2008 23:22:31 +0200Quoted 3 lines
Me wondering what impact this
synchronize_rcu()can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?Quoted 5 lines
The cost is enormous, we really can't use it here.
I have a patch that did top-level socket destruction using RCU, and that didn't use
synchronize_rcu(), and that killed connection rates by up to %20.
Quoted 6 lines
Did you ever figure out why you lost those 20% ?
Quoted 2 lines
I can only imagine what the cost would be if I had to add such a call in there.
Yeah,
sync_rcu()is rediculously expensive, at best 3 jiffies IIRC.
I could make it -much- faster, but at the expense of -serious- CPU overhead. Still, might be useful during boot time (when the system can't do anything useful anyway) to accelerate getting data structures initialized.
Thanx, Paul
David Miller
maintainerFrom: Peter Zijlstra <[email protected]>
Date: Tue, 07 Oct 2008 10:31:30 +0200
Quoted 8 lines · David Miller
Quoted 7 lines
From: Eric Dumazet <[email protected]>
Date: Mon, 06 Oct 2008 23:22:31 +0200Quoted 3 lines
Me wondering what impact this
synchronize_rcu()can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?
Quoted 7 lines
Quoted 5 lines
The cost is enormous, we really can't use it here.
I have a patch that did top-level socket destruction using RCU, and that didn't use
synchronize_rcu(), and that killed connection rates by up to %20.Did you ever figure out why you lost those 20% ?
Probably the RCU delay on a 128 cpu machine :-)
Also I bet batching the socket destruction eliminates all of the cached local state we have in the cpu at the actual socket destruction time.
Quoted 42 lines · Eric Dumazet
Quoted 27 lines · Corey Minyard
Change the UDP hash lock from an rwlock to RCU.
Signed-off-by Corey Minyard <[email protected]>--- include/net/udp.h | 9 +++++---- net/ipv4/udp.c | 47 +++++++++++++++++++++++++++-------------------- net/ipv6/udp.c | 17 +++++++++-------- 3 files changed, 41 insertions(+), 32 deletions(-)include/net/udp.h +1
-1@@ -51,7 +51,7 @@ struct udp_skb_cb {#define UDP_SKB_CB (__skb) ((struct udp_skb_cb *)((__skb)->cb))extern struct hlist_head udp_hash[UDP_HTABLE_SIZE ]; -extern rwlock_t udp_hash_lock; +extern spinlock_t udp_hash_wlock;/* Note: this must match 'valbool' in sock_setsockopt */ @@ -112,12 +112,13 @@ static inline void udp_lib_hash(struct sock *sk)static inline void udp_lib_unhash (struct sock *sk) {include/net/udp.h static inline void udp_lib_hash(struct sock *sk) · line 121
Quoted 10 lines
include/net/udp.h +4
-3-write_lock_bh (&udp_hash_lock); -if (sk_del_node_init (sk)) { +spin_lock_bh (&udp_hash_wlock); +if (sk_del_node_init_rcu (sk)) {inet_sk (sk)->num =0 ;sock_prot_inuse_add (sock_net (sk), sk->sk_prot,-1 ); } -write_unlock_bh (&udp_hash_lock); +spin_unlock_bh (&udp_hash_wlock); +synchronize_rcu ();UDP central rwlock can hurt performance, because of cache line ping pong, so your patch really makes sense.
Quoted 3 lines
Me wondering what impact this
synchronize_rcu()can have on mono-threaded VOIP applications using lot of UDP sockets. What is the maximum delay of this function ?
It delays until all currently executing RCU read-side sections have executed (new ones don't count, just currently executing ones). I'm not sure what this delay is, but I would expect it to be fairly small. This function is only called when a socket is closed, too, so it's not a high-runner. Paul would certainly know better than me.
Quoted 2 lines
For "struct file" freeing, we chose
call_rcu()instead ofsynchronize_rcu()
I'd prefer that, too, but that would mean adding another member to the socket structure.
Quoted 5 lines
Maybe we could add a generic rcu head to struct sock, and use
call_rcu()insk_prot_free()for sockets needing RCU (udp after your patch is applied, maybe tcp on future patches, while I believe previous work on the subject concluded RCU was not giving good results for short lived http sessions) ?
RCU probably wouldn't be a good choice for short-lived http sessions, since you will only get a couple of messages that would matter. I'm not against adding an item to struct sock, but this is not a common thing and struct sock was already big and ugly.
Quoted 3 lines
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()
for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...
That's an interesting thought; I didn't know that capability was there. I can look at that. With this, the short-lived TCP sessions might not matter, though that's a different issue.
-corey
Quoted 3 lines · Eric Dumazet
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...
Be careful!, SLAB_DESTROY_BY_RCU just means the slab page gets RCU-freed, this means that slab object pointers stay pointing to valid memory, but it does _NOT_ mean those slab objects themselves remain valid.
The slab allocator is free to re-use those objects at any time - irrespective of the rcu-grace period. Therefore you will have to be able to validate that the object you point to is indeed the object you expect, otherwise strange and wonderful things will happen.
Quoted 10 lines · Peter Zijlstra
Quoted 2 lines · Eric Dumazet
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()
for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...Be careful!,
SLAB_DESTROY_BY_RCUjust means the slab page gets RCU-freed, this means that slab object pointers stay pointing to valid memory, but it does _NOT_ mean those slab objects themselves remain valid.
Quoted 5 lines
The slab allocator is free to re-use those objects at any time - irrespective of the rcu-grace period. Therefore you will have to be able to validate that the object you point to is indeed the object you expect, otherwise strange and wonderful things will happen.
Thanks for this clarification. I guess we really need a rcu head then :)
Quoted 9 lines · Eric Dumazet
Quoted 9 lines
Quoted 3 lines
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...Be careful!,
SLAB_DESTROY_BY_RCUjust means the slab page gets RCU-freed, this means that slab object pointers stay pointing to valid memory, but it does _NOT_ mean those slab objects themselves remain valid.
Quoted 6 lines
Quoted 5 lines
The slab allocator is free to re-use those objects at any time - irrespective of the rcu-grace period. Therefore you will have to be able to validate that the object you point to is indeed the object you expect, otherwise strange and wonderful things will happen.
Thanks for this clarification. I guess we really need a rcu head then :)
No you just need to make sure that the object you located is still active (f.e. refcount > 0) and that it is really a match (hash pointers may be updated asynchronously and therefore point to the object that has been reused for something else).
Generally it is advisable to use SLAB_DESTROY_BY_RCU because it preserves the cache hot advantages of the objects. Regular RCU freeing will let the object expire for a tick or so which will result in the cacheline cooling down.
Quoted 15 lines · Christoph Lameter
Quoted 14 lines · Eric Dumazet
Quoted 14 lines
Quoted 3 lines
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...Be careful!,
SLAB_DESTROY_BY_RCUjust means the slab page gets RCU-freed, this means that slab object pointers stay pointing to valid memory, but it does _NOT_ mean those slab objects themselves remain valid.The slab allocator is free to re-use those objects at any time - irrespective of the rcu-grace period. Therefore you will have to be able to validate that the object you point to is indeed the object you expect, otherwise strange and wonderful things will happen.
Quoted 6 lines
Quoted 1 line
Thanks for this clarification. I guess we really need a rcu head then :)
No you just need to make sure that the object you located is still active (f.e. refcount > 0) and that it is really a match (hash pointers may be updated asynchronously and therefore point to the object that has been reused for something else).
In some cases, you might be able to not care, but yes, most of the time, you will need to validate the object.
Quoted 3 lines
Generally it is advisable to use
SLAB_DESTROY_BY_RCUbecause it preserves the cache hot advantages of the objects. Regular RCU freeing will let the object expire for a tick or so which will result in the cacheline cooling down.
And SLAB_DESTROY_BY_RCU guarantees that the type of the object will remain the same during any given RCU read-side critical section.
Thanx, Paul
Quoted 16 lines · Christoph Lameter
Quoted 8 lines · Eric Dumazet
Quoted 8 lines
Quoted 3 lines
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...Be careful!,
SLAB_DESTROY_BY_RCUjust means the slab page gets RCU-freed, this means that slab object pointers stay pointing to valid memory, but it does _NOT_ mean those slab objects themselves remain valid.Quoted 6 lines
Quoted 5 lines
The slab allocator is free to re-use those objects at any time - irrespective of the rcu-grace period. Therefore you will have to be able to validate that the object you point to is indeed the object you expect, otherwise strange and wonderful things will happen.
Thanks for this clarification. I guess we really need a rcu head then :)
Quoted 8 lines
No you just need to make sure that the object you located is still active (f.e. refcount > 0) and that it is really a match (hash pointers may be updated asynchronously and therefore point to the object that has been reused for something else).
Generally it is advisable to use
SLAB_DESTROY_BY_RCUbecause it preserves the cache hot advantages of the objects. Regular RCU freeing will let the object expire for a tick or so which will result in the cacheline cooling down.
Seems really good to master this SLAB_DESTROY_BY_RCU thing (I see almost no use of it in current kernel)
1) Hum, do you know why "struct file" objects dont use SLAB_DESTROY_BY_RCU then, since we noticed a performance regression for several workloads at RCUification of file structures ?
2) What prevents an object to be *freed* (and deleted from a hash chain), then re-allocated and inserted to another chain (different keys) ? (final refcount=1)
If the lookup detects a key mismatch, how will it continue to the next item, since 'next' pointer will have been reused for the new chain insertion...
Me confused...
Quoted 27 lines · Eric Dumazet
Quoted 15 lines · Christoph Lameter
Quoted 8 lines · Eric Dumazet
Quoted 8 lines
Quoted 3 lines
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...Be careful!,
SLAB_DESTROY_BY_RCUjust means the slab page gets RCU-freed, this means that slab object pointers stay pointing to valid memory, but it does _NOT_ mean those slab objects themselves remain valid.Quoted 6 lines
Quoted 5 lines
The slab allocator is free to re-use those objects at any time - irrespective of the rcu-grace period. Therefore you will have to be able to validate that the object you point to is indeed the object you expect, otherwise strange and wonderful things will happen.
Thanks for this clarification. I guess we really need a rcu head then :)
Quoted 10 lines
No you just need to make sure that the object you located is still active (f.e. refcount > 0) and that it is really a match (hash pointers may be updated asynchronously and therefore point to the object that has been reused
for something else).
Generally it is advisable to useSLAB_DESTROY_BY_RCUbecause it preserves the
cache hot advantages of the objects. Regular RCU freeing will let the object
expire for a tick or so which will result in the cacheline cooling down.
Quoted 2 lines
Seems really good to master this
SLAB_DESTROY_BY_RCUthing (I see almost no use of it in current kernel)
It is not the easiest thing to use...
Quoted 7 lines
1) Hum, do you know why "struct file" objects dont use
SLAB_DESTROY_BY_RCUthen, since we noticed a performance regression for several workloads at RCUification of file structures ?2) What prevents an object to be *freed* (and deleted from a hash chain), then re-allocated and inserted to another chain (different keys) ? (final refcount=1)
Nothing prevents this from happening. You either have to have some sort of validation step based on object identity (e.g., a generation number that is incremented on each allocation), or have an algorithm that doesn't care if searches sometimes spuriously fail to find something that really is in the list.
Which is one of the reasons that its use is rare. But perhaps more experience with it will show more/better ways to use it.
Quoted 5 lines
If the lookup detects a key mismatch, how will it continue to the next item, since 'next' pointer will have been reused for the new chain insertion...
Me confused...
One way to approach this is to have a generation number that is incremented each time the object is recycled along with a pointer to the list header. The list header contains the most recent generation number of any element in the list. Then if either the generation number of a give element is greater than that of the header when you started the search, or the element's pointer no longer references the list header you started your search from, restart the search. Read-side memory barriers may also be required in some cases.
It may be possible to simplify this in some special cases.
There are probably better ways to approach this, but that is one way.
Thanx, Paul
Quoted 22 lines · Eric Dumazet
Quoted 21 lines · Christoph Lameter
Quoted 8 lines · Eric Dumazet
Quoted 8 lines
Quoted 3 lines
Or just add
SLAB_DESTROY_BY_RCUto slab creation inproto_register()for "struct proto udp_prot/udpv6_prot" so thatkmem_cache_free()done insk_prot_free()can defer freeing to RCU...Be careful!,
SLAB_DESTROY_BY_RCUjust means the slab page gets RCU-freed, this means that slab object pointers stay pointing to valid memory, but it does _NOT_ mean those slab objects themselves remain valid.Quoted 6 lines
Quoted 5 lines
The slab allocator is free to re-use those objects at any time - irrespective of the rcu-grace period. Therefore you will have to be able to validate that the object you point to is indeed the object you expect, otherwise strange and wonderful things will happen.
Thanks for this clarification. I guess we really need a rcu head then :)
No you just need to make sure that the object you located is still active (f.e. refcount > 0) and that it is really a match (hash pointers may be updated asynchronously and therefore point to the object that has been reused for something else).
Quoted 6 lines
Quoted 3 lines
Generally it is advisable to use
SLAB_DESTROY_BY_RCUbecause it preserves the cache hot advantages of the objects. Regular RCU freeing will let the object expire for a tick or so which will result in the cacheline cooling down.Seems really good to master this
SLAB_DESTROY_BY_RCUthing (I see almost no use of it in current kernel)
There is (AFAIK) only 1 user, the anon_vma stuff.
Quoted 9 lines
1) Hum, do you know why "struct file" objects dont use
SLAB_DESTROY_BY_RCUthen, since we noticed a performance regression for several workloads at RCUification of file structures ?2) What prevents an object to be *freed* (and deleted from a hash chain), then re-allocated and inserted to another chain (different keys) ? (final refcount=1)
If the lookup detects a key mismatch, how will it continue to the next item, since 'next' pointer will have been reused for the new chain insertion...
Right, you can't have lists with items like that. You can only do matching lookups. What you do is:
find_get_obj(key)
{
rcu_read_lock()
again:
obj = lookup(key);
if (!obj)
goto out;
/*
* if we can't get a ref, the item got freed concurrently
* try again
*/
if (!get_ref_unless_zero(obj))
goto again;
/*
* if we did get a ref, but its not the object we expected
* try again
*/
if (obj->key != key) {
put_ref(obj);
goto again;
}
out:
rcu_read_unlock();
return obj;
}
Which is basically what we do with the lockless pagecache, where we don't need the RCU because the page-frames are never freed.
Quoted 6 lines · Eric Dumazet
1) Hum, do you know why "struct file" objects dont use
SLAB_DESTROY_BY_RCUthen,
since we noticed a performance regression for several workloads at RCUification
of file structures ?
Because my patches were not accepted that fix the issue.
http://lkml.org/lkml/2006/6/16/144
Quoted 4 lines
2) What prevents an object to be *freed* (and deleted from a hash chain), then
re-allocated and inserted to another chain (different keys) ? (final refcount=1)
Nothing.
Quoted 5 lines
If the lookup detects a key mismatch, how will it continue to the next item,
since 'next' pointer will have been reused for the new chain insertion...Me confused...
If there is a mismatch then you have to do another hash lookup. Do an rcu unlock and start over.