[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 $' ', %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,' ',/* mov $' ', %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, ®ion); /* 创建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, ®s); /* 运行 */ 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 "); break; } } }
上面已经简略地注释了各行代码。我们大体上搞明白了创建、加载、运行虚拟机的基本流程。