跟我一起写个虚拟机 .Net 7(一)

主要参考和借鉴 silan-liu 微微笑的蜗牛 的 《听说你想写个虚拟机》系列

最近《中文编程语言——青语言》发布了,我表示即震惊又惭愧,几年前就开始准备也想自己写个中文编程,奈何每次自己都想的大而全导致遗落在某个角落。

在看了青语言的源码后,我感觉我又可以了。

为了避免自己好高骛远,还是从最底层的来,不要想着一下就搞定,可以多借鉴和学习已有的模式,从而真正积累起来。

最起码我还在路上。

虚拟机

虚拟机就像名字说的那种,它就是一个虚拟机,像VMware一样的虚拟机,但是,这个系列写下来,顶多写一个类似C#运行时(CLR) 或者 java的虚拟机(JVM)。

当然,肯定比CLR小很多就是了,主要还是偏向虚拟机 VMware的概念,偏向于用软件来模拟硬件的CPU,寄存器,堆栈等设备信息,来实现指令的执行,这样的虚拟机。

还是比较基础的,对计算机编译基础想有简单动手能力的,可以跟我一起,我动手能力有点差,正好适合我。

依稀记得在大学时期用汇编写51的时钟案例了,可惜太早了,都忘求了,现在,慢慢捡起来。

同时,也能让你理解一门语言是如何实现跨平台的(实际上就是每个平台运行了相应的虚拟机 [ 运行时/解释器 ] )

今天的任务

主要是通过最少的指令来实现一个加法,并发结果输出到控制台上。

大概有以下几个指令

指令集
/// <summary>
/// 指令集
/// </summary>
public enum InstructionSet
{
    /// <summary>
    /// PUSH 5; 
    /// 将数据放入栈中
    /// </summary>
    PUSH,
    /// <summary>
    /// 加法
    /// 取出栈中的两个操作数,执行加法操作,然后,放入栈中
    /// </summary>
    ADD,
    /// <summary>
    /// 取栈顶数据
    /// </summary>
    POP,
    /// <summary>
    /// 虚拟机停止运行
    /// </summary>
    HALT
}
指令集

这里有一个重点,我提一下那就是指令集是CPU带的,比如ARM指令集,Intel 指令集,以及RISC(精简指令集)。

指令集对应的其实就是汇编集,比如 ARM汇编,指令(机器码)与汇编(指令码)是一一对应的。

指令就是CPU对外提供的它能支持的各种操作,比如,加减乘除,跳转,判断等,而CPU与CPU之间是有差异的,它们对外提供的指令集不同。但是,大部分是相似的。

所以,真正执行指令的是CPU,而映射到虚拟机这个概念,执行的就是虚拟机本身。

目前提供了4条最简单的指令。

栈结构

栈结构很容易理解,相对于队列来讲。队列是先进先出。

5eabb77e944039aee30206eda8835cfe.png

从图上可以看到,从左侧进入,右侧出去,里面是有序的,从右到左排列,就像去超市结账排队一样。

栈是后进先出。

7fd05c28f42a179fb7c77f7b74b42413.png

从口里进去,然后,从口里出去,就像堆了一堆砖头,你总得从最上边把砖头拿走(最下边的不好抽出来)。

理解了堆栈的结构,就有助于理解,接下来的操作了。

逻辑

PUSH,就是堆栈的进操作。堆栈指针要在执行后+1;(累加),它有一个操作数,比如:PUSH 5 ; 就是把5压到堆栈上。

POP,就是堆栈的出操作。堆栈指针要在执行后-1;(累减),它只能获取一个数据,比如:POP ; 就弹出栈顶的数据。

ADD,就是对加数与被加数进行加操作,它只能从栈里获取两个数据,并把结果PUSH到堆栈上。

HALT ,没有操作数,就是任务执行完毕的意思。

寄存器相关

/// <summary>
/// 指令指针寄存器
/// </summary>
public int IP { get; private set; } = 0;
/// <summary>
/// 栈指针寄存器
/// </summary>
public int StackPointer { get; private set; } = -1;
/// <summary>
/// 栈
/// </summary>
public int[] Stack { get; private set; } = new int[256];

主要是IP,指令指针寄存器和栈指针寄存器以及我们所定义的栈。

控制指令指针IP,就可以让CPU(虚拟机)从所设定的位置,继续往下执行我们给它的指令集合(程序)。

而,栈指针寄存器(sp)是控制和表现Stack栈结构的辅助索引。

虚拟机相关

