Virtualbox源码分析14 Emulation manager2


EM - Execution Monitor / Manager

14.1 用模拟执行多条指令解决过频繁VMExit

因为发生一次VMExit,CPU需要切换上下文消耗大量性能,为了提升性能,当CPU执行部分执行指令的时候,EM会检查发生VMExit的这个RIP是否曾经发生过VMExit,如果曾经发生过多次VMExit事件,则会从这条指令开始模拟执行最多cMaxInstructions条指令。

可以模拟执行的指令列表:

typedef enum EMEXITTYPE
{
    EMEXITTYPE_INVALID = 0,
    EMEXITTYPE_IO_PORT_READ,
    EMEXITTYPE_IO_PORT_WRITE,
    EMEXITTYPE_IO_PORT_STR_READ,
    EMEXITTYPE_IO_PORT_STR_WRITE,
    EMEXITTYPE_MMIO,
    EMEXITTYPE_MMIO_READ,
    EMEXITTYPE_MMIO_WRITE,
    EMEXITTYPE_MSR_READ,
    EMEXITTYPE_MSR_WRITE,
    EMEXITTYPE_CPUID,
    EMEXITTYPE_RDTSC,
    EMEXITTYPE_MOV_CRX,
    EMEXITTYPE_MOV_DRX,
  	//只用于RAW-mode
    EMEXITTYPE_INVLPG,
    EMEXITTYPE_LLDT,
    EMEXITTYPE_RDPMC,
    EMEXITTYPE_CLTS,
    EMEXITTYPE_STI,
    EMEXITTYPE_INT,
    EMEXITTYPE_SYSCALL,
    EMEXITTYPE_SYSENTER,
    EMEXITTYPE_HLT
    /** @} */
} EMEXITTYPE;

比如在CPUID的VMExit handle里

HMVMX_EXIT_DECL hmR0VmxExitCpuid(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient)
{
    ...
	  //调用EMHistoryUpdateFlagsAndTypeAndPC获取是否需要模拟执行很多指令
    VBOXSTRICTRC rcStrict;
    PCEMEXITREC pExitRec = EMHistoryUpdateFlagsAndTypeAndPC(pVCpu,
                                                            EMEXIT_MAKE_FT(EMEXIT_F_KIND_EM | EMEXIT_F_HM, EMEXITTYPE_CPUID),
                                                            pVCpu->cpum.GstCtx.rip + pVCpu->cpum.GstCtx.cs.u64Base);
    if (!pExitRec)
    {
      	//不需要模拟执行很多指令,只模拟执行一条指令
        rcStrict = IEMExecDecodedCpuid(pVCpu, pVmxTransient->cbExitInstr);
        ....
    }
    else
    {
        //模拟执行多条指令
        rcStrict = EMHistoryExec(pVCpu, pExitRec, 0);
        ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_ALL_GUEST);
    }
    return rcStrict;
}

EM用hash表来保存已发生过的VMExit中断信息,hash表保存在VCpu->em.s.aExitRecords,用1024项,用RIP地址的后20位作为hash表的index。

EMHistoryUpdateFlagsAndTypeAndPC

VMM_INT_DECL(PCEMEXITREC) EMHistoryUpdateFlagsAndTypeAndPC(PVMCPUCC pVCpu, uint32_t uFlagsAndType, uint64_t uFlatPC)
{
  uint64_t     uExitNo    = pVCpu->em.s.iNextExit - 1;
  PEMEXITENTRY pHistEntry = &pVCpu->em.s.aExitHistory[(uintptr_t)uExitNo & 0xff];
  pHistEntry->uFlagsAndType = uFlagsAndType;
  pHistEntry->uFlatPC       = uFlatPC;

  return emHistoryAddOrUpdateRecord(pVCpu, uFlagsAndType, uFlatPC, pHistEntry, uExitNo);
}

EMHistoryAddExit

指令将指令加入EMHistory hash table里

