按关键词阅读: virtio 收发 数据包 NET
1、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 i 。
2、nt 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。
3、virtqueue *_vq, unsigned int *len) 返回使用过的buffer , len为写入到buffer中数据的长度 。
获取数据 , 释放buffer,更新vring描述符表格中的index 。
virtqueue_disable_cb() disable_cb()驱动会在初始化时注册一个回调函数 , 的中断 。
device也就是关闭已经使用了 , buffer不再需要知道一个Guest通常在这个virtqueue回调函数中使用 , 用于关闭再次的回调函数调用 。
virtqueue_enable_cb() 与 disable_cb()刚好相反 , 用于重新开启设备中断的上报 。
vring的创建流程 在 。
4、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, virtnet_alloc_que 。
5、ues() -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(*v 。
6、i-rq) * vi-max_queue_pairs, GFP_KERNEL);
if (!vi-rq) goto err_rq;
INIT_DELAYED_WORK(&vi-refill, refill_work);
for (i = 0;
i max_queue_pairs;
i+) vi-rqi.pages = NULL;
netif_napi_add(vi-dev, &vi-rqi.napi, virtnet_poll, napi_weight);
sg_init_table(vi-rqi.sg, ARRAY_SIZE(vi-rqi.sg);
/初始化收端的scatterlist sg 。
7、_init_table(vi-sqi.sg, ARRAY_SIZE(vi-sqi.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, call 。
8、backs, 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 。
9、. 读写状态位;
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, 。
10、 /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的编号 * 。
11、/ 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_ 。
12、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_QU 。
13、EUE_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_QUE 。
14、UE_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( 。
15、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_DEVIC 。
【virtio|virtio net数据包的收发】16、E(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);
个收包队列默认256 n-vqs0.rx_vq = virtio_add_queue(vdev, 256, virtio_net_h 。
17、andle_rx);
vqs0.tx_vq = virtio_add_queue(vdev, 256, virtio_net_handle_tx_bh);
. 64个n-ctrl_vq = virtio_add_queue(vdev, 64, virtio_net_handle_ctrl);
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。
18、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_ 。
19、open = virtnet_open, .ndo_stop = virtnet_close, .ndo_start_xmit = start_xmit, 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 。
20、 virtnet_info *vi = sq-vq-vdev-priv;
unsigned num_sg;
unsigned hdr_len;
bool can_push;
pr_debug(%s: xmit %p %pMn, 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 = 。
21、 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 vi 。
22、rtqueue_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。
23、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_NE 。
24、T_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;
/ 。
25、* 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 。
26、 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 *), uns 。
27、igned int total_out, unsigned int total_in, unsigned int out_sgs, unsigned int in_sgs, void *data, gfp_t gfp) . /* Were about to use some buffers from the free list. */ vq-vq.num_free -= total_sg;
空闲的描述符索引/free_head;
for (n = 0;
n vring.desci.flags = VRING_DESC_F_NEXT;
vq-vring.desci.addr = sg_phys( 。
28、sg);
vq-vring.desci.len = sg-length;
prev = i;
i = vq-vring.desci.next;
/通过next字段找到下一个可用的desc . /* Last one doesnt continue. */ vq-vring.descprev.flags &= VRING_DESC_F_NEXT;
/* Update free pointer */ vq-free_head = i;
add_head: /* Set token. */ vq-datahead = data;
/* Put entry in available array (bu 。
29、t dont update avail-idx until they * do sync). */ avail = (vq-vring.avail-idx & (vq-vring.num-1);
vq-vring.avail-ringavail = 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+ 。
30、;
Kick /* This is very unlikely, but theoretically possible. * just in case. */ ?kickvq-num_added=65536/ if (unlikely(vq-num_added = (1 free_head;
找到第一片可用的;
描述符desc的vringbuffer将需要发送sg list2.从信息读取并填充 addr: guest 的物理地址 的长度buffer :len flags:VRING_DESC_F_NEXT表示该片buffer还有后续片 3.将最后一片占用的desc的flag作下标记 , 表示buff 。
31、er片的终结;
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 tru 。
32、e;
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 noti 。
33、fy 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 queues selector into the notification register to * signal the other end */ iowrite16(vq-index, vp_dev-ioaddr + VIRTIO_PCI_QUEUE_NOTIFY 。
34、);
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);
tx_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + n-tx_timeout);
q-tx_waiting = 1;
virtio_queue_set_notifi 。
35、cation(vq, 0);
. virtio_net_device_realize n-vqs0.tx_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, virtio_net_tx_timer, &n-vqs0);
/创建发包定时器 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 int 。
36、32_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 / -q 。
37、emu_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, 。
38、 len, 0);
/取消内存映射,更新 used_ringidx中的 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 = .typ 。
39、e = 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(NetClientStat 。
40、e *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 virtio_net_receive / ret = nc-info-receive(nc, data, size);
. static ssize_t virtio_net_receive(NetClientState *nc, con 。
41、st uint8_t *buf, size_t size) . while (offset rx_vq, &elem) = 0) rx_vq, &elem, total, i+);
rx_vq, i);
rx_vq);
vp_find_vqs(find_vqs) - vp_try_to_find_vqs - setup_vq callbacksrxq2vq(i) = skb_recv_done;
callbackstxq2vq(i) = skb_xmit_done;
vp_interrupt - vp_vring_interrupt - vring_interrupt - vq-vq.call 。
42、back static void skb_recv_done(struct virtqueue *rvq) struct virtnet_info *vi = rvq-vdev-priv;
struct receive_queue *rq = &vi-rqvq2rxq(rvq);
/* Schedule NAPI, Suppress further interrupts if successful. */ if (napi_schedule_prep(&rq-napi) virtqueue_disable_cb(rvq);
/NAPI的调度函数 。
把设备的napi_struct实例添加到当前CP 。
43、U的softnet_data的poll_list中 ,以便于接下来进行轮询 。
然后设置NET_RX_SOFTIRQ标志位来触发软中断 。
__napi_schedule(&rq-napi);
static int virtnet_poll(struct napi_struct *napi, int budget) . again: while (received vq, &len) != NULL) num;
received+;
if (rq-num max / 2) refill, 0);
/refill_work /* Out of packets? */ if (received vq);
napi_complete(napi);
vq, r) & -qemu端又生产包了 , 则重新开启轮询模式 。
来源:(未知)
【学习资料】网址:/a/2021/0321/0021742163.html
标题:virtio|virtio net数据包的收发