头图

QEMU/KVM是在Linux中被广泛使用的虚拟化技术之一,而virtio作为一个半虚拟化I/O事实上的标准[1],是QEMU/KVM在I/O虚拟化部分的默认实现。virtio-net是virtio标准中的网卡设备,被广泛应用。本文将会沿着虚拟化,virtio半虚拟化I/O,virtio-net的基本情况这条路线逐渐深入。

下面在第一节介绍虚拟化的基本知识和虚拟化IO的相关方法,在第二小节介绍引入virtio的必要性和其基本知识,在第三小节介绍virtio-net的相关内容。

虚拟化基本知识

虚拟机已经悄然渗透到我们的生活中,随处可见。比如,申请云服务器的时候,我们申请到的机器就是一台虚拟机;在编写完服务端代码,部署我们的服务时,经常采用docker,kata等容器技术;再比如,我们可能需要VMware来创建一个隔离的环境来运行某些恶意的服务;亦或是自制了一个操作系统,但是又不想用硬件调试,那我们就可以用qemu来进行测试;甚至在新能源的车机上,都可能通过虚拟化技术运行着多套操作系统。看到这里,大家可能自然产生了一个疑问,这么多虚拟化的方法有什么异同,各自又有什么特点和适用场景呢?本节将会带领大家走进虚拟化,一窥虚拟化的基本知识,本小节内容和图片参考[2]、[3]、[8]。在深入之前,首先要确定什么是虚拟化。虚拟化的定义是指抽象和模拟计算机的硬件和软件的技术。计算机系统大概可以划分为四个层次:硬件、操作系统、库和用户程序,如下图所示:

图片

而本节开始的描述的虚拟化场景中,都是抽象了这个结构中的某个层次,并为上层服务提供接口。根据虚拟化层次的不同,我们可以将虚拟化技术划分为不同的种类:

  • 直接对硬件资源进行虚拟化,也叫做系统级虚拟化。是通过模拟和抽象硬件资源,来提供虚拟化的ISA接口。我们常用的VMware,Xen,qemu都是属于这个类型的。
  • 操作系统层次的虚拟化,也叫做轻量级虚拟化,进程虚拟化。操作系统本身就是对硬件的一种虚拟化,容器技术进一步通过namespace、Cgroups和overlayfs来提供对资源的隔离和限制,实现对操作系统的虚拟化。docker容器就是属于这个类型的。
  • 应用层程序运行环境的虚拟化。对API调用进行虚拟化,无论是通过语言运行时还是拦截调用进行转换,都可以实现对宿主机设备的中的API虚拟化的目标。JVM,Wine等技术就属于这个类型。

本文主要讨论系统级虚拟化,下面介绍系统虚拟化的基本知识。

系统虚拟化

首先介绍一下系统级虚拟化知识中会出现的相关名词

  • Guest OS:运行在虚拟机里面的操作系统
  • Host OS:Type-2架构下支持虚拟机运行的操作系统
  • VMM/Hypervisor:表示虚拟机资源管理器,是负责为虚拟机提供虚拟的硬件资源的,前者通常用于Type-2类型的虚拟机场景,后者通常用于Type-1类型的虚拟机场景
  • Type-1/Type-2:Type-1/Type-2是两种虚拟化的架构,前者表示虚拟机资源管理器直接和硬件交互,后者表示虚拟机资源管理器通过Host OS和硬件交互。

Type-1架构如下图所示:

图片

Type-2架构如下图所示:
图片

接着我们看一下在系统虚拟化的过程中有哪些需要实现的内容。系统虚拟化的任务一般要分为CPU虚拟化,内存虚拟化,I/O虚拟化。本文的virtio半虚拟化就是I/O虚拟化方法的一种,目前I/O虚拟化速度成为整个系统的瓶颈,所以越来越被人重视,而virtio半虚拟化就是解决这个问题的利器。CPU虚拟化是将CPU的能力抽象出来,可以给上层模拟出多个相同或者不同架构的CPU。根据指令执行所需要的权限不同,指令可以分为特权指令和非特权指令。同时根据指令执行是否访问物理资源,我们可以将指令分为敏感指令和非敏感指令。所以如果所有的敏感指令都是特权指令,我们就很容易实现CPU虚拟化,我们将这样的指令集称为可虚拟化架构,反之称之为不可虚拟化架构。内存虚拟化需要保证虚拟机看到的是从0开始并且连续的,这是通过引入了一层新的地址空间GPA来解决的,Host OS直接接触物理内存,所以会有HPA(Host Phyical Address),同时Host OS上面运行着Guest OS作为自己的程序,所以此时引入了GPA(Guest Phyical Address)和GVA(Guest Virtual Address)。当Guest OS想要访问内存时,首先从GVA通过页表找到GPA,再通过页表找到HPA,如下图所示:

