美文网首页程序员
虚拟机VM ( java 版本)(1)

虚拟机VM ( java 版本)(1)

作者: zidea | 来源:发表于2019-05-09 11:36 被阅读31次

    今天通过学习来尝试自己去写一个虚拟机,我们一切先从简单开始。做事总是需要找一个原因,也就是为什么做这个件事,做这件事的意义,发明蒸汽机为了人类从繁重的劳动中解放出来更多思考,发明计算机为了辅助人类从重复计算和繁重计算中解放出来去享受生活吗? 结果带来负效应 996。

    virtual-machind.jpg

    CLR常用简写词语,CLR是公共语言运行库(Common Language Runtime)和Java 虚拟机一样也是一个运行时环境,它负责资源管理(内存管理和垃圾收集等),并保证应用和底层操作系统之间必要的分离。CLR存在两种不同的翻译名称:公共语言运行库和公共语言运行时。

    VM 的基本结构

    stack vm 结构图

    虚拟机实际上对物理计算机进行抽象和虚拟化,所以要写一个虚拟机,首先需要对计算机内部简单结构进行了解。我们开发应该是理论先行。

    • 还要一个主要方法
    • 内存
      • 数据内存(data memory)
      • 指令内存(code memory)
        这两个部分都是用于存储数据和源码内存

    字节码相比于机器码是更高级以及更有有规律可寻
    字节码作为工作的最小单元

    • fetch : fetch 不断循环地执行读取 data memory 和 code memory 获取源码
    • decode : 负责将字节码进行解码为指令供 execute 执行
    • 寄存器(registers)中保存了 sp 栈指针、fp 栈帧指针和 ip 程序指针
      通过 ip 指针的自增一行一行读取字节码然后由 decode 将字节码翻译成指令来执行
    • 栈虚拟机
      • 操作数栈持有操作数
      • 操作数可以入栈或出栈进行操作
      • 栈的大小是由每一个方法显式确定的
    public class HelloWorld {
        public static void main(String[] args) {
            System.out.println("hello world");
        }
    }
    

    定义字节码

    vm_instruction.JPG

    表中列出应该是助记符应该理解为字节码的别名,字节码应该是以字节为单位的 16进制的数值,为了便于阅读这些字节码将字节码对应别名。

    • 例如 iadd 为表示对 inter 型进行加法
    vm_function.JPG

    这张图要说起来还是比较复杂的,应该说相当复杂了,大家需要理解一下什么是栈帧。动画也好电影也好都是由一帧一帧图所组成的。这样按一定循序不断切换的图片最终形成了我们看到的精彩的电影。

    栈帧也是我们程序的组成部分,很少有像我这样了解 CG 的程序员吧,所以我们的程序也是🈶一个一个画面来组成的,程序运行也是一个个按一定顺序,顺序不是固定的,根据用户的交互而定。所以栈帧里存放是一个一个的代码块,栈帧入栈时候会记录下调用这个栈帧的栈帧的信息,也就是当这个栈帧结束后他会返回到上一个栈帧。就是这样连续的或者说一层套一层的栈帧组成我们应用。

    通过字节码学习我们知道助记符是帮助我们对应记忆字节码,在 class 文件中只有两个字节的 16 进制字节码。我们先创建一个字节码类Bytecode

    public class Bytecode {
        public static final int IADD = 1;
        public static final int ISUB = 2;
        public static final int IMUL = 3;
        public static final int BR = 6;
        public static final int POP = 15;
        public static final int HALT = 16;
    }
    
    

    然后写我们的 VM

    • data 表示数据内存

    • code 代码内存

    • stack 栈

    • ip 寄存器指针

    • sp 栈指针

    • fp 栈帧指针

    
    import static vm.Bytecode.*;
    
    public class VM {
        int[] data;
        int[] code;
        int[] stack;
    
        int ip;
        int sp = -1;
        int fp;
    
        public VM(int[] code, int main, int datasize){
            this.code = code;
            data = new int[datasize];
            stack = new int[100];
        }
    
        public void cpu(){
            int opcode = code[ip]; //fetch
            ip++;
            switch (opcode){
                case HALT:
                    return;
            }
        }
    }
    
    • code[ip] 获取字节,然后根据分支去做不同操作。
    import static vm.Bytecode.*;
    
    public class Client {
        static int[] hello = {
                HALT
        };
    
        public static void main(String[] args) {
          VM vm = new VM(hello,0,0);
          vm.cpu();
        }
    }
    

    定义出所有字节码,Bytecode这个类提供字节码信息。

    public class Bytecode {
        public static final int IADD = 1;
        public static final int ISUB = 2;
        public static final int IMUL = 3;
        public static final int ILT = 4;
        public static final int IEQ = 5;
        public static final int BR = 6;
        public static final int BRT = 7;
        public static final int BRF = 8;
        public static final int ICONST = 9;
        public static final int LOAD = 10;
        public static final int GLOAD = 11;
        public static final int STORE = 12;
        public static final int GSTORE = 13;
        public static final int PRINT = 14;
        public static final int POP = 15;
        public static final int HALT = 16;
    }
    
    

    然后我们就可以运行我们创建好虚拟机,

    public class Client {
        static int[] hello = {
                ICONST, 99,
                PRINT,
                HALT
    
        };
    
        public static void main(String[] args) {
          VM vm = new VM(hello,0,0);
          vm.cpu();
        }
    }
    

    cpu 不断从 data 内存中读取数据进行指令的匹配,每一取出指令后将寄存器的指针下移一个位置。这样不断循环知道 data 中没有更多指令为止。

        public void cpu(){
            while (ip< code.length){
                int opcode = code[ip]; //fetch
                ip++;
                switch (opcode){
                    case ICONST:
                        break;
                    case PRINT:
                        System.out.println("hey vm");
                        break;
                    case HALT:
                        return;
                }
                opcode = code[ip];
            }
            ip++;
        }
    

    实现 cpu 的功能,首先是 fetch 动作从 code 根据寄存器指针 ip 获取字节码,上面的代码如果输入 PRINT 对应就可以在控制打印出 hey vm。

    我们 cpu 还需要有计算功能,如果指令(助记符为 ICONST)表示 int 类型数值进行入栈的操作,所以 stack[sp] = v; 并且下移一个指针。而 PRINT 命令是打印栈顶的值,所以在 PRINT 中会现有一个弹出动作然后进行打印。

        public void cpu(){
            while (ip< code.length){
                int opcode = code[ip]; //fetch
                ip++;
                switch (opcode){
                    case ICONST:
                        int v = code[ip];
                        ip++;
                        sp++;
                        stack[sp] = v;
                        break;
                    case PRINT:
                        v = stack[sp];
                        sp--;
                        System.out.println(v);
                        break;
                    case HALT:
                        return;
                }
                opcode = code[ip];
            }
            
        }
    

    添加跟踪功能,可以将 cpu 执行所有指令进行打印以便于观察。

    public void cpu(){
            while (ip< code.length){
                int opcode = code[ip]; //fetch
                if(trace){
                    System.out.printf("%04d: %d\n",ip,opcode);
                }
                ip++;
                switch (opcode){
                    case ICONST:
                        int v = code[ip];
                        ip++;
                        sp++;
                        stack[sp] = v;
                        break;
                    case PRINT:
                        v = stack[sp];
                        sp--;
                        System.out.println(v);
                        break;
                    case HALT:
                        return;
                }
                opcode = code[ip];
            }
    
        }
    
        public static void main(String[] args) {
          VM vm = new VM(hello,0,0);
          vm.trace = true;
          vm.cpu();
        }
    
    0000: 9
    0002: 14
    99
    0003: 16
    
        public static class Instruction{
            String name;
            int n = 0;
            public Instruction(String name){this(name,0)}
            public Instruction(String name, int nargs){
                this.name = name;
                this.n = nargs;
            }
        }
    

    走到这里我们只是迈出一小步,虽然一小步我们也是迈出去了,看是可笑简单的实现,甚至对开发暂时看没有什么帮助。不过处于喜欢和人类天生的好奇我们还是学了、弄了这个虚拟机。会继续弄下去,希望得到您的支持。


    developer

    相关文章

      网友评论

        本文标题:虚拟机VM ( java 版本)(1)

        本文链接:https://www.haomeiwen.com/subject/bhsznqtx.html