Virtualbox源码分析16 APIC虚拟化1 APIC概念和初始化

文章目录

      • 中断是什么
      • 中断是如何被发送给CPU的
    • 16.1 xAPIC and x2APIC
    • 16.2 Local APIC Page里的寄存器和对应的重要概念
      • APIC ID
      • 中断优先级类型的寄存器
      • Pending中断队列类型的寄存器
      • 中断结束寄存器
      • 用于IPI的寄存器
      • 处理本地中断的寄存器:
      • 用于定时器的寄存器:
      • LVT: 本地向量表
    • 16.3 Local APIC虚拟化
      • 监控Guest访问local APIC的3种方式
      • VMX原生支持的APIC虚拟化:
      • Virtual-APIC Page初始化:
      • xAPIC的Virtual-APIC Page访问VMExit handle

说明: APIC我其实也理解的不是特别明白,只是从网上找些资料和阅读源码理解,如果有错误,麻烦在下面评论里指出。

虚拟化有个重要的功能: APIC虚拟化,本章重点介绍这个功能

中断是什么

一般来说,中断主要是由一些硬件设备产生的,表示这些硬件有一些重要的事件需要通知处理器,比如某些从外部设备请求的数据准备好了,需要通知处理器对其进行读取等。当然这里所谓的“一般来说”是指也可以通过软件的方式来触发中断,比如调用INT n指令,当然这种方式产生的中断和通过意见产生的中断最终的处理方式会有很大的不同。 因此从种类来分,可以将中断分为通过硬件产生的外部中断(External interrupt)和通过软件产生的软件中断(Software interrupt)。不管是外部中断还是软件中断,每个中断都有一个中断号与之对应,对于外部中断来说,可使用的中断号范围从16到255(0到15)为系统预留的中断号,而对于软件中断来说,可使用的中断号为0到255。除此之外,16到255范围内的中断是可以通过EFLAGS中的IF flag进行disable的,如果EFLAGS中的IF flag被清零,则表示当前CPU不接受这个范围内的中断,如果其被置为1,则表示当前CPU可以正常处理这个范围内的中断。

中断是如何被发送给CPU的

中断在进入CPU之前,首先会进入一个被称为Advanced Programmable Interrupt Controller(APIC)的控制器中,可以说,每个CPU都有一个APIC,被称为该CPU的Local APIC(LAPIC)。每个LAPIC由一系列的寄存器组成,这些寄存器控制了LAPIC如何将中断送到处理器中。

APIC又根据实现不同,分成LAPIC和x2APIC,这两个APIC寄存器都一样,唯一不同的是LAPIC通过内存访问APIC寄存器,x2APIC通过MSR寄存器访问APIC寄存器。

16.1 xAPIC and x2APIC

LOCAL APIC:直连PROCESSOR,每个PROCESSOR一个,可以看成是一个独立的硬件,有自己的一堆寄存器,可以进行读写来控制APIC的某些特性和设置。

LAPIC有分成两种实现方法:

1)xAPIC:APIC寄存器被映射到4KB大小的MMIO内存区,OS通过MMIO内存访问APIC。

2)x2APIC: 一部分MSR地址区间为APIC寄存器预留,访问APIC是通过MSR。这样的好处是不用再担心内存地址的冲突问题.

LAPIC可以从以下几个来源接收到中断:

1)Locally connected I/O devices: 直接连接到处理器的本地中断引脚(LINT0和LINT1)的I/O设备触发的中断

2)Externally connected I/O devices :外部链接的I/O设备发出的中断

3) Inter-processor interrupts (IPIs) :CPU可以使用IPI机制向系统总线上的另一个或者多个CPU发送中断。

4)APIC timer generated interrupts:APIC定时器中断

5)Performance monitoring counter interrupts :性能计数器中断

6)Thermal Sensor interrupts :热传感器中断 (power)

7)APIC internal error interrupts :APIC内部异常中断

LAPIC架构图

Virtualbox源码分析16 APIC虚拟化1 APIC概念和初始化_第1张图片

Virtualbox源码分析16 APIC虚拟化1 APIC概念和初始化_第2张图片

16.2 Local APIC Page里的寄存器和对应的重要概念

APIC ID

