vhost net 的目的是为了避免在host kerne上做一次qemu的调度,提升性能。
xmit: 让vm的数据报在 host的内核就把报文发送出去。
rcv:
核心数据结构
vhost_poll
是vhost里最关键的一个数据结构。
1 2 3 4 5 6 7 8 9 10
| 27 /* Pol> > file (eventfd or socket) */ 28 /* Note: there's nothing vhost specific about this structure. */ 29 struct vhost_poll { 3> > > > poll_tabl> > > > > > > > table; 3> > > > wait_queue_head_> > > > *wqh; 3> > > > wait_queue_> > > > > > wait; 3> > > > struct vhost_wor> > > > work; 3> > > > unsigned lon> > > > > > mask; 3> > > > struct vhost_de> > > > *dev; 36 };
|
- table:每次负责把wait域放倒wqh里。vhost_net_open将它的执行函数vhost_poll_func
- wqh:它的wqh被初始化指向一个eventfd的ctx,
- wait:每次把wait放倒这个wqh链表里,当guest vm的发送报文时,wait被摘下,
并执行其对应的func,vhost_net_open将该func被初始化为vhost_poll_wakeup。
vhost_poll_wakeup负责将work放入对应vhost_dev下的work_list链表中。
- work: 每个vhost_dev有一个thread,负责从work_list链表里的摘除work节点,
并执行work节点对应的fn. fn是真正干活的的函数。
对于rx vhost_virqueue, vhost_net_open将该fn初始化为handle_rx_kick
对于tx vhost_virqueue, vhost_net_open将该fn初始化为handle_tx_kick
对于rx vhost_virqueue, vhost_net_open将该fn初始化为
handle_rx_kick.
- mask:是需要监听的eventfd的事件集合
- dev: 该vhost_poll对应的vhost_dev;
以guest VM发送一个报文为例
1 2 3 4 5 6 7 8 9 10
| file: drivers/net/virtio_net.c > .ndo_start_xmi> > > > start_xmit, > > static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) > > > er> > xmit_skb(sq, skb); > > > > return virtqueue_add_outbuf(sq->vq, sq->sg, num_sg, skb, GFP_ATOMIC); > > > > > return virtqueue_add(vq, &sg, num, 1, 0, data, gfp); > > > virtqueue_kick(sq->vq); > > > > virtqueue_notify(vq); > > > > > vq->notify(_vq) > > > > > > iowrite16(vq->index, (void __iomem *)vq->priv);
|
前端驱动最终guest vm因io操作造成vm exit. host vm 调用
处理这个io 请求。
1 2
| static int vmx_handle_exit(struct kvm_vcpu *vcpu) kvm_vmx_exit_handlers[exit_reason](vcpu);
|
1 2 3 4 5 6 7 8 9 10 11 12
| > > > kvm_vmx_exit_handlers: [EXIT_REASON_IO_INSTRUCTION> > > > > > handle_io, > > > static int handle_io(struct kvm_vcpu *vcpu) > > > > return kvm_fast_pio_out(vcpu, size, port); > > > > > static int emulator_pio_out_emulated(struct x86_emulate_ctxt *ctxt, int size, unsigned short port, const void *val, unsigned int count) > > > > > > emulator_pio_in_out > > > > > > kernel_pio(struct kvm_vcpu *vcpu, void *pd) > > > > > > > int kvm_io_bus_write(struct kvm_vcpu *vcpu, enum kvm_bus bus_idx, gpa_t addr, > > > > > > > > __kvm_io_bus_write > > > > > > > > > dev->ops->write(vcpu, dev, addr, l, v) > > > > > > > > > dev->ops is ioeventfd_ops 相当ioeventfd_write > > > > > > > > > > eventfd_signal(p->eventfd, 1); > > > > > > > > > > > wake_up_locked_poll(&ctx->wqh, POLLIN);
|
vhost net init
1 2 3 4 5 6 7 8 9
| vhost_net_open . vhost_dev_init > > vhost_poll_init(&vq->poll, vq->handle_kick, POLLIN, dev); > > . init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup); > > . init_poll_funcptr(&poll->table, vhost_poll_func); > > . vhost_work_init(&poll->work, fn); . vhost_poll_init(n->pol> > VHOST_NET_VQ_TX, handle_tx_net, POLLOUT, dev); . vhost_poll_init(n->pol> > VHOST_NET_VQ_RX, handle_rx_net, POLLIN, dev); . f->private_dat> > n;
|
vhost_worker
vhost set owner: 为每个vhost_dev,启动一个进程,该进程函数是vhost_worker,
它反复从该dev下的work_list链上取一个节点下来,
(该节点类型是vhost_work), 执行该节点下的fn函数。
每个dev下有个双向链work_list, 链中的每个节点是一个vhost_work
vhost_worker
顺序摘下一个节点,执行这个节点下的fn函数。
对于vhost_virtqueue RX/TX来说,这个fn就是`handle_rx_kick,handle_tx_kick.
1 2 3 4 5
| > vhost_net_set_owner > > vhost_dev_set_owner > > > worke> > kthread_create(vhost_worker, dev, "vhost-%d", current->pid); > > > dev->worke> > worker; > > > wake_up_process(worker);
|
每个vhost_virqueue下有一个vhost_poll。
VHOST_SET_VRING_KICK,将vhost_poll下的wqh(wait_queue_head_t类型)指向eventf下对应
的wqh,并将vhost_poll下的wait挂在wqh下。
poll->wqh被赋值指向 eventfd对应的eventfd_ctx下的wqh, 这样一个virqueue下的poll就跟eventfd关联上了!!!
最终poll的wait被放入到了eventfd的wqh链表里。
vhost_net_ioctl VHOST_SET_VRING_KICK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| vhost_vring_ioctl . case VHOST_SET_VRING_KICK: > > eventf> > f.fd == -> > NUL> > eventfd_fget(f.fd); > > vq->kic> > eventfp > > vhost_poll_start(&vq->poll, vq->kick); > > . mas> > file->f_op->poll(file, &poll->table); 此处file是vq->kick, 即vq对应的eventfd > > . 所以相当于调用 eventfd_poll > > > > poll_wait(file, &ctx->wqh, wait); > > . vhost_poll_wakeup(&poll->wait, 0, 0, (void *)mask); > > > > vhost_poll_queue(poll); > > > > . vhost_work_queue(poll->dev, &poll->work); > > > > > > list_add_tail(&work->node, &dev->work_list); > > > > > > wake_up_process(dev->worker); > > > > > > . static int vhost_worker(void *data) > > > > > > > > wor> > list_first_entry(&dev->work_list, struct vhost_work, node); > > > > > > > > work->fn(work);
|
eventfd在vhost net里被用来处理从vm发出的报文。
以vhost_dev_net 下的rx virqueue为例。
poll是该virqueue下的poll。
1 2 3 4 5 6 7
| eventfd_poll(struct file *file, poll_table *wait) file:vrqueue使用的eventfd. wait virqueue下的tx poll对应的poll_table . poll_wait(file, &ctx->wqh, wait); > > p->_qproc(filp, wait_address, p); > > 相当于 vhost_poll_func(filp, wait_address, p) p为poll table。 _qproc: 是vhost_poll_func。通过vhost_dev_init入口,被init_poll_funcptr(&poll->table, vhost_poll_func); wait_address:
|
vhost_poll_func:每次都将vhost_poll下的wait放倒vhost_poll下的wqh链表里(即evetnfd下的ctx的wqh链表里)。
以为它被初始化为vhost_poll下的poll_table的函数指针,因此对vhost_poll下的poll_table调用poll_wait的时候,
相当于对vhost_poll调用vhost_poll_func。
vhost_poll_wakeup 将传入的wait转换为一个vhost poll
将该poll下work挂在到poll对应的vhost_dev.
1 2 3 4 5
| vhost_poll_wakeup > vhost_poll_queue(poll); > > vhost_work_queue(poll->dev, &poll->work); > > > list_add_tail(&work->node, &dev->work_list); > > > wake_up_process(dev->worker);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 117 static unsigned int eventfd_poll(struct file *file, poll_table *wait) 118 { 11> > > > struct eventfd_ctx *ct> > file->private_data; 12> > > > unsigned int event> > 0; 12> > > > u64 count; 122 12> > > > poll_wait(file, &ctx->wqh, wait); 12> > > > smp_rmb(); 12> > > > coun> > ctx->count; 126 12> > > > if (coun> > 0) 12> > > > > > > > events |= POLLIN; 12> > > > if (count == ULLONG_MAX) 13> > > > > > > > events |= POLLERR; 13> > > > if (ULLONG_MA> > > > count) 13> > > > > > > > events |= POLLOUT; 133 13> > > > return events; 135 }
|
1 2 3 4 5
| 42 static inline void poll_wait(struct fil> > filp, wait_queue_head_> > wait_address, poll_table *p) 43 { 4> > > > if (p && p->_qproc && wait_address) 4> > > > > > > > p->_qproc(filp, wait_address, p); 46 }
|
TODO1: Why vp->notify
is vp_notify
1 2 3 4 5 6 7 8 9 10 11 12
| drivers/virtio/virtio_pci_modern.c 315 static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev, 31> > > > > > > > > > > > > > > > > > struct virtio_pci_vq_info *info, 31> > > > > > > > > > > > > > > > > > unsigned index, 31> > > > > > > > > > > > > > > > > > void (*callback)(struct virtqueue *vq), 31> > > > > > > > > > > > > > > > > > const char *name, 32> > > > > > > > > > > > > > > > > > u16 msix_vec) ... 35> > > > /* create the vring */ 35> > > > v> > vring_new_virtqueue(index, info->num, 35> > > > > > > > > > > > > > > > SMP_CACHE_BYTES, &vp_dev->vdev, 35> > > > > > > > > > > > > > > > true, info->queue, vp_notify, callback, name);
|
TODO2: why vm exit call vmx_handle_exit
TODO3: why dev->ops->write 相当
TODO4: “wake_up_locked_poll add_wait_queue 为例说明wait list的使用。
__wake_up_common “
1 2 3 4
| wake_up_locked_poll > __wake_up_locked_key > > list_for_each_entry_safe(curr, next, &q->task_list, task_list) { > > > curr->func(curr, mode, wake_flags, key)
|