QEMU 与KVM概述
2022-01-26 本文已影响0人
川人1588
虚拟化简介
虚拟化思想
虚拟化的主要思想是,通过分层将底层的复杂、难用的资源虚拟抽象成简单、易用的资源,提供给上层使用。如:
- 计算机进程对计算资源进行抽象,每个进程都认为自己独占整个计算及系统资源。
- TCP/IP协议将网卡设备进行虚拟化,应用只需要关心地址和端口即可。
虚拟机简介
- 进程可以理解为一个虚拟机,有自己独立的地址空间和独立的CPU和寄存器;
- 模拟器是另一种形式的虚拟机,可以是一种硬件指令集(instruction set architecture,ISA)编译的程序运行在另一种目标ISA上,一般有两种方式实现:
- 二进制翻译
- 解释方式实现
- 高级语言虚拟机;
- 系统虚拟机
系统虚拟化的历史
虚拟化技术编年史.jpeg硬件虚拟化特性
- A new guest operating mode – the processor can switch into a guest mode, which has all the regular privilege levels of the normal operating modes, except that system software can selectively request that certain instructions, or certain register accesses, be trapped.
一个新的客户操作模式——处理器能够转换到客户机模式,它提供了常规操作模式的特权级别,不同之处在于系统软件可以选择性地要求特定的指令或者特定的寄存器访问陷入。 - Hardware state switch – when switching to guest mode and back, the hardware switches the control registers that affect processor operation modes, as well as the segment registers that are difficult to switch, and the instruction pointer so that a control transfer can take effect.
硬件状态转换——当转换到客户机模式以及从客户机模式返回时,硬件会更换影响处理器操作模式的控制寄存器、非常难以转换的段寄存器,以及指令指针,从而使得控制传输能够生效 - Exit reason reporting – when a switch from guest mode back to host mode occurs, the hardware reports the reason for the switch so that software can take the appropriate action.
退出原因报告——当从客户机模式转换到主机模式时,硬件报告转换的原因,从而使软件采取合适的动作
QEMU 与KVM架构
QEMU
- 完成用户程序模拟:将一个平台编译的二进制文件运行在另一个不同的平台,如一个arm指令集的二进制,通过QEMU的TCG引擎的处理之后,ARM指令被转换成TCG中间代码,让后在转换为目的平台代码。
- 系统虚拟化模拟 :模拟一个完整的系统虚拟机,支持X86,ARM,MIPS,PPC等,早期通过TCG完成各种硬件平台模拟。
KVM
- kvm本身为一个内核模块,导出了一系列的接口到用户空间;
- 最开始kvm只负责最核心的CPU和内存虚拟化部分,使用QEMU作为其用户态组件,负责完成大量外设的模拟。
QEMU与KVM架构
qemu 与kvm整体架构图cpu虚拟化
内存虚拟化
设备虚拟化
- QEMU模拟
- virtIO
- 设备直通
- SR-IOV(单根输入输出虚拟化):硬件虚拟化
中断虚拟化
- Intel 8259 支持单CPU
- I/O APIC:I/O advanced programmable interrupt controller
- LAPIC:local advanced programmable interrupt controller
KVM接口
- 全局ioctl接口
- 虚拟机相关ioctl接口
- vCPU相关ioctl接口
调用kvm接口示例
#include <stdio.h>
#include <stdlib.h>
#include <linux/kvm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#define err_exit(x) do{perror((x));return 1;}while(0)
int main(int argc, char *argv[]) {
int kvm, vmfd, vcpufd, ret;
/* 输出al+bl的值 */
const uint8_t code[] = {
0xba, 0xf8, 0x03, /* mov $0x3f8,%dx */
0x00, 0xd8, /* add %bl,%al */
0x04, '0', /* add $0x30,%al */
0xee, /* out %al,(%dx) */
0xb0, '\n', /* mov $0x0a,%al */
0xee, /* out %al,(%dx) */
0xf4, /* hlt */
};
uint8_t *mem;
struct kvm_sregs sregs;
size_t mmap_size;
struct kvm_run *run;
kvm = open("/dev/kvm", O_RDWR|O_CLOEXEC);
if (kvm==-1) {
err_exit("Open /dev/kvm failed!\n");
}
ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
if (ret==-1) {
err_exit("KVM_GET_API_VERSION");
} else if(ret!=12) {
err_exit("KVM_GET_API_VERSION not 12");
}
/* 检查是否存在KVM_CAP_USER_MEMORY extension */
ret = ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_USER_MEMORY);
if (ret==-1) {
err_exit("KVM_CHECK_EXTENSION");
}
if (!ret) {
err_exit("Required extension KVM_CAP_USER_MEM not available");
}
/* 创建虚拟机 */
vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
if (vmfd==-1) {
err_exit("KVM_CREATE_VM");
}
/* 申请4096字节内存 */
mem = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if (!mem) {
err_exit("allocation guest memory");
}
/* 将测试代码复制到这块内存 */
memcpy(mem, code, sizeof(code));
struct kvm_userspace_memory_region region= {
.slot = 0,
.guest_phys_addr= 0x1000, //虚拟机物理内存空间的位置
.memory_size = 0x1000, //物理空间的大小
.userspace_addr = (uint64_t)mem, //在宿主机上虚拟空间的地址
};
/* 通知VM内存区域,设置内存地址 */
ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion);
if (ret==-1) {
err_exit("KVM_SET_USER_MEMORY_REGION");
}
/* 创建VCPU */
vcpufd= ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
if (vcpufd==-1) {
err_exit("KVM_CREATE_VCPU");
}
/* 获取可映射的内存的大小 */
ret = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL); //获取kvm_run结构体大小
if (ret==-1) {
err_exit("KVM_GET_VCPU_MMAP_SIZE");
}
mmap_size = ret;
if (mmap_size<sizeof(*run)) {
err_exit("KVM_GET_VCPU_MMAP_SIZE unexpectedly small");
}
/* 将kvm_run结构体映射到用户空间 */
run = mmap(NULL, mmap_size, PROT_READ|PROT_WRITE, MAP_SHARED, vcpufd, 0);
if (!run) {
err_exit("mmap vcpu");
}
/* 设置cs寄存器 */
ret = ioctl(vcpufd, KVM_GET_SREGS, &sregs);
if (ret==-1) {
err_exit("KVM_GET_SREGS");
}
sregs.cs.base = 0;
sregs.cs.selector = 0;
ret = ioctl(vcpufd, KVM_SET_SREGS, &sregs);
if (ret==-1) {
err_exit("KVM_SET_SREGS");
}
struct kvm_regs regs = {
.rip = 0x1000,
.rax = 2,
.rbx = 2,
.rflags = 0x2,
};
ret = ioctl(vcpufd, KVM_SET_REGS, ®s);
if (ret==-1) {
err_exit("KVM_SET_REGS");
}
while(1) {
//vm entry, 进入non root 模式运行
ret = ioctl(vcpufd, KVM_RUN, NULL);
if (ret==-1) {
err_exit("KVM_RUN");
}
switch (run->exit_reason) {
case KVM_EXIT_HLT:
puts("KVM_EXIT_HLT");
return 0;
case KVM_EXIT_IO:
/* 因为IO退出虚机,vm exit陷入kvm,调用qemu处理IO请求,进入guest root模式 ring3*/
if (run->io.direction==KVM_EXIT_IO_OUT && run->io.size==1 && run->io.port==0x3f8 && run->io.count==1) {
putchar(*(((char *)run) + run->io.data_offset)); //处理虚拟机IO操作
} else {
fprintf(stderr, "unhandled KVM_EXIT_IO\n");
fprintf(stdout, "KVM_EXIT_IO size:%d port:0x%x count:%d\n", run->io.size, run->io.port, run->io.count);
return 1;
}
break;
case KVM_EXIT_FAIL_ENTRY:
fprintf(stderr, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason=0x%llx\n",
(unsigned long long)run->fail_entry.hardware_entry_failure_reason);
return 1;
case KVM_EXIT_INTERNAL_ERROR:
fprintf(stderr, "KVM_EXIT_INTERNAL_ERROR: suberror=0x%x\n", run->internal.suberror);
return 1;
default:
fprintf(stderr, "exit_reason=0x%x\n", run->exit_reason);
return 0;
}
}
}