系统启动之后,硬件会给每一个本地APIC分配一个唯一的APIC ID,在多核系统中,本地APIC ID也会被BIOS和OS当作处理器ID。部分处理器支持APIC ID可以被软件修改,但是软件需要避免修改APIC ID,因为只有部分处理器支持。

中断优先级类型的寄存器

TPR: Task Priority Register

本地APIC还定义了任务优先级和处理器优先级,用于确定处理中断的顺序。任务优先级类是任务优先级寄存器(TPR)的位7:4的值,可以由软件写入(TPR是读/写寄存器);任务优先级允许软件设置中断处理器的优先级阈值。此机制使操作系统能够暂时阻止低优先级中断,以免干扰处理器正在执行的高优先级工作。使用任务优先级阻止此类中断的能力是由于TPR控制处理器优先级寄存器(PPR)的值.

PPR : Processor Priority Register(处理器优先级)是一个0-15范围内的值,它保存在7:4位中,PPR是只读寄存器。处理器优先级类表示处理器正在执行的当前优先级。TPR更新之后需要同时更新PPR

处理器将只传送那些中断优先级高于PPR中处理器优先级的中断。如果处理器优先级为0,则PPR不禁止任何中断的传递;如果为15,则处理器禁止所有中断的传递。

Pending中断队列类型的寄存器

IRR(Interrupt Request Register)IRR包含已接受但尚未发送到处理器进行服务的活动中断请求。当本地APIC接受中断时,它在IRR中设置对应于接受中断向量的位。当处理器内核准备好处理下一个中断时,本地APIC清除设置的最高优先级IRR位,并设置相应的ISR位。然后,ISR中设置的最高优先级位的向量被分派到处理器核心进行服务。

TMR (Trigger Mode Register)当接收到IRR中的中断时,对应TMR清除用于edge-triggered中断,并被设置为edge-triggered中断。如果在一个中断向量的EOI周期时设置了TMR位,则会向所有I/O apic发送EOI消息。

ISR (In-service Register)正在处理中的中断。

高优先级中断:

如果本地APIC接收到中断优先级高于当前使用的中断的中断,并且中断在处理器核心中启用,则本地APIC立即向处理器发送更高优先级的中断(无需等待写入EOI寄存器)。然后中断当前正在执行的中断处理程序,以便可以处理更高优先级的中断。当高优先级中断的处理完成后,中断中断的服务将恢复。

中断结束寄存器

EOI: End Of Interrupt Register

对于除NMI、SMI、INIT、extert、start-up或INIT-Deassert传递模式以外的所有中断,中断处理程序必须包括对中断结束(EOI)寄存器的写入。此写入必须发生在处理程序例程的末尾,即IRET指令之前的某个时间。此操作表示当前中断的服务已完成,本地APIC可以从ISR发出下一个中断。

用于IPI的寄存器

ICR: 中断命令寄存器是一个64位的本地APIC寄存器,它允许在处理器上运行的软件指定处理器间中断(IPI)并将其发送到系统中的其他处理器。一个CPU要发送IPI到其他CPU,软件必须设置ICR以指示要发送的IPI消息的类型以及目标处理器。

LDR:Logical Destination Register : logical destination mode 用于选择目标处理器。

DFR: Destination Format Register:logical destination mode 用于选择目标处理器。

Determining IPI Destination:通过寄存器决定IPI需要发送到哪些CPU中:

对应VirtualBox代码可以参考下一章里的apicGetDestCpuSet函数

1) Physical Destination Mode

目标处理器由其本地APIC ID(ICR寄存器中)指定, 物理目的地模式下不支持广播IPI(MDA的位28-31为1)或I/O子系统以最低优先级传输模式启动的中断

2) Logical Destination Mode

IPI目的地是使用8位消息目的地地址(MDA)指定的,该地址输入ICR的目的地字段中。在接收到使用逻辑目的地模式发送的IPI消息时,本地APIC将消息中的MDA与其LDR和DFR中的值进行比较,以确定它是否应接受和处理IPI。对于逻辑目的地模式的两种配置,当与最低优先级传送模式结合时,软件负责确保包含在IPI或I/O子系统中断中或由其寻址的所有本地apic都存在并能够接收中断。

又分成两种Model:

​ Flat Model :DFR位28到31是1111,
Cluster Model :DFR位28到31是0000,

