OVS之vhost-net中VM通信(九)

一、版本说明

qemu版本:2.6.0
内核版本:3.10.102

二、简而言之

  • vhost模块需要提前加载,注册一个misc的设备,供虚拟机启动时候使用。
  • 虚拟机创建的时候,会初始化一个tap设备,然后启动一个vhost _$(qemu-kvm_pid)的线程,配置vring等承载数据的高度。
  • guest和host进行网络数据IO的时候,只负责数据IO的中断,中断消息等由kvm模块负责。
  • guest发包的时候,virtio模块负责发送数据包加入链接,然后通知kvm模块,kvm模块通过ioeventfd通知vhost模块,此时可以将有包的堆栈挂在work_list上,然后激活线程vhost进行收包操作,收到包之后传递给tap设备,再往内核协议栈中上
  • guest收包的时候,首先是vhost的往tap设备发包,然后将包加入到其中一个,然后将挂在work_list,激活线程vhost,vhost进行收包的操作,然后传递到其中,然后vhost通过irqfd通知kvm模块,kvm模块给guest发送中断,guest会通过中断,到NAPI,执行轮询,接收数据包,然后上传到协议栈。

以上的流程如下:
在这里插入图片描述

三、创建网络设备

3.1 主机qemu层

qemu-kvm启动虚拟机的时候,在命令行的-netdev tap,…中指定vhost=on选项,初始化设备的同时,会创建vhost的内核线程。vl.c中初始化main–>net_init_clients–>net_init_netdev–>net_client_init–>net_client_init1–>net_client_init_fun[]–>net_init_tap网口。

3.1.1 net_init_tap–>net_init_tap_one

  • 调用open("/dev/vhost-net", O_RDWR),会调用到vhost_net_open。
  • vhost_net_init–>vhost_dev_init–>vhost_set_backend_type设置为内核模式。
  • vhost_net_init–>vhost_dev_init–>vhost_kernel_set_owner–>vhost_kernel_call最终调用ioctl设置所有者,这个时候会启动vhost_ $(qemu_pid)的进程。
  • vhost_net_init–>vhost_dev_init–>vhost_kernel_get_features最终调用到vhost的vhost_net_ioctl获取vhost的功能存入vdev->features。
  • vhost_net_init–>vhost_dev_init–>vhost_virtqueue_init–>vhost_kernel_set_vring_call调用ioctl。

device_set_realized–>virtio_device_realize–>virtio_bus_device_plugged–>virtio_net_get_features–>vhost_net_get_features突破vdev->features赋给vdev->host_features。

3.2 主机vhost模块

3.2.1 vhost_net_open

  • 创建vhost_net的数据结构。
  • 为vhost申请virtqueue,rx和tx两个变量,并初始化。
  • 调用初始化vhost_dev_init虚拟设备。初始化数据结构,然后给设备挂上handle_tx_kick和handle_rx_kick两个worker。
  • 给vhost_net挂上handle_tx_net和handle_rx_net两个worker。
  • 把vhost_net记录在文件数据结构的private_data指针。
  • vhost_net_ioctl(VHOST_SET_OWNER)。
  • vhost_net_set_owner–>vhost_dev_set_owner调用kthread_create创建vhost_worker进程

四、配置通信方式

4.1 主机qemu层

  • main–>configure_accelerator–>accel_init_machine–>kvm_init注册kvm_io_listener,其中包括kvm_io_ioeventfd_add接口
  • kvm_io_ioeventfd_add–>kvm_set_ioeventfd_pio–>kvm_vm_ioctl配置KVM_IOEVENTFD,这里会调用ioctl对vhost模块进行配置

4.2 主机kvm模块

kvm_vm_fops–>kvm_vm_ioctl–>kvm_ioeventfd–>kvm_assign_ioeventfd

4.2.1 kvm_assign_ioeventfd

  • kvm_iodevice_init添加ioeventfd_ops到->dev->ops,ioeventfd_ops中主要关注ioeventfd_write。
  • kvm_io_bus_register_dev主要是讲_ioeventfd->dev记录到kvm->buses[id]–>dev中。
  • 将kvm->ioeventfds添加到_ioeventfd的链表中。

