打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Enjoy hacking ? Blog Archive ? [译]一种针对Linux的虚...

[译]一种针对Linux的虚拟化框架

no comment

Linux 内核支持多种虚拟化机制,而且这很有可能随着虚拟化的进步而增多,新的机制也将会被发现(例如 lguest)。但是,所有这些虚拟化机制都在 linux 之上运行,那么他们又是如何利用下层的内核来服务于 I/O 虚拟化呢?问题的答案是 virtio,它为hypervisor和常见的驱动集提供了高效的抽象。让我们来探索 virtio 、学习 Linux 将很快成为虚拟机监管程序不二选择的原因吧。

简而言之,virtio 是一种准虚拟化hypervisor中、位于设备之上的抽象层。virtio 是由 Rusty Russell 在探寻他自己的虚拟化解决方案(lguest)时开发。本文以介绍准虚拟化和模拟设备开始,然后探究 virtio 的细节。关注的焦点在于 2.6.30 内核版本之后的virtio 框架。

Linux 扮演着hypervisor的角色。正如我的文章《关于 Linux 作为监管程序的文章》所说,Linux 提供了多种具有不同特征和优势的hypervisor。例子包括基于内核的虚拟机(KVM),lguest,以及 User­mode Linux。Linux 拥有这些不同的hypervisor解决方案,每种解决方案又有着各自的需求,因而加重了操作系统的负担。其中一种负担就是设备的虚拟化。Virtio 并不是采用多种设备模拟机制(网络设备、块设备、其他设备),而是为这些设备的模拟提供了一个通用的前端,用来使接口标准化,以及能够增加代码复用程度。

完全虚拟化和准虚拟化
——————————–

让我们以两种不同类型的虚拟化机制的快速讨论开始:完全虚拟化和准虚拟化。对于完全虚拟化的情形,客户操作系统运行在位于裸机之上的监管程序之上。它感知不到自己已经被虚拟化了,而且在这样的结构下下并不需要改动就可以运行。相反,对于准虚拟化,客户操作系统不仅能感知到自己运行在监管程序之上,而且还包含了能够使客户与监管程序切换更高效的代码(图 1)。

在完全虚拟化的机制中,hypervisor必须在会话的最底层模拟硬件设备(例如,一个网络驱动)。尽管在这个层次的模拟是精准的,但它并不高效,也很复杂。在准虚拟化的情形下,客户能和监管程序协同工作来确保更为高效的模拟。这样做对于准虚拟化的代价是,客户操作系统感知到它被虚拟而且需要改动才能运行。

图 1 完全虚拟化和准虚拟化环境的设备模拟

硬件跟随虚拟化而继续改变。新的处理器引入高级的指令来让客户操作系统和hypervisor之间的切换更为高效。而且,硬件也同样为输入/输出(I/O)虚拟化而改变。

但是在传统的完全虚拟化环境中,hypervisor必须捕获这些请求,然后模拟真正硬件的行为。尽管这样做提供了最大的灵活性(也就是说,运行一个未修改过的操作系统),但它确实引入了低效率(见图 1 左侧)。图 1 的右侧显示了准虚拟化的情形。这里,客户操作系统能够意识到它运行在一个hypervisor之上,而且还包含作为前端的驱动程序。hypervisor实现了特定设备模拟的后端驱动。这些前端和后端程序是需要引入 virtio 的地方,它们为模拟设备的开发提供了标准化的接口,同时增进了代码重用并提高了效率。

针对 Linux 客户操作系统的一种抽象
—————————————————

从前面的部分你可以看出,virtio 是在准虚拟化hypervisor中模拟一些通用设备的抽
象。这种设计允许hypervisor导出一些通用的的模拟设备并提供应用程序接口(API)来
让这些设备可以被使用。图 2 证明了为什么这点很重要。有了准虚拟化监管程序之后,
客户操作系统能够在后端驱动处理特殊设备模拟的帮助下实现一组通用接口。后端驱
动只需要实现前端驱动要求的行为而无需通用。