typedef enum XAPICDESTMODE
{
    XAPICDESTMODE_PHYSICAL = 0,
    XAPICDESTMODE_LOGICAL
} XAPICDESTMODE;

两种Delivery Mode

1) Broadcast/Self Delivery Mode

2) Lowest Priority Delivery Mode

处理本地中断的寄存器:

ESR:错误状态寄存器( the error status register ),记录中断处理时的错误信息。

LVT Error Register允许在检测到APIC错误时发送中断到CPU。LVT Error寄存器还提供了屏蔽APIC错误中断的开关,但只屏蔽触发APIC错误中断,APIC的错误任然会保存在ESR寄存器中。

用于定时器的寄存器:

CCR:Current Count Register 当前计数寄存器(只读)

ICR:Initial Count Register。初始计数寄存器(可读可写)

DCR: 除法配置寄存器(the divide configuration register)用于决定定时器频率。

APIC定时器提供3种mode:

1)One-shot mode: 一次性触发模式,定时器通过编程其初始计数寄存器(ICR)启动。然后,将初始计数值复制到当前计数寄存器(CCR)中,开始倒计时。当定时器达到零时,产生定时器中断,定时器保持其0值,直到重新编程。

2)periodic mode: 当计数达到0时,CCR自动从ICR重新加载,并生成计时器中断,同时重复倒计时。如果在倒计时过程中设置了ICR,则将使用新的初始计数值重新开始计数。ICR是读写寄存器;当前计数寄存器是只读的。

APIC计时器频率将是处理器的总线时钟或核心晶体时钟频率(当在CPUID[0x15]中枚举TSC/核心晶体时钟比率时)除以DCR中指定的值。

3) TSC-Deadline Mode:允许软件使用本地APIC计时器在绝对时间发出中断信号。在TSC-Deadline Mode下,忽略对ICR的写入;而CCR始终读取0。取而代之的是,计时器的行为是由IA32_TSC_DEADLINE控制的。相比于一次性触发模式,这样可以得到更高的精度(因为时间戳的生成时CPU的时钟,而不是CPU总线频率)

在LVT Timer的第17:18位决定用哪种模式的定时器

LVT TImer bits【18:17】:
00b:One-shot mode
01b: periodic mode
10b: TSC-Deadline mode,

LVT: 本地向量表

允许软件指定本地中断传送到CPU的方式,不同的中断源有不同的LVT寄存器

APIC的7种中断源和对应的LVT寄存器

1) LVT CMCI:指定当支持CMCI的计算机检查库中出现已更正的计算机检查错误计数达到阈值的溢出条件时中断传递 (machine check)

2)LVT Timer:指定APIC计时器发出中断信号时的中断传递

3)LVT Thermal Monitor: 指定热传感器产生中断时的中断传送 (power manager使用)

4)LVT Performance Counter:指定当性能计数器在溢出时生成中断时的中断传递

5)Locally connected I/O devices :

​ LVT LINT0 :直接连接到处理器的本地中断引脚(LINT0)的I/O设备发生的中断

​ LVT LINT1: 直接连接到处理器的本地中断引脚(LINT1)的I/O设备发生的中断

6)APIC internal error interrupts:LVT Error Register:指定APIC检测到内部错误时的中断传递

7)Inter-processor interrupts (IPIs)

8)Externally connected I/O devices

Virtualbox源码分析16 APIC虚拟化1 APIC概念和初始化_第3张图片
​ 图16.3

这些寄存器里定义了中断发送的属性:Delivery Mode, Status, Trigger Mode等

Deliver mode:中断传送模式

typedef enum XAPICDELIVERYMODE
{
  	//发送给Destination Filed列出的所有CPU, level, edge触发均可
    XAPICDELIVERYMODE_FIXED               = 0,
  	//Destination Filed列出的所有CPU里优先级最低的CPU,level, edge触发均可
    XAPICDELIVERYMODE_LOWEST_PRIO         = 1,
  	//SMI 系统管理中断,vertor field必须是0,只能edge触发
    XAPICDELIVERYMODE_SMI                 = 2,
  	//NMI 不可屏蔽中断,vector field被忽略,发送给Destination Field列出的所有CPU,
    XAPICDELIVERYMODE_NMI                 = 4,
  	//INIT,发送给Destination Filed列出的所有CPU,LAPIC收到后执行INIT中断,让CPU初始化
    XAPICDELIVERYMODE_INIT                = 5,
  	//start-up IPI(SIPI)
    XAPICDELIVERYMODE_STARTUP             = 6,
    //发送给Destination Filed列出的所有CPU。CPU收到该中断后,认为这是一个PIC发送的中断请求,并回应INTA信号,
    XAPICDELIVERYMODE_EXTINT              = 7
} XAPICDELIVERYMODE;
ExtINT用于PIC接在APIC上的情况

