首页 技术 正文
技术 2022年11月14日
0 收藏 351 点赞 3,978 浏览 5771 个字

参考资料:

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 #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*)
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/bashQEMU=/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/Imagesudo $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$

完。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,085
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,560
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,409
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,182
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,819
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,902