图片

最后来到了I/O虚拟化部分。I/O虚拟化需要复用外设的接口资源,提供给客户机使用。外设的接口资源通常由MMIO寄存器、PMIO寄存器、中断模块和DMA模块四部分组成。对I/O的的虚拟化需要同时考虑这四种资源。I/O虚拟化有三种方式,分别为模拟、透传、半虚拟化:

  • 模拟是通过对指令的模仿进行虚拟化,模拟不需要专用硬件的支持,但是性能比较弱。
  • 透传是Guest OS直接访问I/O硬件,不经过Host OS,这样的虚拟化有更高的性能;但是透传在设备共享,动态迁移场景下有不小的问题。
  • 半虚拟化的事实标准是Virtio。是通过一个Host OS和Guest OS共同认可的通信方式,加快通信的速度,在具有适用性的同时,大幅提升了速度。这也是本文接下来的主角,会在virtio基本知识小节介绍virtio的具体情况。

在介绍virtio的具体知识之前,再简单介绍一下QEMU/KVM,本篇文章是以QEMU/KVM作为背景的。

QEMU/KVM

virtio运用在多个虚拟化的产品中,QEMU/KVM就是其中之一。QEMU是由Fabrice Bellard提出的一个通过仿真来虚拟化的项目。完整地实现了处理器虚拟化、内存虚拟化以及I/O设备。但是由于采用了仿真方法,虚拟化的性能比较差。而KVM是由Avi Kivity提出的,一经提出便迅速进入Linux主线,它采用的是半虚拟化的方法。只提供处理器虚拟化和内存虚拟化,不提供I/O虚拟化。既然QEMU功能全面而性能薄弱,而KVM性能强劲但是功能薄弱,一个很自然的想法是将两者结合在一起。QEMU/KVM就是让KVM处理处理器虚拟化和内存虚拟化,运行在内核态;让QEMU处理I/O虚拟化,运行在用户态。QEMU/KVM的架构如下图所示:

图片

其中,QEMU通过ioctl与/dev/kvm交互;1个QEMU进程可以启动1个VM;1个VM里面的vcpu是由一个线程负责的。

virtio基本知识

在了解了virtio在虚拟化整个框架的位置后,下面介绍一下virtio的基本知识及必要性,本小节内容和图片参考[4]。

virtio是一个半虚拟化的I/O方法,是由IBM的Rusty Russell在2007年提出的。通过仿真虚拟化I/O的方法适用性比较强,但是由于经历了Guest OS、QEMU、Host OS多个阶段,所以会产生多次VM Exit和VM Entry,性能比较弱,而I/O成为了整个系统的瓶颈,这也是迫切需要virtio的原因。

virtio具体架构如下所示:
图片

主要的改进在于,不直接采用现有的硬件驱动,淘汰了客户机驱动,设备模拟层,VMM中的硬件驱动三层架构,而是采用一种更为高级别的抽象,减少了多层沟通的开销。virtio相比于仿真,不再是全部通过中断的方法通知读取数据,而是通过更轻量化的事件机制异步通信;采用Vring的方法在前后端中共享内存;也不会再完整地模拟寄存器的行为来控制设备和驱动,因为内存的性能无法和寄存器相比,失去了模拟的意义,直接使用内存中的一块区域;也不需要每一次都进入到KVM module里面,减少了VM-exit的次数,降低了开销。

其中Virtio Device是QEMU模拟出来的设备,是virtio的backend,负责操作物理设备。Virtio Driver是Guest OS中的驱动,是virtio的frontend,例如Virtio-net,Virtio-blk,负责发送Guest OS的I/O请求。前端和后端设备之间是通过Virtqueue来实现的,virtqueue是数据操作的接口,它具体是通过vring来实现的,但是只会暴露给外界virtqueue的接口。前后端通信是通过Notification机制来进行的。

virtio网络设备是virtio架构中的一员,负责虚拟化网卡的工作。主要由前端驱动和后端设备组成,随着后端设备放置位置的不同,性能也会不同。本文介绍的是virtio-net,它的后端设备放在qemu进程里面。
图片

virtio-net相关内容

下面具体解析virtio-net的相关内容,本小节内容和图片参考[5],[6],[7],[8],[9],[10],[11],[12]。virtio-net主要涉及内容有Virtio Device,Virtio Driver,Virtqueue,Notification,如下图所示:
图片

他们的作用已经在virtio基本知识小节介绍过了,下面说明各个部分的具体情况,来看一下virtio-net是如何起作用的.

Virtio Device

首先来看Virtio Device。virtio-net后端设备负责和tap虚拟网卡通信,进而控制实际硬件。它是在qemu中利用面向对象的思想实现的。virtio设备和相关总线关系如下图所示:

