Defer skb allocation in virtio_net recv
Subsystems (5)
cover Defer skb allocation in virtio_net recv
This patch-set has deferred virtio_net skb allocation in receiving path for both big packets and mergeable buffers. It reduces skb pre-allocations and skb frees. This patch-set also add a new API detach_unused_bufs in virtio. Recv skb queue has been removed in virtio_net. It is based on previous Rusty and Michaels' review, patch has split into two:
[PATCH 1/2] virtio: Add detach unused buffer from vring [PATCH 2/2] virtio_net: Defer skb allocation in receive path
I copied Rusty's comment as [PATCH 1/2] commit message.
This patch is built against Dave's net-next tree.
Thanks
Shirley
PATCH 1/2 virtio: Add detach unused buffer from vring
There's currently no way for a virtio driver to ask for unused buffers, so it has to keep a list itself to reclaim them at shutdown. This is redundant, since virtio_ring stores that information. So add a new hook to do this: virtio_net will be the first user.
---
drivers/virtio/virtio_ring.c | 25 +++++++++++++++++++++++++ include/linux/virtio.h | 4 ++++ 2 files changed, 29 insertions(+), 0 deletions(-)
drivers/virtio/virtio_ring.c +25 -0
@@ -334,6 +334,30 @@ static bool vring_enable_cb(struct virtqueue *_vq)return true; } +static void *vring_detach_unused_buf (struct virtqueue *_vq) +{ +struct vring_virtqueue *vq =to_vvq (_vq); +unsigned int i; +void *buf; + +START_USE (vq); + +for (i =0 ; i < vq->vring.num; i++) { +if (!vq->data[i]) +continue ; +/* detach_buf clears data, so grab it now. */ + buf = vq->data[i]; +detach_buf (vq, i); +END_USE (vq); +return buf; + } +/* That should have freed everything. */ +BUG_ON (vq->num_free != vq->vring.num); + +END_USE (vq); +return NULL ; +} +irqreturn_t vring_interrupt (int irq,void *_vq) {struct vring_virtqueue *vq =to_vvq (_vq);
@@ -360,6 +384,7 @@ static struct virtqueue_ops vring_vq_ops = { .kick = vring_kick, .disable_cb = vring_disable_cb, .enable_cb = vring_enable_cb, + .detach_unused_buf = vring_detach_unused_buf, }; struct virtqueue *vring_new_virtqueue(unsigned int num,
include/linux/virtio.h +4 -0
@@ -51,6 +51,9 @@ struct virtqueue {* This re-enables callbacks; it returns "false" if there are pending * buffers in the queue, to detect a possible race between the driver * checking for more work, and enabling callbacks. +* @detach_unused_buf: detach first unused buffer +* vq: the struct virtqueue we're talking about. +* Returns NULL or the "data" token handed to add_buf * * Locking rules are straightforward: the driver is responsible for * locking. No two operations may be invoked simultaneously, with the exception
@@ -71,6 +74,7 @@ struct virtqueue_ops {void (*disable_cb)(struct virtqueue *vq);bool (*enable_cb)(struct virtqueue *vq); +void *(*detach_unused_buf)(struct virtqueue *vq); };/**
Re: [PATCH 1/2] virtio: Add detach unused buffer from vring
Quoted 6 lines · Shirley Ma
There's currently no way for a virtio driver to ask for unused buffers, so it has to keep a list itself to reclaim them at shutdown. This is redundant, since
virtio_ringstores that information. So add a new hook to do this:virtio_netwill be the first user.Signed-off-by Shirley Ma <[email protected]>
Quoted 71 lines
---
drivers/virtio/virtio_ring.c | 25 +++++++++++++++++++++++++ include/linux/virtio.h | 4 ++++ 2 files changed, 29 insertions(+), 0 deletions(-)drivers/virtio/virtio_ring.c +25
-0@@ -334,6 +334,30 @@ static bool vring_enable_cb(struct virtqueue *_vq)return true; } +static void *vring_detach_unused_buf (struct virtqueue *_vq) +{ +struct vring_virtqueue *vq =to_vvq (_vq); +unsigned int i; +void *buf; + +START_USE (vq); + +for (i =0 ; i < vq->vring.num; i++) { +if (!vq->data[i]) +continue ; +/* detach_buf clears data, so grab it now. */ + buf = vq->data[i]; +detach_buf (vq, i); +END_USE (vq); +return buf; + } +/* That should have freed everything. */ +BUG_ON (vq->num_free != vq->vring.num); + +END_USE (vq); +return NULL ; +} +irqreturn_t vring_interrupt (int irq,void *_vq) {struct vring_virtqueue *vq =to_vvq (_vq);@@ -360,6 +384,7 @@ static struct virtqueue_ops vring_vq_ops = { .kick = vring_kick, .disable_cb = vring_disable_cb, .enable_cb = vring_enable_cb, + .detach_unused_buf = vring_detach_unused_buf, }; struct virtqueue *vring_new_virtqueue(unsigned int num,include/linux/virtio.h +4
-0@@ -51,6 +51,9 @@ struct virtqueue {* This re-enables callbacks; it returns "false" if there are pending * buffers in the queue, to detect a possible race between the driver * checking for more work, and enabling callbacks. +* @detach_unused_buf: detach first unused buffer +* vq: the struct virtqueue we're talking about. +* Returns NULL or the "data" token handed to add_buf * * Locking rules are straightforward: the driver is responsible for * locking. No two operations may be invoked simultaneously, with the exception @@ -71,6 +74,7 @@ struct virtqueue_ops {void (*disable_cb)(struct virtqueue *vq);bool (*enable_cb)(struct virtqueue *vq); +void *(*detach_unused_buf)(struct virtqueue *vq); };/**
Re: [PATCH 1/2] virtio: Add detach unused buffer from vring
In reply to Shirley MaQuoted 6 lines · Shirley Ma
There's currently no way for a virtio driver to ask for unused buffers, so it has to keep a list itself to reclaim them at shutdown. This is redundant, since
virtio_ringstores that information. So add a new hook to do this:virtio_netwill be the first user.Signed-off-by Shirley Ma <[email protected]>
Amit
PATCH 2/2 virtio_net: Defer skb allocation in receive path
In reply to Shirley Mavirtio_net receives packets from its pre-allocated vring buffers, then it delivers these packets to upper layer protocols as skb buffs. So it's not necessary to pre-allocate skb for each mergable buffer, then frees extra skbs when buffers are merged into a large packet. This patch has deferred skb allocation in receiving packets for both big packets and mergeable buffers to reduce skb pre-allocations and skb frees. It frees unused buffers by calling detach_unused_buf in vring, so recv skb queue is not needed.
--- drivers/net/virtio_net.c | 426 +++++++++++++++++++++++++++------------------- 1 files changed, 247 insertions(+), 179 deletions(-)
drivers/net/virtio_net.c +247 -179
@@ -56,8 +56,7 @@ struct virtnet_info/* Host will merge rx buffers for big packets (shake it! shake it!) */ bool mergeable_rx_bufs; -/* Receive & send queues. */ -struct sk_buff_head recv; +/* Send queue. */ struct sk_buff_head send;/* Work struct for refilling if we run low on memory. */
@@ -75,34 +74,44 @@ struct skb_vnet_hdr {unsigned int num_sg; }; +struct padded_vnet_hdr { +struct virtio_net_hdr hdr; +/* +* virtio_net_hdr should be in a separated sg buffer because of a +* QEMU bug, and data sg buffer shares same page with this header sg. +* This padding makes next sg 16 byte aligned after virtio_net_hdr. +*/ +char padding[6 ]; +}; +static inline struct skb_vnet_hdr *skb_vnet_hdr (struct sk_buff *skb) {return (struct skb_vnet_hdr *)skb->cb; } -static void give_a_page (struct virtnet_info *vi,struct page *page) -{ - page->private = (unsigned long )vi->pages; - vi->pages = page; -} - -static void trim_pages (struct virtnet_info *vi,struct sk_buff *skb) +/* +* private is used to chain pages for big packets, put the whole +* most recent used list in the beginning for reuse +*/ +static void give_pages (struct virtnet_info *vi,struct page *page) { -unsigned int i; +struct page *end; -for (i =0 ; i <skb_shinfo (skb)->nr_frags; i++) -give_a_page (vi,skb_shinfo (skb)->frags[i].page); -skb_shinfo (skb)->nr_frags =0 ; - skb->data_len =0 ; +/* Find end of list, sew whole thing into vi->pages. */ +for (end = page; end->private; end = (struct page *)end->private); + end->private = (unsigned long )vi->pages; + vi->pages = page; }static struct page *get_a_page (struct virtnet_info *vi,gfp_t gfp_mask) {struct page *p = vi->pages; -if (p) +if (p) { vi->pages = (struct page *)p->private; -else +/* clear private here, it is used to chain pages */ + p->private =0 ; + }else p =alloc_page (gfp_mask);return p; }
@@ -118,99 +127,142 @@ static void skb_xmit_done(struct virtqueue *svq)netif_wake_queue (vi->dev); } -static void receive_skb (struct net_device *dev,struct sk_buff *skb, -unsigned len) +static void set_skb_frag (struct sk_buff *skb,struct page *page, +unsigned int offset,unsigned int *len) { -struct virtnet_info *vi =netdev_priv (dev); -struct skb_vnet_hdr *hdr =skb_vnet_hdr (skb); -int err; -int i; +int i =skb_shinfo (skb)->nr_frags; +skb_frag_t *f; + + f = &skb_shinfo (skb)->frags[i]; + f->size =min ((unsigned )PAGE_SIZE - offset, *len); + f->page_offset = offset; + f->page = page; + + skb->data_len += f->size; + skb->len += f->size; +skb_shinfo (skb)->nr_frags++; + *len -= f->size; +} -if (unlikely (len <sizeof (struct virtio_net_hdr ) +ETH_HLEN )) { -pr_debug ("%s: short packet %i\n" , dev->name, len); - dev->stats.rx_length_errors++; - goto drop; - } +static struct sk_buff *page_to_skb (struct virtnet_info *vi, +struct page *page,unsigned int len) +{ +struct sk_buff *skb; +struct skb_vnet_hdr *hdr; +unsigned int copy, hdr_len, offset; +char *p; -if (vi->mergeable_rx_bufs) { -unsigned int copy; -char *p =page_address (skb_shinfo (skb)->frags[0 ].page); + p =page_address (page); -if (len >PAGE_SIZE ) - len =PAGE_SIZE ; - len -=sizeof (struct virtio_net_hdr_mrg_rxbuf ); +/* copy small packet so we can reuse these pages for small data */ + skb =netdev_alloc_skb_ip_align (vi->dev,GOOD_COPY_LEN ); +if (unlikely (!skb)) +return NULL ; -memcpy (&hdr->mhdr, p,sizeof (hdr->mhdr)); - p +=sizeof (hdr->mhdr); + hdr =skb_vnet_hdr (skb); - copy = len; -if (copy >skb_tailroom (skb)) - copy =skb_tailroom (skb); +if (vi->mergeable_rx_bufs) { + hdr_len =sizeof hdr->mhdr; + offset = hdr_len; + }else { + hdr_len =sizeof hdr->hdr; + offset =sizeof (struct padded_vnet_hdr ); + } -memcpy (skb_put (skb, copy), p, copy); +memcpy (hdr, p, hdr_len); - len -= copy; + len -= hdr_len; + p += offset; -if (!len) { -give_a_page (vi,skb_shinfo (skb)->frags[0 ].page); -skb_shinfo (skb)->nr_frags--; - }else { -skb_shinfo (skb)->frags[0 ].page_offset += -sizeof (hdr->mhdr) + copy; -skb_shinfo (skb)->frags[0 ].size = len; - skb->data_len += len; - skb->len += len; - } + copy = len; +if (copy >skb_tailroom (skb)) + copy =skb_tailroom (skb); +memcpy (skb_put (skb, copy), p, copy); -while (--hdr->mhdr.num_buffers) { -struct sk_buff *nskb; + len -= copy; + offset += copy; - i =skb_shinfo (skb)->nr_frags; -if (i >=MAX_SKB_FRAGS ) { -pr_debug ("%s: packet too long %d\n" , dev->name, - len); - dev->stats.rx_length_errors++; - goto drop; - } - - nskb = vi->rvq->vq_ops->get_buf (vi->rvq, &len); -if (!nskb) { -pr_debug ("%s: rx error: %d buffers missing\n" , - dev->name, hdr->mhdr.num_buffers); - dev->stats.rx_length_errors++; - goto drop; - } +while (len) { +set_skb_frag (skb, page, offset, &len); + page = (struct page *)page->private; + offset =0 ; + } -__skb_unlink (nskb, &vi->recv); - vi->num--; +if (page) +give_pages (vi, page); -skb_shinfo (skb)->frags[i] =skb_shinfo (nskb)->frags[0 ]; -skb_shinfo (nskb)->nr_frags =0 ; -kfree_skb (nskb); +return skb; +} -if (len >PAGE_SIZE ) - len =PAGE_SIZE ; +static int receive_mergeable (struct virtnet_info *vi,struct sk_buff *skb) +{ +struct skb_vnet_hdr *hdr =skb_vnet_hdr (skb); +struct page *page; +int num_buf, i, len; + + num_buf = hdr->mhdr.num_buffers; +while (--num_buf) { + i =skb_shinfo (skb)->nr_frags; +if (i >=MAX_SKB_FRAGS ) { +pr_debug ("%s: packet too long\n" , skb->dev->name); + skb->dev->stats.rx_length_errors++; +return -EINVAL ; + } -skb_shinfo (skb)->frags[i].size = len; -skb_shinfo (skb)->nr_frags++; - skb->data_len += len; - skb->len += len; + page = vi->rvq->vq_ops->get_buf (vi->rvq, &len); +if (!page) { +pr_debug ("%s: rx error: %d buffers missing\n" , + skb->dev->name, hdr->mhdr.num_buffers); + skb->dev->stats.rx_length_errors++; +return -EINVAL ; } - }else { - len -=sizeof (hdr->hdr); +if (len >PAGE_SIZE ) + len =PAGE_SIZE ; -if (len <=MAX_PACKET_LEN ) -trim_pages (vi, skb); +set_skb_frag (skb, page,0 , &len); + + --vi->num; + } +return 0 ; +} - err =pskb_trim (skb, len); -if (err) { -pr_debug ("%s: pskb_trim failed %i %d\n" , dev->name, - len, err); +static void receive_buf (struct net_device *dev,void *buf,unsigned int len) +{ +struct virtnet_info *vi =netdev_priv (dev); +struct sk_buff *skb; +struct page *page; +struct skb_vnet_hdr *hdr; + +if (unlikely (len <sizeof (struct virtio_net_hdr ) +ETH_HLEN )) { +pr_debug ("%s: short packet %i\n" , dev->name, len); + dev->stats.rx_length_errors++; +if (vi->mergeable_rx_bufs || vi->big_packets) +give_pages (vi, buf); +else +dev_kfree_skb (buf); +return ; + } + +if (!vi->mergeable_rx_bufs && !vi->big_packets) { + skb = buf; + len -=sizeof (struct virtio_net_hdr ); +skb_trim (skb, len); + }else { + page = buf; + skb =page_to_skb (vi, page, len); +if (unlikely (!skb)) { dev->stats.rx_dropped++; - goto drop; +give_pages (vi, page); +return ; } +if (vi->mergeable_rx_bufs) +if (receive_mergeable (vi, skb)) { +dev_kfree_skb (skb); +return ; + } } + hdr =skb_vnet_hdr (skb); skb->truesize += skb->data_len; dev->stats.rx_bytes += skb->len; dev->stats.rx_packets++;
@@ -267,110 +319,119 @@ static void receive_skb(struct net_device *dev, struct sk_buff *skb, frame_err: dev->stats.rx_frame_errors++; -drop:dev_kfree_skb (skb); } -static bool try_fill_recv_maxbufs (struct virtnet_info *vi,gfp_t gfp) +static int add_recvbuf_small (struct virtnet_info *vi,gfp_t gfp) {struct sk_buff *skb; -struct scatterlist sg[2 +MAX_SKB_FRAGS ]; -int num, err, i; -bool oom = false; - -sg_init_table (sg,2 +MAX_SKB_FRAGS ); -do { -struct skb_vnet_hdr *hdr; +struct skb_vnet_hdr *hdr; +struct scatterlist sg[2 ]; +int err; - skb =netdev_alloc_skb_ip_align (vi->dev,MAX_PACKET_LEN ); -if (unlikely (!skb)) { - oom = true; -break ; - } + skb =netdev_alloc_skb_ip_align (vi->dev,MAX_PACKET_LEN ); +if (unlikely (!skb)) +return -ENOMEM ; -skb_put (skb,MAX_PACKET_LEN ); +skb_put (skb,MAX_PACKET_LEN ); - hdr =skb_vnet_hdr (skb); -sg_set_buf (sg, &hdr->hdr,sizeof (hdr->hdr)); + hdr =skb_vnet_hdr (skb); +sg_set_buf (sg, &hdr->hdr,sizeof hdr->hdr); -if (vi->big_packets) { -for (i =0 ; i <MAX_SKB_FRAGS ; i++) { -skb_frag_t *f = &skb_shinfo (skb)->frags[i]; - f->page =get_a_page (vi, gfp); -if (!f->page) -break ; +skb_to_sgvec (skb, sg +1 ,0 , skb->len); - f->page_offset =0 ; - f->size =PAGE_SIZE ; + err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,2 , skb); +if (err <0 ) +dev_kfree_skb (skb); - skb->data_len +=PAGE_SIZE ; - skb->len +=PAGE_SIZE ; +return err; +} -skb_shinfo (skb)->nr_frags++; - } +static int add_recvbuf_big (struct virtnet_info *vi,gfp_t gfp) +{ +struct scatterlist sg[MAX_SKB_FRAGS +2 ]; +struct page *first, *list =NULL ; +char *p; +int i, err, offset; + +/* page in sg[MAX_SKB_FRAGS + 1] is list tail */ +for (i =MAX_SKB_FRAGS +1 ; i >1 ; --i) { + first =get_a_page (vi, gfp); +if (!first) { +if (list) +give_pages (vi, list); +return -ENOMEM ; } +sg_set_buf (&sg[i],page_address (first),PAGE_SIZE ); - num =skb_to_sgvec (skb, sg+1 ,0 , skb->len) +1 ; -skb_queue_head (&vi->recv, skb); +/* chain new page in list head to match sg */ + first->private = (unsigned long )list; + list = first; + } - err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 , num, skb); -if (err <0 ) { -skb_unlink (skb, &vi->recv); -trim_pages (vi, skb); -kfree_skb (skb); -break ; - } - vi->num++; - }while (err >= num); -if (unlikely (vi->num > vi->max)) - vi->max = vi->num; - vi->rvq->vq_ops->kick (vi->rvq); -return !oom; + first =get_a_page (vi, gfp); +if (!first) { +give_pages (vi, list); +return -ENOMEM ; + } + p =page_address (first); + +/* sg[0], sg[1] share the same page */ +/* a separated sg[0] for virtio_net_hdr only during to QEMU bug*/ +sg_set_buf (&sg[0 ], p,sizeof (struct virtio_net_hdr )); + +/* sg[1] for data packet, from offset */ + offset =sizeof (struct padded_vnet_hdr ); +sg_set_buf (&sg[1 ], p + offset,PAGE_SIZE - offset); + +/* chain first in list head */ + first->private = (unsigned long )list; + err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,MAX_SKB_FRAGS +2 , + first); +if (err <0 ) +give_pages (vi, first); + +return err; } -/* Returns false if we couldn't fill entirely (OOM). */ -static bool try_fill_recv (struct virtnet_info *vi,gfp_t gfp) +static int add_recvbuf_mergeable (struct virtnet_info *vi,gfp_t gfp) { -struct sk_buff *skb; -struct scatterlist sg[1 ]; +struct page *page; +struct scatterlist sg;int err; -bool oom = false; - -if (!vi->mergeable_rx_bufs) -return try_fill_recv_maxbufs (vi, gfp); -do { -skb_frag_t *f; + page =get_a_page (vi, gfp); +if (!page) +return -ENOMEM ; - skb =netdev_alloc_skb_ip_align (vi->dev,GOOD_COPY_LEN ); -if (unlikely (!skb)) { - oom = true; -break ; - } +sg_init_one (&sg,page_address (page),PAGE_SIZE ); - f = &skb_shinfo (skb)->frags[0 ]; - f->page =get_a_page (vi, gfp); -if (!f->page) { - oom = true; -kfree_skb (skb); -break ; - } + err = vi->rvq->vq_ops->add_buf (vi->rvq, &sg,0 ,1 , page); +if (err <0 ) +give_pages (vi, page); - f->page_offset =0 ; - f->size =PAGE_SIZE ; +return err; +} -skb_shinfo (skb)->nr_frags++; +/* Returns false if we couldn't fill entirely (OOM). */ +static bool try_fill_recv (struct virtnet_info *vi,gfp_t gfp) +{ +int err; +bool oom = false; -sg_init_one (sg,page_address (f->page),PAGE_SIZE ); -skb_queue_head (&vi->recv, skb); +do { +if (vi->mergeable_rx_bufs) + err =add_recvbuf_mergeable (vi, gfp); +else if (vi->big_packets) + err =add_recvbuf_big (vi, gfp); +else + err =add_recvbuf_small (vi, gfp); - err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,1 , skb);if (err <0 ) { -skb_unlink (skb, &vi->recv); -kfree_skb (skb); + oom = true;break ; } - vi->num++; + ++vi->num; }while (err >0 );if (unlikely (vi->num > vi->max)) vi->max = vi->num;
@@ -408,15 +469,14 @@ static void refill_work(struct work_struct *work)static int virtnet_poll (struct napi_struct *napi,int budget) { struct virtnet_info *vi = container_of(napi, struct virtnet_info, napi); -struct sk_buff *skb =NULL ; +void *buf;unsigned int len, received =0 ; again:while (received < budget && - (skb = vi->rvq->vq_ops->get_buf (vi->rvq, &len)) !=NULL ) { -__skb_unlink (skb, &vi->recv); -receive_skb (vi->dev, skb, len); - vi->num--; + (buf = vi->rvq->vq_ops->get_buf (vi->rvq, &len)) !=NULL ) { +receive_buf (vi->dev, buf, len); + --vi->num; received++; }
@@ -496,9 +556,9 @@ static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb)/* Encode metadata header at front. */ if (vi->mergeable_rx_bufs) -sg_set_buf (sg, &hdr->mhdr,sizeof (hdr->mhdr)); +sg_set_buf (sg, &hdr->mhdr,sizeof hdr->mhdr);else -sg_set_buf (sg, &hdr->hdr,sizeof (hdr->hdr)); +sg_set_buf (sg, &hdr->hdr,sizeof hdr->hdr); hdr->num_sg =skb_to_sgvec (skb, sg+1 ,0 , skb->len) +1 ;return vi->svq->vq_ops->add_buf (vi->svq, sg, hdr->num_sg,0 , skb);
@@ -916,8 +976,7 @@ static int virtnet_probe(struct virtio_device *vdev) dev->features |=NETIF_F_HW_VLAN_FILTER ; } -/* Initialize our empty receive and send queues. */ -skb_queue_head_init (&vi->recv); +/* Initialize our empty send queue. */ skb_queue_head_init (&vi->send); err =register_netdev (dev);
@@ -952,25 +1011,34 @@ free:return err; } +static void free_unused_bufs (struct virtnet_info *vi) +{ +void *buf; +while (vi->num) { + buf = vi->rvq->vq_ops->detach_unused_buf (vi->rvq); +if (!buf) +continue ; +if (vi->mergeable_rx_bufs || vi->big_packets) +give_pages (vi, buf); +else +dev_kfree_skb (buf); + --vi->num; + } +} + static void __devexit virtnet_remove(struct virtio_device *vdev) {struct virtnet_info *vi = vdev->priv; -struct sk_buff *skb;/* Stop all the virtqueues. */ vdev->config->reset (vdev); -/* Free our skbs in send and recv queues, if any. */ -while ((skb =__skb_dequeue (&vi->recv)) !=NULL ) { -kfree_skb (skb); - vi->num--; - } +/* Free our skbs in send queue, if any. */ __skb_queue_purge (&vi->send); -BUG_ON (vi->num !=0 ); -unregister_netdev (vi->dev);cancel_delayed_work_sync (&vi->refill); +free_unused_bufs (vi); vdev->config->del_vqs (vi->vdev);
Re: [PATCH 2/2] virtio_net: Defer skb allocation in receive path
Quoted 9 lines · Shirley Ma
virtio_netreceives packets from its pre-allocated vring buffers, then it delivers these packets to upper layer protocols as skb buffs. So it's not necessary to pre-allocate skb for each mergable buffer, then frees extra skbs when buffers are merged into a large packet. This patch has deferred skb allocation in receiving packets for both big packets and mergeable buffers to reduce skb pre-allocations and skb frees. It frees unused buffers by callingdetach_unused_bufin vring, so recv skb queue is not needed.Signed-off-by Shirley Ma <[email protected]>
...
drivers/net/virtio_net.c free: · line 1020
Quoted 7 lines
drivers/net/virtio_net.c +7
-0+static void free_unused_bufs (struct virtnet_info *vi) +{ +void *buf; +while (vi->num) { + buf = vi->rvq->vq_ops->detach_unused_buf (vi->rvq); +if (!buf) +continue ;
Do you mean 'break' here?
drivers/net/virtio_net.c free: · line 1028
Quoted 8 lines
drivers/net/virtio_net.c +8
-0+if (vi->mergeable_rx_bufs || vi->big_packets) +give_pages (vi, buf); +else +dev_kfree_skb (buf); + --vi->num; + } +} +
Amit
Re: [PATCH 2/2] virtio_net: Defer skb allocation in receive path
Hello Amit,
Sorry for late response. I am just back from vacation.
Quoted 9 lines · Amit Shah
drivers/net/virtio_net.c free: · line 1020
Quoted 7 lines
drivers/net/virtio_net.c +7
-0+static void free_unused_bufs (struct virtnet_info *vi) +{ +void *buf; +while (vi->num) { + buf = vi->rvq->vq_ops->detach_unused_buf (vi->rvq); +if (!buf) +continue ;Do you mean 'break' here?
Nope, it means break since the buffer usage is not sorted by descriptors from my understanding. It breaks when vi->num reaches 0.
Thanks
Shirley
Michael S. Tsirkin
maintainerRe: [PATCH 2/2] virtio_net: Defer skb allocation in receive path
Quoted 15 lines · Shirley Ma
Hello Amit,
Sorry for late response. I am just back from vacation.
Quoted 9 lines · Amit Shah
drivers/net/virtio_net.c free: · line 1020
Quoted 7 lines
drivers/net/virtio_net.c +7
-0+static void free_unused_bufs (struct virtnet_info *vi) +{ +void *buf; +while (vi->num) { + buf = vi->rvq->vq_ops->detach_unused_buf (vi->rvq); +if (!buf) +continue ;Do you mean 'break' here?
Quoted 5 lines
Nope, it means break since the buffer usage is not sorted by descriptors from my understanding. It breaks when
vi->numreaches 0.Thanks
Shirley
t
I don't understand.detach_unused_buf has:
+ if (!vq->data[i])
+ continue;
so it will never return NULL unless no more buffers? breaking here ad BUG_ON(vi->num) as Amit suggests seems cleaner than looping forever if there's a bug.
Signature · 1 line
MST
Re: [PATCH 2/2] virtio_net: Defer skb allocation in receive path
Quoted 3 lines · Michael S. Tsirkin
so it will never return NULL unless no more buffers? breaking here ad
BUG_ON(vi->num)as Amit suggests seems cleaner than looping forever if there's a bug.
Agree. I will change to break as Amit suggested and put BUG_ON and resubmit the patch.
Thanks
Shirley
PATCH v2 2/2 virtio_net: Defer skb allocation in receive path
virtio_net receives packets from its pre-allocated vring buffers, then it delivers these packets to upper layer protocols as skb buffs. So it's not necessary to pre-allocate skb for each mergable buffer, then frees extra skbs when buffers are merged into a large packet. This patch has deferred skb allocation in receiving packets for both big packets and mergeable buffers to reduce skb pre-allocations and skb frees. It frees unused buffers by calling detach_unused_buf in vring, so recv skb queue is not needed.
--- drivers/net/virtio_net.c | 427 +++++++++++++++++++++++++++------------------- 1 files changed, 248 insertions(+), 179 deletions(-)
drivers/net/virtio_net.c +248 -179
@@ -56,8 +56,7 @@ struct virtnet_info/* Host will merge rx buffers for big packets (shake it! shake it!) */ bool mergeable_rx_bufs; -/* Receive & send queues. */ -struct sk_buff_head recv; +/* Send queue. */ struct sk_buff_head send;/* Work struct for refilling if we run low on memory. */
@@ -75,34 +74,44 @@ struct skb_vnet_hdr {unsigned int num_sg; }; +struct padded_vnet_hdr { +struct virtio_net_hdr hdr; +/* +* virtio_net_hdr should be in a separated sg buffer because of a +* QEMU bug, and data sg buffer shares same page with this header sg. +* This padding makes next sg 16 byte aligned after virtio_net_hdr. +*/ +char padding[6 ]; +}; +static inline struct skb_vnet_hdr *skb_vnet_hdr (struct sk_buff *skb) {return (struct skb_vnet_hdr *)skb->cb; } -static void give_a_page (struct virtnet_info *vi,struct page *page) -{ - page->private = (unsigned long )vi->pages; - vi->pages = page; -} - -static void trim_pages (struct virtnet_info *vi,struct sk_buff *skb) +/* +* private is used to chain pages for big packets, put the whole +* most recent used list in the beginning for reuse +*/ +static void give_pages (struct virtnet_info *vi,struct page *page) { -unsigned int i; +struct page *end; -for (i =0 ; i <skb_shinfo (skb)->nr_frags; i++) -give_a_page (vi,skb_shinfo (skb)->frags[i].page); -skb_shinfo (skb)->nr_frags =0 ; - skb->data_len =0 ; +/* Find end of list, sew whole thing into vi->pages. */ +for (end = page; end->private; end = (struct page *)end->private); + end->private = (unsigned long )vi->pages; + vi->pages = page; }static struct page *get_a_page (struct virtnet_info *vi,gfp_t gfp_mask) {struct page *p = vi->pages; -if (p) +if (p) { vi->pages = (struct page *)p->private; -else +/* clear private here, it is used to chain pages */ + p->private =0 ; + }else p =alloc_page (gfp_mask);return p; }
@@ -118,99 +127,142 @@ static void skb_xmit_done(struct virtqueue *svq)netif_wake_queue (vi->dev); } -static void receive_skb (struct net_device *dev,struct sk_buff *skb, -unsigned len) +static void set_skb_frag (struct sk_buff *skb,struct page *page, +unsigned int offset,unsigned int *len) { -struct virtnet_info *vi =netdev_priv (dev); -struct skb_vnet_hdr *hdr =skb_vnet_hdr (skb); -int err; -int i; - -if (unlikely (len <sizeof (struct virtio_net_hdr ) +ETH_HLEN )) { -pr_debug ("%s: short packet %i\n" , dev->name, len); - dev->stats.rx_length_errors++; - goto drop; - } +int i =skb_shinfo (skb)->nr_frags; +skb_frag_t *f; + + f = &skb_shinfo (skb)->frags[i]; + f->size =min ((unsigned )PAGE_SIZE - offset, *len); + f->page_offset = offset; + f->page = page; + + skb->data_len += f->size; + skb->len += f->size; +skb_shinfo (skb)->nr_frags++; + *len -= f->size; +} -if (vi->mergeable_rx_bufs) { -unsigned int copy; -char *p =page_address (skb_shinfo (skb)->frags[0 ].page); +static struct sk_buff *page_to_skb (struct virtnet_info *vi, +struct page *page,unsigned int len) +{ +struct sk_buff *skb; +struct skb_vnet_hdr *hdr; +unsigned int copy, hdr_len, offset; +char *p; -if (len >PAGE_SIZE ) - len =PAGE_SIZE ; - len -=sizeof (struct virtio_net_hdr_mrg_rxbuf ); + p =page_address (page); -memcpy (&hdr->mhdr, p,sizeof (hdr->mhdr)); - p +=sizeof (hdr->mhdr); +/* copy small packet so we can reuse these pages for small data */ + skb =netdev_alloc_skb_ip_align (vi->dev,GOOD_COPY_LEN ); +if (unlikely (!skb)) +return NULL ; - copy = len; -if (copy >skb_tailroom (skb)) - copy =skb_tailroom (skb); + hdr =skb_vnet_hdr (skb); -memcpy (skb_put (skb, copy), p, copy); +if (vi->mergeable_rx_bufs) { + hdr_len =sizeof hdr->mhdr; + offset = hdr_len; + }else { + hdr_len =sizeof hdr->hdr; + offset =sizeof (struct padded_vnet_hdr ); + } - len -= copy; +memcpy (hdr, p, hdr_len); -if (!len) { -give_a_page (vi,skb_shinfo (skb)->frags[0 ].page); -skb_shinfo (skb)->nr_frags--; - }else { -skb_shinfo (skb)->frags[0 ].page_offset += -sizeof (hdr->mhdr) + copy; -skb_shinfo (skb)->frags[0 ].size = len; - skb->data_len += len; - skb->len += len; - } + len -= hdr_len; + p += offset; -while (--hdr->mhdr.num_buffers) { -struct sk_buff *nskb; + copy = len; +if (copy >skb_tailroom (skb)) + copy =skb_tailroom (skb); +memcpy (skb_put (skb, copy), p, copy); - i =skb_shinfo (skb)->nr_frags; -if (i >=MAX_SKB_FRAGS ) { -pr_debug ("%s: packet too long %d\n" , dev->name, - len); - dev->stats.rx_length_errors++; - goto drop; - } + len -= copy; + offset += copy; - nskb = vi->rvq->vq_ops->get_buf (vi->rvq, &len); -if (!nskb) { -pr_debug ("%s: rx error: %d buffers missing\n" , - dev->name, hdr->mhdr.num_buffers); - dev->stats.rx_length_errors++; - goto drop; - } +while (len) { +set_skb_frag (skb, page, offset, &len); + page = (struct page *)page->private; + offset =0 ; + } -__skb_unlink (nskb, &vi->recv); - vi->num--; +if (page) +give_pages (vi, page); -skb_shinfo (skb)->frags[i] =skb_shinfo (nskb)->frags[0 ]; -skb_shinfo (nskb)->nr_frags =0 ; -kfree_skb (nskb); +return skb; +} -if (len >PAGE_SIZE ) - len =PAGE_SIZE ; +static int receive_mergeable (struct virtnet_info *vi,struct sk_buff *skb) +{ +struct skb_vnet_hdr *hdr =skb_vnet_hdr (skb); +struct page *page; +int num_buf, i, len; + + num_buf = hdr->mhdr.num_buffers; +while (--num_buf) { + i =skb_shinfo (skb)->nr_frags; +if (i >=MAX_SKB_FRAGS ) { +pr_debug ("%s: packet too long\n" , skb->dev->name); + skb->dev->stats.rx_length_errors++; +return -EINVAL ; + } -skb_shinfo (skb)->frags[i].size = len; -skb_shinfo (skb)->nr_frags++; - skb->data_len += len; - skb->len += len; + page = vi->rvq->vq_ops->get_buf (vi->rvq, &len); +if (!page) { +pr_debug ("%s: rx error: %d buffers missing\n" , + skb->dev->name, hdr->mhdr.num_buffers); + skb->dev->stats.rx_length_errors++; +return -EINVAL ; } - }else { - len -=sizeof (hdr->hdr); +if (len >PAGE_SIZE ) + len =PAGE_SIZE ; + +set_skb_frag (skb, page,0 , &len); + + --vi->num; + } +return 0 ; +} + +static void receive_buf (struct net_device *dev,void *buf,unsigned int len) +{ +struct virtnet_info *vi =netdev_priv (dev); +struct sk_buff *skb; +struct page *page; +struct skb_vnet_hdr *hdr; -if (len <=MAX_PACKET_LEN ) -trim_pages (vi, skb); +if (unlikely (len <sizeof (struct virtio_net_hdr ) +ETH_HLEN )) { +pr_debug ("%s: short packet %i\n" , dev->name, len); + dev->stats.rx_length_errors++; +if (vi->mergeable_rx_bufs || vi->big_packets) +give_pages (vi, buf); +else +dev_kfree_skb (buf); +return ; + } - err =pskb_trim (skb, len); -if (err) { -pr_debug ("%s: pskb_trim failed %i %d\n" , dev->name, - len, err); +if (!vi->mergeable_rx_bufs && !vi->big_packets) { + skb = buf; + len -=sizeof (struct virtio_net_hdr ); +skb_trim (skb, len); + }else { + page = buf; + skb =page_to_skb (vi, page, len); +if (unlikely (!skb)) { dev->stats.rx_dropped++; - goto drop; +give_pages (vi, page); +return ; } +if (vi->mergeable_rx_bufs) +if (receive_mergeable (vi, skb)) { +dev_kfree_skb (skb); +return ; + } } + hdr =skb_vnet_hdr (skb); skb->truesize += skb->data_len; dev->stats.rx_bytes += skb->len; dev->stats.rx_packets++;
@@ -267,110 +319,119 @@ static void receive_skb(struct net_device *dev, struct sk_buff *skb, frame_err: dev->stats.rx_frame_errors++; -drop:dev_kfree_skb (skb); } -static bool try_fill_recv_maxbufs (struct virtnet_info *vi,gfp_t gfp) +static int add_recvbuf_small (struct virtnet_info *vi,gfp_t gfp) {struct sk_buff *skb; -struct scatterlist sg[2 +MAX_SKB_FRAGS ]; -int num, err, i; -bool oom = false; - -sg_init_table (sg,2 +MAX_SKB_FRAGS ); -do { -struct skb_vnet_hdr *hdr; +struct skb_vnet_hdr *hdr; +struct scatterlist sg[2 ]; +int err; - skb =netdev_alloc_skb_ip_align (vi->dev,MAX_PACKET_LEN ); -if (unlikely (!skb)) { - oom = true; -break ; - } + skb =netdev_alloc_skb_ip_align (vi->dev,MAX_PACKET_LEN ); +if (unlikely (!skb)) +return -ENOMEM ; -skb_put (skb,MAX_PACKET_LEN ); +skb_put (skb,MAX_PACKET_LEN ); - hdr =skb_vnet_hdr (skb); -sg_set_buf (sg, &hdr->hdr,sizeof (hdr->hdr)); + hdr =skb_vnet_hdr (skb); +sg_set_buf (sg, &hdr->hdr,sizeof hdr->hdr); -if (vi->big_packets) { -for (i =0 ; i <MAX_SKB_FRAGS ; i++) { -skb_frag_t *f = &skb_shinfo (skb)->frags[i]; - f->page =get_a_page (vi, gfp); -if (!f->page) -break ; +skb_to_sgvec (skb, sg +1 ,0 , skb->len); - f->page_offset =0 ; - f->size =PAGE_SIZE ; + err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,2 , skb); +if (err <0 ) +dev_kfree_skb (skb); - skb->data_len +=PAGE_SIZE ; - skb->len +=PAGE_SIZE ; +return err; +} -skb_shinfo (skb)->nr_frags++; - } +static int add_recvbuf_big (struct virtnet_info *vi,gfp_t gfp) +{ +struct scatterlist sg[MAX_SKB_FRAGS +2 ]; +struct page *first, *list =NULL ; +char *p; +int i, err, offset; + +/* page in sg[MAX_SKB_FRAGS + 1] is list tail */ +for (i =MAX_SKB_FRAGS +1 ; i >1 ; --i) { + first =get_a_page (vi, gfp); +if (!first) { +if (list) +give_pages (vi, list); +return -ENOMEM ; } +sg_set_buf (&sg[i],page_address (first),PAGE_SIZE ); - num =skb_to_sgvec (skb, sg+1 ,0 , skb->len) +1 ; -skb_queue_head (&vi->recv, skb); +/* chain new page in list head to match sg */ + first->private = (unsigned long )list; + list = first; + } - err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 , num, skb); -if (err <0 ) { -skb_unlink (skb, &vi->recv); -trim_pages (vi, skb); -kfree_skb (skb); -break ; - } - vi->num++; - }while (err >= num); -if (unlikely (vi->num > vi->max)) - vi->max = vi->num; - vi->rvq->vq_ops->kick (vi->rvq); -return !oom; + first =get_a_page (vi, gfp); +if (!first) { +give_pages (vi, list); +return -ENOMEM ; + } + p =page_address (first); + +/* sg[0], sg[1] share the same page */ +/* a separated sg[0] for virtio_net_hdr only during to QEMU bug*/ +sg_set_buf (&sg[0 ], p,sizeof (struct virtio_net_hdr )); + +/* sg[1] for data packet, from offset */ + offset =sizeof (struct padded_vnet_hdr ); +sg_set_buf (&sg[1 ], p + offset,PAGE_SIZE - offset); + +/* chain first in list head */ + first->private = (unsigned long )list; + err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,MAX_SKB_FRAGS +2 , + first); +if (err <0 ) +give_pages (vi, first); + +return err; } -/* Returns false if we couldn't fill entirely (OOM). */ -static bool try_fill_recv (struct virtnet_info *vi,gfp_t gfp) +static int add_recvbuf_mergeable (struct virtnet_info *vi,gfp_t gfp) { -struct sk_buff *skb; -struct scatterlist sg[1 ]; +struct page *page; +struct scatterlist sg;int err; -bool oom = false; - -if (!vi->mergeable_rx_bufs) -return try_fill_recv_maxbufs (vi, gfp); -do { -skb_frag_t *f; + page =get_a_page (vi, gfp); +if (!page) +return -ENOMEM ; - skb =netdev_alloc_skb_ip_align (vi->dev,GOOD_COPY_LEN ); -if (unlikely (!skb)) { - oom = true; -break ; - } +sg_init_one (&sg,page_address (page),PAGE_SIZE ); - f = &skb_shinfo (skb)->frags[0 ]; - f->page =get_a_page (vi, gfp); -if (!f->page) { - oom = true; -kfree_skb (skb); -break ; - } + err = vi->rvq->vq_ops->add_buf (vi->rvq, &sg,0 ,1 , page); +if (err <0 ) +give_pages (vi, page); - f->page_offset =0 ; - f->size =PAGE_SIZE ; +return err; +} -skb_shinfo (skb)->nr_frags++; +/* Returns false if we couldn't fill entirely (OOM). */ +static bool try_fill_recv (struct virtnet_info *vi,gfp_t gfp) +{ +int err; +bool oom = false; -sg_init_one (sg,page_address (f->page),PAGE_SIZE ); -skb_queue_head (&vi->recv, skb); +do { +if (vi->mergeable_rx_bufs) + err =add_recvbuf_mergeable (vi, gfp); +else if (vi->big_packets) + err =add_recvbuf_big (vi, gfp); +else + err =add_recvbuf_small (vi, gfp); - err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,1 , skb);if (err <0 ) { -skb_unlink (skb, &vi->recv); -kfree_skb (skb); + oom = true;break ; } - vi->num++; + ++vi->num; }while (err >0 );if (unlikely (vi->num > vi->max)) vi->max = vi->num;
@@ -408,15 +469,14 @@ static void refill_work(struct work_struct *work)static int virtnet_poll (struct napi_struct *napi,int budget) { struct virtnet_info *vi = container_of(napi, struct virtnet_info, napi); -struct sk_buff *skb =NULL ; +void *buf;unsigned int len, received =0 ; again:while (received < budget && - (skb = vi->rvq->vq_ops->get_buf (vi->rvq, &len)) !=NULL ) { -__skb_unlink (skb, &vi->recv); -receive_skb (vi->dev, skb, len); - vi->num--; + (buf = vi->rvq->vq_ops->get_buf (vi->rvq, &len)) !=NULL ) { +receive_buf (vi->dev, buf, len); + --vi->num; received++; }
@@ -496,9 +556,9 @@ static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb)/* Encode metadata header at front. */ if (vi->mergeable_rx_bufs) -sg_set_buf (sg, &hdr->mhdr,sizeof (hdr->mhdr)); +sg_set_buf (sg, &hdr->mhdr,sizeof hdr->mhdr);else -sg_set_buf (sg, &hdr->hdr,sizeof (hdr->hdr)); +sg_set_buf (sg, &hdr->hdr,sizeof hdr->hdr); hdr->num_sg =skb_to_sgvec (skb, sg+1 ,0 , skb->len) +1 ;return vi->svq->vq_ops->add_buf (vi->svq, sg, hdr->num_sg,0 , skb);
@@ -916,8 +976,7 @@ static int virtnet_probe(struct virtio_device *vdev) dev->features |=NETIF_F_HW_VLAN_FILTER ; } -/* Initialize our empty receive and send queues. */ -skb_queue_head_init (&vi->recv); +/* Initialize our empty send queue. */ skb_queue_head_init (&vi->send); err =register_netdev (dev);
@@ -952,25 +1011,35 @@ free:return err; } +static void free_unused_bufs (struct virtnet_info *vi) +{ +void *buf; +while (1 ) { + buf = vi->rvq->vq_ops->detach_unused_buf (vi->rvq); +if (!buf) +break ; +if (vi->mergeable_rx_bufs || vi->big_packets) +give_pages (vi, buf); +else +dev_kfree_skb (buf); + --vi->num; + } +BUG_ON (vi->num !=0 ); +} + static void __devexit virtnet_remove(struct virtio_device *vdev) {struct virtnet_info *vi = vdev->priv; -struct sk_buff *skb;/* Stop all the virtqueues. */ vdev->config->reset (vdev); -/* Free our skbs in send and recv queues, if any. */ -while ((skb =__skb_dequeue (&vi->recv)) !=NULL ) { -kfree_skb (skb); - vi->num--; - } +/* Free our skbs in send queue, if any. */ __skb_queue_purge (&vi->send); -BUG_ON (vi->num !=0 ); -unregister_netdev (vi->dev);cancel_delayed_work_sync (&vi->refill); +free_unused_bufs (vi); vdev->config->del_vqs (vi->vdev);
Michael S. Tsirkin
maintainerRe: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 18 lines · Shirley Ma
virtio_netreceives packets from its pre-allocated vring buffers, then it delivers these packets to upper layer protocols as skb buffs. So it's not necessary to pre-allocate skb for each mergable buffer, then frees extra skbs when buffers are merged into a large packet. This patch has deferred skb allocation in receiving packets for both big packets and mergeable buffers to reduce skb pre-allocations and skb frees. It frees unused buffers by callingdetach_unused_bufin vring, so recv skb queue is not needed.Signed-off-by Shirley Ma <[email protected]>--- drivers/net/virtio_net.c | 427 +++++++++++++++++++++++++++------------------- 1 files changed, 248 insertions(+), 179 deletions(-)drivers/net/virtio_net.c +0
-0
drivers/net/virtio_net.c struct virtnet_info · line 61
Quoted 8 lines
drivers/net/virtio_net.c +1
-2@@ -56,8 +56,7 @@ struct virtnet_info/* Host will merge rx buffers for big packets (shake it! shake it!) */ bool mergeable_rx_bufs; -/* Receive & send queues. */ -struct sk_buff_head recv; +/* Send queue. */ struct sk_buff_head send;
How hard is it to get rid of send queue, btw?
Quoted 556 lines
drivers/net/virtio_net.c +247
-177/* Work struct for refilling if we run low on memory. */ @@ -75,34 +74,44 @@ struct skb_vnet_hdr {unsigned int num_sg; }; +struct padded_vnet_hdr { +struct virtio_net_hdr hdr; +/* +* virtio_net_hdr should be in a separated sg buffer because of a +* QEMU bug, and data sg buffer shares same page with this header sg. +* This padding makes next sg 16 byte aligned after virtio_net_hdr. +*/ +char padding[6 ]; +}; +static inline struct skb_vnet_hdr *skb_vnet_hdr (struct sk_buff *skb) {return (struct skb_vnet_hdr *)skb->cb; } -static void give_a_page (struct virtnet_info *vi,struct page *page) -{ - page->private = (unsigned long )vi->pages; - vi->pages = page; -} - -static void trim_pages (struct virtnet_info *vi,struct sk_buff *skb) +/* +* private is used to chain pages for big packets, put the whole +* most recent used list in the beginning for reuse +*/ +static void give_pages (struct virtnet_info *vi,struct page *page) { -unsigned int i; +struct page *end; -for (i =0 ; i <skb_shinfo (skb)->nr_frags; i++) -give_a_page (vi,skb_shinfo (skb)->frags[i].page); -skb_shinfo (skb)->nr_frags =0 ; - skb->data_len =0 ; +/* Find end of list, sew whole thing into vi->pages. */ +for (end = page; end->private; end = (struct page *)end->private); + end->private = (unsigned long )vi->pages; + vi->pages = page; }static struct page *get_a_page (struct virtnet_info *vi,gfp_t gfp_mask) {struct page *p = vi->pages; -if (p) +if (p) { vi->pages = (struct page *)p->private; -else +/* clear private here, it is used to chain pages */ + p->private =0 ; + }else p =alloc_page (gfp_mask);return p; }@@ -118,99 +127,142 @@ static void skb_xmit_done(struct virtqueue *svq)netif_wake_queue (vi->dev); } -static void receive_skb (struct net_device *dev,struct sk_buff *skb, -unsigned len) +static void set_skb_frag (struct sk_buff *skb,struct page *page, +unsigned int offset,unsigned int *len) { -struct virtnet_info *vi =netdev_priv (dev); -struct skb_vnet_hdr *hdr =skb_vnet_hdr (skb); -int err; -int i; - -if (unlikely (len <sizeof (struct virtio_net_hdr ) +ETH_HLEN )) { -pr_debug ("%s: short packet %i\n" , dev->name, len); - dev->stats.rx_length_errors++; - goto drop; - } +int i =skb_shinfo (skb)->nr_frags; +skb_frag_t *f; + + f = &skb_shinfo (skb)->frags[i]; + f->size =min ((unsigned )PAGE_SIZE - offset, *len); + f->page_offset = offset; + f->page = page; + + skb->data_len += f->size; + skb->len += f->size; +skb_shinfo (skb)->nr_frags++; + *len -= f->size; +} -if (vi->mergeable_rx_bufs) { -unsigned int copy; -char *p =page_address (skb_shinfo (skb)->frags[0 ].page); +static struct sk_buff *page_to_skb (struct virtnet_info *vi, +struct page *page,unsigned int len) +{ +struct sk_buff *skb; +struct skb_vnet_hdr *hdr; +unsigned int copy, hdr_len, offset; +char *p; -if (len >PAGE_SIZE ) - len =PAGE_SIZE ; - len -=sizeof (struct virtio_net_hdr_mrg_rxbuf ); + p =page_address (page); -memcpy (&hdr->mhdr, p,sizeof (hdr->mhdr)); - p +=sizeof (hdr->mhdr); +/* copy small packet so we can reuse these pages for small data */ + skb =netdev_alloc_skb_ip_align (vi->dev,GOOD_COPY_LEN ); +if (unlikely (!skb)) +return NULL ; - copy = len; -if (copy >skb_tailroom (skb)) - copy =skb_tailroom (skb); + hdr =skb_vnet_hdr (skb); -memcpy (skb_put (skb, copy), p, copy); +if (vi->mergeable_rx_bufs) { + hdr_len =sizeof hdr->mhdr; + offset = hdr_len; + }else { + hdr_len =sizeof hdr->hdr; + offset =sizeof (struct padded_vnet_hdr ); + } - len -= copy; +memcpy (hdr, p, hdr_len); -if (!len) { -give_a_page (vi,skb_shinfo (skb)->frags[0 ].page); -skb_shinfo (skb)->nr_frags--; - }else { -skb_shinfo (skb)->frags[0 ].page_offset += -sizeof (hdr->mhdr) + copy; -skb_shinfo (skb)->frags[0 ].size = len; - skb->data_len += len; - skb->len += len; - } + len -= hdr_len; + p += offset; -while (--hdr->mhdr.num_buffers) { -struct sk_buff *nskb; + copy = len; +if (copy >skb_tailroom (skb)) + copy =skb_tailroom (skb); +memcpy (skb_put (skb, copy), p, copy); - i =skb_shinfo (skb)->nr_frags; -if (i >=MAX_SKB_FRAGS ) { -pr_debug ("%s: packet too long %d\n" , dev->name, - len); - dev->stats.rx_length_errors++; - goto drop; - } + len -= copy; + offset += copy; - nskb = vi->rvq->vq_ops->get_buf (vi->rvq, &len); -if (!nskb) { -pr_debug ("%s: rx error: %d buffers missing\n" , - dev->name, hdr->mhdr.num_buffers); - dev->stats.rx_length_errors++; - goto drop; - } +while (len) { +set_skb_frag (skb, page, offset, &len); + page = (struct page *)page->private; + offset =0 ; + } -__skb_unlink (nskb, &vi->recv); - vi->num--; +if (page) +give_pages (vi, page); -skb_shinfo (skb)->frags[i] =skb_shinfo (nskb)->frags[0 ]; -skb_shinfo (nskb)->nr_frags =0 ; -kfree_skb (nskb); +return skb; +} -if (len >PAGE_SIZE ) - len =PAGE_SIZE ; +static int receive_mergeable (struct virtnet_info *vi,struct sk_buff *skb) +{ +struct skb_vnet_hdr *hdr =skb_vnet_hdr (skb); +struct page *page; +int num_buf, i, len; + + num_buf = hdr->mhdr.num_buffers; +while (--num_buf) { + i =skb_shinfo (skb)->nr_frags; +if (i >=MAX_SKB_FRAGS ) { +pr_debug ("%s: packet too long\n" , skb->dev->name); + skb->dev->stats.rx_length_errors++; +return -EINVAL ; + } -skb_shinfo (skb)->frags[i].size = len; -skb_shinfo (skb)->nr_frags++; - skb->data_len += len; - skb->len += len; + page = vi->rvq->vq_ops->get_buf (vi->rvq, &len); +if (!page) { +pr_debug ("%s: rx error: %d buffers missing\n" , + skb->dev->name, hdr->mhdr.num_buffers); + skb->dev->stats.rx_length_errors++; +return -EINVAL ; } - }else { - len -=sizeof (hdr->hdr); +if (len >PAGE_SIZE ) + len =PAGE_SIZE ; + +set_skb_frag (skb, page,0 , &len); + + --vi->num; + } +return 0 ; +} + +static void receive_buf (struct net_device *dev,void *buf,unsigned int len) +{ +struct virtnet_info *vi =netdev_priv (dev); +struct sk_buff *skb; +struct page *page; +struct skb_vnet_hdr *hdr; -if (len <=MAX_PACKET_LEN ) -trim_pages (vi, skb); +if (unlikely (len <sizeof (struct virtio_net_hdr ) +ETH_HLEN )) { +pr_debug ("%s: short packet %i\n" , dev->name, len); + dev->stats.rx_length_errors++; +if (vi->mergeable_rx_bufs || vi->big_packets) +give_pages (vi, buf); +else +dev_kfree_skb (buf); +return ; + } - err =pskb_trim (skb, len); -if (err) { -pr_debug ("%s: pskb_trim failed %i %d\n" , dev->name, - len, err); +if (!vi->mergeable_rx_bufs && !vi->big_packets) { + skb = buf; + len -=sizeof (struct virtio_net_hdr ); +skb_trim (skb, len); + }else { + page = buf; + skb =page_to_skb (vi, page, len); +if (unlikely (!skb)) { dev->stats.rx_dropped++; - goto drop; +give_pages (vi, page); +return ; } +if (vi->mergeable_rx_bufs) +if (receive_mergeable (vi, skb)) { +dev_kfree_skb (skb); +return ; + } } + hdr =skb_vnet_hdr (skb); skb->truesize += skb->data_len; dev->stats.rx_bytes += skb->len; dev->stats.rx_packets++;@@ -267,110 +319,119 @@ static void receive_skb(struct net_device *dev, struct sk_buff *skb, frame_err: dev->stats.rx_frame_errors++; -drop:dev_kfree_skb (skb); } -static bool try_fill_recv_maxbufs (struct virtnet_info *vi,gfp_t gfp) +static int add_recvbuf_small (struct virtnet_info *vi,gfp_t gfp) {struct sk_buff *skb; -struct scatterlist sg[2 +MAX_SKB_FRAGS ]; -int num, err, i; -bool oom = false; - -sg_init_table (sg,2 +MAX_SKB_FRAGS ); -do { -struct skb_vnet_hdr *hdr; +struct skb_vnet_hdr *hdr; +struct scatterlist sg[2 ]; +int err; - skb =netdev_alloc_skb_ip_align (vi->dev,MAX_PACKET_LEN ); -if (unlikely (!skb)) { - oom = true; -break ; - } + skb =netdev_alloc_skb_ip_align (vi->dev,MAX_PACKET_LEN ); +if (unlikely (!skb)) +return -ENOMEM ; -skb_put (skb,MAX_PACKET_LEN ); +skb_put (skb,MAX_PACKET_LEN ); - hdr =skb_vnet_hdr (skb); -sg_set_buf (sg, &hdr->hdr,sizeof (hdr->hdr)); + hdr =skb_vnet_hdr (skb); +sg_set_buf (sg, &hdr->hdr,sizeof hdr->hdr); -if (vi->big_packets) { -for (i =0 ; i <MAX_SKB_FRAGS ; i++) { -skb_frag_t *f = &skb_shinfo (skb)->frags[i]; - f->page =get_a_page (vi, gfp); -if (!f->page) -break ; +skb_to_sgvec (skb, sg +1 ,0 , skb->len); - f->page_offset =0 ; - f->size =PAGE_SIZE ; + err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,2 , skb); +if (err <0 ) +dev_kfree_skb (skb); - skb->data_len +=PAGE_SIZE ; - skb->len +=PAGE_SIZE ; +return err; +} -skb_shinfo (skb)->nr_frags++; - } +static int add_recvbuf_big (struct virtnet_info *vi,gfp_t gfp) +{ +struct scatterlist sg[MAX_SKB_FRAGS +2 ]; +struct page *first, *list =NULL ; +char *p; +int i, err, offset; + +/* page in sg[MAX_SKB_FRAGS + 1] is list tail */ +for (i =MAX_SKB_FRAGS +1 ; i >1 ; --i) { + first =get_a_page (vi, gfp); +if (!first) { +if (list) +give_pages (vi, list); +return -ENOMEM ; } +sg_set_buf (&sg[i],page_address (first),PAGE_SIZE ); - num =skb_to_sgvec (skb, sg+1 ,0 , skb->len) +1 ; -skb_queue_head (&vi->recv, skb); +/* chain new page in list head to match sg */ + first->private = (unsigned long )list; + list = first; + } - err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 , num, skb); -if (err <0 ) { -skb_unlink (skb, &vi->recv); -trim_pages (vi, skb); -kfree_skb (skb); -break ; - } - vi->num++; - }while (err >= num); -if (unlikely (vi->num > vi->max)) - vi->max = vi->num; - vi->rvq->vq_ops->kick (vi->rvq); -return !oom; + first =get_a_page (vi, gfp); +if (!first) { +give_pages (vi, list); +return -ENOMEM ; + } + p =page_address (first); + +/* sg[0], sg[1] share the same page */ +/* a separated sg[0] for virtio_net_hdr only during to QEMU bug*/ +sg_set_buf (&sg[0 ], p,sizeof (struct virtio_net_hdr )); + +/* sg[1] for data packet, from offset */ + offset =sizeof (struct padded_vnet_hdr ); +sg_set_buf (&sg[1 ], p + offset,PAGE_SIZE - offset); + +/* chain first in list head */ + first->private = (unsigned long )list; + err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,MAX_SKB_FRAGS +2 , + first); +if (err <0 ) +give_pages (vi, first); + +return err; } -/* Returns false if we couldn't fill entirely (OOM). */ -static bool try_fill_recv (struct virtnet_info *vi,gfp_t gfp) +static int add_recvbuf_mergeable (struct virtnet_info *vi,gfp_t gfp) { -struct sk_buff *skb; -struct scatterlist sg[1 ]; +struct page *page; +struct scatterlist sg;int err; -bool oom = false; - -if (!vi->mergeable_rx_bufs) -return try_fill_recv_maxbufs (vi, gfp); -do { -skb_frag_t *f; + page =get_a_page (vi, gfp); +if (!page) +return -ENOMEM ; - skb =netdev_alloc_skb_ip_align (vi->dev,GOOD_COPY_LEN ); -if (unlikely (!skb)) { - oom = true; -break ; - } +sg_init_one (&sg,page_address (page),PAGE_SIZE ); - f = &skb_shinfo (skb)->frags[0 ]; - f->page =get_a_page (vi, gfp); -if (!f->page) { - oom = true; -kfree_skb (skb); -break ; - } + err = vi->rvq->vq_ops->add_buf (vi->rvq, &sg,0 ,1 , page); +if (err <0 ) +give_pages (vi, page); - f->page_offset =0 ; - f->size =PAGE_SIZE ; +return err; +} -skb_shinfo (skb)->nr_frags++; +/* Returns false if we couldn't fill entirely (OOM). */ +static bool try_fill_recv (struct virtnet_info *vi,gfp_t gfp) +{ +int err; +bool oom = false; -sg_init_one (sg,page_address (f->page),PAGE_SIZE ); -skb_queue_head (&vi->recv, skb); +do { +if (vi->mergeable_rx_bufs) + err =add_recvbuf_mergeable (vi, gfp); +else if (vi->big_packets) + err =add_recvbuf_big (vi, gfp); +else + err =add_recvbuf_small (vi, gfp); - err = vi->rvq->vq_ops->add_buf (vi->rvq, sg,0 ,1 , skb);if (err <0 ) { -skb_unlink (skb, &vi->recv); -kfree_skb (skb); + oom = true;break ; } - vi->num++; + ++vi->num; }while (err >0 );if (unlikely (vi->num > vi->max)) vi->max = vi->num;@@ -408,15 +469,14 @@ static void refill_work(struct work_struct *work)static int virtnet_poll (struct napi_struct *napi,int budget) { struct virtnet_info *vi = container_of(napi, struct virtnet_info, napi); -struct sk_buff *skb =NULL ; +void *buf;unsigned int len, received =0 ; again:while (received < budget && - (skb = vi->rvq->vq_ops->get_buf (vi->rvq, &len)) !=NULL ) { -__skb_unlink (skb, &vi->recv); -receive_skb (vi->dev, skb, len); - vi->num--; + (buf = vi->rvq->vq_ops->get_buf (vi->rvq, &len)) !=NULL ) { +receive_buf (vi->dev, buf, len); + --vi->num; received++; }@@ -496,9 +556,9 @@ static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb)/* Encode metadata header at front. */ if (vi->mergeable_rx_bufs) -sg_set_buf (sg, &hdr->mhdr,sizeof (hdr->mhdr)); +sg_set_buf (sg, &hdr->mhdr,sizeof hdr->mhdr);else -sg_set_buf (sg, &hdr->hdr,sizeof (hdr->hdr)); +sg_set_buf (sg, &hdr->hdr,sizeof hdr->hdr); hdr->num_sg =skb_to_sgvec (skb, sg+1 ,0 , skb->len) +1 ;return vi->svq->vq_ops->add_buf (vi->svq, sg, hdr->num_sg,0 , skb);@@ -916,8 +976,7 @@ static int virtnet_probe(struct virtio_device *vdev) dev->features |=NETIF_F_HW_VLAN_FILTER ; } -/* Initialize our empty receive and send queues. */ -skb_queue_head_init (&vi->recv); +/* Initialize our empty send queue. */ skb_queue_head_init (&vi->send); err =register_netdev (dev);@@ -952,25 +1011,35 @@ free:return err; } +static void free_unused_bufs (struct virtnet_info *vi) +{ +void *buf; +while (1 ) { + buf = vi->rvq->vq_ops->detach_unused_buf (vi->rvq); +if (!buf) +break ; +if (vi->mergeable_rx_bufs || vi->big_packets) +give_pages (vi, buf); +else +dev_kfree_skb (buf); + --vi->num; + } +BUG_ON (vi->num !=0 ); +} + static void __devexit virtnet_remove(struct virtio_device *vdev) {struct virtnet_info *vi = vdev->priv; -struct sk_buff *skb;/* Stop all the virtqueues. */ vdev->config->reset (vdev); -/* Free our skbs in send and recv queues, if any. */ -while ((skb =__skb_dequeue (&vi->recv)) !=NULL ) { -kfree_skb (skb); - vi->num--; - } +/* Free our skbs in send queue, if any. */ __skb_queue_purge (&vi->send); -BUG_ON (vi->num !=0 ); -unregister_netdev (vi->dev);cancel_delayed_work_sync (&vi->refill); +free_unused_bufs (vi); vdev->config->del_vqs (vi->vdev);
Re: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 1 line · Michael S. Tsirkin
How hard is it to get rid of send queue, btw?
Pretty straight forward. I prefer a separate patch for send side so I didn't include it in this patch.
Thanks
Shirley
Michael S. Tsirkin
maintainerRe: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 8 lines · Shirley Ma
Quoted 1 line · Michael S. Tsirkin
How hard is it to get rid of send queue, btw?
Pretty straight forward. I prefer a separate patch for send side so I didn't include it in this patch.
Thanks
Shirley
Yes, separate patch is best.
Re: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 1 line · Michael S. Tsirkin
Yes, separate patch is best.
The send side patch is done, I will submit it when this patch has been reviewed. :)
thanks
Shirley
Michael S. Tsirkin
maintainerRe: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 5 lines · Shirley Ma
Quoted 1 line · Michael S. Tsirkin
Yes, separate patch is best.
The send side patch is done, I will submit it when this patch has been reviewed. :)
Hey, why wait :)
Quoted 2 lines
thanks
Shirley
Re: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 11 lines · Michael S. Tsirkin
Quoted 4 lines · Shirley Ma
Quoted 1 line · Michael S. Tsirkin
Yes, separate patch is best.
The send side patch is done, I will submit it when this patch has
been
Quoted 1 line
reviewed. :)
Hey, why wait :)
Because: the send side patch is built on top of this patch and uses free_unused_bufs() func.
Thanks
Shirley
Michael S. Tsirkin
maintainerRe: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 9 lines · Shirley Ma
Quoted 8 lines · Michael S. Tsirkin
Quoted 5 lines · Shirley Ma
Quoted 1 line · Michael S. Tsirkin
Yes, separate patch is best.
The send side patch is done, I will submit it when this patch has been reviewed. :)
Quoted 8 lines
Quoted 2 lines
Hey, why wait :)
Because: the send side patch is built on top of this patch and uses
free_unused_bufs()func.Thanks
Shirley
Now that's in, how does the send patch look?
Signature · 1 line
MST
Re: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
Quoted 2 lines · Michael S. Tsirkin
Now that's in, how does the send patch look?
Thanks. I will submit it today. It's a simple patch.
Shirley
Rusty Russell
AppliedRe: [PATCH v2 2/2] virtio_net: Defer skb allocation in receive path
In reply to Shirley MaQuoted 9 lines · Shirley Ma
virtio_netreceives packets from its pre-allocated vring buffers, then it delivers these packets to upper layer protocols as skb buffs. So it's not necessary to pre-allocate skb for each mergable buffer, then frees extra skbs when buffers are merged into a large packet. This patch has deferred skb allocation in receiving packets for both big packets and mergeable buffers to reduce skb pre-allocations and skb frees. It frees unused buffers by callingdetach_unused_bufin vring, so recv skb queue is not needed.Signed-off-by Shirley Ma <[email protected]>
Thanks, applied!
After testing I'll send it to DaveM.
Rusty.
Send skb queue can also be reduced with detach_unused_buf API by it is not a part of this patch.
Shirley