Delivery State : 传送状态

0: idel: 当前没有中断

1: send pending: IOAPIC已经收到该中断,但该中断还未发送给LAPIC

Trigger mode:触发模式,表示该中断由什么方式触发

typedef enum XAPICTRIGGERMODE
{
  	//边沿触发 delivery mode is NMI,SMI or INIT, timer and error interrupts. 
    XAPICTRIGGERMODE_EDGE = 0,
  	//电平触发中断 delivery mode is ExINT, I/O APIC
    XAPICTRIGGERMODE_LEVEL
} XAPICTRIGGERMODE;

Vector里保存了中断向量号(最大256个),CPU根据中断向量号找到对应的中断处理函数处理中断。

16.3 Local APIC虚拟化

每个逻辑处理器都有属于自己的local APIC,当guest读取local apic寄存器时将返回物理的local APIC寄存器值,同样,当guest写local APIC寄存器时,也将写入物理的local APIC寄存器。

例如,guest可以通过Local APIC 的ICR寄存器向其他cpu发送IPI,也可以提升local APIC的TPR值来屏蔽外部中断,guest的这些行为将更改物理资源,严重干扰Host运行环境,所以VMX实现了APIC虚拟化。

监控Guest访问local APIC的3种方式

  1. Local APIC使用xAPIC模式时,可以通过EPT物理地址监控获取Guest访问Local APIC

  2. Local APIC使用x2APIC模式时,通过设置MSR-bitmap监控Guest返回Local APIC(VirtualBox实现了,但是没有开启)

  3. VMX引入了原生支持local APIC虚拟化机制,并模拟写入和读取 (VirtuualBox实现并开启)

VMX原生支持的APIC虚拟化:

当TPR shadow位为1的时候,启用“virtual-APIC PAGE“的影子页面(shadow page),这个影子页面和物理APIC-page一样是4K大小。在这个virtual-APIC page页面的80H位置上存一份TPR shadow数据,即VTPR(virtual TPR),当Gutest访问APIC-access page页面内80h位置时,将访问到这份VTPR数据

APIC-registrer virtualization (APIC寄存器虚拟化):控制项是Secondary controls里的VMX_PROC_CTLS2_APIC_REG_VIRT位,当这一位被置1的时候, virtual-APIC page页面内存在每个local APIC寄存器对应的一份影子数据(虚拟local APIC寄存器),将启用virtual-APIC page页面内可访问的local APIC虚拟寄存器。

Virtual-interrupt delivery(虚拟中断的提交),控制项是Secondary controls里的VMX_PROC_CTLS2_VIRT_INTR_DELIVERY项,当这位置一的时候,修改virtual-APIC page的某些状态会发生pending virtual-interrupt,需要对pending virtual-interrupt进行评估才可以通过guest-IDT提交

当APIC-registrer virtualization 位1 时,virtual-APIC page页面内存在咩歌local APIC寄存器对应的一份影子数据(虚拟local APIC寄存器),即可以访问这些虚拟local APIC寄存器

当APIC-registrer virtualization 位0 时,又分成两个情况:

1)Virtual-interrupt delivery是0时,只有偏移量为80H的VTPR寄存器是有效的,其他数据无效(不可访问)

2)Virtual-interrupt delivery是1时,除了偏移量为80H的VTPR寄之外,偏移是B0H的VEOI寄存器,和偏移是300H

的VICR寄存器的低32位可以写访问,但不能读访问,其他数据无效。

当GuestOS访问Virtual-APIC page上的无效数据的时候,会产生VMExit中断,控制项是Secondary controls里的VMX_PROC_CTLS2_VIRT_APIC_ACCESS项。

Virtualbox实现里,开启了VMX_PROC_CTLS2_VIRT_APIC_ACCESS和TPR shadow,没有开启APIC-registrer virtualization和APIC-registrer virtualization,代码如下:

