使用KVM的API编写一个简易的AArch64虚拟机
参考资料:
Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化
Linux虚拟化KVM-Qemu分析(三)之KVM源码(1)
Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)
作者:彭东林
邮箱: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、虚拟机代码
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, ®);
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、链接脚本
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$
完。
使用KVM的API编写一个简易的AArch64虚拟机的更多相关文章
- 基于OpenGL编写一个简易的2D渲染框架-06 编写一个粒子系统
在这篇文章中,我将详细说明如何编写一个简易的粒子系统. 粒子系统可以模拟许多效果,下图便是这次的粒子系统的显示效果.为了方便演示,就弄成了一个动图. 图中,同时显示了 7 种不同粒子效果,看上去效果挺 ...
- 使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包
使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包 前端监控,埋点,数据收集,性能监控 Beacon API https://caniuse.com/beacon 优点,请 ...
- C#编写一个简易的文件管理器
编写一个简易的文件管理器,通过本次实验,练习 TreeView.ListView 和SplitContainer 控件的使用,同时熟悉 C#文件系统的操作方法以及 File 类和 Directory类 ...
- 用Java语言编写一个简易画板
讲了三篇概博客的概念,今天,我们来一点实际的东西.我们来探讨一下如何用Java语言,编写一块简易的画图板. 一.需求分析 无论我们使用什么语言,去编写一个什么样的项目,我们的第一步,总是去分析这个项目 ...
- 基于OpenGL编写一个简易的2D渲染框架-01 创建窗口
最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的 ...
- python 正则的使用 —— 编写一个简易的计算器
在 Alex 的博客上看到的对正则这一章节作业是编写一个计算器,要求能计算出下面的算式. 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 + ...
- day-1 用python编写一个简易的FTP服务器
从某宝上购买了一份<Python神经网络深度学习>课程,按照视频教程,用python语言,写了一个简易的FTP服务端和客户端程序,以前也用C++写过聊天程序,编程思路差不多,但是pytho ...
- 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形
阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...
- Python Django 编写一个简易的后台管理工具4-添加admin模版
导入admin后台模版 可以在网上任意搜索模版,我这里也提供一个地址github 拷贝admin后台的html文件至项目的templates文件夹 创建static文件夹,将admin后台的js,im ...
随机推荐
- Cocos Creator 源码解读:引擎启动与主循环
前言 预备 不知道你有没有想过,假如把游戏世界比作一辆汽车,那么这辆"汽车"是如何启动,又是如何持续运转的呢? 如题,本文的内容主要为 Cocos Creator 引擎的启动流程和 ...
- 硬核卸载Vue(删除)
第一步 查找vue位置 打开 cmd 输入 where vue 第二步 进入文件 直接cv(复制粘贴) 随便打开个文件 第三步 删除vue 删除前缀vue的所有 进入node_modules 删除@v ...
- 想学 iOS 开发高阶一点的东西,从何开始?
前言 如果你正在学习 iOS, 或者正在从事IOS开发? 还是一个一个迷茫的待就业大学生,或是公司的到一个半老员工? 现在到了开发的一个阶段了,基本的东西很熟了,想着提高技术? 学习难一点的东西,不知 ...
- Java Spring Cloud服务间调用
A服务是用户服务,B服务某个需求需要用户信息,而B服务无法连接用户的数据库(分库),需要让A服务查询用户信息. 在B服务写一个接口去调用A服务的某个请求 /** * 访问A服务 */ @FeignCl ...
- 分享JDK解压版(ZIP)
目录 由于安装版本的jdk不太方便,所以我分享一下如何去获取解压版的jdk,jdk配置的话看这个文章 一.先下载exe版本的jdk安装程序: 二.使用7-ZIP解压工具 2.1 JDK8的解压目录 2 ...
- 1到n整数中1出现的次数
1到n整数中1出现的次数 题目描述 输入一个整数n, 求1~n这n个整数的十进制表示中1出现的次数. 例如, 输入12, 1~12这些整数中包含1的数字有1, 10, 11和12, 1一共出现了4次 ...
- ner处理数据的方式
ner处理数据的方式biodef load_data(filename): features = [] labels = [] f = open(filename, encoding='utf-8') ...
- 是什么让我节省了60%的编码时间?使用MBG
MyBatis Generator简介 业务需求不断变更,数据库表结构不断修改,是我们逃不出的宿命.工欲善其事,必先利其器,是时候祭出神器了:MyBatis Generator(简称:MBG),它是一 ...
- JAVA基础之接口与内部类
接口与内部类 目录 接口与内部类 1. Lambda表达式 1. 关于懒计算 2. Predicate接口 3. 关于方法引用 4. 关于构造器引用 5. 关于变量的作用域 2. 内部类 1. 局部内 ...
- redis-server文件启动cmd一闪而过
工作上需要在本地装redis,所以就帮别人排查了一个问题,就是redis服务双击了之后不能起来,就是一个黑色的cmd框一闪而过,正常的是这样的: 然而,我当时第一次接触windows上的redis服务 ...