惠伟:virtio代码分析(一)-qemu部分zhuanlan.zhihu.com
假如我们加个参数vhost=on,vhost定义了一堆api,qemu把virtio收发包和用于通知收发包的功能offload给kernel vhost-net了,这包就不用从kernel到用户态的qemu,再从qemu共享给guest,直接从kernel共享给guest,减少一次kernel到用户态qemu的复制开销。
qemu对vhost-net初始化,重点关注qemu把address_space发给kernel vhost-net了,同时内核创建了一个线程叫做vhost-worker,是真正收发包干活的。
代码语言:javascript复制vhost_dev_init
├─vhost_virtqueue_init
| └─vhost_set_vring_call
└─memory_listener_register
└─listener_add_address_space
├─vhost_begin
├─vhost_log_global_start
| └─vhost_migration_log
| └─vhost_dev_set_log
| └─vhost_kernel_set_vring_add
├─vhost_log_start
├─vhost_region_addnop
| └─vhost_region_add_section
└─vhost_commit
└─vhost_kernel_set_mem_table(VHOST_SET_MEM_TABLE)
└─调用到内核vhost_set_memory
└─vhost_new_umem_range
通知机制这样处理,把负责通知的fd分别给了kvm和vhost-net,以后kvm就通知给vhost-net了,不再和qemu通信。如果qemu模拟中断,处理中断注入和开关中断 。还有一点就把把内核vring的三个地址设置和用户态qemu一样,而qemu又来于guest中virtio-net driver写pci配置,所以最终host kernel vhost net就和guest中driver指向同一个地址了。
代码语言:javascript复制vhost_net_start
├─virtio_pci_set_guest_notifiers
| └─virtio_pci_set_guest_notifier
├─vhost_net_start_one
| ├─vhost_net_start_one
| | └─vhost_dev_enable_notifiers
| | └─virtio_bus_set_host_notifier
| | ├─event_notifier_init
| | └─virtio_pci_ioeventfd_assign
| └─vhost_dev_start
| └─vhost_virtqueue_start
| ├─vhost_kernel_set_vring_num
| ├─vhost_kernel_set_vring_base把内核vring的三个地址设置和用户态一样
| ├─vhost_kernel_set_vring_endian
| ├─vhost_virtqueue_set_addr
| | └─vhost_kernel_set_vring_addr
| ├─vhost_kernel_set_vring_kick
| └─vhost_kernel_set_vring_call
└─vhost_set_vring_enable
内核vhost的结构体,很虚拟vhost_net内嵌vhost_dev,vhost_dev有2 2*queue个vhost_poll。
代码语言:javascript复制struct vhost_net {
struct vhost_dev dev;
struct vhost_net_virtqueue vqs[VHOST_NET_VQ_MAX];
struct vhost_poll poll[VHOST_NET_VQ_MAX];
};
内核初始化
代码语言:javascript复制vhost_net_open
├─vhost_dev_init
| └─vhost_poll_init
├─vhost_poll_init
└─vhost_poll_init
vhost_net_ioctl
├─vhost_net_set_backend
| └─vhost_net_enable_vq
| └─vhost_poll_start//把tap的fd给vhost
├─vhost_net_set_features
├─vhost_net_set_owner
├─vhost_dev_ioctl
| ├─vhost_set_memory
| ├─vhost_set_log_base
| └─vhost_set_log_fd
└─vhost_vring_ioctl
├─set vring相关的那一坨
└─vhost_poll_start
qemu进程系统调用init所有poll
代码语言:javascript复制vhost_poll_init
├─init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup)
├─init_poll_funcptr(&poll->table, vhost_poll_func)
└─vhost_work_init
qemu对/dev/vhost-net开始poll那2个poll
代码语言:javascript复制vhost_poll_start(n->poll)
├─tun_chr_poll
| └─poll_wait
| └─vhost_poll_func
| └─add_wait_queue加到tun文件的wqh中,
注意vhost_poll中wqh是一个指针指向文件wqh
└─vhost_poll_wakeup
└─vhost_poll_queue
└─vhost_work_queue
└─wake_up_process把worker加入到work_list中,让vhost-worker干活
对qemu传递过来的kick event fd开始pool,guest kick kvm,kvm再通知vhost-net,开始poll再2*queue个poll
代码语言:javascript复制vhost_vring_ioctl(VHOST_SET_VRING_KICK)
└─vhost_poll_start(vq->pool)
vhost-woker调用,kick通知过来的是vq,需要转换成vhost-net结构体。translate_desc是难点,guest写了一个自己的物理地址在vring.addr中,guest的整体物理内存映射在qemu的虚拟地址空间中,guest物理地址开始是p1,在qemu中开始是u1,然后这个地址p-p1 u1就是报文要放置的qemu的地址。
代码语言:javascript复制vhost_worker
├─handle_rx_net
| └─handle_rx
| ├─get_rx_bufs
| | └─vhost_get_vq_desc
| | └─translate_desc
| ├─tun_recvmsg
| | └─tun_do_read
| | └─tun_ring_recv
| | └─skb_array_consume如果有skb则返回,
没有则把currentadd_wait_queue然后调度走等来了skb再wake_up
| └─vhost_add_used_and_signal_n
| ├─vhost_add_used
| └─vhost_signal通知guest发送完成
├─handle_tx_net
| └─handle_tx
| ├─vhost_get_vq_desc
| | └─translate_desc
| ├─tun_sendmsg
| | └─netif_rx_ni
| | └─netif_rx_internal
| | └─enqueue_to_backlog
| | └─____napi_schedule触发do_softIRQ
| ├─vhost_add_used_and_signal_n
| | ├─vhost_add_used
| | └─vhost_signal通知guest接收完成
| └─vhost_log_write记录dirty page
├─handle_tx_kick
| └─handle_tx
└─handle_rx_kick
└─handle_tx
qemu进程调用,只有在vhost-net releas/stop/set_owner/set_backend时操作
代码语言:javascript复制vhost_poll_flush
└─vhost_work_flush
├─init_completion
├─vhost_work_init
├─vhost_work_queue
| └─wake_up_process
└─wait_for_completion
vhost-worker调用
代码语言:javascript复制vhost_flush_work
└─complete
热迁移时获取dirty page,因为qemu和vhost-net共享,直接读写就行了
代码语言:javascript复制migration_bitmap_sync
└─memory_global_dirty_log_sync
└─memory_region_sync_dirty_bitmap
└─vhost_log_sync
└─vhost_sync_dirty_bitmap
└─vhost_dev_sync_region
└─memory_region_set_dirty
vhost-worker触发softirq,softirq收包给了openvswitch
代码语言:javascript复制process_backlog
└─__netif_receive_skb
└─__netif_receive_skb_core
└─rx_handler也就是netdev_frame_hook
└─netdev_port_receive
openvswitch发包wake_up vhost-worker
代码语言:javascript复制ovs_vport_send
└─tun_net_xmit
├─skb_array_produce
└─wake_up_interruptible_poll(socket)
待补充开启vq->iotlb