虚拟化是一种资源管理技术,它将计算机的各种实体资源(CPU、内存、存储、网络等)予以抽象和转化出来,并提供分割、重新组合,以达到最大化利用物理资源的目的。
VMM(Virtual Machine Monitor),虚拟机监控器,也称为Hypervisor,VMM软件层实现了虚拟化功能。
软件虚拟化是指通过VMM层纯软件的环境来模拟执行客户机里的指令。其中QEMU可以将使用客户机指令集的二进制代码转换为宿主机指令集的二进制代码,然后交给实际的物理平台执行。
硬件虚拟化技术就是指计算机硬件本身提供能力让客户机指令独立执行,而不完全需要VMM截获并重定向指令。
x86架构为为客户机提供了受限的运行环境(non-root mode), VMM运行在root mode,拥有完整的硬件访问控制权限。Intel在其x86 CPU中加入硬件虚拟化的支持——Intel Virtualization Technology,简称Intel VT。
半虚拟化是在软件虚拟化的基础上,修改客户机操作系统,让其配合VMM,可以提升性能,简化VMM的复杂度,同时不依赖硬件虚拟化,跨平台支持较好。典型的半虚拟化技术就是virtio。
全虚拟化与半虚拟化不同的是,全虚拟化不需要修改客户机操作系统,客户机不知道自身运行在虚拟环境中。VMM软件捕获处理客户机操作系统指令,发往硬件,相对于半虚拟化,极大增加了VMM复杂性。
在硬件虚拟化技术出现之前,软件实现的全虚拟化不如VMM和客户机操作系统协同运作的半虚拟化,硬件虚拟化技术的兴起,让由硬件虚拟化辅助的全虚拟化全面超过了半虚拟化。
从软件框架的角度上,根据虚拟化层是直接位于硬件之上还是在一个宿主操作系统之上,将虚拟化划分为Type1和Type2,
Type1类的Hypervisor直接运行在硬件之上,没有宿主机操作系统,Hypervisor直接控制硬件资源和客户机。典型框架为Xen、Vmware ESX。
Type2类的Hypervisor运行在一个宿主机操作系统之上(Vmware Workstation)或者系统里面(KVM),Hypervisor作为宿主机操作系统中的一个应用程序,客户机就是在宿主机操作系统上的一个进程。
KVM与Xen相比较,KVM利用了现有的Linux内核代码构建了Hypervisor,可以复用进程调度、内存管理等代码。
kvm原理如下图所示。Guest作为客户机运行应用程序,KVM则作为宿主机Host的内核模块,KVM用户态为QEMU。KVM 负责模拟虚拟机的CPU运行,内存管理,设备管理等;QEMU则模拟虚拟机的IO设备接口以及用户态控制接口。QEMU通过设备文件/dev/kvm进行IOCTL控制KVM模块的运行过程。
KVM是必须使用硬件虚拟化辅助技术(如Intel VT-x、AMD-V)的Hypervisor,在CPU运行效率方面有硬件支持,效率是比较高的;在有Intel EPT特性支持的平台上,内存虚拟化的效率也较高。IO方面有纯软件模拟IO设备、半虚拟化驱动(virtio)、设备直接分配(Intel VT-d)、单根I/O虚拟化(SR-IOV)。
QEMU/KVM提供了全虚拟化环境,可以让客户机不经过任何修改就能运行在KVM环境中。KVM在I/O虚拟化方面,传统的方式是使用QEMU纯软件的方式来模拟I/O设备,效率较低。可以在客户机中使用半虚拟化驱动(Paravirtualized Drivers,PV Drivers)来提高客户机的性能。
KVM虚拟机在配置磁盘的时候,可以指定IDE、SATA、Virtio、Virtio-SCSI几种磁盘,从虚拟的方式来看IDE、SATA是纯软件模拟的磁盘,Virtio、Virtio-SCSI是半虚拟化的磁盘,通过改造虚拟机系统的驱动来达到提升性能的目标。
Virtio驱动主要是绕过QEMU软件模拟这一层,让虚拟机的操作系统可以和内核的虚拟化层直接通信,提高通信效率。
磁盘IO从客户机到宿主机存储流程如下图所示。
kvm模块在内核源码中的路径为“\linux-4.14.191\arch\x86\kvm”,该内核模块的入口函数根据平台的不同,分别位于svm.c与vmx.c。SVM是AMD,VMX是intel。
复制
1 2 |
./arch/x86/kvm/svm.c:module_init(svm_init) ./arch/x86/kvm/vmx.c:module_init(vmx_init) |
目录“linux-4.14.191\virt\kvm”下,存放了架构性质的文件,独立于处理器架构,主要提供了公用的方法和数据结构。kvm_main.c中的kvm_init函数是kvm的入口函数。
复制
1 2 3 4 |
linux-4.14.191/virt/kvm/kvm_main.c(kvm_init) linux-4.14.191/virt/kvm/vfio.c linux-4.14.191/virt/kvm/irqchip.c linux-4.14.191/virt/kvm/eventfd.c |
KVM的初始化可以分为两步:
kvm_arch_init进行体系结构相关的初始化
cpuhp_setup_state_nocalls设置CPU热插拔时的回调函数
register_reboot_notifier 注册重启时回调函数
kmem_cache_create 创建用于分配kvm_vcpu结构体的slab缓存
注册设备
注册vfio操作集
代码如下:
复制
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align, struct module *module) { int r; int cpu; //判断硬件支持能力,初始化MSR,MMU寄存器组,初始化定时器结构 r = kvm_arch_init(opaque); if (r) goto out_fail; /* * kvm_arch_init makes sure there's at most one caller * for architectures that support multiple implementations, * like intel and amd on x86. * kvm_arch_init must be called before kvm_irqfd_init to avoid creating * conflicts in case kvm is already setup for another implementation. */ // 创建工作队列,用于处理vM的shutdown操作 r = kvm_irqfd_init(); if (r) goto out_irqfd; if (!zalloc_cpumask_var(&cpus_hardware_enabled, GFP_KERNEL)) { r = -ENOMEM; goto out_free_0; } r = kvm_arch_hardware_setup(); if (r < 0) goto out_free_0a; for_each_online_cpu(cpu) { smp_call_function_single(cpu, kvm_arch_check_processor_compat, &r, 1); if (r < 0) goto out_free_1; } //设置CPU热插拔时的回调函数 r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_STARTING, "kvm/cpu:starting", kvm_starting_cpu, kvm_dying_cpu); if (r) goto out_free_2; //注册重启时回调函数 register_reboot_notifier(&kvm_reboot_notifier); /* A kmem cache lets us meet the alignment requirements of fx_save. */ if (!vcpu_align) vcpu_align = __alignof__(struct kvm_vcpu); //创建用于分配kvm_vcpu结构体的slab缓存 kvm_vcpu_cache = kmem_cache_create("kvm_vcpu", vcpu_size, vcpu_align, SLAB_ACCOUNT, NULL); if (!kvm_vcpu_cache) { r = -ENOMEM; goto out_free_3; } // 创建用于分配kvm_async_pf的sab缓存 r = kvm_async_pf_init(); if (r) goto out_free; kvm_chardev_ops.owner = module; kvm_vm_fops.owner = module; kvm_vcpu_fops.owner = module; //注册设备,用于用户空间操作 r = misc_register(&kvm_dev); if (r) { pr_err("kvm: misc device register failed\n"); goto out_unreg; } // 注册suspend/Resume时的操作函数 register_syscore_ops(&kvm_syscore_ops); kvm_preempt_ops.sched_in = kvm_sched_in; kvm_preempt_ops.sched_out = kvm_sched_out; r = kvm_init_debug(); if (r) { pr_err("kvm: create debugfs files failed\n"); goto out_undebugfs; } //注册vfio操作集 r = kvm_vfio_ops_init(); WARN_ON(r); return 0; |
用户态程序打开字符设备文件/dev/kvm获得文件描述符fd,通过ioctl系统调用操作kvm内核模块,例如传入指令KVM_CREATE_KVM,即可创建一个VM虚拟机。
misc_register用于注册该字符设备驱动,该字符设备文件用于操作kvm内核模块。
虚拟机创建完成后,会注册第二个字符设备文件/dev/kvm-vm,该字符设备文件用于创建vcpu,设置内存区间,分配中断等。
创建vcpu后,会注册第三个字符设备文件/dev/kvm-vcpu,该字符设备文件用于创建vcpu,设置内存区间,分配中断等。
每个字符设备文件都定义了函数操作集,其关系如下图所示。
KVM结构体在KVM的系统架构中代表一个具体的虚拟机。当通过VM_CREATE_KVM指令字创建一个新的KVM虚拟机之后,就会创建一个新的KVM结构体对象。
复制
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
//代表一个具体的虚拟机 struct kvm { spinlock_t mmu_lock; struct mutex slots_lock; struct mm_struct *mm; /* userspace tied to this vm */ //KVM虚拟机所分配到的内存 slot,以数组形式存储这些slot的地址信息。 struct kvm_memslots __rcu *memslots[KVM_ADDRESS_SPACE_NUM]; //虚拟机中包含的vcpu结构体,一个虚拟CPU对应一个vCPU结构体。 struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; /* * created_vcpus is protected by kvm->lock, and is incremented * at the beginning of KVM_CREATE_VCPU. online_vcpus is only * incremented after storing the kvm_vcpu pointer in vcpus, * and is accessed atomically. */ atomic_t online_vcpus; int created_vcpus; int last_boosted_vcpu; struct list_head vm_list; struct mutex lock; //kvm虚拟机中的IO总线 struct kvm_io_bus __rcu *buses[KVM_NR_BUSES]; #ifdef CONFIG_HAVE_KVM_EVENTFD struct { spinlock_t lock; struct list_head items; struct list_head resampler_list; struct mutex resampler_lock; } irqfds; struct list_head ioeventfds; #endif //KVM虚拟机中的页表、MMU等运行时状态信息。 struct kvm_vm_stat stat; //KVM的软件arch方面所需要的一些参数 struct kvm_arch arch; refcount_t users_count; ... }; |
硬件虚拟化使用vCPU ( Virtual CPU)描述符来描述虚拟CPU。vCPU描述符类似于操作系统中的进程描述符(或进程控制块),其本质是一个结构体,
复制
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
struct kvm_vcpu { //指向此vcpu所属的虚拟机对应的kvm结构 struct kvm *kvm; #ifdef CONFIG_PREEMPT_NOTIFIERS struct preempt_notifier preempt_notifier; #endif int cpu; //vcpu id用于唯一标识该vcpu int vcpu_id; int srcu_idx; int mode; unsigned long requests; unsigned long guest_debug; int pre_pcpu; struct list_head blocked_vcpu_list; struct mutex mutex; //运行时参数,包含寄存器信息,内存信息,虚拟机状态等动态信息 struct kvm_run *run; int guest_xcr0_loaded; struct swait_queue_head wq; struct pid __rcu *pid; int sigset_active; sigset_t sigset; struct kvm_vcpu_stat stat; unsigned int halt_poll_ns; bool valid_wakeup; #ifdef CONFIG_HAS_IOMEM int mmio_needed; int mmio_read_completed; int mmio_is_write; int mmio_cur_fragment; int mmio_nr_fragments; struct kvm_mmio_fragment mmio_fragments[KVM_MAX_MMIO_FRAGMENTS]; #endif #ifdef CONFIG_KVM_ASYNC_PF struct { u32 queued; struct list_head queue; struct list_head done; spinlock_t lock; } async_pf; #endif #ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT /* * Cpu relax intercept or pause loop exit optimization * in_spin_loop: set when a vcpu does a pause loop exit * or cpu relax intercepted. * dy_eligible: indicates whether vcpu is eligible for directed yield. */ struct { bool in_spin_loop; bool dy_eligible; } spin_loop; #endif bool preempted; //存储有KVM 虚拟机的运行时参数,如定时器、中断、内存槽等方面的信息。 struct kvm_vcpu_arch arch; struct dentry *debugfs_dentry; }; |
本次有关虚拟化知识就介绍到这里了。
PS:前往公众号“知书码迹”,可以查看最新内容!关注即可免费领取面试&Linux技术书籍!