展示自己、证明自己
分类: C/C++
2014-07-07 10:36:31
转载:http://blog.csdn.net/lux_veritas/article/details/9383643
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
虚拟机运行概览
首先直观的了解一下利用QEMU运行客户虚拟机的流程。
在命令行中运行QEMU的系统模式的可执行文件,参数声明虚拟CPU的个数,内存大小,指定已经安装好的硬盘镜像,启动QEMU虚拟机主窗口。启动命令格式举例:
qemu-system-x86_64 --enable-kvm -cpu host \ -smp cores=4,threads=2,sockets=4 \ -m 16384 -k en-us -hda /pps/guohongwei/vm_test/ubuntu.img -monitor stdio
客户系统运行之前,QEMU作为全系统模拟软件,需要为客户系统模拟出CPU、主存以及I/O设备,使客户系统就像运行在真实硬件之上,而不用对客户系统代码做修改。
如 概览部分所示,由用户为客户系统指定需要的虚拟CPU资源(包括CPU核心数,SOCKET数目,每核心的超线程数,是否开启NUMA等等),虚拟内存资 源,具体参数设置参见${QEMU}/qemu-options.hx。创建QEMU主线程,执行QEMU系统的初始化,在初始化的过程中针对每一个虚拟 CPU,单独创建一个posix线程。每当一个虚拟CPU线程被调度到物理CPU上执行时,该VCPU对应的一套完整的寄存器集合被加载到物理CPU上, 通过VM-LAUNCH或VM-RESUME指令切换到非根模式执行。直到该线程时间片到,或者其它中断引发虚拟机退出,VCPU退出到根模式,进行异常 处理。如下图所示,当用户运行QEMU的System Mode的可执行文件时,QEMU从${QEMU}/vl.c的main函数执行主线程。以下着重分析,客户系统启动之前,QEMU所做的初始化工作:
进入vl.c的main函数,首先有一个很长的for(;;)循环,用于分析处理通过命令行传进来的参数,进行相应系统的初始化设置。比如创建多少VCPU,是否开启NUMA,分配多少虚拟内存资源等等。
2.选择虚拟化方案:
configure_accelerator()函数,选择使用哪种虚拟化解决方案。accel_list[]数组声明了QEMU使用的系统模拟方案。“tcg”模式是不 使用任何硬件虚拟化辅助方式,采用基于二进制指令翻译的方式,将目标平台的指令代码通过一个叫做TCG的模块翻译为本机可以执行的指令。“xen”、 “kvm”分别为两种主流的开源虚拟化解决方案。本文主要针对kvm这种硬件辅助的虚拟化解决方案。
新版本的QEMU(1.4)中,cpu_exec_init_all()函数只负责注册主存与IO内存两个顶层的memory_region,并且注册memory_listener。
在完成了QEMU自身的初始化工作后,便开始了客户系统核心的初始化工作,主要是 QEMU根据命令行参数,为客户系统创建虚拟的CPU、内存、I/O资源。核心过程是machine->init(&args),对于 x86目标平台,实际调用的是pc_init1函数。下面着重分析该函数。
4.1 VCPU初始化pc_cpus_init()
void pc_cpus_init(const char *cpu_model) { int i; /* init CPUs */ if (cpu_model == NULL) { #ifdef TARGET_X86_64 cpu_model = "qemu64"; #else cpu_model = "qemu32"; #endif } for (i = 0; i < smp_cpus; i++) { if (!cpu_x86_init(cpu_model)) { fprintf(stderr, "Unable to find x86 CPU definition\n"); exit(1); } } }
4.2 pc_memory_init初始化主存空间
利用mmap系统调用,在QEMU主线程的虚拟地址空间中申明一段连续的大小的空间用 于客户机物理内存映射。在QEMU的内存管理结构中逐步添加subregion。此处添加了低于4g的memory_region,高于4g的 memory_region,BIOS的memory_region。并调用bochs_bios_init,初始化BIOS。
客户系统的执行流程
在创建了全部VCPU后,这些VCPU线程并没有被立即调度执行,直至vl.c的 main函数执行完全部初始化工作后,调用resume_all_vcpus(),将pcpu->stop和 pcpu->stopped 置为false,当VCPU线程再次被调度到物理CPU上执行时,VCPU正式开始工作。
由于pc_memory_init阶段已将BIOS初始化,创建了“bios.bin”文件到内存的映射,已处于非根模式下的VCPU到客户物理地址0xFFFF0(即“bios.bin”文件被映射到的地址的一个线性偏移)处,获取第一条指令,开始执行客户系统代码。
此时多个VCPU线程由宿主系统轮流调度执行,QEMU主线程处于循环中,用于接收来 自客户系统传回的I/O模拟请求。每一个VCPU线程,只要被调度的物理CPU上,便切到非根模式执行客户系统代码,产生需要退出的异常时(如EPT缺 页,处理I/O指令等),保存异常原因到VMCS,切换到根模式下,由KVM捕获该异常,查询VMCS异常号执行相应处理,完成后再切换回非根模式,如此 循环往复执行下去。