共计 2666 个字符,预计需要花费 7 分钟才能阅读完成。
title: 云计算_05_虚拟化_KVM虚拟化技术 date: 2022-04-25 23:42:49.0 updated: 2022-05-10 15:01:55.073 url: /archives/cloud05 categories:
- 云计算
- 可信计算 tags:
- 云计算
- 虚拟化
- KVM
1 KVM虚拟化架构
1.1 主流虚拟化架构对比
2 KVM CPU虚拟化
作为VMM,KVM分为两部分,分别是运行于Kernel模式的KVM内核模块和运行于User模式的Qemu模块。这里的Kernel模式和User模式,实际上指的是VMX根模式下的特权级0和特权级3。另外,KVM将虚拟机所在的运行模式称为Guest模式。所谓Guest模式,实际上指的是VMX的非根模式。
利用VT-x技术的支持,KVM中的每个虚拟机可具有多个虚拟处理器VCPU,每个VCPU对应一个Qemu线程,VCPU的创建、初始化、运行以及退出处理都在Qemu线程上下文中进行,需要Kernel、User和Guest三种模式相互配合,其工作模型如图2.1所示。Qemu线程与KVM内核模块间以ioctl的方式进行交互,而KVM内核模块与客户软件之间通过VM Exit和VM entry操作进行切换。
Qemu线程以ioctl的方式指示KVM内核模块进行VCPU的创建和初始化等操作,主要指VMM创建VCPU运行所需的各种数据结构并初始化。其中很重要的一个数据结构就是VMCS,其初始化配置见附2。
初始化工作完成之后,Qemu线程以ioctl的方式向KVM内核模块发出运行VCPU的指示,后者执行VM entry操作,将处理器由kernel模式切换到Guest模式,中止宿主机软件,转而运行客户软件。注意,宿主机软件被中止时,正处于Qemu线程上下文,且正在执行ioctl系统调用的kernel模式处理程序。客户软件在运行过程中,如发生异常或外部中断等事件,或执行I/O操作,可能导致VM exit,将处理器状态由Guest模式切换回Kernel模式。KVM内核模块检查发生VM exit的原因,如果VM exit由于I/O操作导致,则执行系统调用返回操作,将I/O操作交给处于User模式的Qemu线程来处理,Qemu线程在处理完I/O操作后再次执行ioctl,指示KVM切换处理器到Guest模式,恢复客户软件的运行;如果VM exit由于其它原因导致,则由KVM内核模块负责处理,并在处理后切换处理器到Guest模式,恢复客户机的运行。
3 Qemu/KVM
可以看到,对于宿主机来说,一个虚拟机对应一个Qemu进程,Qemu的作用可以理解为,为Qemu内部的虚拟机VM,创建内存,创建vcpu,或者可以换个角度来说,是其内部的VM使用了Qemu进程映射的内存,但是Qemu实际映射的内存,其实是由KVM来实现的,Qemu只是调用了KVM内核接口来分配内存、vcpu等操作,这里可以理解为我们普通的进程申请内存空间,都得通过linux内核给的接口来实现内存申请是一样的。 而对于宿主机来说,Qemu只是一个普通的进程,宿主机并不把它认为是Guest OS。
而且Qemu对于KVM来说,主要是用来做IO的虚拟化,简单地说就是IO的操作模拟。 加入Qemu进程中的VM发生了VM-Exit事件,会进入Root模式的KVM中,KVM会分析引起事件的原因是什么?如果是IO事件,这里就分为几种不同的方式。之前我们已经提到过Qemu/KVM实现的IO虚拟化是有全虚拟化和半虚拟化两种方式的。
3.1 IO全虚拟化
全虚拟化我们已经讲过,他是使用软件模拟的方式。这种方式,当VM发生IO事件时,如果这些操作发生了VM-Exit,那么在KVM判断过后,发现是IO事件,会将这个IO请求交给Qemu进程处理,例如发网络数据包、输出字符等。
于是qemu进程将此操作代替Guest完成后并执行相应的“回调”,通过ioctl调用告诉KVM IO操作已完成,进入VM继续运行。这个过程就涉及到VM-Exit和VM-Entry操作,会造成较大的开销。
当然在这个图中,不能完全的描述IO过程,例如如果是IO事件,KVM将事件交给Qemu进程处理后,就类似我们打印机IO,CPU并不会等待IO执行完成,而是采用了DMA的方式,在IO处理的期间,Qemu中的虚拟机也是可以继续运行的。当IO操作结束后,通过设置vcpu或者cpu中断告知VM IO事件已经处理完成,vcpu中断也是通过设置相应的中断寄存器实现的。
3.2 IO半虚拟化
前面文章我们也说过了,IO半虚拟化。qemu/kvm的半虚拟化实现,是通过virtio 驱动来实现的,通过了共享内存的方式来实现优化。上图是KVM/Qemu Virtio 网络虚拟化的一个图,相较于之前的全虚拟化,Virtio通过在Guest的Driver层引入了两个队列和相应的队列就绪描述符与qemu-kvm层Virtio Backend进行通信,并用文件描述符来替代之前的中断。
Virtio front-end与后端之间通过Vring buffer交互,在qemu中,使用事件循环机制来描述buffer的状态,这样当buffer中有数据的时候,qemu-kvm会监听到eventfd的事件就绪,于是就可以读取数据后发送到tap设备,当有数据从tap设备过来的时候,qemu将数据写入到buffer,并设置eventfd,这样front-end监听到事件就绪后从buffer中读取数据。
可以看到,这种方式不像全虚拟化方式,频繁发生VM-Exit和VM-Entry事件,减少了处理器的开销。但是这种方式依然存在问题,可以看到存在多次内存拷贝,对于网络io来说,效果不怎么好,于是vhost-net出现了。这个我们简单说一下,有时间再细说。
vhost-net 绕过了 QEMU 直接在Guest的front-end和backend之间通信,减少了数据的拷贝,特别是减少了用户态到内核态的拷贝。性能得到大大加强,就吞吐量来说,vhost-net基本能够跑满一台物理机的带宽。vhost-net需要内核支持,Redhat 6.1 后开始支持,默认状态下是开启的。