Virtualbox源码分析4:VMM虚拟化实现源码分析1

Virtualbox源码分析4:VMM虚拟化框架实现源码分析

Intel和AMD都有自己VMM框架, Intel的叫做VMX, AMD的叫做SVM,两个实现原理类似,只是CPU指令,VMCS结构体不同,所以虚拟化软件需要同时支持VMX和SVM两套VMM。
VMX实现代码在VMM\VMMR0\HMVMXR0.cpp
SVM实现代码在VMM\VMMR0\HMSVMR0.cpp
本文已VMX为举例讲解:

4.1 VMX原理

Intel手册Vol 3C第23章到第33章详细讲解了VMX的原理,有兴趣可以下载下来看,下面先简单介绍一些重要的概念。

4.1.1 VMX的状态转化:

VMX有两种状态: root状态和non-root状态
在这里插入图片描述
其中几个重要的指令:
VMXON: 进入VMX root模式
VMXOFF:退出VMX root模式
VMExit:non-root模式进入到root模式 。这个可以由Guest主动调用,也可以是一些异常导致CPU主动触发。
VMEntry: root模式进入non-roo模式,即CPU开始运行Guest OS代码。
如果大家熟悉windows系统调用可以吧root模式理解成操作系统内核,non-root模式理解成操作系统应用层,vmexit理解成syscall或者异常处理,VMEntry理解成sysexit。

4.1.2 VMCS

在虚拟化中,为了实现vCPU,既要模拟CPU的运行,又要记录vCPU的状态(包括对vCPU运行的控制信息), vCPU的状态,就保护到一个叫做VMCS到结构体里,具体描述可以参考Intel手册24.3 ORGANIZATION OF VMCS DATA
VMM给每个vCPU都分配了一个VMCS结构体,每个VMCS都有三个状态(Launched,clear和active状态),每个实体CPU上只能有一个VMCS出于active/launched状态
下面是Virtual box里的定义:
hm_vmx.h

/** VMCS launch state clear. */
#define VMX_V_VMCS_LAUNCH_STATE_CLEAR                           RT_BIT(0)
/** VMCS launch state active. */
#define VMX_V_VMCS_LAUNCH_STATE_ACTIVE                          RT_BIT(1)
/** VMCS launch state current. */
#define VMX_V_VMCS_LAUNCH_STATE_CURRENT                         RT_BIT(2)
/** VMCS launch state launched. */
#define VMX_V_VMCS_LAUNCH_STATE_LAUNCHED                        RT_BIT(3)

下图是3个状态之间的转换关系
在这里插入图片描述
VMClEAR:传入一个物理地址,CPU会把当前VMCS里的数据拷贝到传入的物理地址中,并吧当前CPU的VMCS状态设置成clear状态
VMPTRLD(ACTIVE): 传入一个物理地址, 把当前CPU的VMCS指针指向这个物理地址
VMLAUNCH: 当VMM设置好VMCS之后,就可以执行VMLAUNCH让CPU切换到GuestOS状态,运行GuestOS里的代码。
在下一章的VMXR0SetupVM这个函数的解析里,可以看到使用代码是如何设置一个VMCS内容的。

VirtualBox里对VMCS对定义代码子啊HMInternal.h里

