深度探索Linux系统虚拟化:原理与实现
上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);
  }
}