1. QEMU简介

QEMU是一套由Fabrice Bellard所编写的以GPL许可证分发源码的machine emulator。可以在不同的主机(X86,PowerPC,ARM,Sparc)上对不同的CPU(比如x86,PowerPC,ARM,Sparc)进行仿真。
Qemu支持全系统仿真,这种情况下,QEMU里可以跑一个完全无修改的操作系统。也支持linux的用户模式仿真,这种情况下,QEMU可以帮助为一个CPU编译的程序,跑在另一个CPU上。
QEMU本身可以跑在不同的宿主机上,比如windows,linux, Mac OSX。Host和guest的CPU可以是不同的。
QEMU的主要用途是在一个OS上跑另一个OS。此外,QEMU还可以用来debugging,因为虚拟机可以很方便地暂停,它的状态也可以被查看,保存,恢复执行。

2. QEMU实现原理

作为一个CPU仿真器(CPU emulator), Qemu需要解决一些经典而又困难的问题:
• 翻译指令缓存的管理(Management of the translated code cache)
• 寄存器分配(Register allocation)
• 条件转移指令的优化(Condition code optimizations)
• 代码块的连接(Direct block chaining)
• 内存管理(Memory management)
• 支持自修改指令(Self-modifying code support)
• 支持异常(Exception support)
• 硬件中断(Hardware interrupts)
• 用户模式仿真(User mode emulation)

2.1 可移植的动态翻译技术

qemu运用动态翻译的技术将guest binary instructions动态翻译成host binary instructions。之后由host运行翻译后的指令。动态翻译采用了Intermediate Rerepresentation的技术,使得dynamic tranlator做到与架构无关。如图所示:

Qemu的动态翻译在qemu-0.9之前的版本都采用dyngen的动态翻译技术,而从qemu-0.10开始的版本开始采用TCG(Tiny Code Generator)的翻译技术。
参考:QEmu TCG Enhancements for Speeding-up the Emulation of SIMD instructions.pdf
2.1.1 Dyngen
1. 把每个guest CPU的指令分割成更少更简单的指令,我们称之为micro operations,每个micro operation是通过一小块c代码实现的。
2. gcc,接收micro operations implemented in C,生成对应host cpu的object file.
3. dyngen运行时将object file作为输入,产生一个dynamic code generator, 这个dynamic code generator会在运行时生成连接几个micro operation的host function。
Dyngen动态翻译技术,如图所示:

qemu 模拟gpio中断 qemu cpu flag_缓存


2.1.2 TCG(Tiny Code Generator)

TCG将guest指令通过TCG前端转换成TCG ops,之后TCG后端,将TCG ops转换成host指令。

如果想要让QEMU运行在一个新的处理器上,我们需要关注后端。

如果想要让QEMU仿真一个新的处理器,我们需要关注前端。

TCG一开始是一个C编译器的后端,之后它被简化,并用在qemu上,TCG使得qemu不再依赖于特定版本的gcc。

下图描述了TCG动态翻译的流程:

qemu 模拟gpio中断 qemu cpu flag_物理地址_02

3. QEMU实现细节

3.1 翻译基本块和缓存(Translated Blocks and Translation Cache)

当QEMU第一次处理guest cpu的指令,它会一直翻译这些指令,直到下一个jump指令或一些更改static CPU state的指令(这些指令在翻译期间无法被推导出来)。这些指令组成一个Translated Block.
Translation Cache是一个16MByte的缓存,保存了最近使用的TBs,当缓存满了,里面的TBs就会被flush掉。

3.2 寄存器分配(Register allocation)

QEMU使用一个固定的寄存器分配策略。也就是说每个guest寄存器和host寄存器或内存地址,是存在一个固定的映射的。
对于大多数host来说,我们可以简单地把所有的guest寄存器都映射到内存,仅仅把少部分临时寄存器存到host寄存器中,对临时寄存器的分配,是在每个guest cpu的描述中被hard code的。
这样保持了简洁和可移植,未来的QEMU版本,可能会用一个动态寄存器分配策略,以减少一些不必要的移动。

3.3 条件转移指令优化(Condition code optimizations)

好的CPU conditional code仿真对性能是很重要的。QEMU使用lazy condition code evaluation:它不计算x86指令执行后的condition code(EFlags),只是把operand,result,the type of operation记录下来。知道这三种信息,我们就可以恢复所有的condition code。

3.4 基本快链接(Direct block chaining)

在一个Translate Block执行之后,QEMU使用仿真的Program Counter和其他CPU信息,找到下一个Translate Block。如果这个Tranlate Block还没有被翻译,就会执行翻译,否则直接跳到这个TB进行执行。
有时候,我们已经知道下一个Program Counter的信息,所以可以直接把这些Tranlate Block打包成一串,直接顺序执行(比如某些包含分支指令的基本块)。

3.5 内存管理(Memory management)

QEMU使用mmap()系统调用来仿真guest MMU。只要仿真的OS不使用host OS的预留区域,这个方法就是work的。
为了能够启动任意的OS,QEMU支持MMU软件仿真。在这种模式下,每次内存访问,虚拟的MMU会把虚拟地址转换为物理地址的机制。QEMU使用一个地址翻译缓存来加速地址转换的过程。
为了避免在每次MMU mapping变化的时候flush掉translated code,QEMU在translation cache中使用物理地址作为index。当MMU mapping变化时,TB链也会被重置,因为跳转目标的物理地址可能会改变。

3.6 用户模式仿真(User mode emulation)

在CPU层面,user mode emulation就是一个full system emulation的子集。不需要进行MMU仿真,因为QEMU假设用户内存映射是host os去处理。QEMU包括一个通用的linux system call converter来处理大小端问题。