深度探索Linux系统虚拟化:原理与实现
上QQ阅读APP看书,第一时间看更新

2.2.3 准备中断0x15的处理函数以及设置IVT

当CPU执行指令int 0x15后,将进入中断处理流程,CPU将从IVT表中的第0x15项取出中断处理函数的地址,然后跳转到中断处理函数执行。这个中断处理函数由BIOS实现,存储在BIOS ROM中。在典型的x86架构的32位系统中,系统启动后,主板的BIOS ROM将被映射到内存地址空间0x000F0000~0x000FFFFF处。也就是说,中断发生后,最终是跳转到地址空间0x000F0000~0x000FFFFF中的中断0x15对应的处理函数处运行。因此,对于kvmtool来讲,需要在Guest的0x000F0000~0x000FFFFF这段内存中准备好0x15号中断的中断处理函数,包括:

1)在BIOS ROM中准备中断处理函数的实现。

2)设置IVT中0x15号表项的地址指向0x15号中断的处理函数。

具体代码如下:


commit 2f3976eeee4e0421c136c3431990a55cbf0f2bbf
kvm: BIOS E820 memory map emulation
kvmtool.git/include/kvm/bios.h
01 #define MB_BIOS_BEGIN           0x000f0000
02 #define MB_BIOS_END         0x000fffff

kvmtool.git/bios.c
03 void setup_bios(struct kvm *kvm)
04 {
05     unsigned long address = MB_BIOS_BEGIN;
06     …
07     address = BIOS_NEXT_IRQ_ADDR(address, bios_int10_size);
08     bios_setup_irq_handler(kvm, address, 0x15, bios_int15,
09          bios_int15_size);
10     …
11 }

12 static void bios_setup_irq_handler(struct kvm *kvm, …)
13 {
14     struct real_intr_desc intr_desc;
15     void *p;
16
17     p = guest_flat_to_host(kvm, address);
18     memcpy(p, handler, size);
19     intr_desc = (struct real_intr_desc) {
20         .segment    = REAL_SEGMENT(address),
21         .offset     = REAL_OFFSET(address),
22     };
23     interrupt_table__set(&kvm->interrupt_table, 
24         &intr_desc, irq);
25 }

根据第5行代码可见,变量address的初始值MB_BIOS_BEGIN,是主板BIOS ROM映射在Guest内存地址空间中的起始位置,第7行的宏BIOS_NEXT_IRQ_ADDR就是在BIOS ROM中确定0x15号中断的处理函数的起始存储地址,根据代码可见,这块内存区域位于0x10号中断的处理函数的后面。然后第8、9行代码中的函数bios_setup_irq_handler将0x15号中断的处理函数bios_int15的实现复制到这里,即BIOS ROM区域,具体见第18行代码。因为address是Guest视角的物理地址GPA(Guest Physical Adddress),而在kvmtool中使用这个地址时,需要将其转换为Host可用的地址HVA(Host Virtual Address),第17行代码中的函数guest_flat_to_host负责这个转换,其中ram_start就是Host在HVA空间为Guest分配的GPA的基址:


commit 2f3976eeee4e0421c136c3431990a55cbf0f2bbf
kvm: BIOS E820 memory map emulation
kvmtool.git/include/kvm/kvm.h
static inline void *guest_flat_to_host(struct kvm *self, 
unsigned long offset)
{
    return self->ram_start + offset;
}

除了复制中断处理函数实现到BIOS ROM外,函数bios_setup_irq_handler还负责设置IVT表项。第14行代码创建了一个临时的IVT表项,第19~22行代码组织这个IVT表项,设置其中的段地址和段内偏移地址。组织好表项后,调用函数interrupt_table__set记录到了数据结构kvm->interrupt_table中:


commit 2f3976eeee4e0421c136c3431990a55cbf0f2bbf
kvm: BIOS E820 memory map emulation
kvmtool.git/interrupt.c
void interrupt_table__set(struct interrupt_table *self, 
struct real_intr_desc *entry, unsigned int num)
{
    if (num < REAL_INTR_VECTORS)
        self->entries[num] = *entry;
}

在组织好所有的IVT表项后,setup_bios最后调用函数interrupt_table__copy将kvm->interrupt_table中的IVT表项一次性地复制到IVT。因为IVT存储在物理内存0处,所以下面代码中传给guest_flat_to_host的第2个参数为0:


commit 2f3976eeee4e0421c136c3431990a55cbf0f2bbf
kvm: BIOS E820 memory map emulation
kvmtool.git/bios.c
void setup_bios(struct kvm *kvm)
{
    …
    p = guest_flat_to_host(kvm, 0);
    interrupt_table__copy(&kvm->interrupt_table, p, 
REAL_INTR_SIZE);
}

kvmtool.git/interrupt.c
void interrupt_table__copy(struct interrupt_table *self, 
void *dst, unsigned int size)
{
    …
    memcpy(dst, self->entries, sizeof(self->entries));
}