Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1502120
  • 博文数量: 228
  • 博客积分: 1698
  • 博客等级: 上尉
  • 技术积分: 3241
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-24 21:49
个人简介

Linux

文章分类

全部博文(228)

文章存档

2017年(1)

2016年(43)

2015年(102)

2014年(44)

2013年(5)

2012年(30)

2011年(3)

分类: LINUX

2014-07-17 16:12:22

在qemu源码分析系列(一)简单介绍了下qemu相关的背景知识,本文将详细分析qemu的核心 -- 动态翻译器。

为了更容易理解动态翻译技术,我们暂时忽略掉qemu的其他模块,如用户交互模块,硬件模拟等模块,而是从数据结构的设计,数据结构之间的操作及其 应用等方面来进行详细地分析,重点关注动态翻译器和微操作库(micro-ops library)的原理,至于细节的东西可以放在以后去深入分析。

qemu利用了一种可移植的动态代码翻译器以快速地完成客户代码的仿真。qemu本身并不能识别它主机体系结构的指令集,作为替代,每一个客户机指 定一个c语言实现的微操作库以及一个客户机代码反汇编器和翻译器,用来将客户代码转换成微操作表,这些微操作可以被认为是一种虚拟机,尽管仅仅是对客户系 统模拟的一种优化而已。另外,这些操作本身包括寄存器转化,显示的条件代码更新代码,按位操作,整型和浮点型数学函数,内存加载和存储操作等。

2.1 翻译

2.1.1 基本块的翻译

图1描述了一小段带条件跳转x86代码以及其对应的微操作指令表示,每一条op_开头的微操作指令都将被拷贝到翻译缓冲区中,微操作指令中看起来像 参数的常量被称为折叠常量(const folding),在2.3.4接将会进行详细的解释,现在只要把它看成是qemu中定义的微操作指令的参数就可以了。微操作指令的参数中,tb是比较特 殊的,它指向与当前翻译缓冲区相关的元数据。另外,JNZ指令对应的微操作指令看起来比较怪异,控制流处理相关的细节将会在2.2.3节和2.2.4节详 细介绍。
image

2.1.2 同步的错误安全舱口

严格的按照“基本块”(basic block)的方式译码将会使得翻译缓冲区中包含一条或多条客户机指令,同时几乎每一条指令都可能产生同步的fault(比方说MMU fault)。为了便于理解,在翻译的时候我们重点考虑比较直接的客户机控制流(比方说,分支跳转:条件跳转和非条件跳转),毕竟,同步的错误 (fault)很少出现。对于同步错误(fault)需要一种恢复机制,用来处理翻译缓冲区中的fault。qemu使用longjmp()从翻译缓冲区 中跳到仿真器核心代码中。这里描述的意思是:当在执行翻译缓冲区的代码时,遇到了fault,就需要将执行路径切换到qemu的代码中,另外,当qemu 处理完fault后,会重新创建一个翻译缓冲区。

2.1 翻译缓冲区的高超技巧

qemu采用了一些不同寻常的技巧来提高性能:

  • 冗长的、额外开销大的主机操作(如仿真客户的MMU操作)并不会被直接放进翻译缓冲区,而是以可以从微操作中调用的帮助函数的形式存在
    • 一方面减少了对翻译缓冲区的占用,另一方面简化了翻译的过程(qemu中事先保存某些固定指令的翻译结果,当动态翻译器碰到这些指令时,直接跳过去执行就行了)
  • 翻译后的微操作指令序列的优化
  • 启发式的译码和翻译


2.2.1 通过函数调用来达到节省翻译缓冲区的技术

目前操作系统中的MMU操作指令自身非常复杂。对于MMU操作会产生大量的访存操作,通常的解决方法是增加一个cache,但这进一步增加代码的复 杂度。如果将每一次访存操作的这些复杂代码都放入翻译缓冲区中,其代价将会非常的昂贵。除了MMU操作指令,一些特殊的客户机指令也是非常复杂的,如 CPUID指令,即使在简化的qemu的实现中,一条x86的CPUID指令都需要75行C代码来实现。与ARPL指令相比,CPUID对于不同的寄存器 内容将会表现是完全不同的行为,因此,必须需要一大串冗长的微操作指令来实现CPUID的功能。

为了优化这种代价昂贵,需要冗长的微操作指令来实现的客户机指令,qemu采用能够实现类似函数调用功能的微操作指令的机制来实现,比如ldl_kernel,实现了内核模式的大量数据的读功能;helper_cpuid,这个微操作包含了CPUID指令的实现。



2.2.2 惰性赋值

每一条指令都会隐含性修改指令指针(EIP),并且每一条指令都会修改处理器条件码(比如零标志位,溢出标志位、进位标志位等),但实际的情况是, 只要指令按照正确的顺序执行,其实客户代码很少去关心这些状态的变化。对于这些条件码除了条件跳转状态位外,我们很少去关注其他的状态位。图1中一段小小 的客户代码都会引起指令指针的修改和条件码修改的大量操作:

  • ADDL操作会修改条件码的零和进位/借位标志位
  • SUBL操作会修改条件码的零和进位/借位标志位
  • ADDL和SUBL都会修改EIP,使其指向下一条指令

由于qemu是按照“基本代码块”为单位进行翻译的,所以只有在整个“基本代码块”翻译完成或显示的读取EIP的时候才会对EIP进行更新,这就避 免了EIP的频繁更新的问题。在实际情况下,ADDL和SUBL都不会去读指令指针EIP,因此,可以优化掉翻译后的微操作指令中对EIP更新的微操作, 具体描述如图2所示。图2中仅仅是在每个“基本代码块”的最后通过op_jmp_im微操作来进行EIP的更新,即每个“基本代码块”只做一次更新操作。

image
转载地址:http://blog.csdn.net/ustc_dylan/article/details/6807731


阅读(770) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~