virtio代码分析(一)-qemu部分

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

virtio内容众多,代码分布于qemu,linux,dpdk等中,而且分为frontend和backend,可以运行于userspace也可以运行于kernelspace,极其难以理解,不看代码只看原理性文档往往流于表面,只有真正看懂了代码才能理解virtio。

以qemu和linux中的virtio-net举例分析代码,这儿只分析qemu部分virtio代码,在qemu中创建一个virtio-net设备,tap作为backend,有2个queue,那么qemu中tx和rx各2个,再加1个controll queue就得创建5个queue了

代码语言:javascript复制
-netdev tap,id=hostnet0,queues=2
-device virtio-net-pci,host_mtu=1450,mq=on,vectors=5,netdev=hostnet0,id=net0,mac=fa:16:3e:d8:fe:81,bus=pci.0,addr=0x3

我们先看数据结构NetClientState,重点关注peer和incoming_queue

代码语言:javascript复制
struct NetClientState {
    NetClientInfo *info;
    int link_down;
    QTAILQ_ENTRY(NetClientState) next;
    NetClientState *peer;
    NetQueue *incoming_queue;
    char *model;
    char *name;
    char info_str[256];
    unsigned receive_disabled : 1;
    NetClientDestructor *destructor;
    unsigned int queue_index;
    unsigned rxfilter_notify_enabled:1;
    int vring_enable;
    int vnet_hdr_len;
    bool is_netdev;
    QTAILQ_HEAD(, NetFilterState) filters;
};

先看qemu中的参数-netdev,创建了2个TAPState,每个TAPState包含一个NetClientState,函数qemu_net_client_setup参数peer为空,所以创建的ncs中peer为空。

代码语言:javascript复制
net_tap_fd_init
  └─qemu_new_net_client
      └─qemu_net_client_setup//这个函数参数peer为NULL

再看qemu的参数-device中有netdev=hostnet0,而-netdev中有id=hostnet0,根据name找到了刚才tap创建的2个NetClientState和queue个数为2

代码语言:javascript复制
typedef struct NICPeers {
    NetClientState *ncs[MAX_QUEUE_NUM];
    int32_t queues;
} NICPeers;
typedef struct NICConf {
    MACAddr macaddr;
    NICPeers peers;
    int32_t bootindex;
} NICConf;
#define DEFINE_NIC_PROPERTIES(_state, _conf)                            
    DEFINE_PROP_MACADDR("mac",   _state, _conf.macaddr),                
    DEFINE_PROP_NETDEV("netdev", _state, _conf.peers)
static Property virtio_net_properties[] = {
DEFINE_NIC_PROPERTIES(VirtIONet, nic_conf),
}
set_netdev
  └─qemu_find_net_clients_excep

根据queue个数生成2个NetClientState,现在有2个NetClientState,成对peer互相指向对方

代码语言:javascript复制
virtio_net_device_realize
  └─qemu_new_nic
      └─qemu_net_client_setup//和tap init相比,peer不为NULL,是tap的NetClientState

qemu对virtio-net-pci的初始化

代码语言:javascript复制
virtio_device_realize
  ├─virtio_net_pci_realize
  ├─virtio_bus_device_plugged
  |   ├─virtio_pci_device_plugged//初始化virtio-pci设备
  |   └─vdev->dma_as = &address_space_memory;//分配dma_as
  └─memory_listener_register
      └─virtio_memory_listener_commit
          └─virtio_init_region_cache

virtio_net_device_realize
  ├─virtio_init
  ├─virtio_net_add_queue
  |   └─virtio_add_queue//rx和tx的handle_output分别是virtio_net_handle_rx和virtio_net_handle_tx_bh
  ├─qemu_bh_new//生成一个qemu bh virtio_net_tx_bh
  └─virtio_add_queue//控制qemu是virtio_net_handle_ctrl

guest把分配的vring地址传递给qemu

代码语言:javascript复制
virtio_pci_common_write
  └─virtio_queue_set_rings
      └─virtio_init_region_cache
          └─address_space_cache_init//这个函数等qemu内存虚拟化时一块分析

给kvm注册一个eventfd,等guest kick时,kvm通知qemu进程

代码语言:javascript复制
virtio_pci_common_write
  └─virtio_pci_start_ioeventfd
      └─virtio_bus_start_ioeventfd
          └─virtio_device_start_ioeventfd_impl
              ├─virtio_bus_set_host_notifier
              |   └─virtio_pci_ioeventfd_assign
              |       └─memory_region_add_eventfd
              └─event_notifier_set_handler(virtio_queue_host_notifier_read)

