Commit f42157cb authored by Rusty Russell's avatar Rusty Russell Committed by David S. Miller

tun: fallback if skb_alloc() fails on big packets

skb_alloc produces linear packets (using kmalloc()).  That can fail,
so should we fall back to making paged skbs.

My original version of this patch always allocate paged skbs for big
packets.  But that made performance drop from 8.4 seconds to 8.8
seconds on 1G lguest->Host TCP xmit.  So now we only do that as a
fallback.
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
Acked-by: default avatarMax Krasnyansky <maxk@qualcomm.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent db543c1f
...@@ -358,6 +358,66 @@ static unsigned int tun_chr_poll(struct file *file, poll_table * wait) ...@@ -358,6 +358,66 @@ static unsigned int tun_chr_poll(struct file *file, poll_table * wait)
return mask; return mask;
} }
/* prepad is the amount to reserve at front. len is length after that.
* linear is a hint as to how much to copy (usually headers). */
static struct sk_buff *tun_alloc_skb(size_t prepad, size_t len, size_t linear,
gfp_t gfp)
{
struct sk_buff *skb;
unsigned int i;
skb = alloc_skb(prepad + len, gfp|__GFP_NOWARN);
if (skb) {
skb_reserve(skb, prepad);
skb_put(skb, len);
return skb;
}
/* Under a page? Don't bother with paged skb. */
if (prepad + len < PAGE_SIZE)
return NULL;
/* Start with a normal skb, and add pages. */
skb = alloc_skb(prepad + linear, gfp);
if (!skb)
return NULL;
skb_reserve(skb, prepad);
skb_put(skb, linear);
len -= linear;
for (i = 0; i < MAX_SKB_FRAGS; i++) {
skb_frag_t *f = &skb_shinfo(skb)->frags[i];
f->page = alloc_page(gfp|__GFP_ZERO);
if (!f->page)
break;
f->page_offset = 0;
f->size = PAGE_SIZE;
skb->data_len += PAGE_SIZE;
skb->len += PAGE_SIZE;
skb->truesize += PAGE_SIZE;
skb_shinfo(skb)->nr_frags++;
if (len < PAGE_SIZE) {
len = 0;
break;
}
len -= PAGE_SIZE;
}
/* Too large, or alloc fail? */
if (unlikely(len)) {
kfree_skb(skb);
skb = NULL;
}
return skb;
}
/* Get packet from user space buffer */ /* Get packet from user space buffer */
static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, size_t count) static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, size_t count)
{ {
...@@ -391,14 +451,12 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, ...@@ -391,14 +451,12 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv,
return -EINVAL; return -EINVAL;
} }
if (!(skb = alloc_skb(len + align, GFP_KERNEL))) { if (!(skb = tun_alloc_skb(align, len, gso.hdr_len, GFP_KERNEL))) {
tun->dev->stats.rx_dropped++; tun->dev->stats.rx_dropped++;
return -ENOMEM; return -ENOMEM;
} }
if (align) if (skb_copy_datagram_from_iovec(skb, 0, iv, len)) {
skb_reserve(skb, align);
if (memcpy_fromiovec(skb_put(skb, len), iv, len)) {
tun->dev->stats.rx_dropped++; tun->dev->stats.rx_dropped++;
kfree_skb(skb); kfree_skb(skb);
return -EFAULT; return -EFAULT;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment