APIC虚拟化

简介:

  中断虚拟化中不可避免需要接触到APIC的虚拟化,其中包括IOAPIC和LAPIC。这里挖个坑,准备逐步写一下从硬件原型到软件模拟的知识,谨作学习记录。

LAPIC硬件

 1. LAPIC内部寄存器布局可参考下图

 大体按照功能分类如下:

  Timer related:

      CCR: Current Count Register
      ICR: Initial Count Register
      DCR: Divide Configuration Register
      Timer: in LVT

  LVT (Local Vector Table):

      Timer
      Local Interrupt
      Performance Monitor Counters
      Thermal Sensor
      Error

  IPI:

      ICR: Interrupt Command Register
      LDR: Logical Destination Register
      DFR: Destination Format Register

  Interrupt State:

      ISR: In-Service Register
      IRR: Interrupt Request Register
      TMR: Trigger Mode Register

 2. LAPIC地址访问方式

  x86 CPU中一共有三种访问APIC的方式:

  •   通过CR8来访问TPR寄存器(仅IA32-e即64位模式)
  •   通过MMIO来访问APIC的寄存器,这是xAPIC模式提供的访问方式
  •   通过MSR来访问APIC的寄存器,这是x2APIC模式提供的访问方式

 3. APIC地址模型

  LAPIC地址为0XFEE00000开始的4K MMIO地址空间,所有CPU的LAPIC地址都是这里。
  访问APIC寄存器为针对0xfee00000的4K以内的偏移量,MMIO地址访问会产生陷出,在VMM中根据偏移量在reg申请的4K空间中操作对应CPU中虚拟出来的寄存器。

  

 

LAPIC虚拟化

  1. lapic寄存器虚拟化(APIC-register virtualization)

  •   寄存器虚拟化是其他中断优化的基础
  •   TPR shadow需要使能
  •   APIC寄存器物理地址需要写到VMCS

  所有CPU访问自己的LAPIC用了相同的物理地址,那么guest中两个虚拟CPU访问自己的虚拟LAPIC也用相同的物理地址,但一个guest只有一套EPT机制,这怎么区分?
  intel发明了apic-access pagevirtual-apic page,这两个page的物理地址写在VMCS中,一个guest只有一个apic-access page,每个虚拟CPU有一个virtual-apic page,guest虚拟CPU读写LAPIC时,EPT把地址翻译指向apic-access page,EPT翻译完了,CPU知道自己此时处于guest模式,正在运行哪个虚拟CPU,加载着哪个VMCS,然后去virtual apic page去拿数据,这样就能保证虚拟CPU访问相同的物理地址拿到不同的结果,而且不需要hypervisor软件介入,比如虚拟CPU读自己的LAPIC ID,那结果肯定不一样,EPT翻译到相同的地址,如果没重定向到virtual-apic page那么结果就一样了。

  需要使能和实现的VMCS中VM-Execution control区域如下所示:

  primary processor-based VM-executeion controls: Use TPR shadow, Virtual-APIC address

  secondary processor-based VM-executeion controls: APIC register virtualization

 

  APIC-Register Virtualization = 1,则对下列寄存器的读访问不会引起APIC Access VM Exit

  •   APIC ID Register、APIC Version Register
  •   TPR、LDR、DFR
  •   Spurious-Interrupt Vector Register
  •   IRR、ISR、TMR、EOI Register
  •   Error Status Register
  •   ICR
  •   LVT Entries、Initial Count Register、Divide Configuration Register
  •   换句话说除了只读的PPR和Current Count Register,APIC中其余所有寄存器都允许从Virtual-APIC Page中读取

   APIC-Register Virtualization = 1,则对下列寄存器的访问不会引起APIC Access VM Exit

  •   APIC ID Register
  •   TPR、LDR、DFR
  •   Spurious-Interrupt Vector Register
  •   Error Status Register
  •   ICR
  •   LVT Entries、Initial Count Register、Divide Configuration Register
  •   相比读取请求,删去了只读的APIC Verson Register以及IRR、ISR、TMR

  2. apic-access使能

  下方标志为需要使能和赋值的VMCS项,需要页面为4K
  Virtualize APIC Accesses 功能可以单独开启,此时它的作用仅仅是将Guest APIC访问的VM Exit从EPT Violaton改为了APIC Access,对Guest的APIC访问仍需要以Trap-and-Emulate方式实现。但是,若进一步开启其他APIC虚拟化功能,则可以将Guest APIC访问转变为访问Virtual-APIC Page中的虚拟寄存器。
  APIC-access address:申请4K空间,通过ept map到GPA的0xfee00000
  Virtual-APIC address:vcpu结构体中apic寄存器的物理地址HPA
  需要实现的VMCS中VM-Execution control区域如下:
  primary processor-based VM-executeion controls: Use TPR shadow, Virtual-APIC address、APIC-access address

  secondary processor-based VM-executeion controls: virtualize apic access、APIC register virtualization、

  相比于只实现寄存器虚拟化变化点:

  • 新增两个退出原因apic_access/apic_write
  • 对apic寄存器操作不再通过EPT violation陷出

  相比于只实现寄存器虚拟化优化点:

  • 执行MMIO指令模拟前不需要先遍历一遍memory slot

  3. X2APIC

  •   X2APIC和apic-access不能共存。
  •   X2APIC也需要APIC-register virtualization使能以保证GUEST可以不陷出。
  •   Use-TPR shadow需要使能。
  •   MSR的bitmap对应的800-8ff需要置位。
  需要实现的VMCS中VM-Execution control区域如下:
  primary processor-based VM-executeion controls: Use TPR shadow, Virtual-APIC address

  secondary processor-based VM-executeion controls: virtualize X2APIC mode、APIC register virtualization

    Read bitmap for low MSRS, Read bitmap for high MSRS, Write bitmap for low MSRS, Write bitmap for high MSRS,

  相比较与只实现寄存器虚拟化变化点:

  •   对apic寄存器操作不通过MMIO陷出,而使用MSR:0x800-0x8ff
  •   GUEST内核需要开启X2APIC支持

  相比较与只实现寄存器虚拟化优化点:

  •   不需要MMIO陷出后的指令解析和执行的模拟,直接读写对应地址的值

  4. Virtual-interrupt delivery

   使能条件: 置位VMCS中如下控制区域
secondary processor-based VM-executeion controls: virtual interrupt delivery

  控制位使能后将自动使能VMCS的Guest-state区域中的Guest interrupt status区域。区域介绍如下:

  Guest interrupt status (16 bits). This field is supported only on processors that support the 1-setting of the
  “virtual-interrupt delivery” VM-execution control. It characterizes part of the guest’s virtual-APIC state and
  does not correspond to any processor or APIC registers. It comprises two 8-bit subfields:
  — Requesting virtual interrupt (RVI). This is the low byte of the guest interrupt status. The processor
  treats this value as the vector of the highest priority virtual interrupt that is requesting service. (The value
  0 implies that there is no such interrupt.)
  — Servicing virtual interrupt (SVI). This is the high byte of the guest interrupt status. The processor treats
  this value as the vector of the highest priority virtual interrupt that is in service. (The value 0 implies that
  there is no such interrupt.)

  Virtual-interrupt delivery使能后,物理核会执行Evaluation of Pending Virtual Interrupts。
  会执行Evaluation of Pending Virtual Interrupts的场景:

  •   VM entry;
  •   TPR virtualization;
  •   EOI virtualization;
  •   self-IPI virtualization;
  •   posted-interrupt processing。

  Virtual-interrupt delivery会在不产生VM exit情况下更新RVI和SVI,大致流程如下:

 

 

   5. post interrupt

  需置位的VMCS Execution control区域如下:

  pin-based VM-execution controls: external-interrupt exiting、Process posted interrupts

    primary processor-based VM-executeion controls: Use TPR shadow, Virtual-APIC address、posted-interrupt notification vector、 posted-interrupt descriptor address

  secondary processor-based VM-executeion controls:APIC register virtualization、Virtual-interrupt delivery

  Posted Interrupt,它引入了一个Posted-Interrupt Notification Vector(VMCS[0x0002](16 bit),仅最低8位有效)和一个64字节(恰好占满一个Cache Line)的Posted-Interrupt Descriptor。后者位于内存中,其地址(HPA)通过Posted-Interrupt Descriptor Address(VMCS[0x2016]/VMCS[0x2017](64 bit full/high))指定,格式如下:

  第0-255位为Posted-Interrupt Requests (PIR),是一个Bitmap,每个位对应一个Vector
  第256位为Outstanding Notification (ON),取1表示有一个Outstanding的Notificaton Event(即中断)尚未处理
  第257-511位为Available,即允许软件使用的Ignored位

  结构体如下:

  

  作用:
    Posted Interrupt是对Virtual-Interrupt Delivery的进一步发展,让我们可以省略Interrupt Acceptance的过程,直接令正在运行的vCPU收到一个虚假中断,而不产生VM Exit。
  前置条件:
  当Non-root模式下收到一个外部中断时,CPU首先完成Interrupt Acceptance和Interrupt Acknowledgement(因为开启Posted Interrupt必先开启Acknowledge Interrupt on Exit),并取得中断的Vector。然后,若Vector与Posted-Interrupt Notification Vector相等,则进入Posted-Interrupt Processing,否则照常产生External Interrupt VM Exit。
  Posted-Interrupt Processing的过程如下:

  •   清除Descriptor的ON位
  •   向CPU的EOI寄存器写入0,执行EOI,至此在硬件APIC上该中断已经处理完毕
  •   令VIRR |= PIR,并清空PIR
  •   设置RVI = max(RVI, PIRV),其中PIRV为PIR的旧值中优先级最高的Vector
  •   最后evaluate pending virtual interrupts

 qemu初始化逻辑:

pc_init1
    pc_cpus_init
        pc_new_cpu
            set cpu's apic_id
            realized: x86_cpu_realizefn
                x86_cpu_apic_create
                    cpu->apic_state
                    object_property_add_child(lapic)
                    qdev_prop_set_uint32(id)
                    apic->apicbase = 0xfee00000 | MSR_IA32_APICBASE_ENABLE;
                x86_cpu_apic_realize
                    device_set_realized
                        apic_common_realize
                            apic_realize
                                memory_region_init_io(apic-msi)
                                s->timer
                                local_apics[s->id] = s
                memory_region_add_subregion_overlap
    pcms->gsi
        qemu_allocate_irqs(gsi_handler)
    i8259_init
        master:i8259_init_chip
            isa_create(TYPE_I8259)
            iobase:0x20
            elcr_addr:0x4d0
            elcr_mask:0xf8
            master:true
            qdev_init_nofail
                pic_realize
                    memory_region_init_io(base_io)
                        pic_ioport_read
                        pic_ioport_write
                    memory_region_init_io(elcr_io)
                        elcr_ioport_read
                        elcr_ioport_write
                    qdev_init_gpio_out
                    qdev_init_gpio_in(pic_set_irq)
                        qdev_init_gpio_in_named_with_opaque
                            gpio_list->in[8]
                    pic_common_realize
                        isa_register_ioport
                        qdev_set_legacy_instance_id
        qdev_connect_gpio_out(pic_irq_request)
            qdev_connect_gpio_out_named
        irq_set[0-7] = qdev_get_gpio_in(0-7)
        slave:i8259_init_chip
            isa_create(TYPE_I8259)
            iobase:0xa0
            elcr_addr:0x4d1
            elcr_mask:0xde
            master:false
            qdev_init_nofail
                pic_realize
        qdev_connect_gpio_out(irq_set[2])
            qdev_connect_gpio_out_named
        irq_set[8-15] = qdev_get_gpio_in(0-7)
    ioapic_init_gsi
        qdev_create(ioapic)
        object_property_add_child(ioapic)
        qdev_init_nofail
            ioapic_common_realize
                ioapic_realize
                    memory_region_init_io(ioapic,0x1000)
                    qdev_init_gpio_in(ioapic_set_irq)
                    ioapics[ioapic_no] = s
                sysbus_init_mmio(0xfec00000)
                    IOAPICCommonState->mmio[n].memory = s->io_memory
        sysbus_mmio_map(0xfec00000)
        gsi_state->ioapic_irq[i] = qdev_get_gpio_in
main_impl
    configure_accelerator
        accel_init_machine
            init_machine:hax_accel_init
    machine_run_board_init
        machine_class->init

 

 

 

 

 

 

posted @ 2022-05-10 19:09  Edver  阅读(1289)  评论(0编辑  收藏  举报