virtio虚拟化框架概述

一.虚拟化背景

virtio由Rusty Russell 开发,原是为支持lguest虚拟化解决方案,目前已成为linux标准的I/O虚拟化框架.本文重点分享virtio框架及学习心得,错漏之处望大家指正.先简单介绍下有关虚拟化的背景及基本术语.       

虚拟化是云计算的基础架构,通过软件接口化及标准化设计,在计算机硬件上创建一个抽象层,支持将单物理机的硬件元素(处理器、内存、存储等)分成多个虚拟计算机,每个虚拟机都会运行自己的操作系统,其行为就像一台独立的计算机.虚拟化技术在提高资源利用率,软件规模化部署,可移植性,灵活性,安全性等方面具有显著优势.当前虚拟化已成为企业IT架构的标准实践.它也是推动云计算经济发展的核心技术.     

在智能终端领域,Google推出cuttlefish虚拟化方案,提供操作系统级虚拟化能力,支持将安卓设备部署在云端.除此之外,在工程效能领域也有重要价值,包括以下方面.

1.使平台和应用开发者不再依赖于物理硬件来开发和验证代码更改.

2.通过与核心框架保持高度一致,以高保真度为重点来复制真实设备的基于框架的行为.

3.在各个 API 级别达到一致的功能水平,与物理硬件上的行为保持一致.

4.实现规模化,能够并行运行多台设备,能够并发执行测试,实现高保真度且入门成本较低.    

5.提供可配置的设备,能够调整外形规格、RAM、CPU 、I/O设备,方便自动化测试及参数调优.

二.虚拟机管理器类型

从软件技术层面看,常见的有操作系统级虚拟化,应用级虚拟化(如arm64上运行arm32应用转义),语言级虚拟化(如java虚拟机,python虚拟机).

虚拟机管理器 (简称vmm,也称hypervisor),为guest os(指vmm运行的os)分配资源,提供运行所需的环境及必要的硬件模拟,也可以看作是虚拟环境中的元操作系统,主要有两种类型.

类型 1 虚拟机管理器直接在物理硬件(通常是服务器)上运行,作用是支持运行多个OS.通常情况下,在虚拟机管理器上创建和运行虚拟机。有些管理器如VMware vSphere支持用户选择要在虚拟机中安装guest操作系统.

类型 2 虚拟机管理器作为应用在主机操作系统中运行,使用类型2虚拟机管理器,一般需要创建虚拟机, 然后在其中安装guest os.以android系统为例,cuttlefish虚拟机管理程序crosvm就是属于类型2的虚拟机管理器.

三.全虚拟化与半虚拟化

不管哪一种vmm,除了提供CPU,内存虚拟化之外,还需要提供I/O虚拟化能力.从技术实现来看,虚拟化实现分为全虚拟化和半虚拟化两种方式.在全虚拟化中,guest os在位于裸机上的虚拟机管理程序之上运行. guest os不知道它正在被虚拟化,并且无需更改即可在此配置中工作.而在半虚拟化中, guest os不仅知道自己正在vmm上运行,而且还包含使guest os到vmm的转换更加高效的代码.    

在完全虚拟化方案中,管理程序必须模拟设备硬件,该设备硬件在会话的最低级别进行模拟.尽管这种抽象模拟很干净,但它也是效率最低且高度复杂的.在半虚拟化方案中,客户和vmm可以协同工作以使模拟高效,半虚拟化方法的缺点是操作系统知道它正在被虚拟化并且需要修改才能工作.硬件随着虚拟化不断变化,新处理器结合了高级指令,使guest os和vmm转换更加高效,I/O虚拟化的硬件也在不断变化,但在传统的全虚拟化环境中,vmm必须捕获这些请求,然后模拟真实硬件的行为,尽管这样做提供了最大的灵活性,但它导致效率低下.对于半虚拟化,guest os包含前端的驱动程序,vmm实现特定设备模拟的后端驱动程序.这些前端和后端驱动程序是半虚拟化主要特点,它们为模拟设备访问的开发提供标准化接口,且代码高度复用.

四.Virtio半虚拟化架构

 Linux 提供了多种具有不同特性的虚拟机解决方案,包括基于内核的虚拟机 (KVM)、 lguest 和用户模式 Linux,以及设备半虚拟化virtio方案. virtio 是半虚拟化管理程序中一组常见模拟设备的抽象,这种设计允许虚拟机管理程序导出一组通用的模拟设备并通过通用的应用程序编程接口(API)访问设备。通过半虚拟化管理程序,guest os可以实现通用的接口,并在后端驱动程序进行特定的设备模拟.后端驱动程序不必是通用的,只要它们实现前端所需的行为即可. virtio 并不提供各种设备模拟机制(用于网络、块和其他驱动程序),而是为这些设备模拟提供公共前端,以标准化接口并提高跨平台代码的重用.            

