qemu运用动态翻译的技术将guest binary instructions动态翻译成host binary instructions,之后由host运行翻译后的指令。在qemu-0.9之前的版本都采用dyngen的动态翻译技术,而从qemu-0.10开始的版本开始采用TCG(Tiny Code Generator)的翻译技术。
TBs ( Translated Blocks )
Qemu将TB定义为碰到下一个jump指令或修改CPU state的指令之前的所有代码称为一个TB。
动态翻译的基本思想就是把每一条Target指令切分成为若干条微指令,每条微指令由一段简单的C代码来实现,运行时通过一个动态代码生成器把这些微指令组合成一个函数,最后执行这个函数,就相当于执行了一条Target指令
从QEMU-0.10.0开始,TCG成为QEMU新的翻译引擎.
TCG的全称为“Tiny Code Generator”, 主要负责分析、优化Target代码以及生成Host代码
Target指令 ----> TCG ----> Host指令
多数CPU指令不外乎以下几大类:数据传送、算术运算、逻辑运算、程序控制;
例如,数据传送包括:传送指令(如MOV)、堆栈操作(PUSH、POP)等
程序控制包括:函数调用(CALL)、转移指令(JMP)等;
基于此,TCG就把微指令按以上几大类定义(见tcg/i386/tcg-target.c)
TCG在生成目标指令的过程中是采用硬编码的,因此,要让TCG运行在不同的Host平台上,就必须为不同的平台编写微操作函数.
QEMU维护着一个称为 CPUState 的数据结构,这个结构包括了Target机CPU的所有寄存器,像EAX,EBP,ESP,CS,EIP,EFLAGS等。它总是代表着Target机的当前状态
用变量env来表示 CPUState 结构,则QEMU每次解析Target指令时,总是以 env.cs+env.eip 为开始地址的。
eg: typedef struct CPUX86State {...} in target-i386/cpu.h
TCG翻译流程
guest binary instructions -> TCG IR(intermediate representation) -> host binary instructions
TCG IR大致可以分为以下几类:
- Mov类操作: mov, movi, ...
- 逻辑操作: and, or, xor, shl, shr, ...
- 算术操作: add, sub, mul, div, ...
- 分支跳转操作: jmp, br, brcond
- 函数调用: call - 内存操作: ld, st
- QEMU的特殊操作: tb_exit, goto_tb, qemu_ld/qemu_st
每条TCG指令的具体用法,参见qemu源码tcg/readme。
在qemu源码中,target-ARCH/* 定义了如何将guest binary instructions 反汇编成 TCG IR,tcg/ARCH 定义了如何將 TCG IR 翻译成 host binary instructions。
Starting from QEMU 1.0, all emulated CPUs run in a single thread.
QEMU的main函数定义在/vl.c中,它也是执行的起点,这个函数的功能主要是建立一个虚拟的硬件环境.
所有的硬件设备都在/hw/ 目录下面,所有的设备都有独自的文件,包括总线,串口,网卡,鼠标等等。它们通过设备模块串在一起,在vl.c中的machine _init中初始化。
/target-arch/目录就对应了相应架构(Guest)的代码,如/target-i386/就对应了x86系列的代码部分。虽然不同架构做法不同,但是都是为了实现将对应客户机CPU架构的TBs转化成TCG的中间代码。这个就是TCG的前半部分.
使用TCG代码生成主机的代码在/tcg/里面,在这个目录里面也对应了不同的架构(Host),分别在不同的子目录里面,如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中,从代码cache到静态代码再回到代码cache,这个过程比较耗时,所以在QEMU中涉及了一个TB链将所有TB连在一起,可以让一个TB执行完以后直接跳到下一个TB,而不用每次都返回到静态代码部分。
cpu_exex() --> prologue --> TB1 --> ... --> TBn --> Epilogue --> cpu_exex()
转载于:https://blog.51cto.com/widder/1625249