typedef struct VMXVMCSINFO
{
    /** @name Auxiliary information.
     * @{ */
    /** Ring-0 pointer to the hardware-assisted VMX execution function. */
    PFNHMVMXSTARTVM             pfnStartVM;
    /** Host-physical address of the EPTP. */
    RTHCPHYS                    HCPhysEPTP;                  这个地方保存了EPT Table的指针
    /** The VMCS launch state, see VMX_V_VMCS_LAUNCH_STATE_XXX. */
    uint32_t                    fVmcsState;                               保存上面介绍的vmcs状态
    /** The VMCS launch state of the shadow VMCS, see VMX_V_VMCS_LAUNCH_STATE_XXX. */
    uint32_t                    fShadowVmcsState;
    /** The host CPU for which its state has been exported to this VMCS. */
    RTCPUID                     idHostCpuState;              主机物理CPU的状态信息
    /** The host CPU on which we last executed this VMCS. */
    RTCPUID                     idHostCpuExec;
    /** Number of guest MSRs in the VM-entry MSR-load area. */
    uint32_t                    cEntryMsrLoad;
    /** Number of guest MSRs in the VM-exit MSR-store area. */
    uint32_t                    cExitMsrStore;
    /** Number of host MSRs in the VM-exit MSR-load area. */
    uint32_t                    cExitMsrLoad;
    /** @} */

    /** @name Cache of execution related VMCS fields.   这些区域是控制Guest OS行为
     *  @{ */
    /** Pin-based VM-execution controls. */
    uint32_t                    u32PinCtls;
    /** Processor-based VM-execution controls. */
    uint32_t                    u32ProcCtls;        设置VMExit事件
    /** Secondary processor-based VM-execution controls. */
    uint32_t                    u32ProcCtls2;      也是设置VMExit事件
    /** VM-entry controls. */
    uint32_t                    u32EntryCtls;       对VM Entry的行为进行控制
    /** VM-exit controls. */
    uint32_t                    u32ExitCtls;       这个地方可以设置VMExit事件
    /** Exception bitmap. */ 
    uint32_t                    u32XcptBitmap;     设置异常事件的bitmap
    /** Page-fault exception error-code mask. */
    uint32_t                    u32XcptPFMask;
    /** Page-fault exception error-code match. */
    uint32_t                    u32XcptPFMatch;
    /** Padding. */
    uint32_t                    u32Alignment0;
    /** TSC offset. */
    uint64_t                    u64TscOffset;
    /** VMCS link pointer. */
    uint64_t                    u64VmcsLinkPtr;
    /** CR0 guest/host mask. */
    uint64_t                    u64Cr0Mask;
    /** CR4 guest/host mask. */
    uint64_t                    u64Cr4Mask;
    /** @} */

    /** @name Host-virtual address of VMCS and related data structures.
     *  @{ */
    /** The VMCS. */
    R0PTRTYPE(void *)           pvVmcs;          当前VMCShost物理地址对应的虚拟地址,方便访问
    /** The shadow VMCS. */
    R0PTRTYPE(void *)           pvShadowVmcs;    用于支持嵌套虚拟化nested-guest shadow VMCS
    /** The virtual-APIC page. */
    R0PTRTYPE(uint8_t *)        pbVirtApic;
    /** The MSR bitmap. */
    R0PTRTYPE(void *)           pvMsrBitmap;    设置msr事件的bitmap
    /** The VM-entry MSR-load area. */
    R0PTRTYPE(void *)           pvGuestMsrLoad;
    /** The VM-exit MSR-store area. */
    R0PTRTYPE(void *)           pvGuestMsrStore;
    /** The VM-exit MSR-load area. */
    R0PTRTYPE(void *)           pvHostMsrLoad;
    /** @} */

    /** @name Real-mode emulation state.             实模式模拟的状态,多用于32位GuestOS在二进制翻译模式下系统启动时的模拟
     * @{ */
    /** Set if guest was executing in real mode (extra checks). */
    bool                        fWasInRealMode;
    /** Set if the guest switched to 64-bit mode on a 32-bit host. */
    bool                        fSwitchedTo64on32Obsolete;
    /** Padding. */
    bool                        afPadding0[6];
    struct
    {
        X86DESCATTR             AttrCS;
        X86DESCATTR             AttrDS;
        X86DESCATTR             AttrES;
        X86DESCATTR             AttrFS;
        X86DESCATTR             AttrGS;
        X86DESCATTR             AttrSS;
        X86EFLAGS               Eflags;
        bool                    fRealOnV86Active;
        bool                    afPadding1[3];
    } RealMode;
    /** @} */

    /** @name Host-physical address of VMCS and related data structures.
     *  @{ */
    /** The VMCS. */
    RTHCPHYS                    HCPhysVmcs;
    /** The shadow VMCS. */
    RTHCPHYS                    HCPhysShadowVmcs;
    /** The virtual APIC page. */
    RTHCPHYS                    HCPhysVirtApic;
    /** The MSR bitmap. */
    RTHCPHYS                    HCPhysMsrBitmap;
    /** The VM-entry MSR-load area. */
    RTHCPHYS                    HCPhysGuestMsrLoad;
    /** The VM-exit MSR-store area. */
    RTHCPHYS                    HCPhysGuestMsrStore;
    /** The VM-exit MSR-load area. */
    RTHCPHYS                    HCPhysHostMsrLoad;
    /** @} */

    /** @name R0-memory objects address for VMCS and related data structures.
     *  @{ */
    /** R0-memory object for VMCS and related data structures. */
    RTR0MEMOBJ                  hMemObj;
    /** @} */

    /** Padding. */
    uint64_t                    au64Padding[2];
} VMXVMCSINFO;