//PostRun的时候调用这个函数,传入uFlatPC这个是发生VMExit的IP地址
VMM_INT_DECL(PCEMEXITREC) EMHistoryAddExit(PVMCPUCC pVCpu, uint32_t uFlagsAndType, uint64_t uFlatPC, uint64_t uTimestamp)
{
  PEMEXITENTRY pHistEntry = &pVCpu->em.s.aExitHistory[(uintptr_t)uExitNo & 0xff];
  pHistEntry->uFlatPC       = uFlatPC;
  pHistEntry->uTimestamp    = uTimestamp;
  pHistEntry->uFlagsAndType = uFlagsAndType;
  pHistEntry->idxSlot       = UINT32_MAX;
  emHistoryAddOrUpdateRecord(pVCpu, uFlagsAndType, uFlatPC, pHistEntry, uExitNo);
}

static PCEMEXITREC emHistoryAddOrUpdateRecord(PVMCPU pVCpu, uint64_t uFlagsAndType, uint64_t uFlatPC,
                                              PEMEXITENTRY pHistEntry, uint64_t uExitNo)
{
    //VMExit RIP地址的后20位作为hash表的index
  # define EM_EXIT_RECORDS_IDX_MASK 0x3ff
    uintptr_t  idxSlot  = ((uintptr_t)uFlatPC >> 1) & EM_EXIT_RECORDS_IDX_MASK;
  	PEMEXITREC pExitRec = &pVCpu->em.s.aExitRecords[idxSlot];
  	if (pExitRec->uFlatPC == uFlatPC)
    {
        //如果hash表命中了
        pHistEntry->idxSlot = (uint32_t)idxSlot;
        //指令类型也相同,找到了已执行过的指令
        if (pExitRec->uFlagsAndType == uFlagsAndType)
        {
            pExitRec->uLastExitNo = uExitNo;
        }
        //指令变了,说明同一个地址里的指令变成了另一个地址,缓存无效,更新pExitRec里的值成新的值
        else
        {
          	//默认enmAction 是EMEXITACTION_NORMAL
            return emHistoryRecordInit(pExitRec, uFlatPC, uFlagsAndType, uExitNo);
        }
    }
  	//如果标记这个record要被释放掉,重置pExitRec成新的值
    else if (pExitRec->enmAction == EMEXITACTION_FREE_RECORD)
    {
        return emHistoryRecordInitNew(pVCpu, pHistEntry, idxSlot, pExitRec, uFlatPC, uFlagsAndType, uExitNo);
    }
    else
    {
        //hash碰撞,用开放地址法解决hash碰撞
        uintptr_t idxOldest     = idxSlot;
        uint64_t  uOldestExitNo = pExitRec->uLastExitNo;
        unsigned  iOldestStep   = 0;
        unsigned  iStep         = 1;
        uintptr_t const idxAdd  = (uintptr_t)(uFlatPC >> 11) & (EM_EXIT_RECORDS_IDX_MASK / 4);
        for (;;)
        {
            //检查下一个hash表
            idxSlot += idxAdd;
            idxSlot &= EM_EXIT_RECORDS_IDX_MASK;
            pExitRec = &pVCpu->em.s.aExitRecords[idxSlot];
            //如果下一个hash表的IP和当前IP一样
            if (pExitRec->uFlatPC == uFlatPC)
            {
                pHistEntry->idxSlot = (uint32_t)idxSlot;
              	//指令也一样,hash命中
                if (pExitRec->uFlagsAndType == uFlagsAndType)
                {
                    pExitRec->uLastExitNo = uExitNo;
                    break;
                }
              	//指令变了,说明同一个地址里的指令变成了另一个地址,缓存无效,更新pExitRec里的值成新的值
                return emHistoryRecordInit(pExitRec, uFlatPC, uFlagsAndType, uExitNo);
            }
					  //如果标记这个record要被释放掉,重置pExitRec成新的值
            if (pExitRec->enmAction == EMEXITACTION_FREE_RECORD)
            {
                return emHistoryRecordInitNew(pVCpu, pHistEntry, idxSlot, pExitRec, uFlatPC, uFlagsAndType, uExitNo);
            }
          
            /* Is it the least recently used one? */
            if (pExitRec->uLastExitNo < uOldestExitNo)
            {
                uOldestExitNo = pExitRec->uLastExitNo;
                idxOldest     = idxSlot;
                iOldestStep   = iStep;
            }

            //如果找了8步都还是碰撞,替换第8项
            iStep++;
            if (RT_LIKELY(iStep < 8 + 1))
            { /* likely */ }
            else
            {
                pExitRec = &pVCpu->em.s.aExitRecords[idxOldest];
                return emHistoryRecordInitReplacement(pHistEntry, idxOldest, pExitRec, uFlatPC, uFlagsAndType, uExitNo);
            }
        }
    }
  	//找到了缓存,根据enmAction决定如果执行
  	switch (pExitRec->enmAction)
    {
        //只有缓存被命中了256次,才设置成EMEXITACTION_EXEC_PROBE,否则不多条执行
        case EMEXITACTION_NORMAL:
        {
            uint64_t const cHits = ++pExitRec->cHits;
            if (cHits < 256)
                return NULL;
            pExitRec->enmAction = EMEXITACTION_EXEC_PROBE;
            return pExitRec;
        }
				//pExitRec->cHits加1, 但不多条执行
        case EMEXITACTION_NORMAL_PROBED:
            pExitRec->cHits += 1;
            return NULL;
        default:
            pExitRec->cHits += 1;
            return pExitRec;
        //缓存连续命中256到512次的时候,是exec_probe状态,但如果在256次exec_probe执行过程中状态没有被改成EMEXITACTION_EXEC_WITH_MAX(执行失败),则状态设置成EMEXITACTION_NORMAL_PROBED,不再多条执行
        case EMEXITACTION_EXEC_PROBE:
        {
            uint64_t const cHits = ++pExitRec->cHits;
            if (cHits < 512)
                return pExitRec;
            pExitRec->enmAction = EMEXITACTION_NORMAL_PROBED;
            return NULL;
        }
    }
}

