QEMU CPU执行源码分析 摘要本文从源码层面剖析 QEMU 的 CPU 执行流程围绕主循环调度、基本块缓存、TCG 动态翻译与主机代码执行四条主线展开。文章先介绍 TCG、CPUState、CPUClass 等核心概念再依次分析 vCPU 线程入口、cpu_exec 主循环、基本块查找与翻译、TCG 翻译流程、主机代码执行以及异常与中断处理帮助读者建立从顶层循环到底层翻译执行的完整认知。1. 引言QEMU 是一款功能强大的开源模拟器支持多种 CPU 架构的模拟与虚拟化。理解 QEMU 的 CPU 执行流程是深入掌握其整体架构的关键。本文将从源码层面剖析 QEMU 如何加载、翻译并执行客户机 CPU 指令帮助读者建立从顶层循环到底层翻译执行的完整认知。2. 核心概念与架构概览在深入源码之前先厘清几个核心概念TCGTiny Code GeneratorQEMU 的动态二进制翻译后端将客户机指令翻译为主机指令。CPUState描述单个 CPU 核心状态的统一结构体包含寄存器、异常状态等。CPUClass定义 CPU 的操作回调如复位、执行、同步状态等。AccelState加速器抽象层TCG 与 KVM 均通过该接口接入。QEMU 的 CPU 执行整体流程可概括为主循环获取 CPU 状态调用 CPUClass 的执行回调进入 TCG 翻译与执行循环最终完成指令的翻译、缓存与执行。3. 主循环与 CPU 执行入口QEMU 的主循环位于softmmu/cpus.c与cpus-common.c中。以 TCG 模式为例每个 vCPU 线程运行qemu_tcg_cpu_thread_fn其核心逻辑如下static void *qemu_tcg_cpu_thread_fn(void *arg) { CPUState *cpu arg; int r; rcu_register_thread(); qemu_mutex_lock_iothread(); qemu_thread_get_self(cpu-thread); cpu-thread_id qemu_get_thread_id(); cpu-can_do_io 1; current_cpu cpu; r qemu_init_cpu_loop(cpu); if (r) { error_report(qemu_init_cpu_loop failed: %d, r); exit(1); } qemu_mutex_unlock_iothread(); /* wait for initial kick-off */ qemu_mutex_lock_iothread(); while (!cpu-stop) { qemu_cond_wait(cpu-halt_cond, iothread_mutex); } qemu_mutex_unlock_iothread(); /* start the cpu */ cpu_exec(cpu); /* terminate the cpu */ qemu_mutex_lock_iothread(); qemu_cpu_is_self(cpu); current_cpu NULL; qemu_mutex_unlock_iothread(); rcu_unregister_thread(); return NULL; }线程启动后主循环调用cpu_exec进入真正的执行阶段。该函数是 CPU 执行的核心入口位于accel/tcg/cpu-exec.c。4. cpu_exec 主循环分析cpu_exec是 TCG 模式下 CPU 执行的主循环其简化流程如下int cpu_exec(CPUState *cpu) { int ret; SyncClocks sc { 0 }; /* 进入执行前同步时钟 */ sync_clocks(sc); /* 初始化执行上下文 */ cpu_exec_enter(cpu); /* 主执行循环 */ while (!cpu-exit_request) { TranslationBlock *tb; uintptr_t last_tb 0; int tb_exit 0; /* 处理中断与异常 */ if (cpu-exception_index 0) { /* 处理异常可能返回用户态或重新调度 */ ret cpu_handle_exception(cpu, ret); if (ret ! 0) { break; } } /* 处理中断 */ if (cpu-interrupt_request CPU_INTERRUPT_HARD) { cpu_handle_interrupt(cpu, last_tb); } /* 查找或翻译基本块 */ tb tb_find(cpu, last_tb, tb_exit); if (!tb) { /* 翻译失败或需要重新翻译 */ break; } /* 执行翻译后的代码 */ cpu_tb_exec(cpu, tb); /* 记录执行状态 */ last_tb (uintptr_t)tb; tb_exit cpu-tb_exit; } cpu_exec_exit(cpu); return ret; }主循环的核心步骤包括异常处理、中断处理、基本块查找与执行。每次循环都会检查退出请求确保外部事件如调试器、停止请求能及时响应。5. 基本块查找与翻译QEMU 以基本块TranslationBlockTB为单位进行翻译与缓存。基本块通常以跳转指令或分支指令结尾。查找流程如下static TranslationBlock *tb_find(CPUState *cpu, uintptr_t last_tb, int tb_exit) { TranslationBlock *tb; CPUClass *cc CPU_GET_CLASS(cpu); target_ulong cs_base, pc; uint32_t flags; /* 获取当前 PC 与状态标志 */ cc-get_pc_and_flags(cpu, pc, cs_base, flags); /* 在哈希表中查找已翻译的基本块 */ tb tb_lookup(cpu, pc, cs_base, flags); if (tb NULL) { /* 未命中进行翻译 */ tb tb_gen_code(cpu, pc, cs_base, flags); } return tb; }翻译过程由tb_gen_code完成其内部调用目标架构的翻译函数将客户机指令逐条转换为 TCG 中间表示IR再由 TCG 后端优化并生成主机指令。6. TCG 翻译流程TCG 翻译的核心流程如下TranslationBlock *tb_gen_code(CPUState *cpu, target_ulong pc, target_ulong cs_base, uint32_t flags) { TranslationBlock *tb; TCGContext *tcg_ctx tcg_ctx; target_ulong next_tb; /* 分配新的 TB 并初始化 */ tb tb_alloc(pc); if (unlikely(!tb)) { /* 缓存已满刷新后重试 */ tb_flush(cpu); tb tb_alloc(pc); } /* 初始化翻译上下文 */ tcg_ctx-tb tb; tcg_ctx-cpu cpu; tcg_ctx-gen_opc_ptr tcg_ctx-gen_opc_buf; tcg_ctx-gen_opparam_ptr tcg_ctx-gen_opparam_buf; /* 调用目标架构的翻译入口 */ cpu-cc-tcg_initialize(); next_tb cpu-cc-tb_start(cpu, tb, cs_base, pc, flags); /* 生成 TCG IR */ cpu-cc-translate(cpu, tb, pc, cs_base, flags); /* 结束翻译生成主机代码 */ tcg_gen_code(tcg_ctx, tb); /* 将 TB 插入哈希表 */ tb_add(tb); return tb; }其中translate回调由各架构实现例如 x86 的translate.c、ARM 的translate.c。翻译器逐条解码客户机指令生成对应的 TCG 操作。7. 主机代码执行翻译完成后QEMU 通过cpu_tb_exec执行生成的主机代码static inline void cpu_tb_exec(CPUState *cpu, TranslationBlock *tb) { uintptr_t ret; /* 记录当前执行的 TB用于调试与异常回溯 */ cpu-current_tb tb; /* 执行翻译后的代码 */ ret tcg_qemu_tb_exec(cpu, tb-tc.ptr); /* 根据返回值处理退出原因 */ switch (ret) { case TB_EXIT_REQUESTED: /* 外部请求退出 */ cpu-exit_request 1; break; case TB_EXIT_ICOUNT_EXPIRED: /* 指令计数到期 */ cpu-icount_decr.u16.low 0; break; default: /* 正常跳转更新 PC */ break; } cpu-current_tb NULL; }tcg_qemu_tb_exec是 TCG 生成的代码入口执行完毕后返回退出原因主循环据此决定继续执行还是处理事件。8. 异常与中断处理异常与中断是 CPU 执行的重要组成部分。QEMU 在每次循环中检查异常与中断请求static int cpu_handle_exception(CPUState *cpu, int *ret) { CPUClass *cc CPU_GET_CLASS(cpu); int exception_index cpu-exception_index; if (exception_index 0) { /* 调用架构相关的异常处理 */ if (cc-handle_cpu_exception) { *ret cc-handle_cpu_exception(cpu, exception_index); } else { *ret -1; } /* 异常已处理清除标志 */ cpu-exception_index -1; return 1; } return 0; }中断处理类似通过检查cpu-interrupt_request标志位调用架构相关的cpu_handle_interrupt回调。9. 总结QEMU 的 CPU 执行流程可以概括为以下几个关键环节主循环调度每个 vCPU 线程运行cpu_exec主循环持续处理异常、中断与基本块执行。基本块缓存以 TB 为单位缓存翻译结果通过哈希表加速查找。动态翻译TCG 将客户机指令翻译为 TCG IR再优化生成主机指令。执行与退出执行主机代码后根据退出原因决定继续执行或处理外部事件。理解这条主线后再深入各架构的翻译实现、TCG 优化器以及 KVM 加速路径就能逐步构建起对 QEMU 整体执行引擎的完整认识。