Gxemul支援ARM,MIPS,PPC内核的虚拟机,这里以oldtestmips机器类型为例,介绍gxemul如何运行
main.cc main() { cpu_init(); 初始化CPU模块,注册不同的cpu family函数 device_init(); 初始化device模块 machine_init(); 初始化machine,注册不同的machine setup函数 emul = emul_new(NULL); 创建虚拟机 get_cmd_args (argc, argv, emul, &diskimages, &n_diskimages) 解析参数 emul_simple_init(emul); 初始化虚拟机 emul_run(emul); 运行虚拟机,虚拟机运行后在这个函数内loop执行指令 emul_destroy(emul); 删除虚拟机 }
cpu.cc cpu_init { ADD_ALL_CPU_FAMILIES; } cpu.cc add_cpu_family(mips_cpu_family_init, ARCH_MIPS); { res = family_init(fp); }
对于mips来说family_init就是mips_cpu_family_init,由tmp_mips_tail.cc使用cpu.h的宏CPU_FAMILY_INIT生成,请在tmp_mips_tail.cc是在编译时由generate_tail.c产生
tmp_mips_tail.cc CPU_FAMILY_INIT(mips,"MIPS") #define CPU_FAMILY_INIT(n,s) int n ## _cpu_family_init( \ struct cpu_family *fp) { \ /* Fill in the cpu_family struct with valid data for this arch. */ \ fp->name = strdup(s); \ fp->cpu_new = n ## _cpu_new; \ fp->list_available_types = n ## _cpu_list_available_types; \ fp->disassemble_instr = n ## _cpu_disassemble_instr; \ fp->register_dump = n ## _cpu_register_dump; \ fp->dumpinfo = n ## _cpu_dumpinfo; \ fp->functioncall_trace = n ## _cpu_functioncall_trace; \ fp->tlbdump = n ## _cpu_tlbdump; \ fp->init_tables = n ## _cpu_init_tables; \ return 1; \ } 展开为: mips_cpu_family_init(struct cpu_family *) { fp->name = strdup(“MIPS”); fp->cpu_new = mips _cpu_new; ….. fp->init_tables = mips _cpu_init_tables; }
machine.cc machine_init() { automachine_init() } automachine.cc automachine_init() { machine_register_oldtestmips() } machine_register_oldtestmips()在machine_test.cc 中通过machine.h的宏定义: #define MACHINE_REGISTER(x) void machine_register_ ## x(void) MACHINE_REGISTER(oldtestmips) { MR_DEFAULT(oldtestmips, "Test-machine for MIPS", ARCH_MIPS, MACHINE_TESTMIPS); machine_entry_add_alias(me, "oldtestmips"); } machine.h的宏定义 #define MR_DEFAULT(x,name,arch,type) struct machine_entry \ *me = machine_entry_new(name,arch,type); \ me->setup = machine_setup_ ## x; \ me->set_default_cpu = machine_default_cpu_ ## x; \ machine_entry_register(me, arch); 展开为: void machine_register_oldtestmips(void) { struct machine_entry *me = machine_entry_new("Test-machine for MIPS", ARCH_MIPS, MACHINE_TESTMIPS); me->setup = machine_setup_oldtestmips me->set_default_cpu = machine_default_cpu_ oldtestmips; machine_entry_register(me, ARCH_MIPS); machine_entry_add_alias(me, "oldtestmips"); }
emul.cc emul_simple_init() { emul_machine_setup(m, extra_argc, extra_argv, 0, NULL); } emul.cc emul_machine_setup() { 创建系统系统内存 machine_memsize_fix(m); memory_amount = (uint64_t)m->physical_ram_in_mb * 1048576; m->memory = memory_new(memory_amount, m->arch); 创建cpu管理单元 CHECK_ALLOCATION(m->cpus = (struct cpu **) malloc(sizeof(struct cpu *) * m->ncpus)); memset(m->cpus, 0, sizeof(struct cpu *) * m->ncpus); m->cpus[i] = cpu_new(m->memory, m, i, m->cpu_name); 向系统内存写入随机数 cpu->memory_rw(cpu, m->memory, i, data, sizeof(data),MEM_WRITE, CACHE_NONE | NO_EXCEPTIONS | PHYSICAL); 添加通用的device device_add(m, device_names[i]); machine设置 machine_setup(m); 将要运行的文件load进入虚拟机 file_load(m, m->memory, name_to_load, &entrypoint, m->arch, &gp, &byte_order, &toc); }
cpu.cc cpu_new { fp->cpu_new(cpu, mem, machine, cpu_id, cpu_type_name) 初始化table,在mips_cpu_family_init中定义为mips_cpu_init_tables fp->init_tables(cpu); }
cpu_new在mips_cpu_family_init时已经设置为mips _cpu_new
cpu_mips.cc mips _cpu_new { 设置内存访问函数 cpu->memory_rw = mips_memory_rw; 设置指令访问函数 cpu->run_instr = mips32_run_instr; cpu->instruction_has_delayslot = mips_cpu_instruction_has_delayslot; 设置异常中断 templ.interrupt_assert = mips_cpu_interrupt_assert; templ.interrupt_deassert = mips_cpu_interrupt_deassert; interrupt_handler_register(&templ); 设置cp0/cp1 cpu->cd.mips.coproc[0] = mips_coproc_new(cpu, 0); cpu->cd.mips.coproc[1] = mips_coproc_new(cpu, 1); 设置虚拟地址到物理地址的转换函数 cpu->translate_v2p = translate_v2p_mmu3k; }
machine.cc machine_setup() { me->setup(machine, cpu); }
setup在machine_init的时候注册,oldtestmips对应的是machine_setup_oldtestmips在machine_test.cc 中通过machine.h的宏定义:
#define MACHINE_SETUP(x) void machine_setup_ ## x(struct machine *machine, struct cpu *cpu) MACHINE_SETUP(oldtestmips) { device_add(machine, tmpstr); 添加于machine相关的device }
gxemul可以load各种格式的文件,这里只分析load bin文件的过程
file.cc file_load() { file_load_raw() } file_raw.cc file_load_raw { 从脚本中分析出load地址和文件名 loadaddr = vaddr = entry = strtoull(filename, NULL, 0); p2 = p+1; 打开对应要运行的文件 f = fopen(strrchr(filename, ':')+1, "r"); 读文件并写入到内存中 len = fread(buf, 1, to_read, f); m->cpus[0]->memory_rw(m->cpus[0], mem, vaddr, &buf[0], len, MEM_WRITE, NO_EXCEPTIONS); 关闭文件 fclose(f); }
emul.cc emul_run() { cpu_run_init(emul->machines[j]); 初始化machine内的CPU timer_start() 启动虚拟机的clock //循环执行 while(go) { timer_poll(); anything = machine_run(emul->machines[j]); if (anything) go = 1; } timer_stop(); 停止虚拟机clock cpu_run_deinit(emul->machines[j]); } machine.cc machine_run() { cpus[i]->run_instr(cpus[i]); 执行指令 machine->tick_functions.f[te](cpus[0], machine->tick_functions.extra[te]); trick }
文后话:
gxemul的源代码使用了大量的宏,按照宏的模板生成代码,这可能和作者想通过统一的模板来表示不同的CPU架构,能是代码简洁且模块化强。但是大量的宏降低了代码的可读性。基于同样的原因gxemul针对CPU架构的一些处理代码使用了文件自动生成,阅读的时候也要注意。