static int hmR0VmxSetupVmcsProcCtls2(PVMCPUCC pVCpu, PVMXVMCSINFO pVmcsInfo)
{
  //设置了VMX_PROC_CTLS2_VIRT_APIC_ACCESS位之后,使得虚拟机在访问Virtual-APIC page无效数据的时候可能产生APIC-access的VM exit
  if (pVM->hm.s.vmx.Msrs.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS)
  {
    fVal |= VMX_PROC_CTLS2_VIRT_APIC_ACCESS;
    hmR0VmxSetupVmcsApicAccessAddr(pVCpu);
	}
  //这个地方可以通过设置VMX_BF_PROC_CTLS2_VIRT_X2APIC_MODE_MASK开启虚拟化x2APIC
  //但VirtualBox没有开启,而且xAPIC和x2APIC只能开启一个。
}
    
static int hmR0VmxSetupVmcsProcCtls(PVMCPUCC pVCpu, PVMXVMCSINFO pVmcsInfo)
{
  //TPR SHAODOW开启
  if (   PDMHasApic(pVM)
        && (pVM->hm.s.vmx.Msrs.ProcCtls.n.allowed1 & VMX_PROC_CTLS_USE_TPR_SHADOW))
    {
        fVal |= VMX_PROC_CTLS_USE_TPR_SHADOW;                
        hmR0VmxSetupVmcsVirtApicAddr(pVmcsInfo);
    }
}

Virtual-APIC Page初始化:

APIC Page结构体定义在VMM\VMMR3\APICInternal.h里,具体内容可以参考Intel手册10.4.1节里的Table 10-1, Local API Register Address Map

Virtualbox源码分析16 APIC虚拟化1 APIC概念和初始化_第4张图片
Virtualbox源码分析16 APIC虚拟化1 APIC概念和初始化_第5张图片

typedef struct XAPICPAGE
{
	/* 0x00 - Reserved. */
  uint32_t                    uReserved0[8];
  /* 0x20 - APIC ID. */
  struct
  {
  uint8_t                 u8Reserved0[3];
  uint8_t                 u8ApicId;
  uint32_t                u32Reserved0[3];
  } id;
  ...
}XAPICPAGE;

这个页面是在apicR3Construct,APIC设备初始化的时候创建的,apicR3Construct这个函数在下一篇介绍APIC虚拟设备的时候介绍,在开启TPR SHAODOW,会把这个页面的物理地址设置到VMCS的VMX_VMCS64_CTRL_VIRT_APIC_PAGEADDR_FULL项里。

DECLINLINE(void) hmR0VmxSetupVmcsVirtApicAddr(PCVMXVMCSINFO pVmcsInfo)
{
    RTHCPHYS const HCPhysVirtApic = pVmcsInfo->HCPhysVirtApic;
    int rc = VMXWriteVmcs64(VMX_VMCS64_CTRL_VIRT_APIC_PAGEADDR_FULL, HCPhysVirtApic);
}
static int apicR3InitState(PVM pVM)
{
	 //申请Virtual-APIC Page的地方
   int rc = SUPR3PageAllocEx(1 /* cPages */, 0 /* fFlags */, &pApicCpu->pvApicPageR3, &pApicCpu->pvApicPageR0,&SupApicPage);
   //这个页面被初始化成全0
   RT_BZERO(pApicCpu->pvApicPageR3, pApicCpu->cbApicPage);
} 
//在创建VMCS的时候,获取Virtual-APIC page的物理地址赋值到vmcsinfo里
static int hmR0VmxAllocVmcsInfo(PVMCPUCC pVCpu, PVMXVMCSINFO pVmcsInfo, bool fIsNstGstVmcs)
{
  if (pVM->hm.s.vmx.Msrs.ProcCtls.n.allowed1 & VMX_PROC_CTLS_USE_TPR_SHADOW)
  {
    if (!fIsNstGstVmcs)
    {
      if (PDMHasApic(pVM))
      {
        //获取apicR3InitState里的page物理地址和对应虚拟地址
        rc = APICGetApicPageForCpu(pVCpu, &pVmcsInfo->HCPhysVirtApic, (PRTR0PTR)&pVmcsInfo->pbVirtApic, NULL /*pR3Ptr*/);
        if (RT_FAILURE(rc))
          return rc;
      }
    }
    else
    {
      //嵌套VMX
      pVmcsInfo->pbVirtApic = (uint8_t *)CPUMGetGuestVmxVirtApicPage(&pVCpu->cpum.GstCtx, &pVmcsInfo->HCPhysVirtApic);
    }
  }
}

