zoukankan      html  css  js  c++  java
  • 基于栈的虚拟机源码剖析

    基于栈的虚拟机源码剖析

             之前我们曾剖析过一个栈虚拟机《栈虚拟机源码剖析》,并实现了一个栈虚拟机《实现一个栈虚拟机》。

             本文我们对Kevin Lynx的《基于栈的虚拟机的实现》进行学习,学习其源码实现原理和技巧,其源码地址为:source code

             有关该基于栈的虚拟机说明,可以直接参考原文,我们不在此赘述。这里,我们主要是对源码进行分析学习。

             该虚拟机对应两个文件:头文件sm.h和源文件sm.c。

             其中,sm.h中定义了虚拟机的指令集(二进制指令集),该指令集为枚举类型:

    enum op_type
    
    {
    
        opHalt, opIn, opOut, opAdd, opSub, opMul, opDiv,
    
        opDup,
    
        opLd, opSt, opLdc, opJlt, opJle, opJgt, opJge, opJeq, opJne, opJmp,
    
        opInvalid
    
    };

             有关这些指令的说明,可以参考stack_machine.txt文件。

             另外,还定义了指令结构体:

    typedef struct Instruction
    
    {
    
        int op;
    
        int arg;
    
    } Instruction;

             Instruction结构体,包含两部分:指令码和操作数。指令码最多有一个操作数。

             此外,还包含了两个宏定义:

    #define CODE_SIZE (1024)
    
    #define DATA_SIZE (1024)

             sm.c文件中定义了堆栈:

    /* the operation stack */
    
    int op_stack[STACK_SIZE];
    
    int op_pos = 0;

             该堆栈op_stack是虚拟机处理数据的场所,op_pos是一个栈索引。堆栈元素类型为int型。

             紧接着定义了指令内存:

    Instruction i_mem[CODE_SIZE];
    
    int pc;

             pc相当于是指令指针寄存器。

    int d_mem[DATA_SIZE];

             d_men为数据内存。

    enum err_code
    
    {
    
        Halt, Okay, errDivByZero, errDMem, errIMem, errStackOverflow, errStackEmpty,
    
        errUnknownOp
    
    };

             err_code为枚举类型的错误码。这里不多作介绍。

    void error( const char *err )
    
    {
    
        fprintf( stderr, err );
    
    }

             error用于将错误信息输出。

    /* get the op code arg(operand) count */
    
    int get_operand_count( int op )
    
    {
    
        int ret;
    
        switch( op )
    
        {
    
        case opLdc:
    
        case opJlt:
    
        case opJle:
    
        case opJgt:
    
        case opJge:
    
        case opJeq:
    
        case opJne:
    
        case opJmp:
    
            ret = 1;
    
            break;
    
        default:
    
            ret = 0;
    
        }
    
        return ret;
    
    }

             get_operand_count函数用于返回指令int型指令op所对应的操作数个数,op最多需要一个操作数。

    int push_op_stack( int i )
    
    {
    
        if( op_pos >= STACK_SIZE )
    
        {
    
            error( "stack overflow" );
    
            return -1;
    
        }
    
        op_stack[op_pos++] = i;
    
        return 0;
    
    }

             push_op_stack用于压栈操作。

    int pop_op_stack()
    
    {
    
        if( op_pos == 0 )
    
        {
    
            error( "stack empty" );
    
            return POP_ERR;
    
        }
    
        return op_stack[--op_pos];
    
    }

             pop_op_stack用于弹栈操作。

    int top_op_stack()
    
    {
    
        if( op_pos == 0 )
    
        {
    
            error( "stack empty" );
    
            return POP_ERR;
    
        }
    
        return op_stack[op_pos-1];
    
    }

             top_op_stack用于返回栈顶元素值,不弹栈。

    #define INC_P( t ) codes+=sizeof(t); size-=sizeof(t)
    
    /* read codes into the i_mem */
    
    int read_instruction( const char *codes, int size )
    
    {
    
        int op_count, loc = 0;
    
        Instruction inst;
    
        while( size > 0 && loc < CODE_SIZE )
    
        {
    
            /* op is 1 byte in the code file */
    
            inst.op = *codes;  
    
            INC_P( char );
    
            op_count = get_operand_count( inst.op );
    
            if( op_count > 0 ) /* has arg */
    
            {
    
                inst.arg = *(int*) codes;
    
                INC_P( int );
    
            }
    
            else
    
            {
    
                inst.arg = 0;
    
            }
    
            i_mem[loc++] = inst;
    
        }
    
        return 1;
    
    }

             read_instruction函数用于从codes读取指令,其中INC_P(t)的定义用于修改读取指令时codes和size的值。将读取到的指令码和对应的操作数存入到i_mem Instruction数组中。

    int step_run()
    
    {
    
        Instruction *inst = &i_mem[pc++];
    
        int ret = Okay;;
    
        switch( inst->op )
    
        {
    
        case opHalt:
    
            {
    
                ret = Halt;
    
            }
    
            break;
    
        case opIn:
    
            {
    
                int i;
    
                printf( "input:" );
    
                scanf( "%d", &i );
    
                push_op_stack( i );
    
            }
    
            break;
    
        case opOut:
    
            {
    
                int i = pop_op_stack();
    
                printf( "output:%d
    ", i );
    
            }
    
            break;
    
        case opAdd:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                push_op_stack( b + a );
    
            }
    
            break;
    
        case opSub:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                push_op_stack( b - a );
    
            }
    
            break;
    
        case opMul:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                push_op_stack( b * a );
    
            }
    
            break;
    
        case opDiv:
    
            {
    
                int a = pop_op_stack();
    
                int b = pop_op_stack();
    
                if( a == 0 )
    
                {
    
                    return errDivByZero;
    
                }
    
                push_op_stack( b / a );
    
            }
    
            break;
    
        case opDup:
    
            {
    
                push_op_stack( top_op_stack() );
    
            }
    
            break;
    
        case opLd:
    
            {
    
                int addr = pop_op_stack();
    
                if( addr < 0 || addr >= DATA_SIZE )
    
                {
    
                    error( "data memory access error" );
    
                    return errDMem;
    
                }
    
                else
    
                {
    
                    push_op_stack( d_mem[addr] );
    
                }
    
            }
    
            break;
    
        case opSt:
    
            {
    
                int val = pop_op_stack();
    
                int addr = pop_op_stack();
    
                if( addr < 0 || addr >= DATA_SIZE )
    
                {
    
                    error( "data memory access error" );
    
                    return errDMem;
    
                }
    
                else
    
                {
    
                    d_mem[addr] = val;
    
                }  
    
            }
    
            break;
    
        case opLdc:
    
            {
    
                push_op_stack( inst->arg );
    
            }
    
            break;
    
        case opJlt:
    
            {
    
                int i = pop_op_stack();
    
                if( i < 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJle:
    
            {
    
                int i = pop_op_stack();
    
                if( i <= 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJgt:
    
            {
    
                int i = pop_op_stack();
    
                if( i > 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJge:
    
            {
    
                int i = pop_op_stack();
    
                if( i >= 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJeq:
    
            {
    
                int i = pop_op_stack();
    
                if( i == 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJne:
    
            {
    
                int i = pop_op_stack();
    
                if( i != 0 )
    
                {
    
                    pc = inst->arg;
    
                }
    
            }
    
            break;
    
        case opJmp:
    
            {
    
                pc = inst->arg;
    
            }
    
            break;
    
        default:
    
            ret = errUnknownOp;
    
        }
    
        return ret;
    
    }

             step_run函数用于执行当前指令。switch-case中虚拟机的二进制指令不作详细介绍,有关指令的定义,属于另一个专题讨论。

    void run()
    
    {
    
        int ret = Okay;
    
        while( ret == Okay )
    
        {
    
            ret = step_run();
    
        }
    
    }

             run函数用于根据step_run的执行结果,逐步执行i_mem中的指令。

    int file_size( FILE *fp )
    
    {
    
        int size;
    
        fseek( fp, 0, SEEK_SET );
    
        fseek( fp, 0, SEEK_END );
    
        size = ftell( fp );
    
        fseek( fp, 0, SEEK_SET );
    
        return size;
    
    }

             两个文件函数:fseek和ftell。

    函数

    原型

    功能

    参考

    fseek

    int fseek(FILE* stream, long offset, int fromwhere);

    设置文件指针stream的位置

    百度百科

    CPLUSPLUS

    ftell

    long ftell(FILE* stream);

    返回当前文件位置,返回FILE指针当前位置

    百度百科

    CPLUSPLUS

             file_size函数用于返回文件的大小。

    extern void dasm_output( const char *file, const Instruction *insts, int size );
    
     
    
    int main( int argc, char **argv )
    
    {
    
        FILE *fp;
    
        char *buf;
    
        int size;
    
        if( argc < 2 )
    
        {
    
            error( "Usage: SM <filename>" );
    
            exit( -1 );
    
        }
    
        fp = fopen( argv[1], "rb" );
    
        if( fp == 0 )
    
        {
    
            error( "Open file failed" );
    
            exit( -1 );
    
        }
    
        size = file_size( fp );
    
        buf = (char*) malloc( size );
    
        fread( buf, size, 1, fp );
    
     
    
        read_instruction( buf, size );
    
        if( argc > 2 )
    
        {
    
            int dflag = atoi( argv[2] );
    
            if( dflag )
    
            {
    
                dasm_output( argv[1], i_mem, CODE_SIZE );
    
            }
    
        }
    
        run();
    
        free( buf );
    
        fclose( fp );
    
        return 0;
    
    }

             main函数:如果参数个数argc小于2,则失败;否则读取argv[1]文件中的指令,如果argc大于2,则检测argv[2],如果为真,则将从argv[1]文件中读取出来的指令进行反汇编dasm_output,并将反汇编后的结果输出到argv[1]对应的反汇编文件中。

             进而,执行run函数,执行从argv[1]中读取出来的虚拟机指令。最后将buf释放,并且将文件指针fp关闭。

             总结

             综上所述,实现一个基于堆栈的虚拟机主要包含以下几个重要模块:

    模块

    说明

    虚拟机二进制指令集

    枚举类型。有关指令集如何定义,属于另一个话题

    指令结构体的定义

    指令码+操作数

    堆栈的实现

    是虚拟机执行指令时,数据处理的场所;op_pos为堆栈的栈顶索引

    指令内存

    Instruction i_mem[CODE_SIZE]; 用于存放待执行的指令;pc为指令指针寄存器

    数据内存

    int d_mem[DATA_SIZE]; 用于存放待处理和已处理的数据

    错误处理机制

    虚拟机各个环节的错误处理,错误类型可以定义为枚举类型

    读取指令

    从文件或终端读取虚拟机的二进制指令

    执行指令

    根据不同指令,进行相应的操作,这些操作发生在堆栈、指令内存、数据内存之间

    测试虚拟机

    从读取指令,到执行执行,测试

             以上是对实现一个基于堆栈虚拟机几个重要的模块说明,另外可以参考《实现一个堆栈虚拟机》中关于虚拟机原理的解析图

             以上是对基于栈的虚拟机的源码剖析,通过对虚拟机源码的学习,我们更进一步了解了虚拟机的实现原理,以及实现中几个重要的模块细节。下一步,我们将实现另一个版本的基于堆栈的虚拟机;另外,学习基于寄存器的虚拟机实现原理和实现一个基于寄存器的虚拟机。

  • 相关阅读:
    SDUT 2143 图结构练习——最短路径 SPFA模板,方便以后用。。 Anti
    SDUT ACM 1002 Biorhythms 中国剩余定理 Anti
    nyist OJ 119 士兵杀敌(三) RMQ问题 Anti
    SDUT ACM 2157 Greatest Number Anti
    SDUT ACM 2622 最短路径 二维SPFA启蒙题。。 Anti
    二叉索引树 区间信息的维护与查询 Anti
    SDUT ACM 2600 子节点计数 Anti
    UVA 1428 Ping pong 二叉索引树标准用法 Anti
    2010圣诞Google首页效果
    Object
  • 原文地址:https://www.cnblogs.com/unixfy/p/3358144.html
Copyright © 2011-2022 走看看