4.3 访客虚拟模块

virtio_dev_probe–>vp_get_features调用到qemu的virtio_pci_config_read–>virtio_ioport_read获取到vdev->host_features

virtio_init注册总线,完成之后会在/ sys / bus下创建virtio目录,内部包含设备和驱动程序两个目录,后续会记录总线支持的设备和驱动。

module_pci_driver为virtio-pci设备驱动加载,完成之后会分别在/ sys / bus / pci / devices和/ sys / devices /下创建virtio-pci的目录。

module_virtio_driver加载virtio-net设备驱动。

4.3.1 virtnet_probe

  • 获取主机是否支持多虚拟机的virtio-net设备。
  • 生成一个网络设备和进行默认设置。
  • 检测一些特性,某些硬件csum,GSO等功能,设置mac。
  • init_vqs进行轴向的初始化工作。
  • 设置dev真实rx,tx本身为1,注册设备。
  • 创建接收缓冲区,注册CPU通知,虚拟网卡启动加电。

init_vqs调用virtnet_alloc_queues函数申请receive_queue、send_queue,记录到virtnet_info中,并给receive_queue添加NAPI的支持,挂上函数。

virtnet_poll调用virtnet_find_vqs–>vp_find_vqs给virtqueue配置中断。创建vring,并vp_find_vqs初始化。调用vp_try_to_find_vqs函数给virtqueue配置中断,最优为msi-x中断,每个变量一个,次之的是共享中断,最次的是in-x的普通中断。

4.3.2 vp_try_to_find_vqs

  • msi-x中断时,当物理和CPU一致时,配置CPU亲和性。
  • 调用setup_vq,给设置每个virtqueue。
  • 调用request_irq申请中断,三种方式如下
    • 使用msi-x中断,有n + 1个向量,一个替换对应的中断,n个分别对应n个变量的vq中断。change中断处理函数vp_config_changed,vq中断处理函数:vring_interrupt。
    • 使用msi-x中断,有2两个vector,一个用来对应change中断,一个对应所有队列的vq中断。change中断处理函数vp_config_changed,vq中断处理函数vring_interrupt
    • 普通int-x中断,change中断和所有vq的中断公用一个中断irq。中断处理函数vp_interrupt。

4.3.3 setup_vq

  • 创建virtio_pci_vq_info,获取队列数量,申请空间给virtio_pci_vq_info->queue使用,并且激活队列通告qemu。
  • vring_new_virtqueue创建结构vring_virtqueue,并且对vring_virtqueue进行初始化。
  • 将创建的vring_virtqueue挂在virtio_pci_vq_info->vq上。

4.3.4 vring_new_virtqueue

  • 创建vring_virtqueue,并初始化其结构下的vring,初始化内容包括队列数量、page地址、vring对齐字节,这些数据会计算vring的avail和used两个变量,avail表示guset端可用,used表示host端已经占用。
  • 在vring_virtqueue的结构下的virtqueue挂上回调函数skb_recv_done和skb_xmit_done,分别是在接收、发送完成之后调用。
  • 在上面的结构上挂上通知函数vp_notify等信息。
  • 最后将virtqueue加入到vdev设备。

五、虚拟机发包

虚拟机中使用pktgen发包,pktgen直接调用netdev的ndo_start_xmit,如果是应用层发包,需要走socket和内核路径。

5.1 guest virtio模块

virtnet_netdev.ndo_start_xmit = start_xmit

  • 调用xmit_skb,该函数计算csum,gso标识记录,最后调用virtqueue_add_outbuf加入virtnet_info->send_queue对应的virtqueue。
  • virtqueue_kick–>virtqueue_notify–>vp_notify发一个VIRTIO_PCI_QUEUE_NOTIFY通知host kernel,并告知队列vq的index。

5.2 host kvm模块

guest virtio模块notify可能导致调用该接口vmx_handle_exit(vcpu_enter_guest)

