TCG 指令计数

TCG 长期以来支持 icount,可在执行过程中统计指令。 不要将它与周期精确模拟混淆:QEMU 不模拟指令在真实硬件上耗时多久, 这属于其他更详细、更慢的微架构模拟工具的工作。

此功能仅用于系统模拟,与多线程 TCG 不兼容。 它可使执行时间更贴近实际墙上时间,避免“慢速”设备在现代硬件上运行过快。 它也提供一定程度的确定性执行,是 QEMU 记录/重放支持的重要组成部分。

核心概念

icount 本质是已执行指令数,保存在定时器子系统的 TimersState 中。 利用该计数可计算 QEMU_CLOCK_VIRTUAL,表示系统从开始执行到现在的经过时间。 根据 icount 模式,每条指令可对应固定纳秒数, 也可随执行调整,使墙上时间与虚拟时间同步。

为计算已执行指令数,翻译器先分配指令执行预算, 上限取决于距离下一个定时器到期还有多久。 预算保存在 vCPU 的 icount_decr 字段中,与 qemu_cpu_kick() 处理机制共享。 每个翻译块开始时检查整个字段,需要时返回外层循环处理退出原因。

对 icount 而言,检查标志前先扣除该翻译块将执行的指令数。 如果预算因此变为负数,就退出主循环,重新生成指令数恰好使预算归零的翻译块。 这样,定时器会在退出主执行循环时准确到期。

处理 MMIO

定时器到期等已知事件可以提前调整预算,MMIO 则不行。 每个加载或存储操作都可能触发 I/O,此时需要最新且准确的 icount。

发生 I/O 访问时,处理方式为:

  • 将未执行指令返还给 icount 预算;

  • 为当前 PC 重新编译仅一条指令 [1] 的块;

  • 退出 CPU 循环,执行重新编译的块。

其他 I/O 操作

MMIO 不是唯一需要准确时钟的操作,I/O 端口指令和系统寄存器访问也是常见情况。 这些指令必须由各架构翻译器处理,因为只有它们知道哪些操作属于 I/O。

翻译器处理此类指令时:

  • 如果启用 icount,必须在生成实际 I/O 代码之前调用 gen_io_start(), 代码形式如下:

if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) {
    gen_io_start();
}
  • 必须在该指令之后立即结束 TB。