EMHistoryExec

模拟执行

VMM_INT_DECL(VBOXSTRICTRC) EMHistoryExec(PVMCPUCC pVCpu, PCEMEXITREC pExitRec, uint32_t fWillExit)
{
  switch (pExitRec->enmAction)
  {
    case EMEXITACTION_EXEC_WITH_MAX:
      {
        //调用IEMExecForExits执行最多pVCpu->em.s.cHistoryExecMaxInstructions,最少pExitRec->cMaxInstructionsWithoutExit条指令
        VBOXSTRICTRC rcStrict = IEMExecForExits(pVCpu, fWillExit,
                                                pExitRec->cMaxInstructionsWithoutExit /* cMinInstructions*/,
                                                pVCpu->em.s.cHistoryExecMaxInstructions,
                                                pExitRec->cMaxInstructionsWithoutExit,
                                                &ExecStats);
        emHistoryExecSetContinueExitRecIdx(pVCpu, rcStrict, pExitRec);

        //如果返回没有实现指令或者已经执行了cHistoryExecMaxInstructions条指令,返回VINF_SUCCESS
        //其他返回值直接返回IEMExecForExits的执行结果
        if (   (   rcStrict != VERR_IEM_INSTR_NOT_IMPLEMENTED
                && rcStrict != VERR_IEM_ASPECT_NOT_IMPLEMENTED)
            || ExecStats.cInstructions == 0)
        { /* likely */ }
        else
          rcStrict = VINF_SUCCESS;
        return rcStrict;
      }
    //尝试多条执行,如果成功,设置成EMEXITACTION_EXEC_WITH_MAX,否则这次成EMEXITACTION_NORMAL_PROBED
    case EMEXITACTION_EXEC_PROBE:
      {
        //调用IEMExecForExits执行最多pVCpu->em.s.cHistoryExecMaxInstructions,最少pVCpu->em.s.cHistoryProbeMinInstructions条指令
        PEMEXITREC   pExitRecUnconst = (PEMEXITREC)pExitRec;
        VBOXSTRICTRC rcStrict = IEMExecForExits(pVCpu, fWillExit,
                                                pVCpu->em.s.cHistoryProbeMinInstructions,
                                                pVCpu->em.s.cHistoryExecMaxInstructions,
                                                pVCpu->em.s.cHistoryProbeMaxInstructionsWithoutExit,
                                                &ExecStats);
        emHistoryExecSetContinueExitRecIdx(pVCpu, rcStrict, pExitRecUnconst);
        //如果执行成功,状态修改成EMEXITACTION_EXEC_WITH_MAX
        if (   ExecStats.cExits >= 2
            && RT_SUCCESS(rcStrict))
        {
          pExitRecUnconst->cMaxInstructionsWithoutExit = ExecStats.cMaxExitDistance;
          pExitRecUnconst->enmAction = EMEXITACTION_EXEC_WITH_MAX;
        }
        else
        {
          //执行失败,enmAction设置成EMEXITACTION_NORMAL_PROBED,不再多条模拟
          pExitRecUnconst->enmAction = EMEXITACTION_NORMAL_PROBED;
          pVCpu->em.s.idxContinueExitRec = UINT16_MAX;
          if (   rcStrict == VERR_IEM_INSTR_NOT_IMPLEMENTED
              || rcStrict == VERR_IEM_ASPECT_NOT_IMPLEMENTED)
            rcStrict = VINF_SUCCESS;
        }
        return rcStrict;
      }
  }//end of switch
}

