前言:本文主要归纳了QEMU的代码结构,特别从代码翻译的角度分析了QEMU是如何将客户机代码翻译成TCG代码和主机代码而且最终执行的过程。而且在最后描述了QEMU和KVM之间联系的纽带。html
申明:本文前面部分从qemu detailed study第七章翻译而来。前端
如咱们所知,QEMU是一个模拟器,它可以动态模拟特定架构的CPU指令,如X86,PPC,ARM等等。QEMU模拟的架构叫目标架构,运行 QEMU的系统架构叫主机架构,QEMU中有一个模块叫作微型代码生成器(TCG),它用来将目标代码翻译成主机代码。以下图所示。算法
咱们也能够将运行在虚拟cpu上的代码叫作客户机代码,QEMU的主要功能就是不断提取客户机代码而且转化成主机指定架构的代码。整个翻译任务分为两个部分:第一个部分是将作目标代码(TB)转化成TCG中间代码,而后再将中间代码转化成主机代码。 后端
QEMU的代码结构很是清晰可是内容很是复杂,这里先简单分析一下整体的结构 架构
主要比较重要的c文件有:/vl.c,/cpus.c, /exec-all.c, /exec.c, /cpu-exec.c. 函数
QEMU的main函数定义在/vl.c中,它也是执行的起点,这个函数的功能主要是创建一个虚拟的硬件环境。它经过参数的解析,将初始化内存,须要的模拟的设备初始化,CPU参数,初始化KVM等等。接着程序就跳转到其余的执行分支文件如:/cpus.c, /exec-all.c, /exec.c, /cpu-exec.c. oop
全部的硬件设备都在/hw/ 目录下面,全部的设备都有独自的文件,包括总线,串口,网卡,鼠标等等。它们经过设备模块串在一块儿,在vl.c中的machine _init中初始化。这里就不讲每种设备是怎么实现的了。 ui
如今QEMU模拟的CPU架构有:Alpha, ARM, Cris, i386, M68K, PPC, Sparc, Mips, MicroBlaze, S390X and SH4.spa
咱们在QEMU中使用./configure 能够配置运行的架构,这个脚本会自动读取本机真实机器的CPU架构,而且编译的时候就编译对应架构的代码。对于不一样的QEMU作的事情都不一样,因此不一样架 构下的代码在不一样的目录下面。/target-arch/目录就对应了相应架构的代码,如/target-i386/就对应了x86系列的代码部分。虽然 不一样架构作法不一样,可是都是为了实现将对应客户机CPU架构的TBs转化成TCG的中间代码。这个就是TCG的前半部分。 .net
这个部分就是使用TCG代码生成主机的代码,这部分代码在/tcg/里面,在这个目录里面也对应了不一样的架构,分别在不一样的子目录里面,如i386就在/tcg/i386中。整个生成主机代码的过程也能够教TCG的后半部分。
/vl.c: 最主要的模拟循环,虚拟机机器环境初始化,和CPU的执行。
/target-arch/translate.c 将客户机代码转化成不一样架构的TCG操做码。
/tcg/tcg.c 主要的TCG代码。
/tcg/arch/tcg-target.c 将TCG代码转化生成主机代码
/cpu-exec.c 其中的cpu-exec()函数主要寻找下一个TB(翻译代码块),若是没找到就请求获得下一个TB,而且操做生成的代码块。
QEMU在 0.9.1版本以前使用DynGen翻译c代码.当咱们须要的时候TCG会动态的转变代码,这个想法的目的是用更多的时间去执行咱们生成的代码。当新的代 码从TB中生成之后, 将会被保存到一个cache中,由于不少相同的TB会被反复的进行操做,因此这样相似于内存的cache,可以提升使用效率。而 cache的刷新使用LRU算法。
编译器在执行器会从源代码中产生目标代码,像GCC这种编译器,它为了产生像函数调用目标代码会产生一些特殊的汇编目标代码,他们可以让编译器须要知道在调用函数。须要什么,以及函数调用之后须要返回什么,这些特殊的汇编代码产生过程就叫作函数的Prologue和Epilogue,这里就叫前端和后段吧。我在其余文章中也分析过汇编调用函数的过程,至于汇编里面函数调用过程当中寄存器是如何变化的,在本文中就再也不描述了。
函数的后端会恢复前端的状态,主要作下面2点:
1. 恢复堆栈的指针,包括栈顶和基地址。
2. 修改cs和ip,程序回到以前的前端记录点。
TCG就如编译器同样能够产生目标代码,代码会保存在缓冲区中,当进入前端和后端的时候就会将TCG生成的缓冲代码插入到目标代码中。
接下来咱们就来看下如何翻译代码的:
客户机代码
TCG中间代码
![]()
主机代码
在QEMU中,从代码cache到静态代码再回到代码cache,这个过程比较耗时,因此在QEMU中涉及了一个TB链将全部TB连在一块儿,可让一个TB执行完之后直接跳到下一个TB,而不用每次都返回到静态代码部分。具体过程以下图:
接下来来看看QEMU代码中中到底怎么来执行这个TCG的,看看它是如何生成主机代码的。
main_loop(...){/vl.c} :
函数main_loop 初始化qemu_main_loop_start()而后进入无限循环cpu_exec_all() , 这个是QEMU的一个主要循环,在里面会不断的判断一些条件,如虚拟机的关机断电之类的。
qemu_main_loop_start(...){/cpus.c} :
函数设置系统变量 qemu_system_ready = 1而且重启全部的线程而且等待一个条件变量。
cpu_exec_all(...){/cpus.c} :
它是cpu循环,QEMU可以启动256个cpu核,可是这些核将会分时运行,而后执行qemu_cpu_exec() 。
struct CPUState{/target-xyz/cpu.h} :
它是CPU状态结构体,关于cpu的各类状态,不一样架构下面还有不一样。
cpu_exec(...){/cpu-exec.c}:
这个函数是主要的执行循环,这里第一次翻译以前说道德TB,TB被初始化为(TranslationBlock *tb) ,而后不停的执行异常处理。其中嵌套了两个无限循环 find tb_find_fast() 和tcg_qemu_tb_exec().
cantb_find_fast()为客户机初始化查询下一个TB,而且生成主机代码。
tcg_qemu_tb_exec()执行生成的主机代码
struct TranslationBlock {/exec-all.h}:
结构体TranslationBlock包含下面的成员:PC, CS_BASE, Flags (代表TB), tc_ptr (指向这个TB翻译代码的指针), tb_next_offset[2], tb_jmp_offset[2] (接下去的Tb), *jmp_next[2], *jmp_first (以前的TB).
tb_find_fast(...){/cpu-exec.c} :
函数经过调用得到程序指针计数器,而后传到一个哈希函数从 tb_jmp_cache[] (一个哈希表)获得TB的因此,因此使用tb_jmp_cache能够找到下一个TB。若是没有找到下一个TB,则使用tb_find_slow。
tb_find_slow(...){/cpu-exec.c}:
这个是在快速查找失败之后试图去访问物理内存,寻找TB。
tb_gen_code(...){/exec.c}:
开始分配一个新的TB,TB的PC是刚刚从CPUstate里面经过using get_page_addr_code()找到的
phys_pc = get_page_addr_code(env, pc);
tb = tb_alloc(pc);
ph当调用cpu_gen_code() 之后,接着会调用tb_link_page(),它将增长一个新的TB,而且指向它的物理页表。
cpu_gen_code(...){translate-all.c}:
函数初始化真正的代码生成,在这个函数里面有下面的函数调用:
gen_intermediate_code(){/target-arch/translate.c}->gen_intermediate_code_internal(){/target-arch/translate.c }->disas_insn(){/target-arch/translate.c}
disas_insn(){/target-arch/translate.c}
函数disas_insn() 真正的实现将客户机代码翻译成TCG代码,它经过一长串的switch case,将不一样的指令作不一样的翻译,最后调用tcg_gen_code。
tcg_gen_code(...){/tcg/tcg.c}:
这个函数将TCG的代码转化成主机代码,这个就不细细说明了,和前面相似。
#define tcg_qemu_tb_exec(...){/tcg/tcg.g}:
经过上面的步骤,当TB生成之后就经过这个函数进行执行.
next_tb = tcg_qemu_tb_exec(tc_ptr) :
extern uint8_t code_gen_prologue[];
#define tcg_qemu_tb_exec(tb_ptr) ((long REGPARM(*)(void *)) code_gen_prologue)(tb_ptr)
经过上面的步骤咱们就解析了QEMU是如何将客户机代码翻译成主机代码的,了解了TCG的工做原理。接下来看看QEMU与KVM是怎么联系的。
在QEMU-KVM中,用户空间的QEMU是经过IOCTL与内核空间的KVM模块进行通信的。
在/vl.c中经过kvm_init()将会建立各类KVM的结构体变量,而且经过IOCTL与已经初始化好的KVM模块进行通信,建立虚拟机。而后建立VCPU,等等。
这个IOCTL是使用最频繁的,整个KVM运行就不停在执行这个IOCTL,当KVM须要QEMU处理一些指令和IO等等的时候就会退出经过这个IOCTL退回到QEMU进行处理,否则就会一直在KVM中执行。
它的初始化过程:
vl.c中调用machine->init初始化硬件设备接着调用pc_init_pci,而后再调用pc_init1。
接着经过下面的调用初始化KVM的主循环,以及CPU循环。在CPU循环的过程当中不断的执行KVM_RUN与KVM进行交互。
pc_init1->pc_cpus_init->pc_new_cpu->cpu_x86_init->qemu_init_vcpu->kvm_init_vcpu->ap_main_loop->kvm_main_loop_cpu->kvm_cpu_exec->kvm_run
这个IOCTL和KVM_RUN是不一样步的,它也是个频率很是高的调用,它就是通常中断设备的中断注入入口。当设备有中断就经过这个IOCTL最终 调用KVM里面的kvm_set_irq将中断注入到虚拟的中断控制器。在kvm中会进一步判断属于什么中断类型,而后在合适的时机写入vmcs。固然在 KVM_RUN中会不断的同步虚拟中断控制器,来获取须要注入的中断,这些中断包括QEMU和KVM自己的,并在从新进入客户机以前注入中断。
总结: 经过这篇文章可以大概的了解QEMU的代码结构,其中主要包括TCG翻译代码的过程以及QEMU和KVM的交互过程。
http://blog.chinaunix.net/uid-26941022-id-3510672.html