接前一篇文章: QEMU源码全解析 —— 块设备虚拟化(1)
本文内容参考:
《 QEMU /KVM源码解析与应用》 —— 李强,机械工业出版社
特此致谢!
上一回讲解了几种虚拟化方式(全虚拟化、半虚拟化和硬件辅助虚拟化)的优缺点及其对比。那么,对于磁盘即块设备来说,选择哪种虚拟化方式比较好呢?比较通行的方式是选择半虚拟化,即虚拟机(监视器)里边的操作系统知道自己是运行在虚拟机中的,虚拟机需要与宿主操作系统进行协作,共同完成虚拟化过程。
那么问题又来了,既然要对虚拟机的操作系统进行修改,使其能够与宿主机系统进行协作,那么具体的实现细节应该是怎样的?是每种设备、甚至是每个厂家的设备都单独区别开来进行(半)虚拟化,还是用一种统一的方式进行(半)虚拟化?这就是本回要重点讲解的内容 —— virtio的基本原理。
virtio的基本原理
在虚拟化技术的早期,不同的虚拟化技术会针对不同磁盘设备(以及网络设备等)实现不同的驱动。相应地,虚拟机里边的操作系统也要根据不同的虚拟化技术、不同的物理存储设备而选择加载不同的驱动。这样一来就产生问题了,由于磁盘设备(以及网络设备等)太多了,驱动纷繁芜杂。这样“各自为战”的情况,一方面不利于维护,另一方面也使得代码呈现肆意生长的态势。就有点类似于家装行业、汽车行业中的零配件,形状尺寸千差万别,后期装修和维修、维护的时候令人头疼。后来,人们(开发者)觉得这不行,得借鉴装修、汽车行业的经验(笔者的臆想),制定统一的标准,也就是要完成标准化过程。于是,virtio就诞生了。virtio全称是Virtual IO,就是虚拟化I/O的意思。上一回曾提到,磁盘和网络等设备都属于外部设备(外设),这里就都属于IO的范畴。
virtio负责对于虚拟机提供统一的接口,也就是说,虚拟机中的操作系统加载的驱动,之前是千差万别的,之后就都统一加载virtio了。
与之对应地,在宿主机中可以实现不同的virtio后端,来适配不同的物理设备。
virtio是一种前后端架构,包括前端驱动(Front-End Driver)和后端设备(Back-End Device)以及自身定义的传输协议。通过传输协议,virtio不仅可以用于QEMU/KVM方案,也可以用于其它的虚拟化方案。如虚拟机可以不必是QEMU,而也可以是其它类型的虚拟机;后端不一定要在QEMU中实现,也可以在内核中实现(这实际上就是vhost方案)。
virtio架构分层
virtio可以分为3层:
- 前端驱动(Front-end drivers)
前端驱动为虚拟机内部的virtio模拟设备对应的驱动,每一种前端设备都需要有对应的驱动才能正常运行。
前端驱动的主要作用是:
1)接收用户态的请求;
2)然后按照传输协议将这些请求进行封装;
3)再写I/O端口;
4) 发送一个通知 到QEMU的后端设备。
- 后端设备/后端驱动(Back-end devices/drivers)
后端设备则是在QEMU中,用来接收前端驱动发过来的I/O请求,然后从接收的数据中按照传输协议的格式进行解析。对于磁盘等需要实际物理设备交互的请求,后端驱动会对物理设备进行操作,从而完成请求,并且会 通过中断机制通知 前端驱动。
- virtio队列(virtio queue,virtqueue)
virtio前端和后端驱动的数据传输通过virtio队列(virtio queue,virtqueue)完成,一个设备会注册若干个virtio队列,每个队列负责处理不同的数据传输。这些队列 有的是控制层面的队列 、 有的是数据层面的队列 。
virtqueue是通过vring实现的,也有的分层方法将virtqueue和vring划分为两层,从而整体将virtio分为4层(如下图所示)。
vring是虚拟机和QEMU之间共享的一段环形缓冲区。当虚拟机需要发送请求到QEMU的时候就准备好数据,将数据描述放到vring中,写一个I/O端口。然后QEMU就能够从vring中读取数据信息,进而从内存中读出数据。QEMU完成请求之后,也将数据结构存放在vring中,前端驱动也就可以从vring中得到数据。
更多内容请看下回。