VMMDECL(VBOXSTRICTRC) IEMExecForExits(PVMCPUCC pVCpu, uint32_t fWillExit, uint32_t cMinInstructions, uint32_t cMaxInstructions,
                                      uint32_t cMaxInstructionsWithoutExits, PIEMEXECFOREXITSTATS pStats)
{
  VBOXSTRICTRC rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, false);
  for (;;)
  {
    //调用IEM里的函数模拟执行一条指令
    uint32_t const cPotentialExits = pVCpu->iem.s.cPotentialExits;
    uint8_t b; IEM_OPCODE_GET_NEXT_U8(&b);
    rcStrict = FNIEMOP_CALL(g_apfnOneByteMap[b]);
    if (   cPotentialExits != pVCpu->iem.s.cPotentialExits
        && cInstructionSinceLastExit > 0 /* don't count the first */ )
    {
      pStats->cExits += 1;
      if (cInstructionSinceLastExit > pStats->cMaxExitDistance)
        pStats->cMaxExitDistance = cInstructionSinceLastExit;
      cInstructionSinceLastExit = 0;
    }
    //模拟执行成功
    if (RT_LIKELY(rcStrict == VINF_SUCCESS))
    {
      pVCpu->iem.s.cInstructions++;
      pStats->cInstructions++;
      cInstructionSinceLastExit++;
      if (RT_LIKELY(pVCpu->iem.s.rcPassUp == VINF_SUCCESS))
      {
        //forceactions里去掉sync cr3,刷新tlb等
        uint64_t fCpu = pVCpu->fLocalForcedActions
          & ( VMCPU_FF_ALL_MASK & ~(  VMCPU_FF_PGM_SYNC_CR3
                                    | VMCPU_FF_PGM_SYNC_CR3_NON_GLOBAL
                                    | VMCPU_FF_TLB_FLUSH
                                    | VMCPU_FF_INHIBIT_INTERRUPTS
                                    | VMCPU_FF_BLOCK_NMIS
                                    | VMCPU_FF_UNHALT ));
				//如果没有forcesaction,或者没有达到执行条数的下线都会继续执行
        if (RT_LIKELY(   (   (   !fCpu
                              || (   !(fCpu & ~(VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC))
                                  && !pVCpu->cpum.GstCtx.rflags.Bits.u1IF))
                          && !VM_FF_IS_ANY_SET(pVM, VM_FF_ALL_MASK) )
                      || pStats->cInstructions < cMinInstructions))
        {
          //如果没有达到执行条数的上限
          if (pStats->cInstructions < cMaxInstructions)
          {
            if (cInstructionSinceLastExit <= cMaxInstructionsWithoutExits)
            {
#ifdef IN_RING0
              if (   !fCheckPreemptionPending
                  || !RTThreadPreemptIsPending(NIL_RTTHREAD))
#endif
              {
                //继续执行下一条指令
                iemReInitDecoder(pVCpu);
                continue;
              }
#ifdef IN_RING0
              rcStrict = VINF_EM_RAW_INTERRUPT;
              break;
#endif
            }
          }
        }
      }
      //其他情况,一律break,退出模拟执行
      break
		}
  }
}

14.2 保存/加载虚拟机(SSM)的callback

emR3Save

