QEMU 简介
根据 QEMU 官方文档 和 Qemu Detailed Study 整理。
相关概念介绍
QEMU
QEMU 是一个通用的、开源的机器仿真器和虚拟机。
在系统模式(System mode)下,QEMU 能运行各种不同架构的操作系统。
在用户模式(User mode)下,QEMU 能运行为其他架构编写的程序。
通常情况下,我们把 QEMU 要模拟的机器称为目标(Target)机器,运行 QEMU 的机器被被称为主机(Host)。
TCG
以下部分内容来自维基百科。
二进制翻译(Binary Translation)指把一种指令集重新编译成另一种指令集,即把源指令序列翻译为目标指令序列。
二进制翻译分为静态二进制翻译(Static binary translation)和动态二进制翻译(Dynamic binary translation,简称 DBT)两种。
前者指在执行程序前就将所有指令翻译好,这是很难正确做到的,因为不是所有的代码都能被翻译器发现(某些程序甚至会在运行过程中生成新的指令);后者通常是执行时即时(Just in Time,简称 JIT)翻译简短的代码序列,然后将结果缓存起来,代码只在被发现和可能的情况下被翻译,并且分支指令会指向已经翻译和保存的代码。
TCG 是 QEMU 所采用的一种动态二进制翻译技术,全称是 Tiny Code Generator。
因为在 TCG 模式下,我们希望把源程序指令序列翻译成能在主机上运行的指令序列,所以 TCG 的 Target 是主机,这点一定要注意。
TCG 的翻译任务主要由两部分组成:第一部分是将目标代码块(翻译块,TB)转换为中间语言(TCG 操作,TCG ops);第二部分是将 TB 的 TCG ops 转换为主机代码。
自然,转换过程中会伴随可选的优化过程。
KVM
以下部分内容来自维基百科。
KVM(基于内核的虚拟机)是一个 FreeBSD 和 Linux 内核的模块,它允许用户空间的程序访问各种处理器的硬件虚拟化功能。
当目标架构与主机架构相同时,QEMU 可以利用 KVM 的特殊功能,如加速功能。
TCG 执行流程简介
代码基于 QEMU 6.0.0 版本。
程序入口
喜闻乐见的只有 5 行的 int main(int argc, char **argv, char **envp) 函数,位于 softmmu/main.c。
void qemu_init(int argc, char **argv, char **envp) 函数位于 softmmu/vl.c,该函数负责初始化工作,具体细节目前我并不关心。
大体概括一下,各个体系结构会用 include/qom/object.h 文件的 DEFINE_TYPES(type_array) 来定义 CPU 相关信息的结构体。
这个结构体包含了指向 static void xxx_cpu_class_init(ObjectClass *oc, void *data) 函数的函数指针,位于 target/xxx/cpu.c 中,其中 xxx 是具体的体系结构名,如 i386、mips。
xxx_cpu_class_init() 函数会调用 static void xxx_cpu_realizefn(DeviceState *dev, Error **errp) 函数。
而 xxx_cpu_realizefn() 函数最终会调用 qemu_init_vcpu() 函数来初始化虚拟 CPU。
创建虚拟 CPU
void qemu_init_vcpu(CPUState *cpu) 函数位于 softmmu/cpus.c,这里着重关心这个调用语句:cpus_accel->create_vcpu_thread(cpu);。
create_vcpu_thread 函数指针在 static void tcg_accel_ops_init(AccelOpsClass *ops) 中被初始化,该函数位于 accel/tcg/tcg-accel-ops.c。
tcg_accel_ops_init 函数指针在 static void tcg_accel_ops_class_init(ObjectClass *oc, void *data) 中被初始化。
这个函数指针在 tcg_accel_ops_type 结构体中,最终通过 include/qemu/module.h 中的 type_init(function) 宏来初始化。
回到 create_vcpu_thread 函数指针,该指针在多线程情况下被初始化为 accel/tcg/tcg-accel-ops-mttcg.c 下的 void mttcg_start_vcpu_thread(CPUState *cpu) 函数,单线程情况下则被初始化为 accel/tcg/tcg-accel-ops-rr.c 下的 void rr_start_vcpu_thread(CPUState *cpu) 函数。
mttcg_start_vcpu_thread() 函数会创建线程执行 static void *mttcg_cpu_thread_fn(void *arg);rr_start_vcpu_thread() 函数会创建线程执行 static void *rr_cpu_thread_fn(void *arg) 。
不论是 mttcg_cpu_thread_fn() 还是 rr_cpu_thread_fn() 函数,最终都会调用 int tcg_cpus_exec(CPUState *cpu) 函数。
而 tcg_cpus_exec() 函数则会调用 cpu_exec() 函数执行代码。
动态翻译
int cpu_exec(CPUState *cpu) 函数位于 accel/tcg/cpu-exec.c,是 TCG 执行代码的主循环。
这里只考虑查找生成动态代码的部分,即调用的 static inline TranslationBlock *tb_find(CPUState *cpu, TranslationBlock *last_tb, int tb_exit, uint32_t cflags) 函数。
tb_find() 函数会调用 accel/tcg/translate-all.c 文件中的 TranslationBlock *tb_gen_code(CPUState *cpu, target_ulong pc, target_ulong cs_base, uint32_t flags, int cflags) 函数来生成动态代码。
tb_gen_code() 函数会调用体系结构相关的 target/xxx/translate.c 文件中的 void gen_intermediate_code(CPUState *cpu, TranslationBlock *tb, int max_insns) 函数来为生成中间代码做准备。
gen_intermediate_code() 函数调用 accel/tcg/translator.c 文件中的 void translator_loop(const TranslatorOps *ops, DisasContextBase *db, CPUState *cpu, TranslationBlock *tb, int max_insns),该函数主体是体系结构无关的,但它又通过关联的函数指针调用体系结构相关的函数来执行翻译。
执行代码
完成翻译后,cpu_exec() 函数调用 accel/tcg/cpu-exec.c 文件中的 static inline void cpu_loop_exec_tb(CPUState *cpu, TranslationBlock *tb, TranslationBlock **last_tb, int *tb_exit) 函数,该函数会调用 static inline TranslationBlock * QEMU_DISABLE_CFI cpu_tb_exec(CPUState *cpu, TranslationBlock *itb, int *tb_exit) 函数来执行动态翻译的代码。
之后的操作我暂时不关心,先看到这里为止。
KVM 执行流程简介
QEMU KVM 模式下,QEMU 与内核 KVM 模块交互,这里只考虑 QEMU 部分,内核部分见 KVM 简介。
KVM 的初始化流程大体和 TCG 部分介绍的差不多。
相关的函数和宏如下:
int main(int argc, char **argv, char **envp),位于softmmu/main.c。void qemu_init(int argc, char **argv, char **envp),softmmu/vl.c。type_init(function),位于include/qemu/module.h。static void kvm_accel_ops_class_init(ObjectClass *oc, void *data),位于accel/kvm/kvm-accel-ops.c。static void kvm_start_vcpu_thread(CPUState *cpu),位于accel/kvm/kvm-accel-ops.c。static void *kvm_vcpu_thread_fn(void *arg),位于accel/kvm/kvm-accel-ops.c。int kvm_init_vcpu(CPUState *cpu, Error **errp),位于accel/kvm/kvm-all.c。int kvm_arch_init_vcpu(CPUState *cs),位于target/xxx/cpu.c。int kvm_cpu_exec(CPUState *cpu),位于accel/kvm/kvm-all.c。
其中,kvm_vcpu_thread_fn() 函数先调用体系结构无关的初始化函数 kvm_init_vcpu(),再执行 kvm_cpu_exec() 函数来实际执行代码。
kvm_init_vcpu() 函数会先调用 static int kvm_get_vcpu(KVMState *s, unsigned long vcpu_id) 函数来与内核交互创建虚拟 CPU(通过 int kvm_vcpu_ioctl(CPUState *cpu, int type, ...) 函数与内核交互),然后调用体系结构相关的初始化函数 kvm_arch_init_vcpu()。
而 kvm_cpu_exec() 函数通过 kvm_vcpu_ioctl() 函数与内核交互,切换到 KVM 来实际执行代码。
具体细节和后续操作我暂时不关心,先鸽了。