图 2 virtio 下的驱动抽象

注意到在现实中(尽管并不这样要求),使用 QEMU 模拟设备发生在用户空间,因此后端驱动与监管者的用户空间发生交互,并通过 QEMU 为 I/O 提供便利。QEMU是一个系统模拟器,它提供了一个客户操作系统虚拟化平台,并且提供了真个系统的模拟(PCI 主机控制器、磁盘、网络、视频硬件、USB 控制器及其他硬件元素)。Virtio API 依赖于一个简单的缓冲区抽象,用来封装客户所需的命令和数据。让我们来看看 virtio API 的内部及其组件。

Virtio 架构
—————
除了前端驱动(客户操作系统中实现)和后端驱动(hypervisor中实现),virtio定义了两个层次来支持客户到hypervisor之间的通信。上层(称为 virtio)是虚拟队列接口,它从概念上将前端驱动和后端驱动联系起来。驱动程序能够使用零个或者多个队列,这取决于它们的需求。例如,virtio 网络驱动使用了两个虚拟队列(一个用于接收,另一个用于传送),而 virtio 块设备驱动只用了一个队列。虚拟队列,实际上被实现为跨越客户和hypervisor的衔接点。但是无论如何,这种方式能够被实现,只要客户和监管程序以相同的方式实现。


图 3 virtio 框架的上层架构

如图 3 所示,列出了 5 个前端驱动:块设备(如磁盘)驱动、网络设备驱动、PCI 驱动、ballon 驱动(用于动态的管理客户内存使用),以及一个控制台驱动。每一个前端驱动在supervisor中有有一个相应的后端驱动。

概念层次结构
——————-

从客户操作系统的角度,一个对象的结构定义为图 4 所示的样子。在最顶层的是virtio_driver,它代表了客户操作系统中的前端驱动。匹配这个驱动的设备被virtio_device(代表客户机的设备)封装。该结构体引用了 virtio_config_ops 结构体(定义了对 virtio 设备的操作及配置)。virtio_device 结构体被 virtqueue(包含一个它所服务的 virtio_device 的引用)。最后,每一个 virtqueue 对象都引用了 virtqueue_ops 对象,这个对象定义了基本的处理hypervisor驱动的队列操作。尽管队列操作是 virtio API 的核心,我还是先简单讨论一下如何识别一个新的设备,然后再探索 virtqueue_ops 操作的更多细节。


图 4 virtio 前端的对象结构

Virtio的过程始于 virtio_driver 的创建以及随后通过 register_virtio_driver 的注册。virtio_driver 结构定义了高级别的设备驱动、驱动支持的设备 ID 的列表、一个特性表(独立于设备类型)以及一个回调函数的列表。当supervisor识别到一个新的设备匹配设备列表里的一个 ID时, probe 函数会被调用(virtio_driver 对象提供),它用来传入virtio_device 对象。该对象以及设备的管理数据(以依赖于驱动的方式)会被缓存起来。依据驱动的类型, virtio_config_ops 函数可能会被调用,用来获得或者设置针对该设备的选项(例如,为一个 virtio_blk 设备获取磁盘的读写状态或者设置块设备的块大小)。注意到 virtio_device 没有到 virtio_queue 的引用(但 virtio_queue 确实引用了virtio_device)。为了识别和 virtio_device 关联的 virtio_queue,你可能会使用到virtio_config_ops 中的 find_vq 函数。该对象返回和 virtio_device 实例关联的virtqueue。find_vq 函数还允许为 virtqueue 指定一个回调函数(见图 4 中的 virtqueue 结构体),该回调函数用来通知客户操作系统有来自于hypervisor的响应。virtqueue 是一个简单的结构体,它包含一个可选的回调函数(当supervisor使用缓冲区时调用)、一个到 virtio_device 的引用、一个到 virtqueue 操作的引用以及一个特殊的 priv 的引用(使用设备的基本操作)。尽管 callback 是可选的,但动态地使能或禁用 callback 是可能的。