VMM_INT_DECL(int) APICGetApicPageForCpu(PCVMCPUCC pVCpu, PRTHCPHYS pHCPhys, PRTR0PTR pR0Ptr, PRTR3PTR pR3Ptr)
{
    PCAPICCPU pApicCpu = VMCPU_TO_APICCPU(pVCpu);
    *pHCPhys = pApicCpu->HCPhysApicPage;
    *pR0Ptr  = pApicCpu->pvApicPageR0;
    if (pR3Ptr)
        *pR3Ptr  = pApicCpu->pvApicPageR3;
    return VINF_SUCCESS;
}

ApicAccessAddr初始化

DECLINLINE(void) hmR0VmxSetupVmcsApicAccessAddr(PVMCPUCC pVCpu)
{
    RTHCPHYS const HCPhysApicAccess = pVCpu->CTX_SUFF(pVM)->hm.s.vmx.HCPhysApicAccess;
    int rc = VMXWriteVmcs64(VMX_VMCS64_CTRL_APIC_ACCESSADDR_FULL, HCPhysApicAccess);
}

hmR0VmxPreRunGuest函数里发现pVCpu->hm.s.vmx.u64GstMsrApicBase没有赋值,调用hmR0VmxMapHCApicAccessPage
static int hmR0VmxMapHCApicAccessPage(PVMCPUCC pVCpu)
{
    PVMCC pVM = pVCpu->CTX_SUFF(pVM);
  	//获取虚拟机内的APIC地址(APICbase地址在每个机器上初始都是一样的,都是fee00000)
    uint64_t const u64MsrApicBase = APICGetBaseMsrNoCheck(pVCpu);
    RTGCPHYS const GCPhysApicBase = u64MsrApicBase & PAGE_BASE_GC_MASK;

    //去掉EPT表里GCPhysApicBase对应原始物理地址
    int rc = PGMHandlerPhysicalReset(pVM, GCPhysApicBase);

    //修改EPT表映射HCPhysApicAccess到GCPhysApicBase,这样访问HCPhysApicAccess这个物理地址相相当于访问Guest的APIC内存
    rc = IOMR0MmioMapMmioHCPage(pVM, pVCpu, GCPhysApicBase, pVM->hm.s.vmx.HCPhysApicAccess, X86_PTE_RW | X86_PTE_P);

    //记录下u64MsrApicBase,下次hmR0VmxPreRunGuest的时候就不会再次映射
    pVCpu->hm.s.vmx.u64GstMsrApicBase = u64MsrApicBase;
    return VINF_SUCCESS;
}

APIC-access page和Virtual-APIC page之间的关系:

APIC-access page是给GuestOS访问的,告诉VMX root,当Guest访问APIC-access page的时候需要发生VMExit,但实际上访问的是Virtual-APIC page(host维护),VMExit会调用IEM里的函数,把数据写入到Virtual-APIC page里。

xAPIC的Virtual-APIC Page访问VMExit handle

因为VirtualBox没有开启APIC-registrer virtualization和APIC-registrer virtualization,所以当GuestOS访问Local APIC页面除80H偏移之外地址的时候,都会触发hmR0VmxExitApicAccess VMExit:

