进程虚拟机简介
一个虚拟机是对原生操作系统的一个高层次的抽象,目的是为了模拟物理机器,本文所谈论的是基于进程的虚拟机,而不是基于系统的虚拟机,基于系统的虚拟机可以用来在同一个平台下去运行多个不同的硬件架构的操作系统,常见的有kvm,xen,vmware等,而基于进程的虚拟机常见的有JVM,PVM(python虚拟机)等,java和python的解释器将java和python的代码编译成JVM和PVM可以识别的字节码.然后JVM和PVM取出字节码依次执行.就好比是汇编语言被编译成了机器码,通过指令寄存器取出每一个机器码开始执行.道理都是相同的.
那么作为一个进程虚拟机需要实现哪些功能呢?,因为虚拟机其实就是模拟物理机器的,而进程虚拟机其实就是去模拟CPU的一些操作,比如取指令,解析指令,执行指令,存储数据等.所以通常来说实现一个进程虚拟机大体上需要完成下面这些功能:
- 将原程序编译成虚拟机可以识别的字节码
- 设计包含指令和相关操作数的数据结构
- 一个用来完成函数调用的调用堆栈
- 一个指令寄存器(或者是指令指针)用于指向下一个要执行的指令
- 一个虚拟的CPU,用来完成指令的分发
- 得到下一条指令
- 解码操作数
- 执行指令
目前主要又两种方式去实现一个进程虚拟机,一个就是基于堆栈的,另外一种则是基于寄存器,典型的像基于堆栈的进程虚拟机有JVM,还有python虚拟机,基于寄存器的典型的又lua,还有 Dalvik VM(安卓的虚拟机).这两种方式最大的区别在于操作数的存取和操作结果的存放方法是不同的.
基于堆栈的虚拟机实现
一个基于堆栈的虚拟机需要实现上面提到的一个特性,但是操作数不是按照内存地址结构存放的,而是基于堆栈数据结构来存放的,当执行一个操作的时候,只要从堆栈中弹出要操作的数据即可,然后将操作结果再次存放到堆栈中即可,下面这幅图是基于堆栈的虚拟机实现的一个两个数相加的操作.
POP 20
POP 7
ADD 20, 7, result
PUSH result
当执行到ADD操作的时候,因为这是一个二元操作符,所有就会从堆栈中,POP两个操作数来完成加法操作,基于堆栈的这种模型,优点就是对于操作数的获取可以通过栈顶指针SP得到,那么对于一条指令来说,只需要知道这条指令需要几个操作数,对于操作数的(位置)地址是不需要知道的,通过SP可以得到,基于堆栈的这种虚拟机的实现对于所有的算术和逻辑操作都可以直接通过POP和PUSH等操作获取操作数和存放操作结果.
基于寄存器的虚拟机实现
基于寄存器实现的虚拟机,操作数的数据结构是存放在CPU的寄存器中的,对于这种模型来说没有PUSH和POP操作,但是指令中需要包含操作数的地址(或者是寄存器),并且指令中需要显示的知道操作数的地址,而基于堆栈的虚拟机指令不包含操作数的信息,操作数直接通过SP得到,例如,在基于寄存器的虚拟机实现下,一个假发操作其指令将会是下面这幅图所示.
ADD R1, R2, R3 ;# Add contents of R1 and R2, store result in R3
就像上面提到的一样,基于寄存器实现的虚拟机是没有PUSH和POP这类操作的,因此加法指令只有一行,不像基于堆栈的虚拟机实现那样,这里需要显示的指明操作数的位置(这里是放在R1,R2寄存器中),优点就是避免了大量PUSH和POP的开销,因此基于寄存器的虚拟机实现在指令分发循环要比基于堆栈的虚拟机要快.
除了上面的提到的可以避免POP和PUSH这类操作带来的开销外,基于寄存器的虚拟机实现还有一些其它的优点, 例如可以实现某些无法在基于堆栈实现的虚拟机中完成的优化操作,假设现在要执行一条减法操作,对于基于寄存器实现的虚拟机来说会将计算的结果保存在一个寄存器中,当这条减法指令再次执行的时候,可以直接得到计算结果,不需要再次执行.
尽管基于寄存器的虚拟机实现又如上诸多的优点,但是相比于基于堆栈的虚拟机实现来说,也存在着一些问题,例如,基于寄存器实现的虚拟机的指令平均长度都要大于基于堆栈的虚拟机实现,因为前者需要将操作数的位置放在指令中,而后者指令是不需要知道操作数的位置的,操作数直接通过SP获得,因为后者的指令长度要小于前者.
总结
本文简单的分析了两种进程虚拟机实现的基本模型,对比其优缺点,因为最近这段时候突然对PVM非常感兴趣,因此有了本文,如要你想知道更多关于进行虚拟机实现的内容,文章的最后会给出一系列的文章供参考.