virtio代码分析(二)-kernel vhost-net部分

2021-02-24 11:25:58 浏览数 (1)

惠伟: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

0 人点赞