热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用KVMAPI实现EmulatorDemo

这边文章来描述如何用KVMAPI来写一个Virtualizer的democode,也就是相当与Qemu,用来做设备模拟。此文是帮助想了解KVM原理已经Qemu原理的人orJustfor

这边文章来描述如何用KVM API来写一个Virtualizer的demo code, 也就是相当与Qemu,用来做设备模拟。 此文是帮助想了解KVM原理已经Qemu原理的人 or Just for fun.

完整的Code在这里: https://github.com/soulxu/kvmsample

这个code其实是很久以前写的,以前在team内部分享过,用来帮助大家理解kvm工作原理。现在既然要开始写code了,就用这个先来个开端。

当然我不可能写一个完整的Qemu,只是写出Qemu中最基本的那些code。这个虚拟机只有一个VCPU和512000000字节内存(其实富裕了) 可以进行一些I/O,当然这些I/O的结果只能导致一些print,没有实际模拟任何设备。所以所能执行的Guest也很简单。

首先来看看Guest有多简单。

1
2
3
4
5
6
7
8
9
.globl _start
.code16
_start:
xorw %ax, %ax

loop1:
out %ax, $0x10
inc %ax
jmp loop1

不熟悉汇编也没关系,这code很简单,基本也能猜到干啥了。对,Guest只是基于at&t汇编写的一个在8086模式下的死循环,不停的向端口0x10写东西。目标就是让这个Guest跑起来了。

我们的目标就是让这个Guest能执行起来。下面开始看我们虚拟机的code了。

我们先来看看main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main(int argc, char **argv) {
int ret = 0;
struct kvm *kvm = kvm_init();

if (kvm == NULL) {
fprintf(stderr, "kvm init fauilt\n");
return -1;
}

if (kvm_create_vm(kvm, RAM_SIZE) < 0) {
fprintf(stderr, "create vm fault\n");
return -1;
}

load_binary(kvm);

// only support one vcpu now
kvm->vcpu_number = 1;
kvm->vcpus = kvm_init_vcpu(kvm, 0, kvm_cpu_thread);

kvm_run_vm(kvm);

kvm_clean_vm(kvm);
kvm_clean_vcpu(kvm->vcpus);
kvm_clean(kvm);
}

这里正是第一个kvm基本原理: 一个虚拟机就是一个进程,我们的虚拟机从这个main函数开始

让我先来看看kvm_init。这里很简单,就是打开了/dev/kvm设备,这是kvm的入口,对kvm的所有操作都是通过对文件描述符上执行ioctl来完成。 这里很简单,就是打开kvm设备,然后将文件描述符返回到我自己创建的一个结构体当中。

然后我们就开始创建一个vm,然后为其分配内存。

1
kvm->vm_fd = ioctl(kvm->dev_fd, KVM_CREATE_VM, 0);

创建一个虚拟机很简单,在kvm设备上执行这么一个ioctl即可,然后会得到新建的vm的文件描述,用来操作这个vm。

然后我们来分配内存,这里最重要的是struct kvm_userspace_memory_region这个数据结构。

1
2
3
4
5
6
7
8
/* for KVM_SET_USER_MEMORY_REGION */
struct kvm_userspace_memory_region {
__u32 slot;
__u32 flags;
__u64 guest_phys_addr;
__u64 memory_size; /* bytes */
__u64 userspace_addr; /* start of the userspace allocated memory */
};

memory_size是guest的内存的大小。userspace_addr是你为其份分配的内存的起始地址,而guest_phys_addr则是这段内存映射到guest的什么物理内存地址。

这里用mmap创建了一段匿名映射,并将地址置入userspace_addr。随后来告诉我们的vm这些信息:

1
ioctl(kvm->vm_fd, KVM_SET_USER_MEMORY_REGION, &(kvm->mem));

这里是来操作我们的vm了,不是kvm设备文件了。

我们有了内存了,现在可以把我们的guest code加载的进来了,这个实现很简单就是打开编译后的二进制文件将其写入我们分配的内存空间当中。 这里所要注意的就是如何编译guest code,这里我们编译出来的是flat binary,不需要什么elf的封装。