static DECLCALLBACK(int) emR3Save(PVM pVM, PSSMHANDLE pSSM)
{
  for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++)
    {
        PVMCPU pVCpu = pVM->apCpusR3[idCpu];
				//保存前一个状态
        SSMR3PutU32(pSSM, pVCpu->em.s.enmPrevState);
        //保存MWait/Monitor的设置
        SSMR3PutU32(pSSM, pVCpu->em.s.MWait.fWait);
        SSMR3PutGCPtr(pSSM, pVCpu->em.s.MWait.uMWaitRAX);
        SSMR3PutGCPtr(pSSM, pVCpu->em.s.MWait.uMWaitRCX);
        SSMR3PutGCPtr(pSSM, pVCpu->em.s.MWait.uMonitorRAX);
        SSMR3PutGCPtr(pSSM, pVCpu->em.s.MWait.uMonitorRCX);
        SSMR3PutGCPtr(pSSM, pVCpu->em.s.MWait.uMonitorRDX);
    }
}

emR3Load

static DECLCALLBACK(int) emR3Load(PVM pVM, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
{
  for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++)
  {
    PVMCPU pVCpu = pVM->apCpusR3[idCpu];
    if (uVersion > EM_SAVED_STATE_VERSION_PRE_SMP)
    {
      //恢复前一个状态,并且把当前状态设置成suspend
      SSM_GET_ENUM32_RET(pSSM, pVCpu->em.s.enmPrevState, EMSTATE);
      pVCpu->em.s.enmState = EMSTATE_SUSPENDED;
    }
    if (uVersion > EM_SAVED_STATE_VERSION_PRE_MWAIT)
    {
      //恢复MWait和Monitor的设置
      rc = SSMR3GetU32(pSSM, &pVCpu->em.s.MWait.fWait);
      rc = SSMR3GetGCPtr(pSSM, &pVCpu->em.s.MWait.uMWaitRAX);
      rc = SSMR3GetGCPtr(pSSM, &pVCpu->em.s.MWait.uMWaitRCX);
      rc = SSMR3GetGCPtr(pSSM, &pVCpu->em.s.MWait.uMonitorRAX);
      rc = SSMR3GetGCPtr(pSSM, &pVCpu->em.s.MWait.uMonitorRCX);
      rc = SSMR3GetGCPtr(pSSM, &pVCpu->em.s.MWait.uMonitorRDX);
    }
}

14.3 MWait和Monitor 指令模拟

SIMD扩展3引入了两条指令(MONITOR和WAIT)以帮助多线程软件提升线程同步

Monitor指令可以指定一个写回属性的内存地址,MWAIT指令让CPU进入hlt状态直到这个指定的内存地址被写入。

下面是一个使用Monitor/MWAIT指令的例子

dword[WorkQueue] = 0//设置monitor addr这个地址写入
MONITOR addr // Setup of eax with addr
//注意在调用MWAIT指令之前必须开中断
// LinearAddress, ECX, EDX = 0
//调用MWAIT这条指令监控addr地址写入,如果addr里的值是0,则循环等待addr被写入新的值
IF (dword[addr] == 0) THEN {
	MWAIT
}

Virtualbox里模拟MWait和Monitor指令

当VMX设置了VMX_EXIT_MWAIT 和 VMX_EXIT_MONITOR, 当GuestOS执行到这两条指令的时候,会发生VMExit

hmR0VmxExitMonitor call IEMExecDecodedMonitor 最终掉到IEM_CIMPL_DEF_1(iemCImpl_monitor, uint8_t, iEffSeg)函数模拟执行这条指令

IEM_CIMPL_DEF_1(iemCImpl_monitor, uint8_t, iEffSeg)
{
    //这条指令只运行在R0执行
    if (pVCpu->iem.s.uCpl != 0)
    {
        return iemRaiseUndefinedOpcode(pVCpu); 
    }
  	//如果GuestCPU不支持Monitor/MWait指令,给GuestOS抛UD异常
    if (!IEM_GET_GUEST_CPU_FEATURES(pVCpu)->fMonitorMWait)
    {
        return iemRaiseUndefinedOpcode(pVCpu);
    }

    /*
     * Check VMX guest-intercept.
     * This should be considered a fault-like VM-exit.
     * See Intel spec. 25.1.1 "Relative Priority of Faults and VM Exits".
     */
    if (   IEM_VMX_IS_NON_ROOT_MODE(pVCpu)
        && IEM_VMX_IS_PROCCTLS_SET(pVCpu, VMX_PROC_CTLS_MONITOR_EXIT))
    {
        IEM_VMX_VMEXIT_INSTR_RET(pVCpu, VMX_EXIT_MONITOR, cbInstr);
    }

    //获取Monitor指令参数
    RTGCPTR  GCPtrMem   = pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT ? pVCpu->cpum.GstCtx.rax : pVCpu->cpum.GstCtx.eax;
    uint32_t uEcx       = pVCpu->cpum.GstCtx.ecx;
    uint32_t uEdx       = pVCpu->cpum.GstCtx.edx;
  	//ecx如果是0,则抛GP异常给GuestOS
    if (uEcx != 0)
    {
        return iemRaiseGeneralProtectionFault0(pVCpu);
    }

    VBOXSTRICTRC rcStrict = iemMemApplySegment(pVCpu, IEM_ACCESS_TYPE_READ | IEM_ACCESS_WHAT_DATA, iEffSeg, 1, &GCPtrMem);
    if (rcStrict != VINF_SUCCESS)
        return rcStrict;

  	//校验输入的地址是否可访问,并获取对应的虚拟机物理地址
    RTGCPHYS GCPhysMem;
    rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, GCPtrMem, IEM_ACCESS_TYPE_READ | IEM_ACCESS_WHAT_DATA, &GCPhysMem);
    if (rcStrict != VINF_SUCCESS)
        return rcStrict;

    //call EM
    rcStrict = EMMonitorWaitPrepare(pVCpu, pVCpu->cpum.GstCtx.rax, pVCpu->cpum.GstCtx.rcx, pVCpu->cpum.GstCtx.rdx, GCPhysMem);
    Assert(rcStrict == VINF_SUCCESS);

    iemRegAddToRipAndClearRF(pVCpu, cbInstr);
    return rcStrict;
}