以图1为例说明virtio实现原理.在qemu-kvm虚拟化方案中,virtio提供设备虚拟化的能力.qemu是类型2虚拟机管理器,运行在linux用户空间中.guest os是qemu创建的运行实例,在guest os中包含了virtio前端驱动,qemu实现了virtio 后端设备,提供设备模拟或通过host kernel转接物理设备.guest os 中的virtio 驱动和qemu中的virtio设备通过virtqueue通信. virtio定义了控制平面和数据平面的分离架构,两者的侧重各有不同,控制平面追求尽可能的灵活以兼容不同的设备和厂商,而数据平面则追求更高的转发效率以便快速的交换数据包.

9bfdeaa1f36420f82c96b547a5dc2cd5.png

五.Virtio规范

由于virtio前端驱动处于guest os中,os种类及厂家各不相同.后端设备模拟处于虚拟机管理器中,且不限于特定的虚拟机管理器,软件的实现由不同厂家独立完成,因此前段驱动和后端设备的交互必须基于开放的标准完成.virtio规范制定了virtio设备的标准,定义了设备状态,功能,通知,复位,配置空间,virtqueue,共享内存,设备操作与设备发现,设备初始化等一些列标准,旨在为virtio设备提供直接、高效、标准和可扩展的机制, 而不是针对每个环境或每个操作系统设计的精品机制,任何基于该规范开发的驱动都能支持其匹配的标准虚拟化设备.目前官网已公布了最新VIRTIO版本1.3. 规范由OASIS委员会负责制定和发布.    

六.virtio驱动实例分析

前端驱动

结合以Android发布的kernel 6.6虚拟声卡驱动,分析下virtio的实现机制.

首先看声卡驱动的注册,在virtio_card.c中定义了声卡驱动.驱动只定义了几个函数和id table. pm sleep相关的我们不必关注,id table定义了虚拟声卡的device id和vendor id,用于匹配虚拟总线上的设备用的,如果匹配到id table中的设备,就执行probe函数,进行设备初始化操作. remove函数执行设备卸载的操作.validate是验证功能和配置空间的函数.前端驱动实现之所以这么简单,在于virtio框架实现了虚拟驱动的主要功能.

ff10794159ef7740a701a150420ec231.png

注册函数module_virtio_driver是module_driver的宏封装,module_driver最终定义了驱动的init函数和exit函数,分别调用register_virtio_driver和unregister_virtio_driver进行注册和卸载.

af0f6d0b48c6782e85db6ab4e78e17a6.png    

register_virtio_driver,其定义在virtio.c中,调用了driver_register进行驱动注册.可以看到实现和普通驱动没有什么不同,唯一需要注意的是driver的bus成员赋值virtio_bus.这真正决定了它是一个virtio类型的驱动.

e92370368ed1adbea6e6c0312a5b4b90.png

虚拟总线

虚拟总线的注册,和其它总线完全一样,直接调用bus_register注册virtio bus结构体.

9bacd9065cf49131f02090b0be935d8c.png

对于virtio bus,我们重点关注它的match函数及probe函数.match函数赋值virtio_dev_match,这个函数就是执行前面提到的驱动的id table和虚拟总线上挂载的设备匹配检查,如果device id和vendor id匹配成功,先执行bus的probe函数,主要是完成vritio规范中定义的设备状态设置,设备feature获取,设备配置等操作.然后就执行driver的probe函数,进入driver的初始化.

不难看出所有virtio设备都会挂载到virtio总线上,并由总线匹配与其绑定的设备和驱动.这是linux驱动开发人员很熟悉的驱动模型,自此并没有看到明显有别于其它类型驱动的地方.    

如下图所示virtio总线,设备,驱动结构,通过virtio总线进行统一管理.

0c523c9bef50bce4ce2762c6737a8b15.png

图2.虚拟设备的总线结构

注册过程及框架层次

再回到虚拟声卡驱动,看看virtsnd_probe函数片段.结构体snd是virtio_snd类型指针,在这之前分配了内存,并赋值snd->dev=vdev.这个vdev是virtsnd_probe传递的virtio_device类型参数,也是从virtio bus的probe函数中传递过来的,稍后展开分析.

17939ed946b2bc754c76169d9c008b2e.png

首先是spin lock的初始化, snd的每个virtqueue都有一个spin lock.接下来virtio_find_vqs函数主要是获取virtqueque. virtqueue是一个简单的结构,起到driver和device通信纽带作用.它标识一个可选的回调函数(当虚拟机管理程序使用缓冲区时调用)、对virtio_device 的引用、对 virtqueue 操作的引用以及引用要使用的底层实现的特殊priv引用.虽然回调是可选的,但可以动态启用或禁用回调.    

