QEMU和KVM的关系

64次阅读
没有评论

共计 3435 个字符,预计需要花费 9 分钟才能阅读完成。

QEMU和KVM的关系

我们已经知道QEMU是作为用户态软件模拟IO,而KVM负责完成CPU、内存等虚拟化,为什么常常把KVM和QEMU联系起来,因为KVM诞生之初就是使用的QEMU作为用户态的设备模拟软件,其实Xen也可以使用QEMU作为用户态组件来实现设备的模拟。

KVM在内核中通过/dev/kvm给用户态的软件,提供了一系列的接口,例如用户态创建、配置、启动虚拟机等,在设备模拟的部分,KVM创立之初就重用了QEMU的设备模拟的部分,从本质上来讲,QEMU和KVM是完全可以不用相互依赖的。举个例子,来展示QEMU和KVM之间的关系,这个例子包括精简版内核,这个内核功能简单,作用只是向I/O端口写入数据;第二部分可以看作是一个精简版的QEMU,它的功能也很简单,就是使用上述内核创建虚拟机,打印出精简内核的端口数据。

精简内核代码

精简内核代码如下:作用是将”SCU HELLO”字符串输出到I/O端口0xf1上,然后调用hlt指令使CPU进入停机状态,直到有中断或者复位信号发生才会继续执行。

test.S 代码如下:
start:
mov $0x53,%al
outb %al,$Oxf1
mov $0x43,%al
outb %al,$Oxf1
mov $0x55,%al
outb %al,$0xf1
mov $0x20,%al
outb %al,$Oxf1
mov $0x48,%al
outb %al,$Oxf1
mov $0x45, %al
outb %al,$Oxf1
mov $0x4c, %al
outb %al,$Oxf1
mov $0x4c, %al
outb %al,$Oxf1
mov $0x4f, %al
outb %al,$Oxf1
mov $0x0a, %al
outb %al,$Oxf1
hlt

说明:

mov $0x48,%al
outb %al,$Oxf1

这是两条x86汇编语言指令,它们的作用是将十六进制数0x48输出到I/O端口0xf1上。具体来说,第一条指令mov $0x48,%al的作用已经在上一个问题中解释过了,它将立即数0x48移动到AL寄存器中。第二条指令outb %al,$0xf1则是将AL寄存器中的值输出到I/O端口0xf1上。outb是x86汇编语言中的一个输出指令,它的作用是将一个字节(8位)的数据输出到指定的I/O端口。

使用如下命令进行编译:

as -32 test.S -o test.o
objcopy -O binary test.o test.bin

第一个命令as -32 test.S -o test.o是使用GNU汇编器(as)将汇编代码文件test.S汇编成目标文件(object file)test.o。其中,-32选项指定生成32位目标文件,-o选项指定输出文件名为test.o

第二个命令objcopy -O binary test.o test.bin是使用GNU二进制文件操作工具(objcopy)将目标文件test.o转换为二进制文件test.bin。其中,-O binary选项指定输出文件格式为二进制文件,test.o为输入文件名,test.bin为输出文件名。

精简版QEMU代码

qemu.c代码如下:

#include <stddef.h>
#include <stdio.h>
#include <linux/kvm.h>
#include <fcntl.h>
#include <sys/mman.h>
int main()
{
    struct kvm_sregs sregs;
    int ret;
    //通过打开/edv/kvm获取系统中KVM子系统的文件描述符
    int kvmfd = open (&quot;/dev/kvm&quot;,O_RDWR);
    //保持应用层与内核统一,获取KVM版本,应用层可以知晓KVM的支持情况
    ioctl(kvmfd, KVM_GET_API_VERSION,NULL);
    // 创建虚拟机,返回一个代表虚拟机的文件描述符
    int vmfd = ioctl(kvmfd, KVM_CREATE_VM,0);
    // 给虚拟机分配物理内存,虚拟机的物理内存是QEMU的位于进程地址空间,这段内存大小为0x1000 4KB
    unsigned char *ram = mmap(NULL,0x1000,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);
    //将精简内核读入内存
    int kfd = open(&quot;test.bin&quot;,O_RDONLY);
    read(kfd,ram,4096);
    struct kvm_userspace_memory_region mem = {
        //slot用来表示不同的内存空间
        .slot = 0,
        //表示这段空间在虚拟机物理内存空间的地址
        .guest_phys_addr = 0,
        //表示这段物理空间的大小
        .memory_size = 0x1000,
        //表示这段物理空间对应宿主机上的虚拟机地址 也就是GPA-&gt;HVA
        .userspace_addr = (unsigned long) ram,
    };
    //为虚拟机分配内存,一个内存条
    ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION,&amp;mem);
    //创建vCPU
    int vcpufd = ioctl(vmfd,KVM_CREATE_VCPU,0);
    //每一个vCPU都有一个struct kvm_run的结构,用来在用户态(qemu)和内核态(kvm)之间共享数据。
    //用户态需要将这段空间映射到用户空间
    int mmap_size = ioctl(kvmfd, KVM_GET_VCPU_MMAP_SIZE, NULL);
    struct kvm_run *run = mmap(NULL,mmap_size, PROT_READ |PROT_WRITE,MAP_SHARED,vcpufd, 0);
    //设置VCPU相关的寄存器,sregs存放的如段寄存器、控制寄存器等特殊寄存器。
    ret = ioctl(vcpufd, KVM_GET_SREGS, &amp;sregs);
    //设置代码段其实地址为0,
    sregs.cs.base = 0;
    sregs.cs.selector = 0;
    ret = ioctl(vcpufd,KVM_SET_SREGS, &amp;sregs);
    //设置通用寄存器,如指令指针寄存器为0,这样就直接从精简内核第一行代码开始执行
    struct kvm_regs regs ={
        .rip =0,
    };
    ret = ioctl(vcpufd,KVM_SET_REGS, &amp;regs);
    while (1)
    {
        //将VCPU调度到物理CPU上运行
        ret = ioctl(vcpufd, KVM_RUN, NULL);
        if( ret == -1)
        {
            printf(&quot;exit unknown\n&quot;);
            return -1;
        }
        //在VCPU运行过程中遇到敏感指令会退出,KVM不能处理会交给用户态的Qemu处理,此时ioctl系统调用就会返回,并且将一些信息保存到kvm_run结构中
        //这样用户程序就可以分析虚拟机退出的原因,然后根据原因进行处理。
        switch (run-&gt;exit_reason){
            //本例中,精简内核执行hlt会产生KVM_EXIT_HLT推出事件。
            case KVM_EXIT_HLT:
                puts(&quot;KVM_EXIT_HLT&quot;);
                return 0;
            case KVM_EXIT_IO:
                putchar(*(((char *)run)+run-&gt;io.data_offset));
                break;
            case KVM_EXIT_FAIL_ENTRY:
                puts(&quot;entry error&quot;);
                return -1;
            default:
                puts(&quot;other error&quot;);
                printf(&quot;exit_reason: %dln&quot;, run-&gt;exit_reason);
                return -1;
        }
    }
}

编译执行:

root@test-ubuntu-no-vgpu:~# gcc qemu.c -o light-qemu
root@test-ubuntu-no-vgpu:~# ./light-qemu
SCU HELLO
KVM_EXIT_HLT

总结

由上述代码可以看出,KVM通过一组ioctl系统调用,向用户空间暴露了接口,这些接口用于创建虚拟机、设置内存、创建VCPU,调度运行等。

参考文献

  • QEMU/KVM源码解析与应用-李强
正文完
 
landery
版权声明:本站原创文章,由 landery 2023-04-18发表,共计3435字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)