VMM_INT_DECL(int) EMMonitorWaitPrepare(PVMCPU pVCpu, uint64_t rax, uint64_t rcx, uint64_t rdx, RTGCPHYS GCPhys)
{
  	//记录下3个寄存器,标记active monitor
    pVCpu->em.s.MWait.uMonitorRAX = rax;
    pVCpu->em.s.MWait.uMonitorRCX = rcx;
    pVCpu->em.s.MWait.uMonitorRDX = rdx;
    pVCpu->em.s.MWait.fWait |= EMMWAIT_FLAG_MONITOR_ACTIVE;
    return VINF_SUCCESS;
}

MWait指令模拟

hmR0VmxExitMwait call IEMExecDecodedMwait最终调用到IEM_CIMPL_DEF_0(iemCImpl_mwait)函数模拟执行这条指令

IEM_CIMPL_DEF_0(iemCImpl_mwait)
{
    //这条指令只运行在R0执行
    if (pVCpu->iem.s.uCpl != 0)
    {
        return iemRaiseUndefinedOpcode(pVCpu);
    }
    //如果GuestCPU不支持Monitor/MWait指令,给GuestOS抛UD异常
    if (!IEM_GET_GUEST_CPU_FEATURES(pVCpu)->fMonitorMWait)
    {
        return iemRaiseUndefinedOpcode(pVCpu);
    }
    //获取MWait的输入,并检查参数
    uint32_t const uEax = pVCpu->cpum.GstCtx.eax;
    uint32_t const uEcx = pVCpu->cpum.GstCtx.ecx;
    if (uEcx != 0)
    {
        //uEcx不接受大于1的输入
        if (uEcx > 1)
        {
            return iemRaiseGeneralProtectionFault0(pVCpu);
        }
        uint32_t fMWaitFeatures = 0;
        uint32_t uIgnore = 0;
      	//如果CPUID(5)中没有设置X86_CPUID_MWAIT_ECX_EXT,不接受uEcx = 1
        CPUMGetGuestCpuId(pVCpu, 5, 0, &uIgnore, &uIgnore, &fMWaitFeatures, &uIgnore);
        if (    (fMWaitFeatures & (X86_CPUID_MWAIT_ECX_EXT | X86_CPUID_MWAIT_ECX_BREAKIRQIF0))
            !=                    (X86_CPUID_MWAIT_ECX_EXT | X86_CPUID_MWAIT_ECX_BREAKIRQIF0))
        {
            return iemRaiseGeneralProtectionFault0(pVCpu);
        }
    }

    //call EM
    VBOXSTRICTRC rcStrict = EMMonitorWaitPerform(pVCpu, uEax, uEcx);

    iemRegAddToRipAndClearRF(pVCpu, cbInstr);
    return rcStrict;
}

