qemu源码架构
前言:本文主要概括了QEMU的代码结构,特别从代码翻译的角度分析了QEMU是如何将客户机代码翻译成TCG代码和主机代码并且最终执行的过程。并且在最后描述了QEMU和KVM之间联系的纽带。
申明:本文前面部分从qemu detailed study第七章翻译而来。
1.代码结构
如我们所知,QEMU是一个模拟器,它能够动态模拟特定架构的CPU指令,如X86,PPC,ARM等等。QEMU模拟的架构叫目标架构,运行 QEMU的系统架构叫主机架构,QEMU中有一个模块叫做微型代码生成器(TCG),它用来将目标代码翻译成主机代码。如下图所示。
我们也可以将运行在虚拟cpu上的代码叫做客户机代码,QEMU的主要功能就是不断提取客户机代码并且转化成主机指定架构的代码。整个翻译任务分为两个部分:第一个部分是将做目标代码(TB)转化成TCG中间代码,然后再将中间代码转化成主机代码。
QEMU的代码结构非常清晰但是内容非常复杂,这里先简单分析一下总体的结构
1. 开始执行:
主要比较重要的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.
2. 硬件模拟
所有的硬件设备都在/hw/ 目录下面,所有的设备都有独自的文件,包括总线,串口,网卡,鼠标等等。它们通过设备模块串在一起,在vl.c中的machine _init中初始化。这里就不讲每种设备是怎么实现的了。
3.目标机器
现在QEMU模拟的CPU架构有:Alpha, ARM, Cris, i386, M68K, PPC, Sparc, Mips, MicroBlaze, S390X and SH4.
我们在QEMU中使用./configure
可以配置运行的架构,这个脚本会自动读取本机真实机器的CPU架构,并且编译的时候就编译对应架构的代码。对于不同的QEMU做的事情都不同,所以不同架
构下的代码在不同的目录下面。/target-arch/目录就对应了相应架构的代码,如/target-i386/就对应了x86系列的代码部分。虽然
不同架构做法不同,但是都是为了实现将对应客户机CPU架构的TBs转化成TCG的中间代码。这个就是TCG的前半部分。
4.主机
这个部分就是使用TCG代码生成主机的代码,这部分代码在/tcg/里面,在这个目录里面也对应了不同的架构,分别在不同的子目录里面,如i386就在/tcg/i386中。整个生成主机代码的过程也可以教TCG的后半部分。
5.文件总结和补充:
/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,并且操作生成的代码块。
2. TCG - 动态翻译
QEMU在
0.9.1版本之前使用DynGen翻译c代码.当我们需要的时候TCG会动态的转变代码,这个想法的目的是用更多的时间去执行我们生成的代码。当新的代
码从TB中生成以后, 将会被保存到一个cache中,因为很多相同的TB会被反复的进行操作,所以这样类似于内存的cache,能够提高使用效率。而
cache的刷新使用LRU算法。
编译器在执行器会从源代码中产生目标代码,像GCC这种编译器,它为了产生像函数调用目标代码会产生一些特殊的汇编目标代码,他们能够让编译器需要知道在调用函数。需要什么,以及函数调用以后需要返回什么,这些特殊的汇编代码产生过程就叫做函数的Prologue和Epilogue,这里就叫前端和后段吧。我在其他文章中也分析过汇编调用函数的过程,至于汇编里面函数调用过程中寄存器是如何变化的,在本文中就不再描述了。
函数的后端会恢复前端的状态,主要做下面2点:
1. 恢复堆栈的指针,包括栈顶和基地址。
2. 修改cs和ip,程序回到之前的前端记录点。
TCG就如编译器一样可以产生目标代码,代码会保存在缓冲区中,当进入前端和后端的时候就会将TCG生成的缓冲代码插入到目标代码中。
接下来我们就来看下如何翻译代码的:
客户机代码
TCG中间代码
主机代码
3. TB链
在QEMU中,从代码cache到静态代码再回到代码cache,这个过程比较耗时,所以在QEMU中涉及了一个TB链将所有TB连在一起,可以让一个TB执行完以后直接跳到下一个TB,而不用每次都返回到静态代码部分。具体过程如下图:
4. QEMU的TCG代码分析
接下来来看看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是怎么联系的。
5. QEMU中的IOCTL
在QEMU-KVM中,用户空间的QEMU是通过IOCTL与内核空间的KVM模块进行通讯的。
1. 创建KVM
在/vl.c中通过kvm_init()将会创建各种KVM的结构体变量,并且通过IOCTL与已经初始化好的KVM模块进行通讯,创建虚拟机。然后创建VCPU,等等。
2. KVM_RUN
这个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
3.KVM_IRQ_LINE
这个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
qemu源码架构的更多相关文章
- qemu 源码调试
1:下载最新的QEMU源码 git clone https://github.com/qemu/qemu.git 2:对qemu进行编译 ./configure --prefix=/usr --lib ...
- 【NopCommerce源码架构学习-一】--初识高性能的开源商城系统cms
很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从中学习很多企业系统.软件开发的规范和一些新的技术.技巧,可以快速地提高我们 ...
- NopCommerce源码架构详解--初识高性能的开源商城系统cms
很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从中学习很多企业系统.软件开发的规范和一些新的技术.技巧,可以快速地提高我们 ...
- NopCommerce源码架构详解
NopCommerce源码架构详解--初识高性能的开源商城系统cms 很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从 ...
- Nop--NopCommerce源码架构详解专题目录
最近在研究外国优秀的ASP.NET mvc电子商务网站系统NopCommerce源码架构.这个系统无论是代码组织结构.思想及分层都值得我们学习.对于没有一定开发经验的人要完全搞懂这个源码还是有一定的难 ...
- NopCommerce源码架构
我们承接以下nop相关的业务,欢迎联系我们. 我们承接NopCommerce定制个性化开发: Nopcommerce二次开发 Nopcommerce主题开发 基于Nopcommerce的二次开发的电子 ...
- GDB调试qemu源码纪录
今天介绍下如何利用gdb调试qemu 1.首先获取qemu源码 获取地址:https://www.qemu.org/ 2.编译安装qemu 进入qemu目录 ./configure --enable- ...
- 一张思维导图辅助你深入了解 Vue | Vue-Router | Vuex 源码架构
1.前言 本文内容讲解的内容:一张思维导图辅助你深入了解 Vue | Vue-Router | Vuex 源码架构. 项目地址:https://github.com/biaochenxuying/vu ...
- 深入解析Underscore.js源码架构
Underscore.js是很有名的一个工具库,我也经常用他来处理对象,数组等,本文会深入解析Underscore源码架构,跟大家一起学习下他源码的亮点,然后模仿他写一个简单的架子来加深理解.他的源码 ...
随机推荐
- soap base64 调用
xsd__base64Binary data; data.__ptr = (unsigned char*) soap_malloc(_soapProxy,picLen); data ...
- Time, Clocks, and the Ordering of Events in a Distributed System
作者:Leslie Lamport(非常厉害的老头了) 在使用消息进行通信的分布式系统中,使用物理时钟对不同process进行时间同步与事件排序是非常困难的.一是因为不同process的时钟有差异,另 ...
- 如何在 Arch Linux 中安装 DNSCrypt 和 Unbound
DNSCrypt 是一个用于对 DNS 客户端和 DNS 解析器之间通信进行加密和验证的协议.它可以阻止 DNS 欺骗或中间人攻击. DNSCrypt 可用于大多数的操作系统,包括 Linux,Win ...
- hduacm 2888 ----二维rmq
http://acm.hdu.edu.cn/showproblem.php?pid=2888 模板题 直接用二维rmq 读入数据时比较坑爹 cin 会超时 #include <cstdio& ...
- 两个activity之间传递数据用startActivityForResult方法。
package com.example.testactivityresquest; import android.app.Activity; import android.content.Intent ...
- zabbix邮件报警
#!/usr/bin/python #coding:utf-8 import smtplib from email.mime.text import MIMEText import sys mail_ ...
- Android Phonebook编写联系人UI加载及联系人保存流程(六)
2014-01-07 11:18:08 将百度空间里的东西移过来. 1. Save contact 我们前面已经写了四篇文章,做了大量的铺垫,总算到了这一步,见证奇迹的时刻终于到了. 用户添加了所有需 ...
- 各种注释--------html,jsp
1. <!--到梦之都XHTML教程的链接--> <a href="http://www.dreamdu.com/xhtml/"> 学习XHTML < ...
- C语言输出规定长度的整数,不够位数前面补零
今天在做ACM题目的时候,遇到了这么一个问题,还真别说,这个以前真的没用过,当时就傻掉了,还好这个世界有Google,通过搜索了解了输出这种格式的C语言实现方法.但是没有找到C++的实现方法,希望知道 ...
- SharePoint 2013 Nintex Workflow 工作流帮助(九)
博客地址 http://blog.csdn.net/foxdave 前叙:假期结束了,知道为什么假期如此短暂吗?因为假期的每天只有半天.春节过完了,新的一年开始了,大家或许之前在新年的时候都许下了自己 ...