HMVMX_EXIT_DECL hmR0VmxExitApicAccess(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient)
{
  //获取VMExit的参数
  hmR0VmxReadExitIntInfoVmcs(pVmxTransient);
  hmR0VmxReadExitIntErrorCodeVmcs(pVmxTransient);
  hmR0VmxReadExitInstrLenVmcs(pVmxTransient);
  hmR0VmxReadIdtVectoringInfoVmcs(pVmxTransient);
  hmR0VmxReadIdtVectoringErrorCodeVmcs(pVmxTransient);
  
  //检查参数
  VBOXSTRICTRC rcStrict = hmR0VmxCheckExitDueToEventDelivery(pVCpu, pVmxTransient);
  if (RT_LIKELY(rcStrict == VINF_SUCCESS))
  {
    if (RT_UNLIKELY(pVCpu->hm.s.Event.fPending))
    {
      return VINF_EM_RAW_INJECT_TRPM_EVENT;
    }
  }
  else
  {
    return rcStrict;
  }
  //获取VMExit的参数
  PVMXVMCSINFO pVmcsInfo = pVmxTransient->pVmcsInfo;
  hmR0VmxReadExitQualVmcs(pVmxTransient);
  int rc = hmR0VmxImportGuestState(pVCpu, pVmcsInfo, IEM_CPUMCTX_EXTRN_MUST_MASK);

  //获取access type,读或者写
  uint32_t const uAccessType = VMX_EXIT_QUAL_APIC_ACCESS_TYPE(pVmxTransient->uExitQual);
  switch (uAccessType)
  {
    case VMX_APIC_ACCESS_TYPE_LINEAR_WRITE:
    case VMX_APIC_ACCESS_TYPE_LINEAR_READ:
      {
        //获取APIC-access page的地址
        RTGCPHYS GCPhys = pVCpu->hm.s.vmx.u64GstMsrApicBase;    
        GCPhys &= PAGE_BASE_GC_MASK;
        //加上APIC偏移
        GCPhys += VMX_EXIT_QUAL_APIC_ACCESS_OFFSET(pVmxTransient->uExitQual);
        //调用IOMR0MmioPhysHandler处理APIC page访问的模拟执行 (在APIC虚拟化设备里介绍)
        rcStrict = IOMR0MmioPhysHandler(pVCpu->CTX_SUFF(pVM), pVCpu,
                                        uAccessType == VMX_APIC_ACCESS_TYPE_LINEAR_READ ? 0 : X86_TRAP_PF_RW, GCPhys);
        if (   rcStrict == VINF_SUCCESS
            || rcStrict == VERR_PAGE_TABLE_NOT_PRESENT
            || rcStrict == VERR_PAGE_NOT_PRESENT)
        {
          //模拟执行成功
          ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_GUEST_RIP | HM_CHANGED_GUEST_RSP | HM_CHANGED_GUEST_RFLAGS
                           | HM_CHANGED_GUEST_APIC_TPR);
          rcStrict = VINF_SUCCESS;
        }
        break;
      }

    default:
      {
        //其他type,可能是VMX_APIC_ACCESS_TYPE_LINEAR_INSTR_FETCH/VMX_APIC_ACCESS_TYPE_LINEAR_EVENT_DELIVERY
        //VMX_APIC_ACCESS_TYPE_PHYSICAL_EVENT_DELIVERY/VMX_APIC_ACCESS_TYPE_PHYSICAL_INSTR
        rcStrict = VINF_EM_RAW_EMULATE_INSTR;
        break;
      }
  }
}

监控CR8的读写:CR8保存了TPR值,需要拦截CR8的写入更新TPR的值,读取CR8寄存器的时候需要返回保存的TPR值。

//IEM里介绍过,Rdmsr和Wrmsr最终调用到IEM里模拟执行
IEM_CIMPL_DEF_4(iemCImpl_load_CrX, uint8_t, iCrReg, uint64_t, uNewCrX, IEMACCESSCRX, enmAccessCrX, uint8_t, iGReg)
{
   ....
	 case 8:
   {
     //TPR只接受0到15范围内的值
     if (uNewCrX & ~(uint64_t)0xf)
     {
     		return iemRaiseGeneralProtectionFault0(pVCpu);
     }
     uint8_t const u8Tpr = (uint8_t)uNewCrX << 4;
     APICSetTpr(pVCpu, u8Tpr);
}
IEM_CIMPL_DEF_2(iemCImpl_mov_Rd_Cd, uint8_t, iGReg, uint8_t, iCrReg)
{
  ...
    case 8:
  		int8_t uTpr;
  		//获取APIC page里的TPR寄存器
      int rc = APICGetTpr(pVCpu, &uTpr, NULL, NULL);
      if (RT_SUCCESS(rc))
        crX = uTpr >> 4;
      else
        crX = 0;
}    

参考资料:
https://blog.csdn.net/omnispace/article/details/61415994
https://blog.csdn.net/borisjineman/article/details/51050094
https://blog.csdn.net/ustc_dylan/article/details/4132046
《处理器虚拟化技术》邓志著
Intel指令的手册

你可能感兴趣的:(VirtualBox源码分析)