CPU纯软件全虚拟化技术
我们在前面的文章中提到了虚拟化技术的大致分类情况,即分为全虚拟化、半虚拟化和硬件辅助虚拟化3大类。而我们虚拟化技术最主要的虚拟主体就是我们的硬件CPU、内存和IO,那么我们的CPU在全虚拟化模式下如何工作,在半虚拟化下如何工作,在硬件辅助虚拟化模式下如何工作?或着说细分下来,我们又可以分为:
- CPU的全虚拟化技术、半虚拟化技术和硬件辅助虚拟化技术,
- 内存的全虚拟化技术、半虚拟化技术和硬件辅助虚拟化技术
- IO设备的全虚拟化技术、半虚拟化技术和硬件辅助虚拟化技术。
本次我们就来说说CPU的全虚拟化技术、半虚拟化技术和硬件辅助虚拟化技术。
不支持硬件辅助虚拟化技术的X86架构下的CPU有4个特权级(ring0~ring3),操作系统是处于最高级别的ring0,应用程序处于最低级别的ring3。
在这种架构下实现CPU的全虚拟化是极其困难的,为什么困难?
- 原先的OS运行在ring0层,拥有对所有硬件的全部特权级;
- 虚拟化之后将OS运行在ring1层,OS就没有权限执行一些特权指令,怎么保证这些特权指令执行;
- 在保证该OS虚拟机的特权指令执行的情况下,保证其他运行的OS虚拟机的安全;
1、模拟仿真技术
最先实现这种CPU全虚拟化技术的是Trap-and-emulation技术,即陷入模式和模拟仿真技术。这种技术通过将OS需求的特权指令通过VMM自动捕获的方式运行后返回去OS。当OS有特权指令产生时,VMM将其自动捕获,将OS所请求的特权指令进行截获,然后通过VMM运行之后将结果返回给OS层。VMM会使用模拟仿真将特权指令模拟仿真的方式执行一遍。
在虚拟化模式下,就存在着2中特殊的指令:特权指令和敏感指令。那么什么是特权指令?什么是敏感指令?
特权指令:系统中有一些操作和管理关键系统资源的指令,这些指令只有在最高特权级上能够正确运行。如果在非最高特权级上运行,特权指令会引发一个异常,处理器会陷入到最高特权级,交由系统软件处理了。
敏感指令:操作特权资源的指令,包括修改虚拟机的运行模式或者下面物理机的状态;读写时钟、中断等寄存器;访问存储保护系统、地址重定位系统及所有的I/O指令。
根据Popek和Goldberg的定义,指令集支持虚拟化的前提是:所有敏感指令都是特权指令。很可惜x86指令集不能满足这个要求。
虚拟化场景下,要求将GuestOS内核的特权解除,从原来的0降低到1或者3。这部分特权指令在Guest OS中发生的时候,就会产生Trap,被VMM捕获,从而由VMM完成。这就是虚拟的本质方法,特权解除和陷入模拟(Privilege deprivileging/Trap-and-Emulation)。虚拟化场景中敏感指令必须被VMM捕获并完成。对于一般 RISC 处理器,如 MIPS,PowerPC 以及SPARC,敏感指令肯定是特权指令,但是x86 例外,x86绝大多数的敏感指令是特权指令,但是由于部分敏感指令不是特权指令,执行这些指令的时候不会自动trap被VMM捕获。
2、二进制翻译技术
采用模拟仿真的方式模拟和虚拟化x86架构的CPU,但是由于x86架构的CPU中,不是所有的敏感指令都是特权指令,所以并不能完全的解决掉那些不是特权指令的敏感指令的模拟仿真问题。例如SGDT, SLDT, SIDT …
由于模拟仿真技术固有的缺陷,导致对CPU的虚拟化并不完整。所以也导致了基于x86的虚拟化难以和其他CPU架构一样实现虚拟化。比如IBM的Power CPU架构就很早具备了虚拟化的技术并使用于实践。
这个现象在1999年得到改善,VMware通过二进制翻译技术完成了对x86 CPU架构的完全虚拟化。
其主要采用优先级压缩技术(Ring Compression)和二进制代码翻译技术(Binary Translation)。优先级压缩技术让VMM和Guest运行在不同的特权级下。对x86架构而言,即VMM运行在最高特权级别Ring 0下,Guest OS运行在Ring 1下,用户应用运行在Ring 3下。因此,Guest OS的核心指令无法直接下达到计算机系统硬件执行,而是需要经过VMM的捕获和模拟执行(部分难以虚拟化的指令需要通过二进制翻译【Binary Translation】技术进行转换)。如下图所示。
特权级我想在这里就不用多说,大家都比较清楚,说说大家可能不清楚的二进制代码翻译技术。二进制翻译技术简称BT,是一种直接翻译可执行二进制程序的技术,能够把一种处理器上的二进制程序翻译到另外一种处理器上执行。二进制翻译技术将机器代码从源机器平台映射(翻译)至目标机器平台,包括指令语义与硬件资源的映射,使源机器平台上的代码“适应”目标平台。因此翻译后的代码更适应目标机器,具有更高的运行时效率。二进制翻译系统是位于应用程序和计算机硬件之间的一个软件层,它很好地降低了应用程序和底层硬件之间的耦合度,使得二者可以相对独立地发展和变化。二进制翻译也是一种编译技术,它与传统编译的差别在于其编译处理对象不同。传统编译处理的对象是某一种高级语言,经过编译处理生成某种机器的目标代码;二进制翻译处理的对象是某种机器的二进制代码,该二进制代码是通过传统编译过程生成的,经过二进制翻译处理后生成另一种机器的二进制代码。
根据不同的实现方式,二进制翻译技术可分为三大类:解释执行,静态翻译和动态翻译。
代码解释执行
解释执行(Interpretation)过程对源机器代码中的每条指令实时解释执行,系统不保存且不缓存解释过的指令,不需要用户干涉,也不进行任何优化。解释器相对容易开发,比较容易与老的体系结构高度兼容,但效率很差。
静态二进制翻译
在静态二进制翻译(SBT,Static BinaryTranslation)中,代码在运行之前被离线翻译,根据目标机器的指令结构生成一个新的程序,然后直接执行这个翻译后生成的程序。静态翻译器的离线翻译过程不会给程序运行带来额外开销,因此可以充分采用各种优化措施生产高质量代码,大大提高运行时效率。
动态二进制翻译
动态二进制翻译(DBT,Dynamic BinaryTranslation)则在程序运行时对执行到的代码片段进行翻译,克服了静态翻译所无法解决的一些困难,如运行时动态信息收集,代码挖掘,自修改代码和精确中断问题。而且动态翻译器对用户完全透明,无需用户干预。虽然动态翻译有上述诸多优点,翻译过程却由于受到动态执行的限制而不能像静态翻译那样进行完全细致的优化,使得翻译生成的代码效率比静态翻译器差。
三种二进制翻译技术的比较
解释执行是最易实现的一种翻译技术,但是其繁琐的实现方式大大降低了翻译系统的执行效率。静态翻译虽然能提供高效的运行时性能,但由于无法在静态环境下覆盖所有代码,无法脱离对解释器的依赖。与上述两种相比,动态翻译很好的解决了代码覆盖、自修改代码和精确中断等诸多问题,同时也能提供可接受的执行效率。因此VMware基于动态二进制翻译技术实现了x86架构的CPU的虚拟化。
典型动态二进制翻译系统结构所示,被翻译的代码称为源机器代码,在宿主机上运行的代码称为目标机器代码,一个典型的动态二进制翻译器主要包括两个模块:翻译引擎和执行引擎。其中翻译器引擎负责将源机器代码翻译代码翻译成目标机器代码;执行引擎负责准备目标机器代码运行的上下文环境(Execution Context)然后从目标机器代码缓存中找到源机器代码对应的目标代码并执行。
其基本运行流程如下:
查找(Lookup)阶段
这个阶段查询目标代码块是否存在于目标代码缓存中,如果存在则返回目标块入口地址,如果不存在则进入翻译阶段。
上下文切换(Context Switch)阶段
当一个目标代码块被查询到或者翻译模块生成的时候,二进制翻译系统会执行一次控制权转移。系统会把控制权交给执行模块去运行该目标代码块,目标代码块运行完毕后系统需要恢复执行引擎的控制权。一次控制权转移需要保存程序的上下文环境。
翻译(Translation)阶段
完成从源机器二进制代码到目标机器二进制代码的翻译。包括解码、中间代码优化、编码三个子阶段。
执行和链接(Executing & Linking)阶段
当基本块被翻译生成目标代码块之后,依照源代码的控制流完成目标代码块之间的直接以及间接跳转的链接,并依次运行目标代码块。
3、总结
在没有CPU硬件辅助虚拟化技术之前,对于X86架构的CPU就采用模拟和二进制翻译的技术对CPU进行虚拟化实现,但是模拟的方式存在固有缺陷,并不完全虚拟化了x86的CPU架构。而二进制翻译技术则采用完全不同的思路实现了x86架构的CPU虚拟化。其实对于x86的CPU虚拟化,其难点就在于对其特权指令和敏感指令的虚拟化实现,当然,在实现了CPU的指令这一难题之后,还有一个难题在等着我们!那就是x86架构的CPU调度问题?
在虚拟化环境下,x86架构的CPU有什么调度问题?
1、 虚拟CPU和物理CPU之间的对应关系?
2、 虚拟CPU和物理CPU之间的资源分配?
3、 虚拟CPU和虚拟CPU之间的优先级?
4、 多核虚拟CPU架构vSMP和vNUMA与物理多核CPU架构SMP和NUMA之间的调度和负载均衡?