TinyEMU源码分析之译码执行
本文属于《 TinyEMU模拟器基础系列教程》之一,欢迎查看其它文章。
本文中使用的代码,均为伪代码,删除了部分源码。
1 CPU指令处理基本原理
RISC-V CPU指令处理的基本流程与大多数CPU架构相似,但也体现了RISC-V架构的特性和优化。
以下是RISC-V CPU指令处理的基本流程:
-
取指令:RISC-V CPU的控制器从内存中取出一条指令,通常通过程序计数器(PC)来确定下一条要执行的指令的地址。指令被读取后,存放在指令寄存器(IR)中等待处理。
-
指令译码:指令译码器对存放在IR中的指令进行译码,确定指令的类型(例如,是加载指令、存储指令、算术指令还是控制指令等)以及所需的操作数。RISC-V架构强调简单且固定的指令长度,这有助于简化译码过程。
-
操作数获取:根据指令译码的结果,RISC-V CPU从寄存器或内存中获取操作数。RISC-V架构通常具有大量的通用寄存器,这有助于减少内存访问次数,提高执行速度。
-
执行指令:RISC-V CPU的控制部件根据译码结果发出控制信号,执行相应的操作。由于RISC-V指令集设计得相对简单且固定,执行过程通常较为高效。
-
写回结果:指令执行完毕后,RISC-V CPU将结果写回到寄存器或内存中。如果指令涉及到内存操作(如存储指令),则结果会被写回到指定的内存地址。
-
更新程序计数器:每条指令执行完毕后,程序计数器(PC)会更新为下一条指令的地址。这通常是通过将PC加上一个固定的值(对于顺序执行的指令)或根据指令中的跳转信息(对于分支或跳转指令)来实现。
-
异常和中断处理:在指令执行过程中,如果发生异常或中断(例如,除零错误、外部中断等),RISC-V CPU会暂停当前指令的执行,转而处理异常或中断。处理完毕后,CPU通常会恢复到异常或中断发生前的状态,并继续执行后续指令。
需要注意的是,RISC-V是一个开源的指令集架构(ISA),不同的实现(即不同的RISC-V处理器)可能会在上述基本流程的基础上进行一些优化或扩展。此外,RISC-V还支持多种扩展指令集,用于增强其在特定领域(如浮点数运算、向量运算等)的性能。这些扩展指令集的处理流程可能与基本流程有所不同。
2 TinyEMU指令处理
虚拟机进入运行,主要在virt_machine_run函数中实现。
for(;;) {
virt_machine_run(s);
}
真正取指令、译码、执行,则是在底下的,glue函数中完成,代码如下:
static void no_inline glue(riscv_cpu_interp_x, XLEN)(RISCVCPUState *s,
int n_cycles1)
{
/* we use a single execution loop to keep a simple control flow
for emscripten */
for(;;) {
// 获取PC
s->pc = GET_PC();
addr = s->pc;
ptr = (uint8_t *)(s->tlb_code[tlb_idx].mem_addend +
(uintptr_t)addr);
code_ptr = ptr;
// 取指(根据PC获取一条指令机器码)
insn = get_insn32(code_ptr);
// 译码
opcode = insn & 0x7f;
rd = (insn >> 7) & 0x1f;
rs1 = (insn >> 15) & 0x1f;
rs2 = (insn >> 20) & 0x1f;
// 执行(解释执行)
switch(opcode) {
case 0x37: /* lui */
if (rd != 0)
s->reg[rd] = (int32_t)(insn & 0xfffff000);
NEXT_INSN;
case 0x17: /* auipc */
if (rd != 0)
s->reg[rd] = (intx_t)(GET_PC() + (int32_t)(insn & 0xfffff000));
NEXT_INSN;
case 0x6f: /* jal */
...
}
}
}
通过一个死循环,不断的取指、译码、执行,从而模拟真实CPU的处理过程。如果执行访问内存的指令,那么会涉及到访存操作。
3 模拟器指令执行效率
模拟器对于指令执行的实现,主要有2种方式:解释执行和翻译执行。
模拟器指令执行的主要方式,除了解释执行和翻译执行外,实际上还可能包括其他执行方式,具体取决于模拟器的设计和目标平台。然而,就常见的执行方式而言,解释执行和翻译执行是最主要的两种。
-
解释执行
解释执行,是模拟器逐条读取指令,并立即执行它们的方式。当模拟器加载程序后,它会读取程序中的每一条指令,然后直接执行该指令对应的操作。这种方式,不需要事先将整个程序翻译成机器语言,因此可以节省存储空间。但是,由于每条指令都需要单独解释和执行,所以执行速度相对较慢。 -
翻译执行(或编译执行)
翻译执行,是指模拟器先将程序中的指令,翻译成目标平台的机器语言,然后再执行这些翻译后的机器码。这种方式,在程序执行前,需要进行一次性的翻译工作,因此会占用较多的存储空间。但是,由于执行的是,已经翻译好的机器码,所以执行速度,通常会比解释执行快。
至于其他可能的执行方式,它们可能会根据模拟器的具体设计和功能而有所不同。例如,一些高级模拟器可能支持即时编译(JIT)技术,它结合了解释执行和编译执行的特点,能够在运行时动态地将热点代码(频繁执行的代码)编译成机器码,从而提高执行效率。
需要注意的是,不同的模拟器和不同的应用场景,可能会采用不同的指令执行方式。因此,在选择和使用模拟器时,需要根据具体需求和性能要求,来进行权衡和选择。同时,对于模拟器的设计和开发人员来说,了解各种执行方式的优缺点和适用场景,也是非常重要的。