guest里的virtio-net通知tx或者rx写mmio,带着vector号,kvm通知qemu

代码语言:javascript复制
virtio_pci_notify_write
  └─virtio_queue_notify
      ├─handle_output
      └─event_notifier_set
virtio_queue_host_notifier_read
  └─virtio_queue_notify_vq
      └─handle_output

不管是发送还是接收,都是guest设置好vring中的address,是guest的physical address,需要qemu调用address_space_map转换成自己的virtual address。

收发包的原理就是tap收到发往guest,从guest收到发往tap,tap和virtio-net-pci peer互相指,从自己NetClientState的peer找到对端的NetClientState,然后找到NetClientState中的incoming_queue,incoming_queue中deliver调用receive函数。

qemu读tap,然后调用virtio_net_receive,如果virtio_net_receive不能receive,等guest kick时再用virtio_net_handle_rx收包。

代码语言:javascript复制
tap_send
  └─qemu_send_packet_async
      └─qemu_send_packet_async_with_flags
          └─qemu_net_queue_send
              └─调用到virtio-net的virtio_net_receive

guest kick时接收,virtio_net_handle_rx is handle_outpu,rxvq是VRING_DESC_F_WRITE,DMA_DIRECTION_FROM_DEVICE,站在guest角度看问题,从qemu到guest内存

代码语言:javascript复制
virtio_net_handle_rx
  └─qemu_flush_queued_packets
      └─qemu_flush_or_purge_queued_packets
          └─qemu_net_queue_flush
              └─qemu_net_queue_deliver
                  └─qemu_deliver_packet_iov
                      └─nc_sendv_compat
                          └─virtio_net_receive

virtio_net_receive_rcu
  ├─virtio_net_process_rss选择接收的queue
  |   └─virtqueue_pop
  |       └─virtqueue_split_pop
  |           └─virtqueue_map_desc
  |               └─dma_memory_map
  |                   └─address_space_map
  ├─virtqueue_fill
  ├─virtqueue_flush
  └─virtio_notify

guest kick时发送virtio_net_handle_tx_bh is handle_output

代码语言:javascript复制
#guest kick时发送virtio_net_handle_tx_bh is handle_output
virtio_net_handle_tx_bh
  └─virtio_net_tx_bh
      ├─virtio_net_flush_tx
      |   ├─virtqueue_pop
      |   |   └─virtqueue_split_pop
      |   |       └─virtqueue_map_desc
      |   |           └─dma_memory_map
      |   |               └─address_space_map
      |   ├─qemu_sendv_packet_async
      |   |   └─qemu_net_queue_send_iov
      |   |       └─qemu_net_queue_deliver_iov
      |   |           └─qemu_deliver_packet_iov
      |   |               └─调用到了对端的peer_receive就是tap_receive
      |   |                  └─tap_write_packet
      |   └─qemu_net_queue_flush
      |       └─qemu_net_queue_deliver
      └─bh的回调函数virtio_net_tx_complete
          ├─virtqueue_push
          |    ├─virtqueue_fill
          |    └─virtqueue_flush
          |        └─vring_used_idx_set
          └─virtio_notify

记录dirty page用于live migration

代码语言:javascript复制
virtqueue_fill
  ├─virtqueue_unmap_sg
  |   └─dma_memory_unmap
  |       └─address_space_unmap
  |           └─invalidate_and_set_dirty
  └─virtqueue_split_fill
      └─vring_used_write
          └─address_space_write_cached
              ├─flatview_write_continue
              |   └─invalidate_and_set_dirty
              └─address_space_cache_invalidate
  • virtio-net tx时如何选择queue号的?

tx时virtnet_select_queue cpu和queue有一个对应关系,是virtnet_set_affinity分配好的,或者用内核的xps确定queue号。

  • rx时又如何选择queue呢?

virtio_net_process_rss选择接收的queue。rx时看virtio-net-pci用什么中断模块,如果用msix可能每个queue有自己的vector号,中断来了直接定位queue,如果共享只能遍历所有queue了。

  • 那么vhost-net/vhost-user/vhost-vdpa时有时怎么处理NetClientState的?

和原来一样没有什么变化

  • qemu做virtio写元数据时如何和guest保证cache coherence?

一个host cpu写,另一个guest host已经读到cache中,怎么搞?

address_space_cache_invalidate

  • qemu做virtio写virtio vring里buf时如果更新dirty page的?

invalidate_and_set_dirty

等补充内容

vIOMMU时virtio地址怎么转换的?

0 人点赞