聲明:本文使用的qemu源碼版本爲qemu-3.1.0-rc0
前言:qemu中採用事件驅動架構和並行架構相結合的方式來工作的。qemu中的線程主要有Vcpu線程,main_loop線程、I/O線程和workthread線程,其中main_loop屬於主線程。
1. 翻譯流程總體框架
2. 具體流程
1> 在vl.c的main函數中創建單板machine
current_machine = MACHINE(object_new(object_class_get_name(OBJECT_CLASS(machine_class))));
2> 在啓動單板的過程中選擇了單板的參數如:
./qemu-system-ppc -M mac99
此時會調用mac99單板對應的模型,其建模過程對應qemu源碼中的hw\ppc\mac_newworld.c文件
3> 初始化CPU(mac_newworld.c中調用)
core99_machine_class_init->(mc->init=ppc_core99_init)
mac_newworld.c函數中ppc_core_init函數創建CPU具體過程如下:
for (i = 0; i < smp_cpus; i++) {
cpu = POWERPC_CPU(cpu_create(machine->cpu_type));
env = &cpu->env;
/* Set time-base frequency to 100 Mhz */
cpu_ppc_tb_init(env, TBFREQ);
qemu_register_reset(ppc_core99_reset, cpu);
}
其中其中 cpu = POWERPC_CPU(cpu_create(machine->cpu_type))實現對於CPU的創建,其函數調用具體如下:(該函數原型位於cpu.c文件中)
CPUState *cpu_create(const char *typename)
{
Error *err = NULL;
CPUState *cpu = CPU(object_new(typename));
object_property_set_bool(OBJECT(cpu), true, "realized", &err);
if (err != NULL) {
error_report_err(err);
object_unref(OBJECT(cpu));
exit(EXIT_FAILURE);
}
return cpu;
}
其中object_new函數會通過調用target/ppc/traslate_init.inc.c文件中的函數來創建CPU
4> 在創建CPU時每一個Vcpu都會創建一個vcpu線程(這就要和TCG翻譯有關了!!!)
ppc_cpu_class_init->ppc_cpu_realize->qemu_init_vcpu
5> 在cpus.c文件中qemu_init_vcpu函數的定義,該函數調用了qemu_tcg_init_vcpu函數
void qemu_init_vcpu(CPUState *cpu)
{
cpu->nr_cores = smp_cores;
cpu->nr_threads = smp_threads;
cpu->stopped = true;
if (!cpu->as) {
/* If the target cpu hasn't set up any address spaces itself,
* give it the default one.
*/
cpu->num_ases = 1;
cpu_address_space_init(cpu, 0, "cpu-memory", cpu->memory);
}
if (kvm_enabled()) {
qemu_kvm_start_vcpu(cpu);
} else if (hax_enabled()) {
qemu_hax_start_vcpu(cpu);
} else if (hvf_enabled()) {
qemu_hvf_start_vcpu(cpu);
} else if (tcg_enabled()) {
qemu_tcg_init_vcpu(cpu);
} else if (whpx_enabled()) {
qemu_whpx_start_vcpu(cpu);
} else {
qemu_dummy_start_vcpu(cpu);
}
while (!cpu->created) {
qemu_cond_wait(&qemu_cpu_cond, &qemu_global_mutex);
}
}
6> qemu_tcg_init_vcpu函數的定義
static void qemu_tcg_init_vcpu(CPUState *cpu)
{
char thread_name[VCPU_THREAD_NAME_SIZE];
static QemuCond *single_tcg_halt_cond;
static QemuThread *single_tcg_cpu_thread;
static int tcg_region_inited;
assert(tcg_enabled());
/*
* Initialize TCG regions--once. Now is a good time, because:
* (1) TCG's init context, prologue and target globals have been set up.
* (2) qemu_tcg_mttcg_enabled() works now (TCG init code runs before the
* -accel flag is processed, so the check doesn't work then).
*/
if (!tcg_region_inited) {
tcg_region_inited = 1;
tcg_region_init();
}
if (qemu_tcg_mttcg_enabled() || !single_tcg_cpu_thread) {
cpu->thread = g_malloc0(sizeof(QemuThread));
cpu->halt_cond = g_malloc0(sizeof(QemuCond));
qemu_cond_init(cpu->halt_cond);
if (qemu_tcg_mttcg_enabled()) {
/* create a thread per vCPU with TCG (MTTCG) */
parallel_cpus = true;
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/TCG",
cpu->cpu_index);
qemu_thread_create(cpu->thread, thread_name, qemu_tcg_cpu_thread_fn,
cpu, QEMU_THREAD_JOINABLE);
} else {
/* share a single thread for all cpus with TCG */
snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "ALL CPUs/TCG");
qemu_thread_create(cpu->thread, thread_name,
qemu_tcg_rr_cpu_thread_fn,
cpu, QEMU_THREAD_JOINABLE);
single_tcg_halt_cond = cpu->halt_cond;
single_tcg_cpu_thread = cpu->thread;
}
#ifdef _WIN32
cpu->hThread = qemu_thread_get_handle(cpu->thread);
#endif
} else {
/* For non-MTTCG cases we share the thread */
cpu->thread = single_tcg_cpu_thread;
cpu->halt_cond = single_tcg_halt_cond;
cpu->thread_id = first_cpu->thread_id;
cpu->can_do_io = 1;
cpu->created = true;
}
}
該函數中調用(這裏就創建了一個vcpu的線程,在執行客戶機代碼時進行翻譯)
qemu_thread_create(cpu->thread, thread_name, qemu_tcg_cpu_thread_fn,cpu, QEMU_THREAD_JOINABLE);
接下來其函數調用關係如下:
7> 接下來重點解析cpu_exec()函數(該函數位於accel\tcg\cpu-exec.c文件下)
在line724會調用 tb = tb_find(cpu, last_tb, tb_exit, cflags)用來查找TB(translation block)(這裏的TB是已經翻譯爲host code 的代碼)
查找到TB之後會調用cpu_loop_exec_tb(cpu, tb, &last_tb, &tb_exit)函數來執行翻譯好的主機代碼。
8> tb_find函數解析
static inline TranslationBlock *tb_find(CPUState *cpu,
TranslationBlock *last_tb,
int tb_exit, uint32_t cf_mask)
{
TranslationBlock *tb;
target_ulong cs_base, pc;
uint32_t flags;
tb = tb_lookup__cpu_state(cpu, &pc, &cs_base, &flags, cf_mask);
if (tb == NULL) {
mmap_lock();
tb = tb_gen_code(cpu, pc, cs_base, flags, cf_mask);
mmap_unlock();
/* We add the TB in the virtual pc hash table for the fast lookup */
atomic_set(&cpu->tb_jmp_cache[tb_jmp_cache_hash_func(pc)], tb);
}
#ifndef CONFIG_USER_ONLY
/* We don't take care of direct jumps when address mapping changes in
* system emulation. So it's not safe to make a direct jump to a TB
* spanning two pages because the mapping for the second page can change.
*/
if (tb->page_addr[1] != -1) {
last_tb = NULL;
}
#endif
/* See if we can patch the calling TB. */
if (last_tb) {
tb_add_jump(last_tb, tb_exit, tb);
}
return tb;
}
該段代碼的主要功能是返回翻譯好的TB,首先調用tb = tb_lookup__cpu_state(cpu, &pc, &cs_base, &flags, cf_mask)函數查看該PC對應的代碼是否已經翻譯如果翻譯了,則直接返回TB如果沒有則需要調用tb_gen_code函數來進行翻譯。如果該基本塊不在cache中,則需要使用tb_gen_code函數進行翻譯並放到cache中。
其中 tb_lookup__cpu_state函數會調用cpu_get_tb_cpu_state獲取當前客戶機的pc寄存器中的值,接下來調用tb_jmp_cache_hash_func根據pc的值獲取存儲TB的hash表的索引,接下來調用atomic_rcu_read(&cpu->tb_jmp_cache[hash])函數來獲取cache中存儲的TB。tb_htable_lookup函數中通過調用get_page_addr_code函數獲取客戶機操作系統的物理內存地址(phys_pc)。
9> tb_hen_code 目標機代碼翻譯爲主機代碼的過程
phys_pc = get_page_addr_code(env, pc) //獲取當前要翻譯的pc對應的物理地址
tb = tb_alloc(pc) //爲該pc分配新的TB與之對應
gen_intermediate_code //調用該函數將target code轉換爲TCG操作碼
tcg_gen_code //使用該函數將TCG操作碼轉換爲host code