转载:

X86体系早期没有在硬件设计上对虚拟化提供支持,因此虚拟化完全通过软件实现。一个典型的做法是通过优先级压缩(Ring Compression)和二进制代码翻译(Binary Translation)相结合,VMM在特权级ring 0, Guest操作系统在非特权级ring 1, Guest应用程序在ring 3。由于Guest操作系统工作在非特权级ring 1中,这样当它执行特权指令时就会触发异常,由VMM截获异常并采用软件方式模拟指令并返回。但由于X86体系在设计之初没有考虑虚拟化,所以一小部分特权指令在ring 1下没有引发异常,所以需要对操作系统的二进制代码进行扫描,一旦发现不友好的指令,将就其换成支持虚拟化的指令块,这些指令块与VMM合作访问受限的虚拟资源,或者显式地触发异常让VMM进一步处理。这种打补丁的方式很难在构架上保证其完整性。于是,下层的硬件加入了虚拟化功能,硬件截获操作系统对敏感指令的执行或者对敏感资源的访问后通过异常的方式报告给VMM,这样就解决了虚拟化的问题。Intel VTx技术是这一方向的代表,它引入了一个新的特殊执行模式用于运行虚拟机,这个模式会将特权指令统统截取并报告给VMM,VMM本身工作在正常模式下,在接收到处理的报告后,通过对目标指令的解码,找到相应的虚拟化模块进行模拟

    对于内存的虚拟化则需要用硬件实现从GVA(Guest Virtual Address)到GPA(Guest Physical Address)再到HPA(Host Physical Address)的两次地址转换。传统非虚拟化的操作系统只通过硬件MMU完成一次从GVA到GPA的虚拟化。

    在I/O的虚拟化方面,VTd技术在北桥引入DMA重映射硬件来提供设备重映射和设备直接分配的功能。设备所有的DMA传输都会被DMA重映射硬件截取,然后根据设备对应的I/O页表,对DMA中的地址进行转换,使设备只能访问特定的内存。这样,设备就可以直接分配给Guest机使用,驱动提供给设备的GPA经过重映射,变为HPA,使得DMA操作得以完成。

    在网络虚拟化方面,VTd技术可以将一个物理网卡直接分配给一个虚机使用,但扩展性差,因为一台服务支持的PCI设备数是有限的,远远不能满足越来越来的Guest机数量。因此SRIOV(Single Root I/O Virtualization)被引入来解决这个问题。SRIOV是PCIe(PCI Express)规范的一个扩展,定义了本质上可以共享的设备。它允许一个PCIe设备,通过是网卡,为每个与其相连的客户机复制一份资源(例如内存资源、中断和DMA数据流),使得数据处理不再依赖VMM。

    在GPU虚拟化方面,如XenGT,它是由Intel在Xen基础上开发的GPU虚拟化开源方案。客户机操作系统不需要任何改动,特权操作会被Xen截获,并且转发到中介(Mediator),中介为每个客户机创建一个GPU上下文(Context),并且在其中模拟特权操作。


1, 普通虚拟化

    传统的KVM实现中,kvm.ko是内核的一个模块主要用来捕获虚机的是上述的针对CPU、内存MMU的特权指令然后负责模拟,对I/O的模拟则是再通过用户态的Qemu进程模拟的,Qemu负责解释I/O指令流,并通过系统调用让Host操作系统上的驱动去完成真正的I/O操作。这其中用户态与内核态切换了两次,数据也需要复制

KVM gpu管理系统 kvm support_操作系统



   用户态的Qemu在启动Guest操作系统时,可以先通过KVM在用户态的字符接口/dev/kvm获取Guest的地址空间(fd_vm)

和KVM本身(fd_kvm),然后通过fd_vm将Guest的物理空间mmap到Qemu进程的虚拟空间(虚机就是一个进程,虚机进程的用户虚拟空间就是Guest的物理空间,映射之后Qemu的虚拟设备就可以很方便的存取Guest地址空间里的数据了),并根据配置信息创建vcpu[N]线程(对虚机来说,虚机进程的线程对应着Guest的处理器),然后Qemu将操作fd_vcpu[N]在自己的进程空间mmap一块KVM的数据结构区域(即下图的shared,包括:Guest的IO信息,如端口号,读写方向,内存地址)。该数据结构用于Qemu和kvm.ko交互,Qemu通过它调用虚拟设备注册的回调函数来模拟设备的行为,并将Guest IO的请求换成系统请求发给Host系统。

    图中vm-exit代表处理器进入host模式,执行kvm和Qemu的逻辑。vm-entry代表处理器进入Guest模式,执行整个Guest系统的逻辑。Qemu通过三个文件描述符同kvm.ko交互,然后kvm.ko通过vmcs这个数据结构同处理器交互,最终达到控制Guest系统的效果。其中fd_kvm主要用于Qemu同KVM本身的交互,比如获取KVM的版本号,创建地址空间、vcpu等fd_vcpu主要用于控制处理器的模式切换,设置进入Guest mode前的处理器状态等等(内存寻址模式,段寄存器、控制寄存器、指令指针等),同时Qemu需要通过fd_vcpu来mmap一块KVM的数据结构区域。fd_vm主要用于Qemu控制Guest的地址空间,向Guest注入虚拟中断等。

KVM gpu管理系统 kvm support_操作系统_02

