虚拟机的通用实现是什么?
虚拟机模拟物理CPU执行的操作,因此,虚拟机应该包含如下的概念:
- 将程序源码编译为符合虚拟机规范的字节码
- 用于存储指令和操作数的一个数据结构
- 用于函数调用操作的一个调用栈结构
- 一个指令指针,其指向下一条将要执行指令的指针
- 一个虚拟的CPU,负责如下的指令调度:取指令、解码操作数、执行指令
虚拟机实现的两种方式
-
基于堆栈的虚拟机架构(eg. Java VM .Net CLR)
-
基于寄存器的虚拟机架构(eg. Lua VM Dalvik VM)
基于堆栈的虚拟机架构(Stack Based Virtual Machines)
基于堆栈的虚拟机实现了一个虚拟机的通用特性,但是存储数据的内存结构是一个栈结构,依照栈FIFO的操作方式,数据出栈执行操作后,操作结果又入栈。
在基于栈的虚拟机中,计算两个数值之和的操作通常会按如下方式执行:
1. POP 20
2. POP 7
3. ADD 20, 7, result
4. PUSH result
由于需要PUSH和POP操作,执行一次加法运算需要执行四条指令。基于栈的虚拟机架构的优势在于,存储在栈中的操作数能够通过栈指针定位。这意味着虚拟机不需要知道操作数的具体地址,只需要调用栈指执行POP操作就可以获取下一个操作数。
在基于栈的虚拟机架构中,所有的算术和逻辑运算都通过出栈、入栈的方式来完成的,并且计算结果最后存储在栈中。
基于寄存器的虚拟机架构(Register Based Virtual Machines)
在基于寄存器的实现中,操作数存储的数据结构是基于CPU寄存器的。这里不存在入栈出栈的操作,但是指令中需要包含存储操作数的寄存器的地址。因此,指令包含了其所需的操作数的具体地址,不同于基于栈的虚拟机模型拥有一个栈指针指向下一个操作数。
例如,在基于寄存器的虚拟机架构中执行一次加法运算,指令大致会按如下方式执行:
1. ADD R1, R2, R3 ; # Add contents of R1 and R2, store result in R3
正如我先前提到的,这里不存在POP或PUSH操作,因此加法运算只需要一条指令即可。但是不同于堆栈模型的是,我们必须指明操作数R1、R2、R3的地址。
其优势在于免去了出入栈所带来的开销以及指令调度循环使得指令执行的更快。另一个优势在于有些性能优化无法在基于堆栈的模型上实施,例如,在代码中存在很多减法表达式时,寄存器模型可以计算一次后便将计算结果存储在寄存器中,再次执行相同减法表达式时使用寄存器存储的值,从而减少了表达式重复计算造成的开销。
基于寄存器模型指令的平均长度大于基于栈模型指令的平均长度,因为基于寄存器模型必须明确在指令中指定存放操作数的寄存器地址,而基于栈模型通过利用栈指针存取操作数,从而缩减了指令的长度。