龙空技术网

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

opendotnet 572

前言:

目前兄弟们对“c语言编写虚拟机程序代码”大致比较关怀,小伙伴们都需要了解一些“c语言编写虚拟机程序代码”的相关文章。那么小编在网摘上网罗了一些关于“c语言编写虚拟机程序代码””的相关知识,希望我们能喜欢,朋友们一起来学习一下吧!

虚拟机,我一直在路上。

之前写了《跟我一起写个虚拟机 .Net 7(一)》,之前有一些没有太明白的小细节,也琢磨明白了,实际上,所牵涉的基础概念是很多的。

比如,基于栈的虚拟机和基于寄存器的虚拟机有什么区别?

什么是图灵机,什么是图灵完备,以及什么是冯诺依曼机,它们之间有什么区别?

以及编译部分的前端和后端分别是什么,也会在后边的序列里,循序渐进的逐步了解。

基于栈的虚拟机与基于寄存器的虚拟机有什么区别?

基于栈的虚拟机就是《跟我一起写个虚拟机 .Net 7(一)》里的stack数组里保存的栈数据,另外像PUSH指令以及POP指令,就是基于栈的虚拟机的常规用法。

举个例子,有以下代码

 int a = 1;

int b = 2;

int c = a + b;

[基于栈] 对应的IL 字节码(C# 字节码)
.locals init(

[0] int32 a,

[1] int32 b,

[2] int32 c

)

IL_0001: ldc.i4.1 //将整数值 1 作为 int32 推送到计算堆栈上

IL_0002: stloc.0 //从计算堆栈顶部弹出当前值,并将其存储在索引 0 处的局部变量列表中。

IL_0003: ldc.i4.2

IL_0004: stloc.1

IL_0005: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上

IL_0006: ldloc.1

IL_0007: add //将两个值相加并将结果推送到计算堆栈上

IL_0008: stloc.2 //从评估堆栈顶部弹出当前值,并将其存储在索引 2 的局部变量列表中。

[基于栈] 对应的JAVA 字节码(bytecode)
stack=2, locals=4, args_size=1

0: iconst_1

1: istore_1

2: iconst_2

3: istore_2

4: iload_1

5: iload_2

6: iadd

7: istore_3

8: iload_3

9: ireturn

[基于寄存器] 对应C语言的汇编(ASM)
mov dword ptr [a],1 

mov dword ptr [b],2

mov eax,dword ptr [b]

mov ecx,dword ptr [a]

add ecx,eax

mov eax,ecx

mov dword ptr [c],eax

[基于寄存器] lua语言字节码(bytecode)
0 params, 3 slots, 0 upvalues, 3 locals, 0 constants, 0 functions

1 [2] LOADI 0 1

2 [3] LOADI 1 2

3 [4] ADD 2 0 1

4 [4] MMBIN 0 1 6 ; __add

5 [5] RETURN1 2

3 slots 意思就是用了三个寄存器

那么基于栈与基于寄存器大致的优缺

为了避免寄存器的兼容性(物理机寄存器数量有限,各个CPU数量不一致),实现跨平台等通用型会采用基于栈的结构,为了让栈的结构更合理,会增加整体的指令集,相对寄存器的指令集就会多一些,所以,基于栈的效率也相对低一些。

Lua实际上也是用的虚拟寄存器结构,并不是真实用的物理级的寄存器。所以,它相对性能高一些。

所以,针对虚拟机级别的也可以采用类似虚拟寄存器的方式来降低指令集的数量来降低次数,从而提升执行效率。

当然,还有更多的点,就需要多学,多看,多思考了。

什么是图灵机,什么是图灵完备,以及什么是冯诺依曼机,它们之间有什么区别?图灵机

图灵机是艾伦·图灵提出的一种计算模型,即,一切计算问题都可以被机器所计算解决。

概念图模型实物图

就像上边的两张图一样,整体原理结构如下

1. 它有一条无限长的纸带(就像老式的磁带机,里面有长长的黑棕色长带子,小时候经常玩)

2. 带字上有紧挨着的一个一个的小方格(有限的指令或者数据)

3. 有一个机器头在纸袋上移来移去(会根据小方格的指令移动到指定的位置继续移动)

4. 这个机器头自己会维持一个内部的状态

很简单的方式就描述了计算机的本质原理。

图灵完备

图灵完备的大致意思就是具备图灵机概念的任何指令集或者程序或者逻辑,即能执行任何可计算问题时,它就是图灵完备的。

具体图灵机的整体原理结构的,就是图灵完备。图灵完备只保证计算的可行性。

一切都为了解决计算而生 。

图灵测试

图灵测试是用来判断机器是否具有智能的方法。

大概方法是说,通过某种方式进行交流(文本,语音,等等),如果你感觉不到对方是机器人,那么,机器就具有智能。

从神经网络算法和深度学习等技术来的时候,实际上智能已经具备了,目前openai的来临,让智能达到了一种前所未有的高度,虽然,还有很多问题待解决。

那么,机器的智力该如何表现,幼儿,青年,中年? 毕竟,它已经有了全人类的知识。

冯诺依曼机

如果说图灵机偏向原理和概念,那么,冯诺依曼机就偏向于结构化,变相的来说,是对图灵机原理的归纳和总结。

大致分成五个部分(规定了指令和数据要使用二进制)

1. 运算(加减乘除)

2. 控制 (类似图灵机的机器头,IP)

3. 存储 (硬盘,内存)

4. 输入 (键盘,鼠标,摄像头)

5. 输出 (屏幕,音响)

如下图

可以明显的看到,跟图灵机的结构还是很相似的。

图灵机与冯诺依曼机的主要区别

一个偏向理论和概念,一个偏向工程化实现,能直接落地。

整体结构也能如下函数表示

y = F(x);

输入了一个x,然后,根据F的内部的处理不同,输出你想要的y。 F就是控制和运算(计算器,控制头),x是输入,y是输出(保存到指定的地方就叫存储)

至此,整个计算的概念和模型都已经有了一个初步的了解,更多的信息,还需要自己多了解。

送个图:

接下来,我们继续基于系列的学习。

多增加指令

实际上我在原有文章的基础上新增了一个JUMP指令,要不然,无法实现简单的if效果。 本来还想实现标签跳转,后来想想,目前是不需要的。

指令集如下

/// <summary>

/// 指令集

/// </summary>

public enum InstructionSet

{

/// <summary>

/// PUSH 5;

/// 将数据放入栈中

/// </summary>

PUSH,

/// <summary>

/// 取栈顶数据

/// </summary>

POP,

/// <summary>

/// 给寄存器赋值

/// SET reg,3

/// </summary>

SET,

/// <summary>

/// MOV reg1,reg2

/// 将寄存器 reg2中的数据移动到 reg1中

/// </summary>

MOV,

/// <summary>

/// 加法

/// 取出栈中的两个操作数,执行加法操作,然后,放入栈中

/// </summary>

ADD,

/// <summary>

/// 减法

/// 取出栈中的两个操作数,执行减法操作,然后,放入栈中

/// </summary>

SUB,

/// <summary>

/// 除法

/// 取出栈中的两个操作数,执行除法操作,然后,放入栈中

/// </summary>

DIV,

/// <summary>

/// 乘法

/// 取出栈中的两个操作数,执行乘法操作,然后,放入栈中

/// </summary>

MUL,

/// <summary>

/// 存储指令,将寄存器的数据放入栈中

/// </summary>

STR,

/// <summary>

/// 将栈顶数据放入寄存器中

/// </summary>

LDR,

/// <summary>

/// IF reg, value,ip;

/// 如果reg中的值等于value,就把IP指针指向ip地址。

/// </summary>

IF,

/// <summary>

/// 打印寄存器中的数据

/// </summary>

LOGR,

/// <summary>

/// 无条件跳转

/// </summary>

JMP,

/// <summary>

/// 虚拟机停止运行

/// </summary>

HALT

}

目前所有指令集如下图:

引入了寄存器

都是通用寄存器,也没有增加别的,感觉也没用,不一定非得模拟物理寄存器

public enum Register

{

A,

B,

C,

D,

E,

F,//A-F通用寄存器

}

虚拟机VM里也增加了寄存器的定义

/// <summary>

/// 寄存器

/// </summary>

public int[] Registers = new int[Enum.GetNames(typeof(Register)).Length];

指令的实现
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;

case InstructionSet.SUB:

{

//从栈中取出两个操作数,相减,然后,保存在栈中

int a = Stack[StackPointer];

StackPointer--;

int b = Stack[StackPointer];

StackPointer--;

int sum = b - a;

StackPointer++;

Stack[StackPointer] = sum;

}

break;

case InstructionSet.MUL:

{

//从栈中取出两个操作数,相乘,然后,保存在栈中

int a = Stack[StackPointer];

StackPointer--;

int b = Stack[StackPointer];

StackPointer--;

int sum = a * b;

StackPointer++;

Stack[StackPointer] = sum;

}

break;

case InstructionSet.DIV:

{

//从栈中取出两个操作数,相除,然后,保存在栈中

int a = Stack[StackPointer];

StackPointer--;

int b = Stack[StackPointer];

StackPointer--;

if (a == 0)

{

Console.WriteLine("除数不能为0");

Runing = false;

}

else

{

int sum = b / a;

StackPointer++;

Stack[StackPointer] = sum;

}

}

break;

case InstructionSet.MOV:

{

IP++;

//将一个寄存器的值,放入到另外一个寄存器里

//目标寄存器

int dr = Instructions[IP];

IP++;

//源寄存器

int sr = Instructions[IP];

//目标寄存器的值为源寄存器的值

Registers[dr] = Registers[sr];

}

break;

case InstructionSet.STR:

{

IP++;

//将指定寄存器中的参数,放入栈中

int r = Instructions[IP];

StackPointer++;

Stack[StackPointer] = Registers[r];

}

break;

case InstructionSet.LDR:

{

int value = Stack[StackPointer];

IP++;

int r = Instructions[IP];

Registers[r] = value;

}

break;

case InstructionSet.IF:

{

//如果寄存器的值和后面的数值相等,则跳转

IP++;

int r = Instructions[IP];

IP++;

if (Registers[r] == Instructions[IP])

{

IP++;

IP = Instructions[IP];

IsJump = true;

Console.WriteLine($"jump if :{IP}");

}

else

{

IP++;

}

}

break;

case InstructionSet.SET:

{

IP++;

int r = Instructions[IP];

IP++;

int value = Instructions[IP];

Registers[r] = value;

}

break;

case InstructionSet.LOGR:

{

IP++;

int r = Instructions[IP];

int value = Registers[r];

Console.WriteLine($"log register_{(Register)r} {value}");

}

break;

case InstructionSet.JMP:

{

IP++;

IP = Instructions[IP];

IsJump = true;

Console.WriteLine($"jump if :{IP}");

}

break;

}

