vmware的vmnet是开源的,因为linux下需要编译模块,而编译模块必须需要源代码,并且网络协议栈完全是linux操作系统内部的事情,因此它必须完全开源才可以,然而vmware的另一个重要的内核模块vmmon却可以尽量少的开放源代码,而保留一些闭源的内容以二进制的方式提供,因为这些内容不依赖于linux操作系统,而只依赖于x86 cpu的指令系统,因此比linux操作系统更加底层,换句话说这些二进制代码不论在哪种操作系统上,只要硬件是x86的,那么它们的代码和执行效果都是一样的。然而,仅有的开源的代码虽然不能让我们知道vmware到底是怎么执行指令的,但是却可以使我们明白vmware执行的大致流程,和vmnet一样,vmmon也有一样专门的源码目录:vmware-distrib/lib/modules/source/vmmon-only,其中有linux一个目录,里面的代码都是特定于linux平台的:
driver.c:提供了字符设备操作接口
hostif.c:host读写guest内存页面的接口,主要用于BT技术
vmcore目录下有一个moduleloop.c文件,其中的Vmx86_RunVM就是运行虚拟机guest os的入口函数,其中我们只能看到一个大致的处理流程,到了关键点的时候,就闭源了,很是不爽!
int Vmx86_RunVM(VMDriver *vm, Vcpuid vcpuid)
{
   ...
   for (;;) {
      crosspage->retval = retval;
    ...
      UCTIMESTAMP(crosspage, SWITCHING_TO_MONITOR);
      Task_Switch(vm, vcpuid);  //guest全在这个函数中执行,执行完返回vmm,此时vmm的对于host有用的数据都在crosspage中
      UCTIMESTAMP(crosspage, SWITCHED_TO_MODULE);
skipTaskSwitch:;
      retval = MODULECALL_USERRETURN;
    ...//判断是否返回vmware-vmx或者继续到guest中执行。是这样的guest os中的系统调用或者任何的ring0指令的调用都会被vmm捕获,这是因为vmm(以及其ring0上ring1的guest os)和host os共享了一段cross的内存,因此即使转到了guest os中也能和host相通信,guest的数据是有必要传给host的,例如需要host为之模拟设备的时候,比如以太网卡。   
   }
   NOT_REACHED();
}
Task_Switch这个函数和操作系统是无关的,它只和x86机器有关,它执行的就是vmm和guest os,否则如果它和linux相关的话,切换寄存器和机器状态后,host中运行的linux将不复存在,它还怎么依赖linux呢?换句话说,它只是执行了guest os,这期间所有的寄存器都已经换掉了,特别是cr3也已经指向了guest os中当前进程的影子页表,加上guest os执行时的BT(二进制翻译)技术,必要的时候会陷入到ring0的vmm中,此时真实主机中运行的linux操作系统已经不在了(x86的指令和数据都需要页表定位,cr3已经换给guest了,linux继续执行的指令当然就定位不到了)。在Task_Switch的调用栈中,最激动人心的就是SwitchToMonitor32函数了,因为马上就要看到guest是如何被调用起来的了,然而它最终以下列代码了事:
const char     *codePtr = &crosspage->contextSwitch.hostXToVmm32[0];
SwitchFn fn = (SwitchFn)codePtr;
fn(hostCS, src, dst, newDstVA);
fn是什么,hostXToVmm32是什么?我们不得而知,我们只知道fn的参数是上次guest os被换下来时的guest的机器状态和内存状态,我们只知道fn是下列结构体的成员:
typedef struct ContextSwitch {
   uint8  hostXToVmm32[WS_CODE_STD]; /* hws_32h_32v -or- hws_64h_32v */
   uint8  vmmXToHostY[WS_CODE_VMM];  /* vws_32h_64v -or- vws_64h_32v */
   uint8  hostXToVmm64[WS_CODE_64];  /* hws_32h_64v -or- hws_64h_64v */
} ContextSwitch;
切换的代吗由二进制实现,这是vmware所不想公开的吗?...
     不管怎样,struct VMCrossPage都是一个很重要的结构体,其中的ContextInfo描述了一台x86机器所需要的所有环境,其中的Task描述了当前进程的所有环境,虚拟一台机器所需要就是这些环境,因此当切换到guest os的时候,实际上物理机器已经完全切到guest os了,因此它已经不再“虚拟”了,而很真实了,加之绝大多数的指令都不要二进制翻译而可以直接执行。事情之所以变得复杂有三点原因,第一是为了保护host os的状态而将guest os降级到了ring1,从而使得它无法直接处理硬件操作;第二是由于guest os看到的物理内存已经不再“物理”,因而无法直接使MMU操作,因此采用了影子页表的技术;第三是由于x86上的一些ring0指令在非ring0执行时并不trap,导致无法知道执行结果,因此必须使用BT配合之。
     以上的三点复杂性中的最后一点是由x86引起的,而另外两点却失所有平台都存在的。其中前两点导致必然需要一个monitor,对于vmware而言就是vmmon,它实现的简单性导致了虚拟机无法嵌套,也就是不能在一个guest os中运行另一个guest os,这是因为vmmon实现本身就没有实现嵌套,如果存在两层的guest os,下面那层尚可以使用host中vmm的影子页表得到物理内存,上面那层呢?它必然需要下面那层的vmm的影子页表得到物理内存,而下面那层的物理内存本身不过是host中的一个.vmem文件,映射进内存也不是连续的,因此构建于其上的上面那个guest os的影子内存也不是真正的物理内存,无法交给MMU去操作的--关键的是,vmm中的影子页表只是直接用了host的mmu,它并没有模拟mmu的细节。由于vmware中运行的guest os和host os分时共享了一套机器环境,实际上是平等的,如果guest os中再来一个guest os,它必然也要和第一个guest os以及host os共享硬件环境,然而谁来监控它呢?由于第二个guest os的monitor在第一个guest os之中,也在ring1,因此根本无法有效监控第二个guest os。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