• 使用KVM的API编写一个简易的AArch64虚拟机


    参考资料:

    Linux虚拟化KVM-Qemu分析(一)

    Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化

    Linux虚拟化KVM-Qemu分析(三)之KVM源码(1)

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

    Linux虚拟化KVM-Qemu分析(五)之内存虚拟化

    Linux虚拟化KVM-Qemu分析(六)之中断虚拟化

    KVM虚拟化基本原理介绍(以ARM64架构为例)

    作者:彭东林

    邮箱:pengdonglin137@163.com

     

    背景

      最近在自学基于AArch64的Qemu/KVM技术,俗话说万事开头难,所以最好先从"hello world"入手。下面会用Qemu在x86上模拟一个AArch64的Host,这个host是从EL2开始运行Host Linux的,由于不支持VHE,所以Host Linux在EL2上完成一些初始化后最终会运行在EL1上。编译内核时使用AArch64的默认内核配置就可以支持KVM,Host跑起来后可以看到/dev/kvm节点,表示已经支持KVM了。然后再在这个Host上运行我们编写的简易版本的虚拟机。可以参考前一篇基于ARM64的Qemu/KVM学习环境搭建

      相关的代码已经上传到github上了:https://github.com/pengdonglin137/kvm_aarch64_simple_vm_demo/archive/version-1.0.tar.gz

    正文

    一、Qemu/KVM架构图

     二、Qemu/KVM/Guest之间的切换

     

     三、代码实现

    下面实现的简易虚拟机内存布局如下:

    RAM:           0x100000 ~ 0x101000

    UART_OUT:   0x8000

    UART_IN:       0x8004

    EXIT:              0x10000

    1、虚拟机代码

    simple_virt.c

      1 #include <sys/types.h>
      2 #include <sys/stat.h>
      3 #include <fcntl.h>
      4 #include <stdlib.h>
      5 #include <stdio.h>
      6 #include <string.h>
      7 #include <assert.h>
      8 #include <fcntl.h>
      9 #include <unistd.h>
     10 #include <sys/ioctl.h>
     11 #include <sys/mman.h>
     12 #include <linux/stddef.h>
     13 #include <linux/kvm.h>
     14 #include <strings.h>
     15 
     16 #include "register.h"
     17 
     18 #define KVM_DEV        "/dev/kvm"
     19 #define GUEST_BIN    "./guest.bin"
     20 #define AARCH64_CORE_REG(x)        (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x))
     21 
     22 int main(int argc, const char *argv[])
     23 {
     24     int kvm_fd;
     25     int vm_fd;
     26     int vcpu_fd;
     27     int guest_fd;
     28     int ret;
     29     int mmap_size;
     30 
     31     struct kvm_userspace_memory_region mem;
     32     struct kvm_run *kvm_run;
     33     struct kvm_one_reg reg;
     34     struct kvm_vcpu_init init;
     35     void *userspace_addr;
     36     __u64 guest_entry = 0x100000;
     37 
     38     // 打开kvm模块
     39     kvm_fd = open(KVM_DEV, O_RDWR);
     40     assert(kvm_fd > 0);
     41 
     42     // 创建一个虚拟机
     43     vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0);
     44     assert(vm_fd > 0);
     45 
     46     // 创建一个VCPU
     47     vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0);
     48     assert(vcpu_fd > 0);
     49 
     50     // 获取共享数据空间的大小
     51     mmap_size = ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, NULL);
     52     assert(mmap_size > 0);
     53 
     54     // 将共享数据空间映射到用户空间
     55     kvm_run = (struct kvm_run *)mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu_fd, 0);
     56     assert(kvm_run >= 0);
     57 
     58     // 打开客户机镜像
     59     guest_fd = open(GUEST_BIN, O_RDONLY);
     60     assert(guest_fd > 0);
     61 
     62     // 分配一段匿名共享内存,下面会将这段共享内存映射到客户机中,作为客户机看到的物理地址
     63     userspace_addr = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
     64         MAP_SHARED|MAP_ANONYMOUS, -1, 0);
     65     assert(userspace_addr > 0);
     66 
     67     // 将客户机镜像装载到共享内存中
     68     ret = read(guest_fd, userspace_addr, 0x1000);
     69     assert(ret > 0);
     70 
     71     // 将上面分配的共享内存(HVA)到客户机的0x100000物理地址(GPA)的映射注册到KVM中
     72     // 
     73     // 当客户机使用GPA(IPA)访问这段内存时,会发生缺页异常,陷入EL2
     74     // EL2会在异常处理函数中根据截获的GPA查找上面提前注册的映射信息得到HVA
     75     // 然后根据HVA找到HPA,最后创建一个将GPA到HPA的映射,并将映射信息填写到
     76     // VTTBR_EL2指向的stage2页表中,这个跟intel架构下的EPT技术类似
     77     mem.slot = 0;
     78     mem.flags = 0;
     79     mem.guest_phys_addr = (__u64)0x100000;
     80     mem.userspace_addr = (__u64)userspace_addr;
     81     mem.memory_size = (__u64)0x1000;
     82     ret = ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &mem);
     83     assert(ret >= 0);
     84 
     85     // 设置cpu的初始信息,因为host使用qemu模拟的cortex-a57,所以这里要
     86     // 将target设置为KVM_ARM_TARGET_CORTEX_A57
     87     bzero(&init, sizeof(init));
     88     init.target = KVM_ARM_TARGET_CORTEX_A57;
     89     ret = ioctl(vcpu_fd, KVM_ARM_VCPU_INIT, &init);
     90     assert(ret >= 0);
     91 
     92     // 设置从host进入虚拟机后cpu第一条指令的地址,也就是上面的0x100000
     93     reg.id = AARCH64_CORE_REG(regs.pc);
     94     reg.addr = (__u64)&guest_entry;
     95     ret = ioctl(vcpu_fd, KVM_SET_ONE_REG, &reg);
     96     assert(ret >= 0);
     97 
     98     while(1) {
     99         // 启动虚拟机
    100         ret = ioctl(vcpu_fd, KVM_RUN, NULL);
    101         assert(ret >= 0);
    102 
    103         // 根据虚拟机退出的原因完成相应的操作
    104         switch (kvm_run->exit_reason) {
    105         case KVM_EXIT_MMIO:
    106             if (kvm_run->mmio.is_write && kvm_run->mmio.len == 1) {
    107                 if (kvm_run->mmio.phys_addr == OUT_PORT) {
    108                     // 输出guest写入到OUT_PORT中的信息
    109                     printf("%c", kvm_run->mmio.data[0]);
    110                 } else if (kvm_run->mmio.phys_addr == EXIT_REG){
    111                     // Guest退出
    112                     printf("Guest Exit!
    ");
    113                     close(kvm_fd);
    114                     close(guest_fd);
    115                     goto exit_virt;
    116                 }
    117             } else if (!kvm_run->mmio.is_write && kvm_run->mmio.len == 1) {
    118                 if (kvm_run->mmio.phys_addr == IN_PORT) {
    119                     // 客户机从IN_PORT发起读请求
    120                     kvm_run->mmio.data[0] = 'G';
    121                 }
    122             }
    123             break;
    124         default:
    125             printf("Unknow exit reason: %d
    ", kvm_run->exit_reason);
    126             goto exit_virt;
    127         }
    128     }
    129 
    130 exit_virt:
    131     return 0;
    132 }

    2、Guest实现

    引导程序 start.S:

     1 #include "register.h"
     2 
     3     .global main
     4     .global start
     5     .text
     6 start:
     7     ldr x0, =SP_REG
     8     mov sp, x0
     9 
    10     bl  main
    11 
    12     ldr x1, =EXIT_REG
    13     mov x0, #1
    14     strb w0, [x1]
    15     b .

    主程序 main.c:

     1 #include "register.h"
     2 
     3 void print(const char *buf)
     4 {
     5     while(buf && *buf)
     6         *(unsigned char *)OUT_PORT = *buf++;
     7 }
     8 
     9 char getchar(void)
    10 {
    11     return *(char *)IN_PORT;
    12 }
    13 
    14 int main(void)
    15 {
    16     char ch[2];
    17 
    18     print("Hello World! I am a Guest!
    ");
    19 
    20     ch[0] = getchar();
    21     ch[1] = '';
    22 
    23     print("Get From Host: ");
    24     print(ch);
    25 
    26     print("
    ");
    27 
    28     return 0;
    29 }

    3、链接脚本

    gcc.ld:

     1 OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
     2 OUTPUT_ARCH(aarch64)
     3 ENTRY(start)
     4 
     5 SECTIONS
     6 {
     7     . = 0x100000;
     8 
     9     .text :
    10     {
    11         *(.text*)
    12     }
    13 
    14     .rodata :
    15     {
    16         . = ALIGN(8);
    17         *(.rodata*)
    18     }
    19 
    20     .data :
    21     {
    22         . = ALIGN(8);
    23         *(.data*)
    24     }
    25 
    26     .bss :
    27     {
    28         . = ALIGN(8);
    29         *(.bss*)
    30         *(COMMON)
    31     }
    32 }

    四、测试运行

    1、编译

    pengdl@pengdl-dell:~/kvm_study/demo/simple_virt$ make
    aarch64-linux-gnu-gcc simple_virt.c -I./kernel_header/include -o simple_virt
    aarch64-elf-gcc -c -march=armv8-a -nostdinc -o start.o start.S
    aarch64-elf-gcc -c -march=armv8-a -nostdinc -o main.o main.c
    aarch64-elf-gcc -march=armv8-a -Tgcc.ld -Wl,-Map=guest.map -nostdlib -o guest start.o main.o
    aarch64-elf-objdump -D guest > guest.dump
    aarch64-elf-objcopy -O binary guest guest.bin
    cp ./guest.bin ./simple_virt ../../share/

    2、启动Host

    #!/bin/bash
    
    QEMU=/home/pengdl/work1/Qemu/qemu-5.1.0/build/bin/qemu-system-aarch64
    #QEMU=/home/pengdl/work/Qemu/qemu-4.1.0/build/bin/qemu-system-aarch64
    kernel_img=/home/pengdl/work1/Qemu/aarch64/linux-5.8/out/64/arch/arm64/boot/Image
    
    sudo $QEMU
        -M virt,gic-version=3,virtualization=on,type=virt 
        -cpu cortex-a57 -nographic -smp 8 -m 8800 
        -fsdev local,security_model=passthrough,id=fsdev0,path=/home/pengdl/kvm_study/share 
        -device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare 
        -drive if=none,file=./ubuntu_40G.ext4,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 
        -append "noinitrd root=/dev/vda rootfstype=ext4 rw" 
        -kernel ${kernel_img} 
        -nic tap 
        -nographic

    3、运行

    pengdl@ubuntu-arm64:~/share$ sudo ./simple_virt  
    Hello World! I am a Guest!
    Get From Host: G
    Guest Exit!
    pengdl@ubuntu-arm64:~/share$ 

    完。

  • 相关阅读:
    空余时间
    日期的获取
    checkbox的样式
    表格
    v-for的一些小demo
    进程在与Windows Process Activation Service通信时出现严重错误 w3wp.exe错误
    c# 一些DateTime.Now的常用语法
    wcf错误 无法激活服务,因为它不支持 ASP.NET 兼容性
    c# 未能加载文件或程序集 相关原因
    IIS配置使网站访问速度提升
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/14083316.html
Copyright © 2020-2023  润新知