kvm虚拟化

 

 

 

[root@localhost cloud_images]# qemu-system-aarch64 -smp 8 -m 8192 -cpu host -M virt -nographic -drive file=vhuser-test1.qcow2,id=hd0   -device virtio-blk-device,drive=hd0   -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22
qemu-system-aarch64: The 'host' CPU type can only be used with KVM
[root@localhost cloud_images]# qemu-system-aarch64   -name vm3 -enable-kvm -smp 8 -m 8192 -cpu host -M virt -nographic -drive file=vhuser-test1.qcow2,id=hd0   -device virtio-blk-device,drive=hd0   -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22
qemu-system-aarch64: -drive file=vhuser-test1.qcow2,id=hd0: Drive 'hd0' is already in use because it has been automatically connected to another device (did you need 'if=none' in the drive options?)
[root@localhost cloud_images]# qemu-system-aarch64   -name vm3 -enable-kvm -smp 8 -m 8192 -cpu host -M virt -nographic -drive file=vhuser-test1.qcow2    -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22

 

 

下面,总结一下qemu-kvm软件的架构特点:

1、Kvm本身只提供两个内核模块。Kvm实现了vcpu和内存的管理;

2、Qemu控制逻辑,负责创建虚拟机,创建vcpu等。

在详细介绍,kvm提供了三种通过不同的io_ctl接口来控制的概念:

1、struct kvm:代表kvm模块本身,用来管理kvm版本信息,创建一个vm;

2、struct vm:代表一个虚拟机。通过vm的io_ctl接口,可以为虚拟机创建vcpu,设置内存区间,创建中断控制芯片,分配中断等等;

3、struct vcpu:代表一个vcpu。通过vcpu的io_ctl接口,可以启动或者暂停vcpu,设置vcpu的寄存器,为vcpu注入中断等等。

 

 

首先,定义一个简单地虚拟机需运行代码:


mov $0x3f8, %dx
add %bl, %al
add $'0', %al
out %al, (%dx)
mov $'\n', %al
out %al, (%dx)
hlt

 


这段代码比较简单,也就是先将al和bl寄存器的值相加(初始默认值均为2),结果转换后,输出至0x3f8端口,最后停机。然后,我们通过gcc和objdump将上述二进制代码转换为机器码,内容如下:


constuint8_t code[]={
  0xba,0xf8,0x03,/* mov $0x3f8, %dx */
  0x00,0xd8,/* add %bl, %al */
  0x04,'0',/* add $'0', %al */
  0xee,/* out %al, (%dx) */
  0xb0,'\n',/* mov $'\n', %al */
  0xee,/* out %al, (%dx) */
  0xf4,/* hlt */
};

 


需要指出的是,运行这段代码需要CPU"unrestricted guest"特性支持。

下面,简略叙述一下QEMU、KVM的交互过程。

先定义并初始化几个变量:

 


/* 向KVM注册用户态内存空间,也即向虚拟机添加“物理内存” */
  /* 注意该“物理内存”是qemu进程向host申请的用户态内存 */
struct kvm_userspace_memory_region region = {
  .slot = 0,
  .guest_phys_addr = 0x1000,    ----物理内存
  .memory_size = 0x1000,
  .userspace_addr = (uint64_t)mem,
};
  /* 通用寄存器信息初始化,此处可以看到a、b寄存器值初始化为2 */
struct kvm_regs regs = {
  .rip = 0x1000,
  .rax = 2,
  .rbx = 2,
  .rflags = 0x2,
};
  /* cs段寄存器信息初始化 */
sregs.cs.base = 0;
sregs.cs.selector = 0;

 


运行过程如下:


void main(){
  /* 打开kvm控制的总设备文件/dev/kvm */
  kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
  /* 检查API版本信息,检测ret值 */
  ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
  if (ret == -1)
    err(1, "KVM_GET_API_VERSION");
  if (ret != 12)
    errx(1, "KVM_GET_API_VERSION %d, expected 12", ret);
/* 创建虚拟机 */ 
  vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
  /* 获取页对齐且初始化为0的一个内存页  0X1000是物理地址*/
  mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  /* 将前述二进制代码拷贝至该页内 */
  memcpy(mem, code, sizeof(code));
  /* 将二进制页赋予虚拟机 */ 
  /* 此时,虚拟机将其当做物理内存,且只有一个slot */
  ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
  /* 创建VCPU,且每个VCPU关联一个struct kvm_run结构体 */
  vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
  /* 计算需要kernel和用户空间共享的struct kvm_run结构体大小 */
  mmap_size = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
  /* 执行共享struct kvm_run结构体*/
  run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
  /* 初始化虚拟机的VCPU寄存器信息,准备运行 */
  ioctl(vcpufd, KVM_GET_SREGS, &sregs);
  ioctl(vcpufd, KVM_SET_REGS, &regs);
  /* 运行 */
  while (1) {
    /* 进入运行 */
    ioctl(vcpufd, KVM_RUN, NULL);
    /* 退出处理 */
    switch (run->exit_reason) {
      case KVM_EXIT_HLT:
        puts("KVM_EXIT_HLT");
        return 0;
      case KVM_EXIT_IO:
        if (run->io.direction == KVM_EXIT_IO_OUT &&
          run->io.size == 1 &&
          run->io.port == 0x3f8 &&
          run->io.count == 1)
          putchar(*(((char *)run) + run->io.data_offset));
        else
          errx(1, "unhandled KVM_EXIT_IO");
        break;
      case KVM_EXIT_FAIL_ENTRY:
        errx(1, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx",
         (unsigned long long)run->fail_entry.hardware_entry_failure_reason);
      case KVM_EXIT_INTERNAL_ERROR:
        errx(1, "KVM_EXIT_INTERNAL_ERROR: suberror = 0x%x",
           run->internal.suberror);
      default:printf("error\n"); break;
    }
  }  
}

 


       上面已经简略地注释了各行代码。我们大体上搞明白了创建、加载、运行虚拟机的基本流程。

 

posted on 2020-11-04 09:55  tycoon3  阅读(444)  评论(0)    收藏  举报

导航