1. QEMU 介绍
QEMU(Quick emulator) : QEMU is a generic and open source machine emulator and virtualizer。官方网站:https://www.qemu.org
QEMU可以独立完整模拟一台计算机。基于指令模拟,性能会很差。
2. KVM 介绍
Kernel Virtual Machine: KVM (for Kernel-based Virtual Machine) is a full virtualization solution for Linux on x86 hardware containing virtualization extensions (Intel VT or AMD-V). It consists of a loadable kernel module, kvm.ko, that provides the core virtualization infrastructure and a processor specific module, kvm-intel.ko or kvm-amd.ko。官方网站: https://www.linux-kvm.org。KVM 是辅助模块,无法独立模拟。搭载QEMU可以完成基于硬件辅助的虚拟化,性能佳。
3. QEMU-KVM 整体框图
qemu 是用户态进程,kvm是内核模块。qemu通过操作/dev/kvm来与kvm进行通信与控制. QEMU与KVM的系统框图如下:
4. KVM 初始化
KVM 包含两个内核模块 kvm.ko以及kvm-intel.ko。kvm-intel.ko依赖kvm.ko。kvm.ko 所有代码在virt/kvm中它没有模块初始化函数,使用默认的初始化函数。然后看kvm-intel.ko 在arch/x86/kvm中vmx.c 有模块初始化函数module_init(vmx_init)
static int __init vmx_init(void) { int r = kvm_init(&vmx_x86_ops, sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx), THIS_MODULE); if (r) return r;
#ifdef CONFIG_KEXEC rcu_assign_pointer(crash_vmclear_loaded_vmcss, crash_vmclear_local_loaded_vmcss); #endif
return 0; } |
函数调用关系如下:
-------
vmx_init
=> kvm_init
=> misc_register(&kvm_dev)
这个函数最主要的功能就是注册了一个杂项设备,其实就是一个字符设备/dev/kvm。这个设备的操作函数如下,
static struct file_operations kvm_chardev_ops = { .unlocked_ioctl = kvm_dev_ioctl, .compat_ioctl = kvm_dev_ioctl, .llseek = noop_llseek, };
static struct miscdevice kvm_dev = { KVM_MINOR, "kvm", &kvm_chardev_ops, }; |
5. QEMU 初始化以及创建虚拟机的过程
QEMU 建立需要与KVM协作来完成虚拟机的创建。QEMU与KVM 使用不同的数据结构来表示虚拟机
QEMU 使用PCMachineState来表示虚拟机
KVM 使用struct kvm 来表示虚拟机
初始化过程包含两部分:
KVM加速器初始化
主板以及CPU初始化
5.1 KVM加速器初始化
本节主要介绍qemu 怎样通过/dev/kvm 来与kvm进行交互创建虚拟机. qemu 的入口函数在vl.c中,main函数,里面有如下函数初始化加速器,configure_accelerator(current_machine,这里就是KVM。函数调用关系如下:
----------
configure_accelerator
=> accel_init_machine
=> acc->init_machine(ms)[kvm_init]
// accel/kvm/kvm-all.c: ac->init_machine = kvm_init;
kvm_init 在accel/kvm/kvm-all.c 中
static int kvm_init(MachineState *ms) ... KVMState *s; ... //打开/dev/kvm 设备 s->fd = qemu_open("/dev/kvm", O_RDWR); ... // 检查kvm模块的api version是否匹配 ret = kvm_ioctl(s, KVM_GET_API_VERSION, 0); ... // 建立虚拟机, 从这里可以看出,qemu 运行一次会建立一个虚拟机,下面会介绍这个函数 do { ret = kvm_ioctl(s, KVM_CREATE_VM, type); } while (ret == -EINTR); ... // 与CPU 架构相关的初始化 ret = kvm_arch_init(ms, s); ... // 注册memory_lister kvm_memory_listener_register(s, &s->memory_listener, &address_space_memory, 0);
// do other things |
kvm_init 主要任务建立虚拟机,并初始化后退出,返回到qemu的main函数中,继续初始其他的设备。下面主要介绍ret = kvm_ioctl(s, KVM_CREATE_VM, type); 建立虚拟机的过程。对应virt/kvm/kvm_main.c。就是调用ioctl系统调用,并传入参数KVM_CREATE_VM。
static long kvm_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) { long r = -EINVAL;
switch (ioctl) { case KVM_GET_API_VERSION: if (arg) goto out; r = KVM_API_VERSION; break; case KVM_CREATE_VM: r = kvm_dev_ioctl_create_vm(arg); break; case KVM_CHECK_EXTENSION: r = kvm_vm_ioctl_check_extension_generic(NULL, arg); break; case KVM_GET_VCPU_MMAP_SIZE: if (arg) goto out; r = PAGE_SIZE; /* struct kvm_run */ #ifdef CONFIG_X86 r += PAGE_SIZE; /* pio data page */ #endif #ifdef KVM_COALESCED_MMIO_PAGE_OFFSET r += PAGE_SIZE; /* coalesced mmio ring page */ #endif break; case KVM_TRACE_ENABLE: case KVM_TRACE_PAUSE: case KVM_TRACE_DISABLE: r = -EOPNOTSUPP; break; default: return kvm_arch_dev_ioctl(filp, ioctl, arg); } out: return r; } |
kvm_dev_ioctl_create_vm 函数的实现如下,主要功能是初始化struct kvm结构体中的各个字段,并且建立这个虚拟机的fd
static int kvm_dev_ioctl_create_vm(unsigned long type) { int r; struct kvm *kvm;
kvm = kvm_create_vm(type); // 分配struct kvm结构体并且初始化 if (IS_ERR(kvm)) return PTR_ERR(kvm); #ifdef KVM_COALESCED_MMIO_PAGE_OFFSET r = kvm_coalesced_mmio_init(kvm); if (r < 0) { kvm_put_kvm(kvm); return r; } #endif r = anon_inode_getfd("kvm-vm", &kvm_vm_fops, kvm, O_RDWR | O_CLOEXEC); //从进程的文件fd中返回一个fd,代表这个虚拟机 if (r < 0) kvm_put_kvm(kvm);
return r; } |
5.2 主板以及CPU初始化
也是在vl.c 中 machine_run_board_init(current_machine) 会完成主板以及CPU基本外设的虚拟化。
在QEMU中 MachineClass 结构体代表一个虚拟机,不同类型的虚拟机有不同的类。继承关系如下:
---------
MachineClass
=> PCMachineClass
=> xxx_PCMachineClass
在hw/i386/pc_q35.c中通过宏DEFINE_Q35_MACHINE 来定义不同的PCMachineClass,比如:
DEFINE_Q35_MACHINE(v2_11, "pc-q35-2.11", NULL, pc_q35_2_11_machine_options);
DEFINE_Q35_MACHINE(v2_10, "pc-q35-2.10", NULL, pc_q35_2_10_machine_options);
通过qemu-system -machine help可以查看支持的machine类型
[[yzg@localhost ~]$ qemu-system-x86_64 -machine help Supported machines are: ...... pc-0.15 Standard PC (i440FX + PIIX, 1996) pc-0.14 Standard PC (i440FX + PIIX, 1996) pc-0.13 Standard PC (i440FX + PIIX, 1996) pc-0.12 Standard PC (i440FX + PIIX, 1996) pc-0.11 Standard PC (i440FX + PIIX, 1996) pc-0.10 Standard PC (i440FX + PIIX, 1996) // 以上为老式主板,下面是q35较新主板 pc-q35-2.9 Standard PC (Q35 + ICH9, 2009) pc-q35-2.8 Standard PC (Q35 + ICH9, 2009) pc-q35-2.7 Standard PC (Q35 + ICH9, 2009) pc-q35-2.6 Standard PC (Q35 + ICH9, 2009) pc-q35-2.5 Standard PC (Q35 + ICH9, 2009) pc-q35-2.4 Standard PC (Q35 + ICH9, 2009) q35 Standard PC (Q35 + ICH9, 2009) (alias of pc-q35-2.11) isapc ISA-only PC none empty machine |
Q35 芯片组 框图如下:
machine_run_board_init 调用关系(以Q35主板为例子)如下:
-------------
machine_run_board_init
=> machine_class->init
=> pc_q35_init
=> pc_cpus_init
=> pc_memory_init
=> pc_basic_device_init
=> pc_vga_init
=> pc_pci_device_init
从函数名字就可以看出,完成了什么功能。这样就完成了整个机器的虚拟化过程。
参考文档:
https://www.binss.me/blog/qemu-note-of-Q35-machine/