您的位置:首页 > 其它

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

2020-12-03 23:17 866 查看

参考资料:

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开始运行Linux的,使用AArch64的默认内核配置就可以支持KVM,Host跑起来后在/dev/下看到kvm节点。然后再在这个Host上运行我们编写的简易版本的虚拟机。可以参考前一篇基于ARM64的Qemu/KVM学习环境搭建

 

  相关的代码已经上传到github上了:https://github.com/pengdonglin137/kvm_aarch64_simple_vm_demo

 

正文

 

一、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
ad8
#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!\n");
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\n", 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!\n");
19
20     ch[0] = getchar();
21     ch[1] = '\0';
22
23     print("Get From Host: ");
24     print(ch);
25
26     print("\n");
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*)
<
ad8
/span>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/pen
15a8
gdl/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$

 

完。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: