程序最美(寻路)

你还在坚持练习你的技术吗?运动员天天训练,音乐家也会演练更难的曲章。你呢?

栈虚拟机源码剖析

栈虚拟机源码剖析

         之前我们介绍过一个《简单虚拟机》,该虚拟机是基于寄存器的。

   本文我们剖析一个栈虚拟机的源代码。该代码来自于《实现一个脚本引擎》中的《Part VII:虚拟机(The Virtual Machine)》,该栈虚拟机的源代码下载地址为:source code

         虚拟机的实现方式有三种:内存、堆栈、寄存器。

         该虚拟机主要包含三部分:一个指令数组、一个字符串表、一个堆栈:

成员

说明

指令数组

存储程序所包含的指令

字符串表

实质上是一个指针数组,代表程序的一个变量或者堆栈中的一个临时变量

堆栈

有整数组成,指向字符串表,以表示什么字符串在堆栈中(这里使用的是整数,而非字符串类的指针);堆栈还存储了布尔值,如果整数是非负数,则它指向一个字符串,如果是负数,则它是一个布尔值(不提倡这样做)

 

         该虚拟机有三个主要的接口函数:Reset、Read、Execute:

接口

说明

Reset

重新初始化虚拟机

Read

用于读取指令

Execute

执行当前在内存中的程序,其内有一个指令指针,根据当前指令,使用switch-case语句执行正确的代码

 

         有关其他内容请参见原文

         该堆栈虚拟机源代码共包含五个文件,其中三个头文件和两个源文件:

文件

名称

说明

头文件

mystring.h

字符串类

stack.h

堆栈类

vm.h

虚拟机类声明

源文件

vm.cpp

虚拟机类实现

main.cpp

测试

 

         我们首先介绍字符串类mystring.h和堆栈类stack.h。

         mystring.h中手工实现了一个字符串类,该字符串类中成员变量只有一个私有型的char* s,该类支持一下操作:

           a)         默认构造函数

           b)        含有char* _s参数的构造函数

           c)         析构函数

           d)        用于返回内部字符串指针的Val

           e)         计算字符串长度Len

           f)          参数为String& n的赋值函数

           g)         参数为char* n的赋值函数

           h)        参数为String& n的字符串连接函数Concatenate

           i)           参数为char* n的字符串连接函数

           j)          打印Print

           k)         输入Input

 

        stack.h定义了一个堆栈类模板Stack<>

        堆栈中每个元素为

        template <class t>

        class ll_link

        {

        public:

                 t      data;

                 ll_link* next;

        };

        从该元素的定义我们可以得知该堆栈是链表型,而非数组型。

        Stack中有两个私有的成员变量,分别为ll_link<t>* llstart, int n,其中 llstart用于标示栈顶元素,n标示堆栈中总共有几个元素。Stack支持以下操作:

        a)         默认构造函数

        b)        析构函数

        c)         清空函数Empty

  d)        压栈操作Push

  e)         弹栈操作Pop

  f)          获取栈顶元素的值GetTop,该函数是inline型的

  g)         获取堆栈中第n个元素的值GetNo,起始号为0,栈顶元素号为n-1

  h)        对堆栈中每个元素进行统一处理DoForAll(void(*process)(t))

  i)           对堆栈中每个元素进行统一处理DoForAll(void(*process)(t, void*), void* arg)

  j)          计算堆栈中元素的个数Len,该函数也是inline型的

  k)         判断堆栈是否为空IsEmpty,该函数是inline型的

 

另外,还重载了两个友元的操作符 <<,用于Push和Pop操作:

  a)         用于Push操作,重载<<操作符operator << (Stack<t>& stack, t node),该函数是inline型的

  b)        用于Pop操作,重载<<操作符operator << (t& node, Stack<t>& stack),该函数也是inline型的

 

下面我们重点分析vm.h和vm.cpp有关虚拟机类声明和实现的两个文件。

vm.h文件中首先定义了一个枚举类型的操作码:

操作码

说明

OP_NOP

0

无操作

OP_PUSH

1

压栈

OP_GETTOP

2

获取栈顶元素值

OP_DISCARD

3

弹栈

OP_PRINT

4

打印

OP_INPUT

5

输入

OP_JMP

6

无条件跳转

OP_JMPF

7

如果为非,则跳转

OP_STR_EQUAL

8

检测两个字符串是否相等

OP_BOOL_EQUAL

9

检测两个布尔型量是否相等

OP_CONCAT

10

连接两个字符串

OP_BOOL2STR

11

布尔型转换字符串型

JUMPTARGET

12

非操作码,用于跳转

 

         紧接着定义了一个指令类Instr,该指令类中有两个成员变量:操作码opcode和操作数operand。另外,还定义了三个构造函数。

 

         然后就是定义了虚拟机类VMachine。VMachine中定义了四个私有型的成员变量:

成员变量

说明

备注

Instr* instr

记录虚拟机中的指令

对应于开头说的指令数组

int   ninstr

记录指令的数目

 

String* str[MAX_STR]

字符串表,MAX_STR=100

对应于开头说的字符串表这里的字符串表相当于内存

VM_Stack stack

堆栈

对应于开头说的堆栈

 

         VMachine中定义了四个用于字符串拷贝的私有型成员函数NewTempCopy()、NewTempCopy(int j)、NewTempCopy(char* s)、DelTempCopy(int i)。

 

         下面我们分析VMachine中的五个公有型函数,其中主要是三个接口函数Read、Execute、Reset。

         构造函数VMachine

         析构函数~VMachine

         重新初始化函数Reset:将字符串表str中的元素全部清空并置为0,将指令数组instr清空,将堆栈stack清空

         读取指令函数Read:这里是预先设定了指令。首先对字符串表str进行赋值,然后对指令数组instr进行赋值。该函数中没有对堆栈stack进行操作。Read对str和instr操作的说明如下,首先对字符串表str进行存储数据:

   str[0] = new String("Answer to the Ultimate Question"

      " of Life, the Universe and Everything > ");

   str[1] = new String;

   str[2] = new String("42");

   str[3] = new String("Right!\n");

   str[4] = new String("Wrong!\n");

         然后对指令数组instr进行指令编排:

   ninstr = 11;                       

   instr = new Instr[ninstr];

   instr[0] = Instr (OP_PUSH,  0);                 str中第0个字符压栈,实际压的是5,因为调用了NewTempCopy

   instr[1] = Instr (OP_PRINT);                    将栈顶元素输出,并将str[5]清空

   instr[2] = Instr (OP_INPUT, 1);                 str[1]进行输入

   instr[3] = Instr (OP_PUSH,  1);                 str[1]压栈,实际压的是5

   instr[4] = Instr (OP_PUSH,  2);                 str[2]压栈,实际压的是6

   instr[5] = Instr (OP_STR_EQUAL);                将栈中元素两次弹栈,并比较两个值是否相等

   instr[6] = Instr (OP_JMPF,  3);                  如果相等,不相等则前进3条指令

   instr[7] = Instr (OP_PUSH,  3);                  str[3]进行压栈,实际压得是5

   instr[8] = Instr (OP_JMP,   2);                  前进2条指令

   instr[9] = Instr (OP_PUSH,  4);                  str[4]进行压栈,实际压得是5

   instr[10]= Instr (OP_PRINT);                     将栈顶元素输出,并将str[5]清空

 

         以上指令的功能是首先将str[0]进行输出,然后对str[1]进行赋值,如果str[1]被赋的值等于str[2]则输出str[3],如果str[1]不等于str[2],则输出str[4]。

         上述指令的执行情况为:

         从执行结果上,我们可以看出PUSH压栈操作实际压得str索引是NewTempCopy新生成的索引,而非源索引。

 

         执行指令函数Execute:根据OP_*操作码,利用switch-case语句进行相应的执行。处理的巧妙之处在于设定了指令计数器ip和指令递增量ipc,根据ip进行相应指令的执行。

         有关Execute代码中存在一个问题:该函数内部自身有重新定义了一个VM_Stack stack,而不是用的VMachine中的成员stack,个人认为函数内部重新定义的stack是多余的,应将其删除。

 

         以上就是对vm.h和vm.cpp的分析。现在就剩下最后一个文件:main.cpp测试代码。首先定义一个VMachine的对象,然后调用Read函数进行相关内存数据的载入和指令的输入。Read完后就是Execute了。

         源程序中Read函数中是预先定义好了指定的内存数据载入和指令编排。可以修改Read函数从而实现手动收入内存数据载入和指令输入。

 

       有关虚拟机的相关思考

         在《简单虚拟机》中,我们介绍了一个基于寄存器的虚拟机实现;本文中我们对一个基于堆栈的虚拟机实现进行了源代码剖析。下面谈谈个人对虚拟机的相关理解。

         我们这里谈的虚拟机可以说是最为上层的虚拟机,是程序实现层的虚拟,用于虚拟一个程序的运行平台或是环境。程序运行无非是需要数据和指令两部分(对应于操作数和操作码)。一个虚拟机的实现,不是是基于寄存器的,还是基于堆栈的,这里的基于什么,实质上是实际的操作运算在哪里进行。也就是说基于寄存器的意思是,实际的操作需要像是在寄存器里操作那样进行,需要遵循寄存器操作的规则;基于堆栈的意思是,操作需要像是在堆栈中那样进行,需要遵循堆栈操作的规则。(我们这里的基于寄存器和基于堆栈都是虚拟的,程序的实际实现还是在内存中进行的,所谓寄存器和堆栈都是相对的概念)。

         在实现基于寄存器和基于堆栈的虚拟机时,都需要有“内存”这个东西,内存用于载入和存储“程序”所需的数据,以备指令来操作处理。指令具体操作的场所在“寄存器”或“堆栈”中,从内存与寄存器或堆栈之间的数据来往对应于Load/Store和Push/Pop操作。内存相当于仓库,用于存储数据。寄存器和堆栈是数据的加工地点。

         通过以上分析和总结,我们可以归纳实现一个虚拟机需要以下几部分:

         1.内存,用于存储代加工或已加工数据

         2.数据加工地点,可选用寄存器也可选用堆栈等

         3.指令集,根据数据加工地点选择,涉及不同的指令,主要涉及内存到数据加工地点的指令、数据加工地点到内存的指令、输入指令、输出指令、算术运算指令、跳转指令等

         4.一小段测试指令,用于验证虚拟机设计和实现是否正确合理

 

         通过以上的讨论,我们对相关虚拟机的知识有了进一步的理解和体会。虚拟机方面的内容还有能多东西需要学习和研究。

         本文我们主要是通过对一栈虚拟机源码剖析,学习了虚拟机相关实现细节和技巧。下一步需要自己手动实现一个不需要自定义string和stack,直接利用库提供的string和stack的基于堆栈的虚拟机,然后再根据自定义的string和stack实现另一个版本的基于堆栈的虚拟机。

另外,虚拟机还包括很多内容,比如存储分配allocation schemes、垃圾收集garbage collection、虚拟机的稳定和高速等等,有待我们进一步学习。

posted on 2013-09-23 22:55  unixfy  阅读(1776)  评论(0编辑  收藏  举报

导航