虽然不是最美化的,但是,调逻辑的话,不是问题,跟着逻辑走,就明白实际原理了。

代码程式

bool mark = false;//可以修改if条件

var result = 0;

if (!mark)

{

result = ((((10 + 4) - 2) * 6) / 5);

}

else

{

result = (((4 - 2) * 6) / 5);

}

Console.WriteLine($"所要达到的结果:{result}");

var program = new List<int>()

{

(int)InstructionSet.SET,(int)Register.F,mark ? 0:1,

(int)InstructionSet.IF,(int)Register.F,0,14,

(int)InstructionSet.PUSH,10,

(int)InstructionSet.PUSH,4,

(int)InstructionSet.ADD,

(int)InstructionSet.JMP,16,

(int)InstructionSet.PUSH,4,

(int)InstructionSet.PUSH,2,

(int)InstructionSet.SUB,

(int)InstructionSet.PUSH,6,

(int)InstructionSet.MUL,

(int)InstructionSet.PUSH,5,

(int)InstructionSet.DIV,

(int)InstructionSet.LDR,(int)Register.A,

(int)InstructionSet.LOGR,(int)Register.A,

(int)InstructionSet.SET,(int)Register.C,9,

(int)InstructionSet.LOGR,(int)Register.C,

(int)InstructionSet.MOV,(int)Register.B,(int)Register.C,

(int)InstructionSet.STR,(int)Register.B,

(int)InstructionSet.LDR,(int)Register.D,

(int)InstructionSet.HALT

};

程序中包含了两种,一种是代码逻辑的表达式实现,另外一种是虚拟机指令集的代码实现。 通过两个结果来印证虚拟机运行的逻辑是正常的。

结果如下

条件为false的情况下的结果

条件为true的情况下的结果

可以看到所要达到的结果跟寄存器A里的结果是一致的,而寄存器A里存的就是我们的结果。

下篇预告

敬请期待下篇《跟我一起写个虚拟机 .Net 7(三)》,会更加的完整以LC-3 教学的汇编语言为模版示例。

代码地址

引用

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

标签: #c语言编写虚拟机程序代码