diff options
author | Eric Dumazet <eric.dumazet@gmail.com> | 2010-09-02 23:09:32 (GMT) |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-09-07 01:24:59 (GMT) |
commit | 1fd63041c49c5c6ed1fe58b7bccc2de462d51e2b (patch) | |
tree | aac2e108e2c8c545669bd9fe655f04ac026f5e15 | |
parent | 9d348af47656a65a697ff55a53daf294ea4d5662 (diff) | |
download | linux-1fd63041c49c5c6ed1fe58b7bccc2de462d51e2b.tar.xz |
net: pskb_expand_head() optimization
pskb_expand_head() blindly takes references on fragments before calling
skb_release_data(), potentially releasing these references.
We can add a fast path, avoiding these atomic operations, if we own the
last reference on skb->head.
Based on a previous patch from David
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | net/core/skbuff.c | 25 |
1 files changed, 20 insertions, 5 deletions
diff --git a/net/core/skbuff.c b/net/core/skbuff.c index c030cf8..2d1bc76 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -779,6 +779,7 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, u8 *data; int size = nhead + (skb_end_pointer(skb) - skb->head) + ntail; long off; + bool fastpath; BUG_ON(nhead < 0); @@ -800,14 +801,28 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, skb_shinfo(skb), offsetof(struct skb_shared_info, frags[skb_shinfo(skb)->nr_frags])); - for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) - get_page(skb_shinfo(skb)->frags[i].page); + /* Check if we can avoid taking references on fragments if we own + * the last reference on skb->head. (see skb_release_data()) + */ + if (!skb->cloned) + fastpath = true; + else { + int delta = skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1; - if (skb_has_frag_list(skb)) - skb_clone_fraglist(skb); + fastpath = atomic_read(&skb_shinfo(skb)->dataref) == delta; + } - skb_release_data(skb); + if (fastpath) { + kfree(skb->head); + } else { + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) + get_page(skb_shinfo(skb)->frags[i].page); + if (skb_has_frag_list(skb)) + skb_clone_fraglist(skb); + + skb_release_data(skb); + } off = (data + nhead) - skb->head; skb->head = data; |