参考资料:

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/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虚拟机的更多相关文章

  1. 基于OpenGL编写一个简易的2D渲染框架-06 编写一个粒子系统

    在这篇文章中,我将详细说明如何编写一个简易的粒子系统. 粒子系统可以模拟许多效果,下图便是这次的粒子系统的显示效果.为了方便演示,就弄成了一个动图. 图中,同时显示了 7 种不同粒子效果,看上去效果挺 ...

  2. 使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包

    使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包 前端监控,埋点,数据收集,性能监控 Beacon API https://caniuse.com/beacon 优点,请 ...

  3. C#编写一个简易的文件管理器

    编写一个简易的文件管理器,通过本次实验,练习 TreeView.ListView 和SplitContainer 控件的使用,同时熟悉 C#文件系统的操作方法以及 File 类和 Directory类 ...

  4. 用Java语言编写一个简易画板

    讲了三篇概博客的概念,今天,我们来一点实际的东西.我们来探讨一下如何用Java语言,编写一块简易的画图板. 一.需求分析 无论我们使用什么语言,去编写一个什么样的项目,我们的第一步,总是去分析这个项目 ...

  5. 基于OpenGL编写一个简易的2D渲染框架-01 创建窗口

    最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的 ...

  6. python 正则的使用 —— 编写一个简易的计算器

    在 Alex 的博客上看到的对正则这一章节作业是编写一个计算器,要求能计算出下面的算式. 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 + ...

  7. day-1 用python编写一个简易的FTP服务器

    从某宝上购买了一份<Python神经网络深度学习>课程,按照视频教程,用python语言,写了一个简易的FTP服务端和客户端程序,以前也用C++写过聊天程序,编程思路差不多,但是pytho ...

  8. 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形

    阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...

  9. Python Django 编写一个简易的后台管理工具4-添加admin模版

    导入admin后台模版 可以在网上任意搜索模版,我这里也提供一个地址github 拷贝admin后台的html文件至项目的templates文件夹 创建static文件夹,将admin后台的js,im ...

随机推荐

  1. Linux编译内核 Ubuntu18.04 -2020.11.04

    Linux编译内核 Ubuntu18.04 -2020.11.04 关闭虚拟机并备份 首先关闭虚拟机,其次直接找到.vmdk所在目录,并压缩该目录实现备份 下载内核源码 Linux内核官网:https ...

  2. MySQL全面瓦解6:查询的基本操作

    概述 提到查询,就回到我们第四篇的SQL语言分类了,DQL(Data QueryLanguage),也就是数据查询语言,实际就是从数据库中获取数据的一种命令方式.我们给数据库发送一个查询语句的命令,数 ...

  3. 【SpringCloud】03.微服务的设计原则

    微服务的设计原则: 一.AKF拆分原则 业界对于可扩展的系统架构设计有一个朴素的理念:通过加机器就可以解决容量和可用性问题(如果一台不行就两台). Y轴(功能)--关注应用中功能划分,基于不同的业务拆 ...

  4. 初次使用flask

    以写的一个小的例子来记录第一次使用: from flask import Flask, render_template import json # 实例化,可视为固定格式 app = Flask(__ ...

  5. 面试题:你有没有搞混查询缓存和Buffer Pool

    一. 关注送书!<Netty实战> 文章公号号首发!连载中!关注微信公号回复:"抽奖" 可参加抽活动 首发地址:点击跳转阅读原文,有更好的阅读体验 使用推荐阅读,有更好 ...

  6. IL角度理解for 与foreach的区别——迭代器模式

    IL角度理解for 与foreach的区别--迭代器模式 目录 IL角度理解for 与foreach的区别--迭代器模式 1 最常用的设计模式 1.1 背景 1.2 摘要 2 遍历元素 3 删除元素 ...

  7. startup乱码解决方法

    1.用记事本方式打开:‪apache-tomcat-8.5.59\conf\logging.properties 2.使用快捷键(Ctrl+H)把UTF-8全部替换为:GBK,进行保存(Ctrl+s) ...

  8. ngx accept_mutex

    尝试获取锁,如果获取了锁,那么还要将当前监听端口全部注册到当前worker进程的epoll当中去  获取失败就需要确保此时ls-fd 没有被 epoll 监听 ngx_int_t ngx_tryloc ...

  9. linux 信号 ctrl + d z c fg bg 作用

    ctrl+c:前台进程终止 后台进程的终止: 方法一:通过jobs命令查看job号(假设为num),然后执行kill %num   $ kill %1 方法二:通过ps命令查看job的进程号(PID, ...

  10. pandas.DataFarme内置的绘图功能参数说明

    可视化是数据探索性分析及结果表达的一种非常重要的形式,因此打算写一个python绘图系列,本文是第一篇,先说一下pandas.DataFrame.plot()绘图功能. pandas.DataFram ...