2, VirtIO

   VirtIO通过共享内存的方式为Guest和Qemu提供了高速的硬盘与网络I/O通道(因为Guest的地址空间早就mmap到Qemu的进程空间)。VirtIO只需要增加VirtIO Driver和VirtIO Device即可直接读写共享内存。如下图,Guest VirtIO驱动通过访问port地址空间向Qemu的VirtIO设备发送IO发起消息。而设备通过读写irqfd或者IOCTL fd_vm将I/O的完成情况通知给Guest的驱动。irqfd和ioeventfd是KVM为用户程序基于内核eventfd机制提供的通知机制,以实现异步的IO处理(这样发起IO请求的vcpu将不会阻塞)。之所以使用PIO而不是MMIO,是因为KVM处理PIO的速度快于MMIO。

KVM gpu管理系统 kvm support_数据结构与算法_03



3, vhost

   VirtIO通过共享内存减小了Guest与Qemu之间数据复制的开销,但Qemu到内核的系统调用还是跑不了。有必要将模拟I/O的逻辑将VirtIO Device挪到内核空间避免系统调用。
   vhost在内核态再增加一个vhost-net.ko模
块, vhost client与vhost backend通过共享内存(virtqueues, unit socket), 文件描述符(ioeventfds), (中断号)irqfds实现快速交换数据的机制。

KVM gpu管理系统 kvm support_KVM gpu管理系统_04




4, vhost-user

   vhost使用了内核态TCP/IP栈导致从Guest到kvm.ko仍然有一个用户态内核态的切换以及数据的拷贝。一些用户态栈议栈如openonload直接在用户态实现网卡驱动再实现BSD兼容的socket接口供上层应用使用,vhost-user(需要将vhost-backend移到用户态)的共享内存技术用于将这些用户态网络设备(又如snabb switch)和用户态的Guest连接起来


5, DPDK support for vhost-user [3]

   DPDK便是一个在用户态可以直接操作物理网卡的库函数,它和vhost-user结合便可以实现类似于snabb switch一样性能强劲的用户态交换机了。2015年6月16日dpdk vhost-user ports特性合并到了ovs社区[1], dpdkvhostuser port将创建unix socket将虚机的virtio-net虚拟网卡的网卡缓冲区共享给物理机上的ovs port设备。


理论部分结束,下面开始安配置配置dpdk + vhost-user

1, sudo apt-get install fuse libfuse-dev dh-autoreconf openssl libssl-dev

   Git clone git://dpdk.org/dpdk

2, 检查硬件网卡是否支持dpdk,在lib/librte_eal/common/include/rte_pci_dev_ids.h文件中搜索0x153A能搜到。

   hua@hua-ThinkPad-T440p:/bak/OpenStack$ sudo lspci -nn|grep Ethernet

   00:19.0 Ethernet controller [0200]: Intel Corporation Ethernet Connection I217-LM [8086:153a] (rev 04)

3, 修改文件config/common_linuxapp

   CONFIG_RTE_BUILD_COMBINE_LIBS=y  #产生单个库文件

   CONFIG_RTE_LIBRTE_VHOST=y        #enable对vhost的支持

4, 编译,安装。DPDK IVSHMEM可以在host-to-guest或guest-to-guest之间采用零拷贝技术

make config T=x86_64-ivshmem-linuxapp-gcc 
   make install T=x86_64-ivshmem-linuxapp-gcc

5, 编译eventfd模块

cd $DPDK/lib/librte_vhost/eventfd_link/
   make 
   sudo insmod eventfd_link.ko

6, uio提供了用户态驱动开发的框架, dpdk中的uid驱动需要定期去poll/select /dev/uioX检查设备是否有中断产生。uio也是通过mmap将设备的内存映射到用户空间,当然用户态也可以通过/sys/class/uio/uioX/maps/mapX来实现对设备内存的访问,所以发送和接受数据是通过操作设备的内存来完成的。

   DPDK除了可以使用uio驱动,还可以使用VFIO驱动。VFIO是用户态的PCI设备驱动开发的框架,它向用户态提供访问硬件设备的接口,也向用户态提供配置IOMMU的接口。它需要内核与BIOS的支持。

 

sudo modprobe uio
   sudo insmod x86_64-ivshmem-linuxapp-gcc/kmod/igb_uio.ko
   $ sudo ./tools/dpdk_nic_bind.py --status
    Network devices using DPDK-compatible driver
    ============================================
    <none>
    Network devices using kernel driver
    ===================================
    0000:00:19.0 'Ethernet Connection I217-LM' if=eth0 drv=e1000e unused=igb_uio *Active*
    Other network devices
    =====================
    <none>
   $ sudo ./tools/dpdk_nic_bind.py --force --bind=igb_uio eth0
   $ sudo tools/dpdk_nic_bind.py -u 0000:00:19.0  # 恢复

7, 大页支持,添加/etc/default/grub后执行update-grub (centos是执行:grub2-mkconfig --output=/boot/grub2/grub.cfg), 通过mount一个大页文件系统,然后通过mmap的文件映射,使得该文件系统中的内存操作实现大页调度,从而解决TLB shrashing的问题。
 

GRUB_CMDLINE_LINUX=iommu=pt intel_iommu=on default_hugepagesz=1G hugepagesz=1G hugepages=8


   之后mount它,mount -t hugetlbfs -o pagesize=1G none /dev/hugepages

8, 编译ovs

git clone https://github.com/openvswitch/ovs.git
   cd ovs && ./boot.sh
   ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --with-dpdk=/bak/openstack/dpdk/x86_64-ivshmem-linuxapp-gcc/ --enable-ssl


   但报错“cannot link with dpdk”,无法继续了。

opendp是一个基于DPDK的用户态协议栈,当需要三层VPN或者NAT时或者其他应用时需要它,https://github.com/opendp/dpdk-odp/wiki