有了内存,下一步就是vcpu了,创建vcpu是在kvm_init_vcpu函数里。 这里最重要的操作只有这个:

1
2
3
vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
...
vcpu->kvm_run = mmap(NULL, vcpu->kvm_run_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu->vcpu_fd, 0);

struct kvm_run是保存vcpu状态的一个数据结构,稍后我们可以看到我们可以从这里得到当陷入后具体陷入原因。

有了内存和vcpu就可以运行了:

1
pthread_create(&(kvm->vcpus->vcpu_thread), (const pthread_attr_t *)NULL, kvm->vcpus[i].vcpu_thread_func, kvm)

这里是另一个kvm基本概念了,一个vcpu就是一个线程。这里让我们为vcpu创建一个线程。

最终我们到了最关键的部分了,就是这个vcpu线程。其实他就是一个循环。 当循环开始的时候,我们让他执行guest code:

1
ret = ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0)

当执行这条语句后,guest code就开始执行了,这个函数就阻塞在这里了。直到something happened而且需要由hypervisor进行处理的时候这个函数才会返回。 比如说I/O发生了,这个函数就会返回了,这里我们就需要通过struct kvm_run中得到具体的陷入原因。我们的guest只是做一些I/O port的操作,所以可以看到 当退出原因是KVM_EXIT_IO时,我将guest的所写入的数据print出来。

到这里这就是这个virtualizer的全部了. 如果你想体验一下,只需要执行make。

1
2
3
4
5
:~/code/kvmsample$ make
cc -c -o main.o main.c
gcc main.c -o kvmsample -lpthread
as -32 test.S -o test.o
ld -m elf_i386 --oformat binary -N -e _start -Ttext 0x10000 -o test.bin test.o

然后执行kvmsample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./kvmsample
read size: 712288
KVM start run
KVM_EXIT_IO
out port: 16, data: 0
KVM start run
KVM_EXIT_IO
out port: 16, data: 1
KVM start run
KVM_EXIT_IO
out port: 16, data: 2
KVM start run
KVM_EXIT_IO
out port: 16, data: 3
....

其实qemu里面的code也就是这样,你也可以在其中找到这个loop,只不过它被qemu内部的各种设备框架所隐藏起来了。


推荐阅读
  • 线程漫谈——线程基础
    本系列意在记录Windwos线程的相关知识点,包括线程基础、线程调度、线程同步、TLS、线程池等。进程与线程理解线程是至关重要的,每个进程至少有一个线程,进程是线程的容器,线程才是真正的执行体,线程必 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • centos6.8 下nginx1.10 安装 ... [详细]
  • Howtobuilda./configure&&make&&makeins ... [详细]
  • 第三周读书笔记《程序员的自我修养》  计划对这本书是精读,这周读了3,4章。第三章目标文件里有什么  首先介绍了目标文件的格式,Windows下是pe-coff, ... [详细]
  • Word2vec,Fasttext,Glove,Elmo,Bert,Flairpre-trainWordEmbedding源码数据Github网址:词向量预训练实现Githubf ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • Gitlab接入公司内部单点登录的安装和配置教程
    本文介绍了如何将公司内部的Gitlab系统接入单点登录服务,并提供了安装和配置的详细教程。通过使用oauth2协议,将原有的各子系统的独立登录统一迁移至单点登录。文章包括Gitlab的安装环境、版本号、编辑配置文件的步骤,并解决了在迁移过程中可能遇到的问题。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • VS用c语言连接mysql,c语言连接mysql完整演示
    #include#includeintmain(){MYSQL*conn;创建一个指向mysql数据类型的指针connmysql_init(NULL);mysql的初始化if(!c ... [详细]
  • 32位ubuntu编译android studio,32位Ubuntu编译Android 4.0.4问题
    问题一:在32位Ubuntu12.04上编译Android4.0.4源码时,出现了关于emulator的错误,关键是其Makefile里的 ... [详细]
  • linux创建文件软连接和硬链接详解
    前言 ... [详细]
  • 字符设备驱动leds
    内核版本:4.12.9编译器:arm-linux-gcc-4.4.3本驱动基于jz2440v2开发板,实现3个led设备的驱动程序。代码如下:1#include ... [详细]
author-avatar
王欣纶淑玲
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有