因为传入CPU的地址需要是物理地址,为了VMM代码方便访问这些物理地址,可以看到很多内容同时保存了物理地址和虚拟地址的指针。

4.1.3 VMExit:VMX异常

VMM调用VMEntry进入Guest OS之后,GuestOS需要与Host交互(比如GuestOS调用设备的IN/OUT指令),则需要通过VMExit退回到VMM里,让host处理相关内容之后再返回GuestOS。
什么样的时间需要处理器触发VMExit异常,则需要在GuestOS启动之前在VMCS里设置好,如上一章的VMXVMCSINIF结构体的里u32ProcCtls项等。
VMExit解释可以具体参考Intel指令手册的第24章

VMCS设置好了VMExit handlle之后,调用

VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, uProcCtls);

就完成了VMExit时间的设定,当GuestOS运行到指定的CPU指令的时候,CPU就会触发VMExit中断。

除了少数不可屏蔽VMExit中断,比如VMX_EXIT_CPUID之外,其他VMExit中断都是可以在GuestOS运行的时候动态控制的。
比如在某个时间,当CPU执行CR8写入指令的时候发现VMExit:

//设置fVal值
fVal |= VMX_PROC_CTLS_CR8_STORE_EXIT;
//commit到VMCS里
VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, fVal);

从commit到VMCS之后,当CPU执行到CR8写入指令的时候,就会触发VMExit异常,进入VMM里。
同意,如果不想当CPU执行CR8写入指令的时候发生异常, 修改VMCS即可。

//设置fVal值
fVal &= ~VMX_PROC_CTLS_CR8_STORE_EXIT;
//commit到VMCS里
VMXWriteVmcs32(VMX_VMCS32_CTRL_PROC_EXEC, fVal);

从commit到VMCS之后,当CPU执行到CR8写入指令的时候,都不会触发VMExit异常。

注释:
VMExit数量和虚拟机性能的关系:
VMExit这条指令会让CPU从non-root模式切换到root模式,这个过程会保留GuestOS的运行上下文,然后切换到HostOS运行上下文,同样当vmexit处理完成从root模式切换到non-root模式的时候,也会触发上下文切换,所以如果GuestOS运行的时候,发生的vmexit过多,会导致GuestOS运行效率大大下降(因为大量的CPU时间片都用来处理上下文切换),所以VMExit的设置需要避免设置一些调用频繁的时间,比如gs切换,rdmsr等。
但如果真的需要监控rdmsr指令怎么办?比如VT模式可以通过rdmsr C0000082(syscall入口)过PG时,需要当PatchGuard代码执行rdmsr C0000082这条指令的时候发生vmexit中断。
Intel非常贴心的设置了各种细粒度控制的办法,比如对msr的访问,可以设置msr bitmap来控制哪些msr寄存器的访问需要产生vmexit中断。