图片
而virtio设备的挂载过程如下:首先创建一个virtio-net-PCI代理设备,挂载到PCI总线上;再创立一个virtio总线,挂载到上面的代码设备上;然后在virtio总线上挂载virtio设备,如virtio_net,virtio_blk等设备。

Virtio Driver

接着是Virtio Driver前端驱动,主要实现代码在linux_source_code/drivers/net/virtio_net.c中。

在讲Virtio Driver之前,首先需要了解一下Linux设备驱动的模型,这是整个driver实现的基础。设备模型是在Linux2.6提出来的,用Class,Bus,Device,和Device driver来抽象设备。Bus上挂载有Device设备和Device driver;Bus和Device、Device driver的拓扑关系可以用来描述一个设备树的依赖关系;热插拔设备的Driver提前注册在Bus上,当一个设备插入Bus时注册Device,此时通过match函数来和Bus上的Driver来匹配,来执行相关函数。

首先介绍一下virtio_bus,bus的结构如下图所示:

图片

virtio_bus是一个bus_type的实例,和pci_bus_type并列;virtio_net驱动其实是一分为二,它利用pci_driver和virtio_driver两个driver实现,所以virtio_pci_driver和virtio_net_driver分别挂载在pci和virtio总线上;他们的device也是类似的结构,device之间通过一个virtio_pci_device连接。在初始化时,virtio设备会先在pci bus上完成初始化,然后在virtio bus上完成初始化。

然后介绍virtio-net driver和device的初始化具体过程:

  • pci和virtio侧的驱动是在系统初始化的时候都注册好了;
  • virtio_net设备是挂载在pci总线上的,在pci_scan_device枚举设备的过程中,发现设备后会注册一个pci_dev;
  • 然后会先引发pci侧的driver和device的match,最后调用register_virtio_device,注册virtio侧device;
  • 注册了virtio侧的device后,会引发virtio侧的match,进而调用driver的virtnet_probe函数,创建virtio-net;

Virtqueue

virtqueue是Virtio Device和Virtio Driver沟通的方式,外界使用这个接口进行通信,而内部则是采用vring的方法实现的;每一个virtio设备可能有多个virtqueue,而在virtio-net中最少有两条virtqueue,分别用来收发信息;也可以配置多条收发队列。

  • virtqueue具体的数据是vring来管理,vring用了三个数据结构用来管理内存:
  • Vring_desc是描述符表,用于记录所有的内存区域,每一项描述符表指向一块内存。
  • 指向的内存是通过scatterlist来组织的,然后会把内存地址放到描述符表的addr项中,长度用len表示。
  • Vring_avail的id是表示可用的描述区域,用于Guest向Host标志数据区域可用。
  • Vring_used的id表示已用的描述符区域,用于Host向Guest标志数据区域可用。

这里解释一下avail和used的意思,两者是站在Host OS的角度来说的。avail就是说现在Host可以读取,意味着Guest OS向内放入了数据而used是说Host使用完成,告知Guest现在可以读取。

最后来看一下virtqueue的通信过程。Guest OS读取数据的流程如下:
图片

①表示virtioDriver从Used队列中获取一个Buffer的位置,并将Data拷贝到Buffer中,用于传送(还可以从符号表分配一个Buffer),然后更新Avail队列中的描述符索引值;②表示Driver通知Device来取数据;③表示Device从Avail队列中获取到描述符索引值,并将描述符索引对应的地址中的数据取出来;最后Device更新Used队列中的描述符索引,为下一次driver获取描述符表做准备;④表示Device通知Driver数据已经取完了。发送过程和接受过程原理相似,方向相反,这里就不再赘述了。

Notificatio

nNotification是前后端通信的机制。guest向host发送通知,采用的是ioeventfd,通知的这个过程叫kick;host向guest发送通知,采用的是irqfd。两者都依赖于内核中的一个功能eventfd。KVM相当于一个代理设备,在QEMU和Guest OS之间互相通知对方,示意图如下:

图片

参考文献

[1] virtio: Towards a De-Facto Standard For Virtual I/O Devices
[2] 深入浅出系统虚拟化:原理与实践
[3] 术道经纬知乎专栏虚拟化部分
[4] virtio网络演化历史
[5] LoyenWang公众号虚拟化专栏
[6] 深度探索Linux系统虚拟化 原理与实现
[7] QEMU/KVM源码解析与应用
[8] 系统虚拟化原理与实现
[9] 蜗窝科技统一设备模型专栏
[10] Linux设备驱动开发详解
[11] Virtio----Vring Introduce
[12] virtio-net 实现机制


Datenlord
14 声望6 粉丝

DatenLord专注打造新一代云原生高性能存储平台,实现跨云、跨数据中心之间数据高速访问,从而极大提升存储系统的可扩展性,满足企业客户海量数据跨地域高速访问需求,在互联网、金融、电信、能源等不同行业均有广...