但是,层次结构中最核心的是 virtqueue_ops,它定义了命令和数据如何在客户和hypervisor之间移动。让我们先来探究从 virtqueue 中添加或者删除的对象。

Virtio 缓冲区
——————

客户(前端)驱动和后端驱动通过缓冲区进行通信。对一次 I/O 来说,客户提供一个或多个表示请求的缓冲区。例如,你可能提供三个缓冲区,第一个表示读请求,随后的两个缓冲区表示响应数据。在内部,这种结构由散集表表示(每一个表项表示一个地址和一个长度)。

核心 API
————-

通过 virtio_device 和 virtqueue(更常见)将前端驱动与 后端驱动联系起来。virtqueue 支持它自己的由 5 个函数组成的 API。你可以使用第一个函数 add_buf 来向hypervisor 发出请求。该请求,如前所说,以散集表的形式存在。对于 add_buf 来说,客户操作系统提供了的 virtqueue(请求将会加入该队列)、散集列表(地址和长度数组)、用作输出(目标是底层hypervisor)的缓冲区数量,以及用作输入(supervisor将为它们储存数据并返回到客户操作系统)的缓冲区数量。当通过 add_buf 向hypervisor发出请求时,客户操作系统能够通过 kick 函数通知hypervisor有新的请求。为了获得最佳的性能,客户操作系统应该在通过 kick 发出通知之前将尽可能多的缓冲区装载到Virtqueue。

来自监管程序的响应通过 get_buf 函数触发。客户操作系统仅需调用该函数或通过 virtqueue 提供的 callback 函数等待通知就可以实现轮询。当客户操作系统得知缓冲区可用时,调用 get_buf 返回完成的缓冲区。

Virtqueue API 的最后两个函数是 enable_cb 和 disable_cb。您可以使用这两个函数来启用或禁用回调进程(通过在 virtqueue 中由 find_vq 函数初始化的 callback 函数)。注意,该回调函数和hypervisor位于独立的地址空间中。因此,该调用是通过一个间接hypervisor调用(比如 kvm_hypercall)触发。

缓冲区的格式、顺序和内容仅对前端和后端驱动程序有意义。内部传输(当前实现中的连接点)仅移动缓冲区,并且不知道它们的内部表示。

Virtio 驱动的例子
————————-

你可以在 Linux 内核的子目录 drivers 里找到各种各样的前端驱动源码。Virtio 网络驱动在./drivers/net/virtio_net.c 中、Virtio 块设备驱动在./drivers/block/virtio_blk.c 中。子目录./drivers/virtio 提供了 virtio 接口的实现(virtio 设备、驱动、virtqueue、以及连接点)。virtio 也在高性能计算(HPC)对 Intel 虚拟机(VM)通过共享内存通信的研究中被使用。具体来说地,它的实现是通过使用 virtio PCI 驱动的一个虚拟 PCI 接口。你通过下面的资源部分获得更多信息。

今天,你可以在 Linux 内核中实践这中准虚拟化架构。所有你需要的只是一个用作监管程序的内核、一个客户操作系统内核以及用来模拟设备的 QEMU。你可以使用KVM,也可以使用 Rusty Russell 的 lguest(改动过的客户 Linux 内核)。这两种虚拟化解决方案都支持 virtio(通过使用 QEMU 系统模拟和用于虚拟化管理的 libvirt)。Rusty 工作的成果便是减少了准虚拟化的代码量以及更快的虚拟设备模拟。但更重要的是,virtio 已经被发现能够提供比当前的商业解决方案更好的性能(对网络 I/O来说是 2-3 倍)。性能的猛增有一个代价,但如果你是使用 Linux 作为你的监管程序和客户操作系统,这无疑是值得的。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Virtio:针对 Linux 的 I/O 虚拟化框架
车载基础软件——内核和中间件核心技术:虚拟化(三)
车载操作系统(七):虚拟化(Hypervisor)
智能汽车车用基础软件的内核和中间件
云计算虚拟化技术
解读三种虚拟化之路连载二:虚拟化实现
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服