需要注意的是,virtqueue并不是在snd虚拟前端驱动中创建的,而是从virtio_device中通过virtio_config_ops 获取的. virtio_device不包含对virtqueue的引用,要识别与此virtio_device关联的virtqueue,使用virtio_config_ops结构和find_vq函数,此函数返回与此virtio_device实例关联的虚拟队列.find_vq函数还允许为virtqueue指定一个回调函数,该函数用于通知guest os来自虚拟机管理程序的缓冲区.

5ce8d77243b31f3c1a34918e3c92eb6d.png

图3. virtio前端对象层次结构

virtio_device_ready语句的作用是使能virtqueue,建立了虚拟声卡设备的信道.调用了virtio_config_ops结构的set_status函数.

virtsnd_build_devs函数真正创建声卡,包括为声卡申请内存及初始化,配置和创建jack,pcm,chmap等音频设备,最后调用snd_card_register完成声卡注册.从用户空间来看,虚拟声卡和物理的声卡设备的接口完全一致,前端驱动及框架封装了虚拟化的底层逻辑,对于用户层面是无感知的,应用程序不需要任何修改即能兼容虚拟化设备.

通过以上分析,不难看出,驱动的核心操作是通过virtio_deivce结构的引用及相关回调实现的,virtio框架实现了设备识别,配置,初始化等高效复用的代码.一个重要的结果是保证了前端驱动的简单化,标准化,易扩展的特征.virtio设计原则是既要解决传统I/O全虚拟化方案的低效问题,也要guest os修改最小且保证兼容性及扩展性,这是架构设计重点考虑的问题.           

虚拟设备

在总线和驱动的操作中,都涉及一个核心的结构virtio_device,包含设备信息及操作.这是虚拟化设备的表示,它封装了virtqueue及vring重要操作和设备信息.应注意到,前端驱动及virtio框架(guest)并不包含此结构的创建,甚至也不能确定虚拟设备以什么方式连接到guest系统中,它是由总线和驱动的probe函数直接或间接的传递来的,事实上从guest的代码中是看不出此设备在哪里完成初始化的.

回到图一virtio框架示例中,virt_device是在vmm中实现的,而guest os中的virtio_device对象可以看做作设备在guest侧的表示,或看作是远程对象的引用.

在virtio 1.3规范第2章Basic Facilities of a Virtio Device中定义了虚拟设备的功能标准,在第4章virtio transport options中定义了设备接入方式,包括PCI, MMIO, Channel IO三种方式,virtio可以使用不同的总线接入,最常用的是PCI. virtio设备可以实现任何类型的PCI设备,包括传统PCI设备或PCI Express设备.使用virtio over PCI总线的virtio设备必须向guest os公开一个满足相应PCI规范(PCI或PCIe)要求的接口.            

对于使用PCI作为虚拟I/O的虚拟机管理器来说,只需要通过PCI设备模拟就可以实现和guest os以PCI协议的方式通信了.对于guest os来说,虚拟设备首先是一个PCI设备,通过vmm分配的PCI地址空间发现PCI设备,并通过PCI PID/VID匹配和virtio_pci_id_table中支持的列表做匹配,如果匹配到,则绑定virtio_pci_driver并执行virtio_pci_probe函数, virtio_pci_probe除了完成PCI协议规定的操作外,还完成virtio_device的创建和初始化,最后调用register_virtio_device注册到虚拟总线(注册函数中virtio_device绑定了virtio_bus),至此完成了虚拟设备的注册,并调用了virtio bus的probe函数,如前章节所述,virtio bus在probe函数中匹配前端驱动.         

注意这里的PCI device id和vendor id和虚拟声卡设备中的id是不同的概念,不过存在一个对应关系.任何PCI供应商ID为0x1AF4且PCI设备ID为0x1000到0x107F的PCI设备都是virtio设备.非过渡设备必须具有通过将0x1040加上virtio设备ID计算得出的PCI设备ID.

七.小结

综上所述,virtio框架实现virtio规范要求,设备通过PCI等成熟协议接入,创建虚拟总线框架以便管理驱动及设备,除此之外并不需要创造或依赖新的技术,新的通信协议或新的软件概念.从PCI角度看,虚拟设备就是普通的PCI设备,从前端设备驱动来看,虚拟设备就是挂载在虚拟总线上的普通设备.如此最大化减少了方案推广的成本,为guest端提供简单,通用,易扩展的解决方案。       

参考文献:    

https://developer.ibm.com/articles/l-virtio/?mhsrc=ibmsearch_a&mhq=virtio

https://www.linux-kvm.org/page/Virtio          
https://docs.oasis-open.org/virtio/virtio/v1.3/csd01/virtio-v1.3-csd01.html#x1-1220001

https://blogs.oracle.com/linux/post/introduction-to-virtio

一文读懂QUIC 协议:更快、更稳、更高效的网络通信

Binder驱动中的流程详解

ANR问题产生原理和分析思路总结

f12f24aad7fc008915d8a1adb13d4d77.gif

长按关注内核工匠微信

Linux内核黑科技| 技术文章| 精选教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OPPO内核工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值