public class VM
{
    public bool Runing { get; private set; }
    /// <summary>
    /// 指令指针寄存器
    /// </summary>
    public int IP { get; private set; } = 0;
    /// <summary>
    /// 栈指针寄存器
    /// </summary>
    public int StackPointer { get; private set; } = -1;
    /// <summary>
    /// 栈
    /// </summary>
    public int[] Stack { get; private set; } = new int[256];
    public void Run(int[] Instructions)
    {
        Start();
        while (Runing)
        {
            int instruction = Instructions[IP];
            Eval((InstructionSet)instruction, Instructions);
            IP++;
        }
        Console.WriteLine("VM 虚拟机 退出运行!");
    }
    private void Eval(InstructionSet instruction, int[] Instructions)
    {
        switch (instruction)
        {
            case InstructionSet.HALT:
                Runing = false;
                Console.WriteLine("虚拟机停止");
                break;
            case InstructionSet.PUSH:
                StackPointer++;
                ++IP;
                Stack[StackPointer] = Instructions[IP];
                break;
            case InstructionSet.POP:
                int popValue = Stack[StackPointer];
                StackPointer--;
                Console.WriteLine($"poped {popValue}");
                break;
            case InstructionSet.ADD:
                //从栈中取出两个操作数,相加,然后,保存在栈中
                int a = Stack[StackPointer];
                StackPointer--;
                int b = Stack[StackPointer];
                StackPointer--;
                int sum = a + b;
                StackPointer++;
                Stack[StackPointer] = sum;
                break;
        }
    }
    public void Start()
    {
        Runing = true;
        IP = 0;
        Stack = new int[256];
        StackPointer = -1;
    }
}

整体逻辑就是,先初始化,start,恢复初始环境,相当于重启,然后,根据程序,依次往下执行,直到遇到停止的指令,才会退出执行。

主函数

static void Main(string[] args)
{
    VM VM = new VM();
    var program = new List<int>()
    {
        (int)InstructionSet.PUSH,5,
        (int)InstructionSet.PUSH,6,
        (int)InstructionSet.ADD,
        (int)InstructionSet.POP,
        (int)InstructionSet.HALT
    };
    VM.Run(program.ToArray());
    Console.ReadLine();
}

因为我的操作码是枚举来的,所以,在C#里需要转码一下,当然,后期的操作码和操作数应该能合成一条指令,类似字节码。

program就是定义的程序(汇编),压入了5,压入了6,执行加法,然后,弹出结果,停止虚拟机的执行。

结果如下

02fbec690442605fb85c79ce46dceb64.png

弹出了结果,11,结果正确。

然后,依次各个退出,顺利完成极简版的虚拟机。

敬请期待下篇《跟我一起写个虚拟机 .Net 7(二)》

代码地址

https://github.com/kesshei/VirtualMachineDemo.git

https://gitee.com/kesshei/VirtualMachineDemo.git

致谢

我主要是看 silan-liu 微微笑的蜗牛 的 《听说你想写个虚拟机》系列。

作者写的真不错,有喜欢的,可以去支持一下。

一键三连呦!,感谢大佬的支持,您的支持就是我的动力!

刚学C#时就一直想找到一种方法可以让.Net程序在未安装framework的电脑上运行,但一直没有找到真正可用的。虽然有些公司发布了可以将.net代码编译成navtive代码以脱离.net环境运行,如Remotesoft DOTNET Linker,Xenocode Postbuild等,但一直没有破解版,用它们编译的程序每次运行会添出个版权信息。最近偶然发现一种方法可以做到真正使程序脱离.net环境运行且没有任何版权信息添出,现将使用方法、源代码、测试程序发布于此: 请首先在文章末尾下载测试程序,是一个用C#做的串口工具,下载解压后有两个文件夹(FrameWork和APP)和一个程序(串口工具.exe),在APP文件夹下有一个名为9527.exe的程序,这个是C#真正的“串口工具.exe”,而主目录下的串口工具.exe只是一个Loader程序,它运行后首先判断电脑上有没有安装framework,若有,则直接启动APP下的9527.exe程序;若无,则通过framework虚拟机(FrameWork下的VM.exe(其实是飞信框架里的FetionVM.exe,修改了它的图标和版权,想改成其它的大家可以用VC改一下,有网友强列要求指出这一点,想不明白为什么,呵呵,让指出就指出吧,还要求指出调用的方法,,其实我连源程序都公布了,用说的这么明白吗))启动9527.exe程序。Loader程序不会打开任何窗口,并在启动真正应用程序后立即退出。Loader程序主函数(VC6.0编写)的代码附件上有
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值