vmx_handle_exit–>kvm_vmx_exit_handlers[exit_reason]–>handle_io–>kvm_fast_pio_out–>emulator_pio_out_emulated–>emulator_pio_in_out–>kernel_pio–>kvm_io_bus_write–>kvm_iodevice_write(dev->ops->write)–>ioeventfd_write–>eventfd_signal–>wake_up_locked_poll–>__wake_up_locked_key–>__wake_up_common–>vhost_poll_wakeup–>vhost_poll_queue–>vhost_work_queue–>wake_up_process

以上操作最终会唤醒vhost_worker。

5.3 host vhost模块

vhost_worker线程是一个死循环

  • 设置当前状态为TASK_INTERRUPTIBLE。
  • 加自旋锁,判断设备的work_list是为空,不为空则获取第一个worker,然后从worker_list中删除,解锁。
  • 如果worker获取到了,则设置线程状态为TASK_RUNNING,调用worker对应的功能模块。
  • 没有则调用schedule进行进程切换。

vhost_worker–>handle_tx_kick–>handle_tx(sock->ops->sendmsg)–>tun_sendmsg–>tun_get_user(内部的tun_alloc_skb?)–>netif_rx_ni(netif_rx没看到多占cpu)–>do_softirq–>call_softirq–>__do_softirq–>net_rx_action–>process_backlog–>__netif_receive_skb–>__netif_receive_skb_core–>netdev_frame_hook–>netdev_port_receive–>ovs_vport_receive–>ovs_dp_process_packet

六、虚拟机收包

pktgen直接从虚拟机外部的tap设备发包,直接调用tap的ndo_start_xmit,即tun_net_xmit。

6.1 host pktgen线程

tun_net_xmit–>vhost_poll_wakeup–>vhost_poll_queue–>vhost_work_queue–>wake_up_process会唤醒vhost_worker。

6.1.1 tun_net_xmit

  • 调用skb_queue_tail,将skb添加到socket的sk_receive_queue。
  • wake_up_interruptible_poll应该会唤醒vhost_poll_wakeup,但是不是特别确定,可以参考这个路径vhost_net_set_backend–>vhost_net_enable_vq–>vhost_poll_start(file–>f_op–>poll)–>tun_chr_poll–>poll_wait。

6.2 host vhost模块

vhost_worker–>handle_rx_kick–>handle_rx–>tun_recvmsg&vhost_add_used_and_signal_n–>vhost_signal–>eventfd_signal–>wake_up_locked_poll–>irqfd_wakeup–>kvm_set_msi–>kvm_irq_delivery_to_apic–>kvm_irq_delivery_to_apic_fast–>kvm_apic_set_irq–>__apic_accept_irq–>kvm_vcpu_kick这个函数的功能就是,判断vcpu是否正在物理cpu上运行,如果是,则让vcpu退出,以便进行中断注入。

6.3 host kvm模块

  • vcpu_enter_guest–>inject_pending_event–>vmx_inject_irq调用vmx_write32()写VMCS的IRQ寄存器。
  • vcpu_enter_guest–>vmx_vcpu_run在执行前,会读取VMCS中的寄存器信息,由于前面写了IRQ,所以vcpu运行就会知道有中断请求到来,再调用GuestOS的中断处理函数处理中断请求。

6.4 guest virtio模块

vp_interrupt–>vp_config_changed & vp_vring_interrupt–>vring_interrupt–>__napi_schedule–>virtnet_poll–>receive_buf–>netif_receive_skb–>__netif_receive_skb–>__netif_receive_skb_core(rx_handler–>br_handle_frame)–>deliver_skb–>ip_rcv–>ip_rcv_finish–>ip_local_deliver–>ip_local_deliver_finish–>udp_rcv–>__udp4_lib_rcv

6.5 UDP

  • __udp4_lib_rcv找到匹配的sock,根据skb的net、四元组和入端口信息,sock会记录连接的状态。
  • __udp4_lib_rcv–>udp_queue_rcv_skb–>__udp_queue_rcv_skb–>sock_queue_rcv_skb添加到sk_receive_queue队列中。

