上QQ阅读APP看书,第一时间看更新
1.5.3 创建处理器
内存准备好了之后,接下来我们创建运行指令的处理器。KVM模块为用户空间提供的API为KVM_CREATE_VCPU,这个API接收一个参数vcpu id,本质上是lapci id:
int setup_vm(struct vm *vm, int ram_size) { … struct vcpu *vcpu = vm->vcpu[0]; vcpu->fd = ioctl(vm->vm_fd, KVM_CREATE_VCPU, vcpu->id); if (vm->vcpu[0]->fd < 0) { fprintf(stderr, "failed to create cpu for vm.\n"); } … }
创建好处理器后,我们需要告知其从内存的哪里开始执行指令,因此我们可以通过更简洁的方式,直接设置代码段和指令指针来指向Guest系统在内存中加载的位置,而不必按照传统的方式来执行(比如处理器重置后从地址0xfffffff0开始执行)。对于x86架构,KVM为VCPU的寄存器定义了两个结构体。一个是结构体kvm_sregs,KVM称其为special registers,包含段寄存器、控制寄存器等。代码段寄存器cs就在这个结构体中:
linux.git/include/linux/kvm.h struct kvm_sregs { /* in */ __u32 vcpu; __u32 padding; /* out (KVM_GET_SREGS) / in (KVM_SET_SREGS) */ struct kvm_segment cs, ds, es, fs, gs, ss; struct kvm_segment tr, ldt; struct kvm_dtable gdt, idt; __u64 cr0, cr2, cr3, cr4, cr8; __u64 efer; __u64 apic_base; __u64 interrupt_bitmap[KVM_IRQ_BITMAP_SIZE(__u64)]; };
通用寄存器、标志寄存器,以及前面刚刚提到的指令指针寄存器eip定义在另一个结构体kvm_regs中:
linux.git/include/linux/kvm.h struct kvm_regs { /* in */ __u32 vcpu; __u32 padding; /* out (KVM_GET_REGS) / in (KVM_SET_REGS) */ __u64 rax, rbx, rcx, rdx; __u64 rsi, rdi, rsp, rbp; __u64 r8, r9, r10, r11; __u64 r12, r13, r14, r15; __u64 rip, rflags; };
系统启动时首先进入16位实模式,后面我们会将Guest加载到段地址为0x1000、偏移地址为0的地方,因此,我们设置代码段寄存器为0x1000,指令指针寄存器为0。根据实模式的寻址方式,可以计算出Guest系统加载的物理地址为0x1000<<4+0,即0x10000。
除了设置cs的selector外,我们还设置了cs的base。这是为了避免每次都要做左移计算,每次设置cs时,都把cs_selector<<4的结果存入descriptor cache中,即cs base。
最后,我们需要设置一下rflags寄存器,按照Intel手册要求,将第2位设置为1,其他位全部初始化为0:
int setup_vm(struct vm *vm, int ram_size) { … // sregs if (ioctl(vcpu->fd, KVM_GET_SREGS, &(vcpu->sregs)) < 0) { fprintf(stderr, "failed to get sregs.\n"); exit(-1); } vcpu->sregs.cs.selector = 0x1000; vcpu->sregs.cs.base = 0x1000 << 4; if (ioctl(vcpu->fd, KVM_SET_SREGS, &(vcpu->sregs)) < 0) { fprintf(stderr, "failed to set sregs.\n"); exit(-1); } // regs if (ioctl(vcpu->fd, KVM_GET_REGS, &(vcpu->regs)) < 0) { fprintf(stderr, "failed to get regs.\n"); exit(-1); } vcpu->regs.rip = 0x0; vcpu->regs.rflags = 0x2; if (ioctl(vcpu->fd, KVM_SET_REGS, &(vcpu->regs)) < 0) { fprintf(stderr, "failed to set regs.\n"); exit(-1); } }