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)); }