6.6 TCP

  • tcp_v4_rcv找到匹配的sock,根据skb的net、四元组和入端口信息,sock会记录连接的状态。
  • tcp_v4_do_rcv–>tcp_rcv_established–>tcp_queue_rcv添加到sk_receive_queue队列中。

七、VXLAN封装

7.1 kernel模块

netdev_frame_hook–>netdev_port_receive–>ovs_vport_receive–>ovs_dp_process_received_packet–>ovs_execute_actions–>do_execute_actions–>do_output–>ovs_vport_send–>vxlan_tnl_send

7.1.1 vxlan_tnl_send

  • 根据vxlan tunnel的ip查找路由。
  • 调用vxlan_xmit_skb封装发送报文。

7.1.2 vxlan_xmit_skb

  • 计算封装vxlan需要的最小空间,并且扩展头部空间。
  • 添加vxlan头。
  • 如果有BGP的头,也添加。
  • udp_tunnel_xmit_skb添加协议头发送。

7.1.3 udp_tunnel_xmit_skb

  • 添加UDP协议头。
  • iptunnel_xmit继续添加协议头,并且发送。

7.1.4 iptunnel_xmit

  • 添加ip协议头。
  • ip_local_out_sk–>__ip_local_out–>__ip_local_out_sk继续添加协议头,并且发送。

7.1.5 __ip_local_out_sk

  • 过netfilter的LOCAL_OUT。
  • 调用dst_output_sk–>ip_output。

7.1.6 ip_output

  • 过netfilter的POST_ROUTING。
  • 调用ip_finish_output。

7.1.7 ip_finish_output

  • 如果报文支持gso,调用ip_finish_output_gso进行分片。
  • 如果报文大于mtu,调用ip_fragment进行分片。
  • 调用ip_finish_output2进行报文发送。

7.1.8 ip_finish_output2

  • __ipv4_neigh_lookup_noref查找邻居子系统。
  • 调用dst_neigh_output–>neigh_hh_output进行报文发送。

7.1.9 neigh_hh_output

  • 封装2层协议头。
  • 调用dev_queue_xmit进行报文发送。

vhost_worker–>handle_tx_kick–>handle_tx(sock->ops->sendmsg)–>tun_sendmsg–>tun_get_user(内部的tun_alloc_skb?)–>netif_rx_ni(netif_rx没看到多占cpu)–>do_softirq–>call_softirq–>__do_softirq–>net_rx_action–>process_backlog–>__netif_receive_skb–>__netif_receive_skb_core–>netdev_frame_hook–>netdev_port_receive–>ovs_vport_receive–>ovs_dp_process_packet–>ovs_execute_actions–>do_execute_actions–>do_output–>ovs_vport_send–>vxlan_tnl_send–>vxlan_xmit_skb–>udp_tunnel_xmit_skb–>iptunnel_xmit–>ip_local_out_sk–>__ip_local_out–>__ip_local_out_sk–>dst_output_sk–>ip_output–>ip_finish_output–>ip_finish_output2–>neigh_hh_output–>dev_queue_xmit–>__dev_xmit_skb–>sch_direct_xmit–>dev_hard_start_xmit–>xmit_one–>netdev_start_xmit–>__netdev_start_xmit–>bond_start_xmit–>__bond_start_xmit–>bond_3ad_xor_xmit–>bond_dev_queue_xmit–>dev_queue_xmit–>__dev_xmit_skb–>sch_direct_xmit–>dev_hard_start_xmit–>xmit_one–>netdev_start_xmit–>__netdev_start_xmit–>ixgbe_xmit_frame

以上是host kernel的整个调用路径,如果需要排查瓶颈,可以定义以下时间记录点

  • netif_rx_ni第一个记录点,vhost的获取到skb之后,第一个接口
  • __netif_receive_skb第二个记录点,这里是过完中断之后,这个点函数没有接口。
  • ovs_vport_receive第三个记录点,查表之前。 ovs_dp_process_packet第四个记录点,查表之后。
  • ovs_vport_send第五个记录点,action执行时间。 vxlan_tnl_send第六个记录点,vxlan开始时间。
  • ip_local_out_sk第七个记录点,vxlan封装完毕时间。
  • ip_output第八个记录点,过完LOCAL_OUT的时间,这个挂不上jprobe,所以记录netfilter POSTROUTING第一个点的时间。
  • ip_finish_output第九个记录点,过完POSTROUTING的时间,这个也挂不上jprobe,所以记录netfilter POSTROUTING的最后一个点的时间。
  • __netdev_start_xmit第十个记录点,接下来调用网卡驱动发送报文。这个点挂不上,所以挂在dev_queue_xmit。

