Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)

612次阅读  |  发布于4年以前

背景

说明:

  1. KVM版本:5.9.1

  2. QEMU版本:5.0.0

  3. 工具:Source Insight 3.5, Visio

  4. 文章同步在博客园:https://www.cnblogs.com/LoyenWang/

  5. 概述

先来两段前戏。

1.1 CPU工作原理

AI的世界,程序的执行不再冰冷,CPU对a.out说,hello啊,world已经ok啦,下来return吧!

既然要说CPU的虚拟化,那就先简要介绍一下CPU的工作原理:

  1. Control Unit:CPU的指挥中心,协调数据的移动;
  2. ALU:运算单元,执行CPU内部所有的计算;
  3. Register:寄存器和Cache,都算是CPU内部的存储单元,其中寄存器可用于存储需要被译码和执行的指令、数据、地址等;

在硬件支持虚拟化之前,Qemu纯软件虚拟化方案,是通过tcg(tiny code generator)的方式来进行指令翻译,翻译成Host处理器架构的指令来执行。硬件虚拟化技术,是让虚拟机能直接执行在Host CPU上,让Host CPU直接来执行虚拟机,结合CPU的实际工作原理,应该怎么来理解呢?来张图:

既然都讲CPU了,那就捎带介绍下ARMv8的寄存器吧:

1.通用寄存器:

2.特殊用途寄存器:

3.CPU的状态PSTATE

好了,ARMv8的介绍该打住了,否则要跑偏了。。。

1.2 guest模式

好了啦,前戏结束,开始直奔主题吧。

  1. 流程分析

不管你说啥,我上来就是一句中国共产党万岁,对不起,跑题了。我上来就是一张Qemu初始化流程图:

除了找到了qemu_init_vcpu的入口,这张图好像跟本文的vcpu的虚拟化关系不是很大,不管了,就算是给后续的Qemu分析打个广告吧。

2.1 vcpu的创建

2.1.1 qemu中vcpu创建

所以,接力棒甩到了KVM内核模块。

2.1.2 kvm中vcpu创建

来一张前文的图:

2.2 vcpu的执行

2.2.1 qemu中vcpu的执行

2.2.2 kvm中vcpu的执行

用户层通过KVM_RUN命令,将触发KVM模块中kvm_arch_vcpu_ioctl_run函数的执行:

  1. Sample Code

简要介绍一下:

  1. tiny_kernel.S,相当于Qemu中运行的Guest OS,完成的功能很简单,没错,就是Hello, world打印;
  2. tiny_qemu.c,相当于Qemu,用于加载Guest到vCPU上运行,最终通过kvm放到物理CPU上运行;

鲁迅在1921年的时候,说过这么一句话:Talk is cheap, show me the code

start:
/* Hello */
mov     $0x48, %al
outb    %al, $0xf1
mov     $0x65, %al
outb    %al, $0xf1
mov     $0x6c, %al
outb    %al, $0xf1
mov     $0x6c, %al
outb    %al, $0xf1
mov     $0x6f, %al
outb    %al, $0xf1
mov     $0x2c, %al
outb    %al, $0xf1

/* world */
mov     $0x77, %al
outb    %al, $0xf1
mov     $0x6f, %al
outb    %al, $0xf1
mov     $0x72, %al
outb    %al, $0xf1
mov     $0x6c, %al
outb    %al, $0xf1
mov     $0x64, %al
outb    %al, $0xf1

mov     $0x0a, %al
outb    %al, $0xf1

hlt
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kvm.h>
#include <sys/mman.h>

#define KVM_DEV     "/dev/kvm"
#define TINY_KERNEL_FILE    "./tiny_kernel.bin"
#define PAGE_SIZE  0x1000

int main(void)
{
    int kvm_fd;
    int vm_fd;
    int vcpu_fd;
    int tiny_kernel_fd;
    int ret;
    int mmap_size;

    struct kvm_sregs sregs;
    struct kvm_regs regs;
    struct kvm_userspace_memory_region mem;
    struct kvm_run *kvm_run;
    void *userspace_addr;

    /* open kvm device */
    kvm_fd = open(KVM_DEV, O_RDWR);
    assert(kvm_fd > 0);

    /* create VM */
    vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0);
    assert(vm_fd >= 0);

    /* create VCPU */
    vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0);
    assert(vcpu_fd >= 0);

    /* open tiny_kernel binary file */
    tiny_kernel_fd = open(TINY_KERNEL_FILE, O_RDONLY);
    assert(tiny_kernel_fd > 0);
    /* map 4K into memory */
    userspace_addr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    assert(userspace_addr > 0);
    /* read tiny_kernel binary into the memory */
    ret = read(tiny_kernel_fd, userspace_addr, PAGE_SIZE);
    assert(ret >= 0);

    /* set user memory region */
    mem.slot = 0;
    mem.flags = 0;
    mem.guest_phys_addr = 0;
    mem.memory_size = PAGE_SIZE;
    mem.userspace_addr = (unsigned long)userspace_addr;
    ret = ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &mem);
    assert(ret >= 0);

    /* get kvm_run */
    mmap_size = ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, NULL);
    assert(mmap_size >= 0);
    kvm_run = (struct kvm_run *)mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu_fd, 0);
    assert(kvm_run >= 0);

    /* set cpu registers */
    ret = ioctl(vcpu_fd, KVM_GET_SREGS, &sregs);
    assert(ret >= 0);
    sregs.cs.base = 0;
    sregs.cs.selector = 0;
    ret = ioctl(vcpu_fd, KVM_SET_SREGS, &sregs);
    memset(&regs, 0, sizeof(struct kvm_regs));
    regs.rip = 0;
    ret = ioctl(vcpu_fd, KVM_SET_REGS, &regs);
    assert(ret >= 0);

    /* vcpu run */
    while (1) {
        ret = ioctl(vcpu_fd, KVM_RUN, NULL);
        assert(ret >= 0);

        switch(kvm_run->exit_reason) {
            case KVM_EXIT_HLT:
                printf("----KVM EXIT HLT----\n");
                close(kvm_fd);
                close(tiny_kernel_fd);
                return 0;
            case KVM_EXIT_IO:
                putchar(*(((char *)kvm_run) + kvm_run->io.data_offset));
                break;
            default:
                printf("Unknow exit reason: %d\n", kvm_run->exit_reason);
                break;
        }
    }

    return 0;
}

为了表明我没有骗人,上一张在Ubuntu16.04的虚拟机上运行的结果图吧:

草草收工吧。

  1. 参考

ARMv8-A Architecture Overview``ARMv8 Techinology Preview``Arm Architecture Reference Manual, Armv8, for Armv8-A architecture profile``Virtual lockstep for fault tolerance and architectural vulnerability analysis

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8