为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

virtio_net数据包的收发

2017-12-10 27页 doc 123KB 129阅读

用户头像

is_882336

暂无简介

举报
virtio_net数据包的收发virtio_net数据包的收发 virtio-net数据包的收发 virtio设备创建 vring的创建流程 Ring的内存分布 发送 接收 virtio设备创建 在virtio设备创建过程中,形成的数据结构如图所示: 从图中可以看出,virtio-netdev关联了两个virtqueue,包括一个send queue和一个receive queue,而具体的queue的实现由vring来承载。 针对 virtqueue 的操作包括: int virtqueue_add_buf( struct vir...
virtio_net数据包的收发
virtio_net数据包的收发 virtio-net数据包的收发 virtio设备创建 vring的创建 Ring的内存分布 发送 接收 virtio设备创建 在virtio设备创建过程中,形成的数据结构如图所示: 从图中可以看出,virtio-netdev关联了两个virtqueue,包括一个send queue和一个receive queue,而具体的queue的实现由vring来承载。 针对 virtqueue 的操作包括: int virtqueue_add_buf( struct virtqueue *_vq, struct scatterlist sg[], unsigned int out, unsigned int in, void *data, gfp_t gfp) add_buf()用于向queue中添加一个新的buffer,参数data是一个非空的令牌,用于识别 buffer,当buffer内容被消耗后,data会返回。 virtqueue_kick() Guest 通知 host 单个或者多个 buffer 已经添加到 queue 中,调用 virtqueue_notify(),notify 函数会向 queue notify(VIRTIO_PCI_QUEUE_NOTIFY)寄存器写入 queue index 来通知 host。 void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len) 返回使用过的buffer,len为写入到buffer中数据的长度。获取数据,释放buffer,更新vring描述符格中的index。 virtqueue_disable_cb() Guest不再需要知道一个buffer已经使用了,也就是关闭device的中断。驱动会在初始化时注册一个回调函数,disable_cb() 通常在这个virtqueue回调函数中使用,用于关闭再次的回调函数调用。 virtqueue_enable_cb() 与 disable_cb()刚好相反,用于重新开启设备中断的上报。 vring的创建流程 在virtio_net dev driver被加载后,会调用virtnet_probe进行设备的识别,创建,初始化。 static struct virtio_driver virtio_net_driver = { .feature_table = features, .feature_table_size = ARRAY_SIZE(features), .driver.name = KBUILD_MODNAME, .driver.owner = THIS_MODULE, .id_table = id_table, .probe = virtnet_probe, <------------识别,初始化入口 .remove = virtnet_remove, .config_changed = virtnet_config_changed, #ifdef CONFIG_PM_SLEEP .freeze = virtnet_freeze, .restore = virtnet_restore, #endif }; 其中进行了virtio net device的属性功能的配置,网络设备的初始化和注册,而vring的创建也在其中: /* Allocate/initialize the rx/tx queues, and invoke find_vqs */ init_vqs(vi); //创建和初始化发送/接收队列 --->virtnet_alloc_queues() --->virtnet_find_vqs() virtnet_alloc_queues创建了图中virtnet_info中的send_queue 和 receive_queue结构,seng和receive queue是成对出现的。 static int virtnet_alloc_queues(struct virtnet_info *vi) { int i; vi->sq = kzalloc(sizeof(*vi->sq) * vi->max_queue_pairs, GFP_KERNEL); if (!vi->sq) goto err_sq; vi->rq = kzalloc(sizeof(*vi->rq) * vi->max_queue_pairs, GFP_KERNEL); if (!vi->rq) goto err_rq; INIT_DELAYED_WORK(&vi->refill, refill_work); for (i = 0; i < vi->max_queue_pairs; i++) { vi->rq[i].pages = NULL; netif_napi_add(vi->dev, &vi->rq[i].napi, virtnet_poll, napi_weight); sg_init_table(vi->rq[i].sg, ARRAY_SIZE(vi->rq[i].sg)); //初始化收端的scatterlist sg_init_table(vi->sq[i].sg, ARRAY_SIZE(vi->sq[i].sg)); //初始化发端的scatterlist } return 0; err_rq: kfree(vi->sq); err_sq: return -ENOMEM; } scatterlist即是一种数组形式的数据结构,每一项成员指向一个page的地址,偏移量,长度等。 通过find vqs来创建vring: static int virtnet_find_vqs(struct virtnet_info *vi) { ...... vi->vdev->config->find_vqs(vi->vdev, total_vqs, vqs, callbacks, names); //vp_find_vqs -> vp_try_to_find_vqs->setup_vq ...... } vdev所对应的config的初始化在pci bus probe阶段: static int virtio_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) { ...... vp_dev->vdev.config = &virtio_pci_config_ops; ...... } virtio_pci_config_ops是针对virtio设备配置的操作,主要包括四个部分: 1. 读写特征位;2. 读写配置空间;3. 读写状态位;4. 重启设备. static const struct virtio_config_ops virtio_pci_config_ops = { .get = vp_get, //读取virtio配置空间的域 .set = vp_set, //设置virtio配置空间的域 .get_status = vp_get_status, //读取状态位 .set_status = vp_set_status, //设置状态位 .reset = vp_reset, //设备的复位 .find_vqs = vp_find_vqs, //virtqueue的创建 .del_vqs = vp_del_vqs, //virtqueue的删除 .get_features = vp_get_features, .finalize_features = vp_finalize_features, .bus_name = vp_bus_name, .set_vq_affinity = vp_set_vq_affinity, }; 其中最核心的是setup_vq(): /* 函数功能:为目标设备获取一个queue vdev:目标设备 index:给目标设备使用的queue的编号 callback:queue的回调函数 name:queue的名字 msix_vec:给queue使用的msix vector的编号 */ static struct virtqueue *setup_vq(struct virtio_device *vdev, unsigned index, void (*callback)(struct virtqueue *vq), const char *name, u16 msix_vec) { //通过VIRTIO_PCI_QUEUE_SEL配置域,选择我们需要的queue的编号index iowrite16(index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL); //virtio_ioport_write //#define VIRTQUEUE_MAX_SIZE 1024 //通过读取VIRTIO_PCI_QUEUE_NUM配置域,获取index编号的queue的大小 num = ioread16(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NUM); //qemu端决定queue的大小, virtio_queue_set_num /* 如果num为0,则该queue不可用 通过读取VIRTIO_PCI_QUEUE_PFN配置域返回queue的地址, 如果该queue的地址非空,则说明已经在被使用了,该queue不可用。 */ if (!num || ioread32(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN)) return ERR_PTR(-ENOENT); info = kmalloc(sizeof(struct virtio_pci_vq_info), GFP_KERNEL); //计算vring所需要的空间大小 size = PAGE_ALIGN(vring_size(num, VIRTIO_PCI_VRING_ALIGN)); //分配size大小的若干个page空间为vring所用 info->queue = alloc_pages_exact(size, GFP_KERNEL|__GFP_ZERO); /* activate the queue 通过VIRTIO_PCI_QUEUE_PFN配置域将vring的地址通告给qemu, 这样index编号的queue有大小,有空间qemu通过该块vring的 共享空间与guest进行数据的交互 */ iowrite32(virt_to_phys(info->queue) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN); //qemu: virtio_queue_set_addr->virtqueue_init /* create the vring */ //对vring内部结构进行具体的初始化 vq = vring_new_virtqueue(index, info->num, VIRTIO_PCI_VRING_ALIGN, vdev, true, info->queue, vp_notify, callback, name); } 关于num的初始值,在qemu初始化virtionet设备时进行了初始化,queue size 即kernel中读取的num初始化为256个 (file:hw/virtio-net.c, line:1597): static void virtio_net_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VirtIONet *n = VIRTIO_NET(dev); NetClientState *nc; int i; virtio_init(vdev, "virtio-net", VIRTIO_ID_NET, n->config_size); n->max_queues = MAX(n->nic_conf.peers.queues, 1); n->vqs = g_malloc0(sizeof(VirtIONetQueue) * n->max_queues); n->vqs[0].rx_vq = virtio_add_queue(vdev, 256, virtio_net_handle_rx); <------------收包队列默认256个 ...... n->vqs[0].tx_vq = virtio_add_queue(vdev, 256, <------------发包队列默认256个 virtio_net_handle_tx_bh); ...... n->ctrl_vq = virtio_add_queue(vdev, 64, virtio_net_handle_ctrl); <------------控制队列默认64个 ...... } virtio_ring的具体结构包含3部分: 描述符数组(descriptor table)用于存储一些关联的描述符,每个描述符都是一个对buffer的描述,包含一个 address/length的配对。 可用的ring(available ring)用于Guest端表示那些描述符链当前是可用的。 使用过的ring(used ring)用于Host端表示那些描述符已经使用。 driver(guest)提供 buffers 给设备,处理 device(host)使用过的 buffers。 Ring的数目必须是2的次幂。结构如下图所示: vring descriptor 用于指向 guest 使用的 buffer。 addr:guest 物理地址 len:buffer 的长度 flags:flags 的值含义包括: VRING_DESC_F_NEXT:用于表明当前 buffer 的下一个域是否有效,也间接表明当前 buffer 是否是 buffers list 的最后 一个。 VRING_DESC_F_WRITE:当前 buffer 是 read-only 还是 write-only,可写表示这个desc是在收包时,host端填充收包 数据用的。 VRING_DESC_F_INDIRECT:表明这个 buffer 中包含一个 buffer 描述符的 list next:所有的 buffers 通过next串联起来组成descriptor table 多个buffer组成一个list由descriptor table指向这些list。 Available ring 指向 guest 提供给设备的描述符,它指向一个 descriptor 链表的头。 Used ring 指向 device(host)使用过的 buffers。 Ring的内存分布 /* The standard layout for the ring is a continuous chunk of memory which looks * like this. We assume num is a power of 2. * * struct vring * { * // The actual descriptors (16 bytes each) * struct vring_desc desc[num]; * * // A ring of available descriptor heads with free-running index. * __u16 avail_flags; * __u16 avail_idx; * __u16 available[num]; * __u16 used_event_idx; //guest端写, * * // Padding to the next align boundary. * char pad[]; * * // A ring of used descriptor heads with free-running index. * __u16 used_flags; * __u16 used_idx; * struct vring_used_elem used[num]; * __u16 avail_event_idx; //qemu端写, virtio_net_tx_bh->virtio_queue_set_notification(q->tx_vq, 1); * }; */ /* We publish the used event index at the end of the available ring, and vice * versa. They are at the end for backwards compatibility. */ #define vring_used_event(vr) ((vr)->avail->ring[(vr)->num]) #define vring_avail_event(vr) (*(__u16 *)&(vr)->used->ring[(vr)->num]) 发送 Guest端: 当Kernel中的网络数据包从内核协议栈下来后,必然要走到设备注册的发送函数, virtio_net 网卡驱动注册的的发送函数为start_xmit。 static const struct net_device_ops virtnet_netdev = { .ndo_open = virtnet_open, .ndo_stop = virtnet_close, .ndo_start_xmit = start_xmit, <-------------------- 发包函数 .ndo_validate_addr = eth_validate_addr, .ndo_set_mac_address = virtnet_set_mac_address, .ndo_set_rx_mode = virtnet_set_rx_mode, .ndo_change_mtu = virtnet_change_mtu, .ndo_get_stats64 = virtnet_stats, .ndo_vlan_rx_add_vid = virtnet_vlan_rx_add_vid, .ndo_vlan_rx_kill_vid = virtnet_vlan_rx_kill_vid, .ndo_select_queue = virtnet_select_queue, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = virtnet_netpoll, #endif }; static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) { ...... /* Free up any pending old buffers before queueing new ones. */ free_old_xmit_skbs(sq); <----------------释放qemu端消化掉的desc /* Try to transmit */ err = xmit_skb(sq, skb); ...... virtqueue_kick(sq->vq); ...... return NETDEV_TX_OK; } 在start_xmit()中,主要的操作是使数据包入vring队列: static int xmit_skb(struct send_queue *sq, struct sk_buff *skb) { struct skb_vnet_hdr *hdr; const unsigned char *dest = ((struct ethhdr *)skb->data)->h_dest; struct virtnet_info *vi = sq->vq->vdev->priv; unsigned num_sg; unsigned hdr_len; bool can_push; pr_debug("%s: xmit %p %pM\n", vi->dev->name, skb, dest); if (vi->mergeable_rx_bufs) hdr_len = sizeof hdr->mhdr; else hdr_len = sizeof hdr->hdr; ...... if (can_push) hdr = (struct skb_vnet_hdr *)(skb->data - hdr_len); else hdr = skb_vnet_hdr(skb); ...... if (can_push) { __skb_push(skb, hdr_len); num_sg = skb_to_sgvec(skb, sq->sg, 0, skb->len); /* Pull header back to avoid skew in tx bytes calculations. */ __skb_pull(skb, hdr_len); } else { sg_set_buf(sq->sg, hdr, hdr_len); num_sg = skb_to_sgvec(skb, sq->sg + 1, 0, skb->len) + 1; } return virtqueue_add_outbuf(sq->vq, sq->sg, num_sg, skb, GFP_ATOMIC); } 在每个进入scatter-gather list的packet之前,需要有一个virtio_net_hdr结构的头部信息,用以 支持checksum offload与TCP/UDP Segmentation offload。所以在上述流程中先使用sg_set_buf(sq->sg, hdr, hdr_len)将virtio-net-hdr的buffer填入了scatter-gather list,如下是virtio_net_hdr的结构: struct skb_vnet_hdr { union { struct virtio_net_hdr hdr; struct virtio_net_hdr_mrg_rxbuf mhdr; }; }; struct virtio_net_hdr { #define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 // Use csum_start, csum_offset #define VIRTIO_NET_HDR_F_DATA_VALID 2 // Csum is valid __u8 flags; #define VIRTIO_NET_HDR_GSO_NONE 0 // Not a GSO frame #define VIRTIO_NET_HDR_GSO_TCPV4 1 // GSO frame, IPv4 TCP (TSO) #define VIRTIO_NET_HDR_GSO_UDP 3 // GSO frame, IPv4 UDP (UFO) #define VIRTIO_NET_HDR_GSO_TCPV6 4 // GSO frame, IPv6 TCP #define VIRTIO_NET_HDR_GSO_ECN 0x80 // TCP has ECN set __u8 gso_type; __u16 hdr_len; /* Ethernet + IP + tcp/udp hdrs */ __u16 gso_size; /* Bytes to append to hdr_len per frame */ __u16 csum_start; /* Position to start checksumming from */ __u16 csum_offset; /* Offset after that to place checksum */ }; skbuffer与sg list的关系 最后调用return virtqueue_add_outbuf(sq->vq, sq->sg, num_sg, skb, GFP_ATOMIC);进入vring操作阶段。 int virtqueue_add_outbuf(struct virtqueue *vq, struct scatterlist sg[], unsigned int num, void *data, gfp_t gfp) { return virtqueue_add(vq, &sg, sg_next_arr, num, 0, 1, 0, data, gfp); } static inline int virtqueue_add(struct virtqueue *_vq, struct scatterlist *sgs[], struct scatterlist *(*next) (struct scatterlist *, unsigned int *), unsigned int total_out, unsigned int total_in, unsigned int out_sgs, unsigned int in_sgs, void *data, gfp_t gfp) { ............ /* We're about to use some buffers from the free list. */ vq->vq.num_free -= total_sg; head = i = vq->free_head; //<-------------------- 空闲的描述符索引 for (n = 0; n < out_sgs; n++) { for (sg = sgs[n]; sg; sg = next(sg, &total_out)) { vq->vring.desc[i].flags = VRING_DESC_F_NEXT; vq->vring.desc[i].addr = sg_phys(sg); vq->vring.desc[i].len = sg->length; prev = i; i = vq->vring.desc[i].next; //通过next字段找到下一个可用的desc } } ............ /* Last one doesn't continue. */ vq->vring.desc[prev].flags &= ~VRING_DESC_F_NEXT; /* Update free pointer */ vq->free_head = i; add_head: /* Set token. */ vq->data[head] = data; /* Put entry in available array (but don't update avail->idx until they * do sync). */ avail = (vq->vring.avail->idx & (vq->vring.num-1)); vq->vring.avail->ring[avail] = head; /* Descriptors and available array need to be set before we expose the * new available array entries. */ virtio_wmb(vq->weak_barriers); vq->vring.avail->idx++; vq->num_added++; /* This is very unlikely, but theoretically possible. Kick * just in case. */ if (unlikely(vq->num_added == (1 << 16) - 1)) //为什么vq->num_added==65536时,才kick, virtqueue_kick(_vq); pr_debug("Added buffer head %i to %p\n", head, vq); END_USE(vq); return 0; } 1.从head = i = vq->free_head;找到第一片可用的desc; 2.从sg list将需要发送buffer信息读取并填充vring的desc描述符; addr: guest的物理地址 len: buffer的长度 flags:VRING_DESC_F_NEXT表示该片buffer还有后续片 3.将最后一片占用的desc的flag作下标记,表示buffer片的终结; 4.更新空闲desc的指针; 5.将skb保存在data[]中作为token,用完后再释放; 6.更新avail描述符,将待发送的第一片buffer在desc中的序号写入空闲的avail ring中,并更新avail描述队列的序号等。 操作的关系图: 在start_xmit中,待发送的信息入队列后,使用virtqueue_kick(sq->vq)通告Host端; bool virtqueue_kick(struct virtqueue *vq) { if (virtqueue_kick_prepare(vq)) return virtqueue_notify(vq); return true; } bool virtqueue_notify(struct virtqueue *_vq) { struct vring_virtqueue *vq = to_vvq(_vq); if (unlikely(vq->broken)) return false; /* Prod other side to tell it about changes. */ if (!vq->notify(_vq)) { //setup_vq->vring_new_virtqueue(.... ,vp_notify, ....); vq->broken = true; return false; } return true; } /* the notify function used when creating a virt queue */ static bool vp_notify(struct virtqueue *vq) { struct virtio_pci_device *vp_dev = to_vp_device(vq->vdev); /* we write the queue's selector into the notification register to * signal the other end */ iowrite16(vq->index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY); return true; } Qemu端: void virtio_queue_notify_vq(VirtQueue *vq) { if (vq->vring.desc) { VirtIODevice *vdev = vq->vdev; trace_virtio_queue_notify(vdev, vq - vdev->vq, vq); vq->handle_output(vdev, vq); <-----------------virtio_net_handle_tx_timer/virtio_net_handle_tx_bh } } #define TX_TIMER_INTERVAL 150000 /* 150 us */ static void virtio_net_handle_tx_timer(VirtIODevice *vdev, VirtQueue *vq) { ............ timer_mod(q->tx_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + n->tx_timeout); q->tx_waiting = 1; virtio_queue_set_notification(vq, 0); ............ } virtio_net_device_realize n->vqs[0].tx_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, virtio_net_tx_timer, &n->vqs[0]); //创建发包定时器 static void virtio_net_tx_timer(void *opaque) { ............ virtio_queue_set_notification(q->tx_vq, 1); //将avail_idx的值写入avail_event_idx中 virtio_net_flush_tx(q); } /* TX */ static int32_t virtio_net_flush_tx(VirtIONetQueue *q) { ............ while (virtqueue_pop(q->tx_vq, &elem)) { //从queue中取出描述符,填充进elem ............ ret = qemu_sendv_packet_async(qemu_get_subqueue(n->nic, queue_index), out_sg, out_num, virtio_net_tx_complete); //qemu_sendv_packet_async -> qemu_net_queue_send_iov->qemu_net_queue_deliver_iov // ->qemu_deliver_packet_iov->tap_receive_iov->tap_write_packet ............ len += ret; virtqueue_push(q->tx_vq, &elem, 0); virtio_notify(vdev, q->tx_vq); if (++num_packets >= n->tx_burst) { break; } } return num_packets; } void virtqueue_push(VirtQueue *vq, const VirtQueueElement *elem, unsigned int len) { virtqueue_fill(vq, elem, len, 0); //取消内存映射,更新 used_ring[idx]中的 id 和 len 字段 virtqueue_flush(vq, 1); //更新 vring_used 中的 idx } 接收 Qemu端: Tap_send -> tap_read_packet -> qemu_send_packet_async -> qemu_send_packet_async_with_flags -> qemu_net_queue_send -> qemu_deliver_packet -> virtio_net_receive static NetClientInfo net_virtio_info = { .type = NET_CLIENT_OPTIONS_KIND_NIC, .size = sizeof(NICState), .can_receive = virtio_net_can_receive, .receive = virtio_net_receive, .cleanup = virtio_net_cleanup, .link_status_changed = virtio_net_set_link_status, .query_rx_filter = virtio_net_query_rxfilter, }; ssize_t qemu_deliver_packet(NetClientState *sender,unsigned flags,const uint8_t *data,size_t size,void *opaque) { ....... if (flags & QEMU_NET_PACKET_FLAG_RAW && nc->info->receive_raw) { ret = nc->info->receive_raw(nc, data, size); } else { ret = nc->info->receive(nc, data, size); //virtio_net_receive } ....... } static ssize_t virtio_net_receive(NetClientState *nc, const uint8_t *buf, size_t size) { ...... while (offset < size) { VirtQueueElement elem; int len, total; const struct iovec *sg = elem.in_sg; total = 0; if (virtqueue_pop(q->rx_vq, &elem) == 0) { <--------从avail ring中取一个desc出来 ...... } ...... /* signal other side */ virtqueue_fill(q->rx_vq, &elem, total, i++); <----------填充used ring } ...... virtqueue_flush(q->rx_vq, i); <----------更新used idx virtio_notify(vdev, q->rx_vq); <----------通知guest机 return size; } Guest端: virtnet_find_vqs -> vp_find_vqs(find_vqs) -> vp_try_to_find_vqs -> setup_vq callbacks[rxq2vq(i)] = skb_recv_done; callbacks[txq2vq(i)] = skb_xmit_done; vp_interrupt -> vp_vring_interrupt -> vring_interrupt -> vq->vq.callback static void skb_recv_done(struct virtqueue *rvq) { struct virtnet_info *vi = rvq->vdev->priv; struct receive_queue *rq = &vi->rq[vq2rxq(rvq)]; /* Schedule NAPI, Suppress further interrupts if successful. */ if (napi_schedule_prep(&rq->napi)) { virtqueue_disable_cb(rvq); //NAPI的调度函数。把设备的napi_struct实例添加到当前CPU的softnet_data的poll_list中, 以便于接下来进行轮询。然后设置NET_RX_SOFTIRQ标志位来触发软中断。 __napi_schedule(&rq->napi); } } static int virtnet_poll(struct napi_struct *napi, int budget) { ...... again: while (received < budget && (buf = virtqueue_get_buf(rq->vq, &len)) != NULL) { <-----从used ring中取包数据 receive_buf(rq, buf, len); --rq->num; received++; } if (rq->num < rq->max / 2) { <--------如果收包队列中可用的desc小于总大小的一半时,回收 if (!try_fill_recv(rq, GFP_ATOMIC)) <-----由于内存不够没有全部回收成功,丢给refill工作队列去干 schedule_delayed_work(&vi->refill, 0); //refill_work } /* Out of packets? */ if (received < budget) { <---------包不多,则开启中断模式 r = virtqueue_enable_cb_prepare(rq->vq); napi_complete(napi); <---------结束轮询 if (unlikely(virtqueue_poll(rq->vq, r)) && <---------qemu端又生产包了,则重新开启轮询模式 napi_schedule_prep(napi)) { virtqueue_disable_cb(rq->vq); __napi_schedule(napi); goto again; } } return received; } virtnet_probe -> init_vqs -> virtnet_alloc_queues -> netif_napi_add(vi->dev, &vi->rq[i].napi, virtnet_poll, napi_weight); static int virtnet_alloc_queues(struct virtnet_info *vi) { ...... INIT_DELAYED_WORK(&vi->refill, refill_work); for (i = 0; i < vi->max_queue_pairs; i++) { vi->rq[i].pages = NULL; netif_napi_add(vi->dev, &vi->rq[i].napi, virtnet_poll, <------注册轮询函数,指定每次最大处理的包数 napi_weight); sg_init_table(vi->rq[i].sg, ARRAY_SIZE(vi->rq[i].sg)); sg_init_table(vi->sq[i].sg, ARRAY_SIZE(vi->sq[i].sg)); } ...... } static int virtnet_open(struct net_device *dev) <-----网卡被up起来时调用 { struct virtnet_info *vi = netdev_priv(dev); int i; for (i = 0; i < vi->max_queue_pairs; i++) { if (i < vi->curr_queue_pairs) /* Make sure we have some buffers: if oom use wq. */ if (!try_fill_recv(&vi->rq[i], GFP_KERNEL)) <------填充desc schedule_delayed_work(&vi->refill, 0); virtnet_napi_enable(&vi->rq[i]); } return 0; }
/
本文档为【virtio_net数据包的收发】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索