void hmR0VmxSetMsrPermission(PVMCPU pVCpu, uint32_t uMsr, VMXMSREXITREAD enmRead, VMXMSREXITWRITE enmWrite)
 {
 	 uint8_t *pbMsrBitmap = (uint8_t *)pVCpu->hm.s.vmx.pvMsrBitmap;
 	 /*
     * MSR Layout:
     *   Byte index            MSR range            Interpreted as
     * 0x000 - 0x3ff    0x00000000 - 0x00001fff    Low MSR read bits.
     * 0x400 - 0x7ff    0xc0000000 - 0xc0001fff    High MSR read bits.
     * 0x800 - 0xbff    0x00000000 - 0x00001fff    Low MSR write bits.
     * 0xc00 - 0xfff    0xc0000000 - 0xc0001fff    High MSR write bits.
     */
    //0x00000000 - 0x00001fff id
    if (uMsr <= 0x00001fff)
        iBit = uMsr;
    //0xc0000000 - 0xc0001fff 是设置在pbMsrBitmap的0x400~0x800
    else if (uMsr - UINT32_C(0xc0000000) <= UINT32_C(0x00001fff))
    {
        iBit = uMsr - UINT32_C(0xc0000000);
        pbMsrBitmap += 0x400;
    }
    //msr读取拦截是设置在0 ~ 0x800里
	if (enmRead == VMXMSREXIT_INTERCEPT_READ)
        ASMBitSet(pbMsrBitmap, iBit);
    else
        ASMBitClear(pbMsrBitmap, iBit);
	//msr写入拦截是设置在0x800 ~ 0xfff里
    if (enmWrite == VMXMSREXIT_INTERCEPT_WRITE)
        ASMBitSet(pbMsrBitmap + 0x800, iBit);
    else
        ASMBitClear(pbMsrBitmap + 0x800, iBit);
 	}

当需要拦截c0000082这个msr读取的时候,只需要设置MSR_K8_LSTAR写拦截,即可拦截到读取这个msr

hmR0VmxSetMsrPermission(pVCpu, MSR_K8_LSTAR, VMXMSREXIT_INTERCEPT_READ, VMXMSREXIT_INTERCEPT_WRITE);

参考资料:
https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf

