听说你想写个虚拟机(二)?

上一篇文章,我们实现了一个最小的虚拟机,但是它还不太完善。今天,我们在原来的基础上继续添砖加瓦,变得更加有血有肉。

新增功能如下:

  1. 实现更多指令,比如 MOV、SUB、DIV、MUL、LOGR、IF、SET、LDR、STR
  2. 引入寄存器。
  3. 实现条件跳转,也就是上面的 IF 指令。

寄存器定义

寄存器在硬件中的功能是用于存取数据。这里呢,我们使用数组来模拟。照葫芦画瓢,定义如下几种寄存器。

  • 通用寄存器:A、B、C、D、E、F。
  • IP 寄存器:IP。
  • 栈顶指针寄存器:SP。

具体定义如下:

// 寄存器类型定义
typedef enum
{
  A,B,C,D,E,F,      // 通用寄存器
  IP,               // IP 寄存器
  SP,               // 栈顶指针寄存器
  NUM_OF_REGISTERS
} Registers;

// 寄存器
int registers[NUM_OF_REGISTERS];

NUM_OF_REGISTERS 是一个取巧的设计,可以很方便的知道有多少个寄存器。

那么获取某个寄存器的方式就很简单了,registers[r]r 是寄存器下标。比如获取 A 寄存器,使用 registers[A] 即可。

另外,为了方便存取 IP、SP,将其定义为宏,简化处理。

#define sp (registers[SP])
#define ip (registers[IP])

同样,将 sp 初始化为 -1ip 初始化为 0

//  初始化寄存器
sp = -1;
ip = 0;

新增指令定义

新增下图中的几种指令:

指令定义

指令实现

有了上一篇的基础后,要完成这几个指令应该是信手拈来。下面,我们一个个来实现。

SET

SET 主要用于给某个寄存器赋值。分 2 步走:

  1. 取出两个参数,分别是寄存器下标和数据。
// 寄存器下标
int r = program[++ip];

// 值
int value = program[++ip];
  1. 更新寄存器的值。
registers[r] = value;

MOV

MOV 用于寄存器之间的赋值。分三步走:

  1. 取出 2 个参数,目标寄存器和源寄存器。
// 目的寄存器
int dr = program[++ip];

// 源寄存器
int sr = program[++ip];
  1. 取出源寄存器的值。
// 源寄存器的值
int sourceValue = registers[sr];
  1. 赋值给目的寄存器。
registers[dr] = sourceValue;

SUB

SUB 用于取出栈中的两个数相减,将结果放回栈中,同时也保存到寄存器 A 中。分 4 步走:

  1. 取出栈中的两个数。
// 从栈中取出两个数
int a = stack[sp--];
int b = stack[sp--];
  1. 计算相减结果,注意顺序。
int result = b - a;
  1. 结果放回栈中。
// 入栈
stack[++sp] = result;
  1. 结果放到寄存器 A 中。
registers[A] = result;

DIV

DIV,除法指令。操作步骤同 SUB。

注意被除数为 0 的情况。

// 从栈中取出两个数,相除,再 push 回栈
int a = stack[sp--];
int b = stack[sp--];

if (a != 0)
{
  int result = b / a;

  // 入栈
  stack[++sp] = result;
  registers[A] = result;
}
else
{
  printf("exception occur, divid 0 \n");
}

MUL

乘法指令,步骤同 SUB。

LOGR

LOGR 用于打印寄存器的值。实现也很简单,分 2 步走:

  1. 取出寄存器的值。
// 寄存器下标
int r = program[++ip];

// 寄存器的值
int value = registers[r];
  1. 调用 printf 进行打印。
printf("log register_%d %d\n", r, value);

STR

STR 相当于存储指令,将指定寄存器中的数据放入栈中。分 2 步走:

  1. 取出寄存器参数。
int r = program[++ip];
  1. 将寄存器中数据放入栈中。
stack[++sp] = registers[r];

LDR

LDR 相当于取数据指令,将栈顶数据放入指定寄存器中。分 2 步走:

  1. 取栈顶数据和寄存器,但栈顶指针不变。
// 只取栈顶数据,sp 指针不变
int value = stack[sp];

// 取出寄存器参数
int r = program[++ip];
  1. 将数据放入寄存器中
registers[r] = value;

IF

唯一有些不同的就是 IF 条件跳转指令。当满足跳转条件时,会直接跳转到新指令处。

前面的实现中,在执行完某个条指令后,会默认指向下一条指令。但如果要进行跳转,ip 就不能再继续加 1,只需将 ip 设为新指令下标即可。

所以呢,需要额外添加变量 is_jump 来标识是否要执行跳转操作。在取指令的 while 循环中,判断若不是跳转操作,则 ip++,继续指向下一条指令。

while (running)
  {
    int instr = program[ip];
    eval(instr);

        // 非跳转,才 +1
    if (!is_jump)
    {
      ip++;
    }
  }

IF 指令的处理,分为下面几个步骤:

  1. 取出前两个参数,分别是寄存器下标和待比较的数据。
// 寄存器下标
int r = program[++ip];

// 要比较的值
int value = program[++ip];
  1. 判断寄存器中的数据与数值 value 是否相等。如果相等,则进行下面的操作。
  • 取出新指令下标,并更新 ip 寄存器的值。
  • 更重要的一点,更新标识 is_jump,表示是跳转操作。
if (registers[r] == value)
{
  // 新指令下标
  ip = program[++ip];

  // 更新为跳转操作
  is_jump = true;
}

可能有同学会疑惑,当 is_jump 更新为 true 之后,什么时候还原呢?很简单啦,在执行每条指令之前,将其置为 false 就好了。

至此,新增的功能就全部实现了。完整代码可点击文末链接 2 查看

总结

这篇文章,我们主要完成了寄存器的定义、几个新指令的实现,其中还包括 IF 跳转指令。希望对你有帮助。

下一篇文章,将会进入更高阶的玩法,写一个更贴近真实定义的虚拟机。包括不限于:

  • 指令定义标准化,操作码+操作数
  • 指令的解析与执行
  • 丰富常见指令集,比如逻辑运算、内存读写
  • 引入标志寄存器
  • 指令以二进制存储
  • 中断处理
  • ...

准备好了吗?让我们迎着微光出发吧~

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,093评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,870评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,133评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,638评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,652评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,173评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,560评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,512评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,048评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,088评论 3 344
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,232评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,856评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,548评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,020评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,152评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,729评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,255评论 2 363

推荐阅读更多精彩内容