VMM_INT_DECL(int) EMMonitorWaitPerform(PVMCPU pVCpu, uint64_t rax, uint64_t rcx)
{
  	//保存两个参数
    pVCpu->em.s.MWait.uMWaitRAX = rax;
    pVCpu->em.s.MWait.uMWaitRCX = rcx;
    //标记MWAIT ACTIVE
    pVCpu->em.s.MWait.fWait |= EMMWAIT_FLAG_ACTIVE;
    if (rcx)
        pVCpu->em.s.MWait.fWait |= EMMWAIT_FLAG_BREAKIRQIF0;
    else
        pVCpu->em.s.MWait.fWait &= ~EMMWAIT_FLAG_BREAKIRQIF0;
    //返回需要VCPU halt,等待外部中断
    return VINF_EM_HALT;
}

EM同时提供了一系列Monitor/MWait相关信息查询函数

EMMonitorIsArmed: 是否设置了monitor active
VMM_INT_DECL(bool) EMMonitorIsArmed(PVMCPU pVCpu)
{
    return RT_BOOL(pVCpu->em.s.MWait.fWait & EMMWAIT_FLAG_MONITOR_ACTIVE);
}

EMMonitorWaitIsActive: 是否有调用了MWait指令等待
VMM_INT_DECL(unsigned) EMMonitorWaitIsActive(PVMCPU pVCpu)
{
    uint32_t fWait = pVCpu->em.s.MWait.fWait;
    return fWait & (EMMWAIT_FLAG_ACTIVE | ((fWait & EMMWAIT_FLAG_ACTIVE) << 1));
}
EMMonitorWaitClear:clear MWait状态,mwait退出
VMM_INT_DECL(void) EMMonitorWaitClear(PVMCPU pVCpu)
{
 	pVCpu->em.s.MWait.fWait &= ~(EMMWAIT_FLAG_ACTIVE | EMMWAIT_FLAG_BREAKIRQIF0);
}
EMShouldContinueAfterHalt: 执行完Hlt指令之后是否可以继续执行GuestOS代码

判断是有中断到来

VMM_INT_DECL(bool) EMShouldContinueAfterHalt(PVMCPU pVCpu, PCPUMCTX pCtx)
{
    if (CPUMGetGuestGif(pCtx))
    {
      	//GuestOS需要开启中断
        if (CPUMIsGuestPhysIntrEnabled(pVCpu))
          	//返回是否有中断被设置上
            return VMCPU_FF_IS_ANY_SET(pVCpu, (VMCPU_FF_UPDATE_APIC | VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC));
				//嵌套VMX检查VMCPU_FF_INTERRUPT_NESTED_GUEST中断
        if (   CPUMIsGuestInNestedHwvirtMode(pCtx)
            && CPUMIsGuestVirtIntrEnabled(pVCpu))
            return VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_NESTED_GUEST);
    }
    return false;
}
EMUnhaltAndWakeUp 唤醒一个VCPU
VMM_INT_DECL(int) EMUnhaltAndWakeUp(PVMCC pVM, PVMCPUCC pVCpuDst)
{
    VMCPU_FF_SET(pVCpuDst, VMCPU_FF_UNHALT);

    /*
     * Wake up the EMT (technically should be abstracted by VMM/VMEmt, but
     * just do it here for now).
     */
#ifdef IN_RING0
   	//如果是R0直接调用GVMMR0SchedWakeUpNoGVMNoLock函数唤醒一个VCPU
    int rc = GVMMR0SchedWakeUpNoGVMNoLock(pVM, pVCpuDst->idCpu);
#elif defined(IN_RING3)
  	//如果是R3调用IOCTL到GVMM唤醒一个VCPU
    int rc = SUPR3CallVMMR0(VMCC_GET_VMR0_FOR_CALL(pVM), pVCpuDst->idCpu, VMMR0_DO_GVMM_SCHED_WAKE_UP, NULL /* pvArg */);
#else
    //其他状态直接返回success
    int rc = VINF_SUCCESS;
#endif
    return rc;
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
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,并在主机操作系统上运行虚拟环境了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值