VirtualBox 是一款开源虚拟机软件。VirtualBox 是由德国 Innotek 公司开发,由Sun Microsystems公司出品的软件,使用Qt编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。Innotek 以 GNU General Public License (GPL) 释出 VirtualBox,并提供二进制版本及 OSE 版本的代码。使用者可以在VirtualBox上安装并且执行Solaris、Windows、DOS、Linux、OS/2 Warp、BSD等系统作为客户Duan操作系统。现在则由甲骨文公司进行开发,是甲骨文公司xVM虚拟化平台技术的一部份。 VirtualBox号称是最强的免费虚拟机软件,它不仅具有丰富的特色,而且性能也很优异!它简单易用,可虚拟的系统包括Windows(从Windows 3.1到Windows10、Windows Server 2012,所有的Windows系统都支持)、Mac OS X、Linux、OpenBSD、Solaris、IBM OS2甚至Android等操作系统!使用者可以在VirtualBox上安装并且运行上述的这些操作系统! 与同性质的VMware及Virtual PC比较下,VirtualBox独到之处包括远端桌面协定(RDP)、iSCSI及USB的支持,VirtualBox在客户Duan操作系统上已可以支持USB 2.0的硬件装置,不过要安装 VirtualBox Extension Pack。 本模块可后台对VBox进行开机关机等操作,中文命名,实现原理很简单,网络上也有很多文章来介绍,因为VBox为我们提供了一系列的命令行,但目前为止还没有人制作出程序来控制,论坛早期有一个模块,但是那位朋友没有开源. 模块使用了匿名管道技术,其余的部分则是在调用VBox提供的命令.
### 回答1: VirtualBox 是一款自由开源的虚拟化软件,可以在各种不同的操作系统上创建和运行虚拟机。如果想要编译 VirtualBox源码,可以按照以下步骤进行操作: 1. 下载源码:首先在 VirtualBox 的官方网站或者代码托管平台上下载最新版本的源码压缩包。 2. 安装依赖:在编译之前,需要先安装一些必要的依赖项。这些依赖项包括编译器、构建工具、各种开发库等。请根据操作系统的不同,使用相应的包管理工具进行安装。 3. 配置编译环境:解压源码压缩包后,进入源码目录运行配置命令。这个命令会自动检测系统环境并生成相应的配置文件。 4. 编译源码:配置完成后,运行编译命令开始编译源码。编译过程可能会持续一段时间,取决于系统的性能和源码的大小。 5. 安装软件:编译完成后,在指定的目录会生成可执行文件。将这些文件拷贝到系统的特定位置,并执行安装命令,完成软件的安装。 编译 VirtualBox源码并不是一件简单的任务,需要有一定的编程和系统知识。同时,由于 VirtualBox 是一个庞大而复杂的项目,编译过程中可能会遇到各种问题和错误。在遇到问题时,可以参考官方文档、社区论坛或者开发者的指导,进行排查和解决。 最后,编译 VirtualBox源码可以让你深入了解这款虚拟化软件的内部工作原理,同时也可以自定义功能或进行二次开发。但对于一般用户而言,并不需要编译源码,直接下载官方发布的预编译版本即可使用。 ### 回答2: 要编译VirtualBox的源代码,首先需要准备开发环境。VirtualBox的源代码是使用C++语言编写的,所以您需要安装相关的编译器和工具,如GCC、make、automake、autoconf等。 接下来,您需要从VirtualBox官方网站下载源代码。VirtualBox的源代码遵循开源许可协议,可以免费获取和使用。下载完成后,解压源代码包到您选择的目录中。 然后,打开终端并导航到源代码的目录中。执行以下命令以生成配置文件: ./configure 此命令将扫描您的系统并检查必要的依赖项是否满足。如果没有安装某个依赖项,您将需要手动安装它们。 完成配置后,您可以执行以下命令来编译VirtualBox的源代码: make 这将使用已安装的编译器将源代码编译成可执行文件。该过程可能需要一些时间,具体取决于您的系统性能和源代码的大小。 编译完成后,您可以通过运行以下命令来安装VirtualBox: make install 此命令将复制编译生成的可执行文件和其他必要的文件到系统路径中。 完成安装后,您就可以使用VirtualBox了。您可以启动VirtualBox并创建和管理虚拟机。 总之,编译VirtualBox的源代码需要准备开发环境、下载源代码、生成配置文件、编译源代码和安装生成的可执行文件。这个过程需要一些技术知识和耐心,但一旦完成,您就可以自定义和修改VirtualBox以满足您的需求。 ### 回答3: VirtualBox 是一款开源的虚拟化软件,它运行在主机操作系统上,并提供了一个虚拟环境,使得用户可以在该环境中运行其他操作系统。 要从源码编译 VirtualBox,首先需要获取源代码。VirtualBox 的源代码可以从官方网站上下载,下载完成后解压到本地。 在进行源码编译之前,需要确保系统中已安装了一些必要的软件和开发工具,如 GCC 编译器、GNU Make 工具、剪贴板管理工具等。同时还需要安装一些开发库和依赖包,如 X11 开发库、SDL 开发库等,这些包的安装可以通过包管理器来进行。对于不同的操作系统,可能需要安装的软件和库略有不同。 接下来,需要配置编译环境。在源代码目录中,执行 "./configure" 命令,该命令会检查系统中的软件和库是否满足编译要求,并生成相应的编译配置。可以通过命令行参数来修改默认配置,如指定编译器、库的路径等。 配置完成后,执行 "make" 命令即可开始编译。编译过程可能需要一段时间,取决于系统的性能和代码的大小。期间会生成许多中间文件和目标文件。如果编译过程中出现错误,可以根据错误提示进行修复,并重新执行 "make" 命令。 编译完成后,可以执行 "make install" 命令将编译好的程序安装到指定位置。安装完成后,就可以使用 VirtualBox 了。 需要注意的是,虚拟化软件对硬件资源要求较高,所以在进行源码编译之前,需要确保计算机硬件满足要求,如支持虚拟化技术的 CPU、足够的内存和磁盘空间等。 总之,通过下载源码、配置编译环境、执行编译命令和安装程序,就可以从源码编译出 VirtualBox,并在主机操作系统上运行虚拟环境了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值