7.2 ovs datapath

netdev_frame_hook–>netdev_port_receive–>ovs_vport_receive–>ovs_dp_process_packet–>ovs_execute_actions–>do_execute_actions–>do_output–>ovs_vport_send–>vxlan_xmit–>rpl_vxlan_xmit–>vxlan_xmit_one

7.2.1 vxlan_xmit_one

  • skb_tunnel_info从skb获取info。
  • 从info获取目的端口和vni,源、目的ip。
  • 路由查找,判断出口和入口不同,防止环,报错目的ip是本机的。
  • 获取ecn信息。
  • vxlan_xmit_skb封包发送。

7.2.2 vxlan_xmit_skb

  • 计算封装vxlan需要的最小空间,并且扩展头部空间。
  • 添加vxlan头。
  • 如果有BGP的头,也添加。
  • udp_tunnel_xmit_skb添加协议头发送。

7.2.3 udp_tunnel_xmit_skb

  • 添加UDP协议头。
  • iptunnel_xmit继续添加协议头,并且发送。

7.2.4 iptunnel_xmit

  • 添加ip协议头。
  • ip_local_out继续添加协议头,并且发送.

7.2.5 rpl_ip_local_out

  • 如果没有gso,则直接调用output_ip继续发送。
  • tnl_skb_gso_segment调用kernel的__skb_gso_segment将报文进行分片,kernel中分片tcp的话会进行比较好的分片,udp的话,其实就是ip分片。而这里也会拷贝ip的协议头,这块没看懂,怪异。
  • 将所有的skb发送出去,调用output_ip->ip_local_out调用协议栈

7.2.6 ip_local_out

此处是kernel中的调用。

  • 过netfilter的LOCAL_OUT
  • 调用dst_output–>ip_output,剩下的参照前面kernel的调用路径。

原文链接:https://zhaozhanxu.com/2016/08/12/SDN/OVS/2016-08-12-virtio-net-vhost-net/

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Open vSwitch(OVS)是一个开源的虚拟交换机软件,用于实现网络交换功能。它提供了一套灵活的网络转发和流量处理机制,并且可以与不同类型的网络虚拟化技术(如OpenStack、KVM等)集成。 OVS-DPDK是OVS的一个变种,使用了Data Plane Development Kit(DPDK)来加速数据包处理。DPDK是一个用于快速数据包处理的开源项目,它提供了一组用户空间的库和驱动程序,使网络应用程序能够绕过操作系统内核,直接访问网络硬件。通过与DPDK集成,OVS-DPDK可以实现更高的数据包处理性能和更低的延迟。 OVS-DPDK具有以下特性: 1. 高性能:使用DPDK加速,可以处理更多的数据包以及更低的延迟。这使得OVS-DPDK非常适合需要高性能的虚拟化环境。 2. 大规模网络:OVS-DPDK支持大规模网络环境,可以处理数十万个虚拟机和大量的网络流量。 3. 高级流量管理:OVS-DPDK提供了丰富的流量管理功能,如流量分类、QoS(Quality of Service)、ACL(Access Control List)等,可以根据应用需求对流量进行精细控制和管理。 4. 灵活的虚拟化集成:OVS-DPDK可以与不同类型的虚拟化技术(如OpenStack、KVM等)集成,提供灵活的网络虚拟化解决方案。 总之,OVS-DPDK是在传统的OVS基础上加入了DPDK加速技术,以提供更高性能和更低延迟的网络转发和流量处理能力。这使得它成为虚拟化环境处理大规模网络流量的理想选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值