TCG JIT 翻译引擎跨架构模拟的核心原理
TCG 是什么
TCG(Tiny Code Generator)是 QEMU 内置的 JIT(Just-In-Time)编译器。它的任务很纯粹:读取 Guest CPU 的指令序列,翻译成宿主 CPU 能直接执行的机器码。
没有 TCG 就没有 QEMU 的跨架构能力。当你在 x86 主机上模拟 ARM 时,ARM 指令不会被 x86 CPU 直接识别——TCG 是那个"翻译官"。
两级翻译架构
TCG 不是直接把 Guest 指令翻译成宿主指令。中间有一层抽象。
Guest 指令 → 前端翻译 → TCG IR → 后端代码生成 → 宿主机器码
(ARM/x86/etc) (中间表示) (x86/ARM/etc)
前端翻译器
每种 Guest 架构有自己的前端翻译器,负责把 Guest 指令解码成 TCG IR。QEMU 支持的架构决定了有多少套前端。目前有:x86、ARM、AArch64、MIPS、PPC、SPARC、RISC-V、LoongArch 等。
后端代码生成器
同样,每种宿主架构有自己的后端,把 TCG IR 编译成对应的宿主机器码。
TCG IR 是一种三地址码(SSA 形式),类似 LLVM IR 但更简单。所有前后端都面向这套中间格式,解耦了"Guest 架构"和"宿主架构"的任意组合。
翻译块与直接块链接
TCG 不是逐条翻译指令,而是按翻译块(Translation Block, TB) 批量翻译。一个 TB 的边界条件:
- 遇到分支/跳转指令
- 遇到系统调用
- 达到 1000 条指令上限(默认)
这样做的好处是:一次翻译多条指令,可以做跨指令优化。
直接块链接(Direct Block Chaining, DBC) 是另一个关键优化:当翻译完一个 TB 后,TCG 会在结尾追加一条跳转指令,直接跳到下一个翻译好的 TB。执行时完全不回 QEMU 主循环,速度最快。
TB_A → TB_B → TB_C → TB_D → ... 直接链接,无 QEMU 开销
如果 TB_B 还没翻译,就先回主循环翻译它,下次就直接跳了。
TCG 的多线程翻译
早期 QEMU 的 TCG 翻译是单线程的,一个 TB 一个 TB 顺序来。10.x 之后引入了 multi-threaded TCG:
thread=single:默认,单线程翻译thread=multi:多线程并行翻译不同 TB
多线程 TCG 在多核 CPU 上加速明显,特别是首次启动虚拟机时(需要翻译大量新 TB)。
启用多线程翻译:
qemu-system-x86_64 -accel tcg,thread=multi ...
TCG 性能:为什么比原生慢
TCG 模拟通常只能达到原生性能的 10%~30%,原因:
- 翻译开销:每条 Guest 指令都要经过解码 → IR → 宿主指令链
- 运行时检查:需要检查特权指令、内存访问有效性、页表翻译
- 内存模型模拟:Guest 的 MMU 和页表需要软件模拟
- 缓存不友好:TB 翻译产物常驻内存,挤占宿主 CPU 缓存
同架构时性能差别更悬殊:KVM 模式下 Guest 指令直接跑在物理核心上,性能 85%~95%。TCG 再怎么优化也追不上硬件加速。
TCG 常见调优
| 选项 | 说明 |
|---|---|
-accel tcg,thread=multi | 多线程翻译 |
-machine accel=tcg | 强制用 TCG(有 KVM 也不用) |
| 足够的内存 | TCG 翻译缓存随 Guest 运行持续增长 |
-bios /dev/null | 跳过 BIOS 加速首次启动 |
| 使用轻量 Guest OS | Alpine、OpenWrt 比 Windows 快得多 |
TCG 的未来
TCG 一直在持续优化。11.0 版本引入了 "nitro" 加速器(AWS Nitro Enclaves 原生支持),还有 WHPX 和 MSHV 加速器的改进。整体方向是:
- 更好的代码生成质量(接近真实编译器)
- 更激进的跨 TB 优化
- 与硬件加速引擎更紧密的协作
但根本限制是 TCG 无法绕开——它要模拟的是"另一套 CPU",本质上就需要翻译。要追求接近原生的性能,还是用 KVM/HVF/WHPX。