必要的准备工作及注意事项:
在开始之前需要做以下工作:
一个C编译器——我使用了 clang 3.4,也可以用其它支持 c99/c11 的编译器;
文本编辑器——我建议使用基于IDE的文本编辑器,我使用 Emacs;
基础编程知识——最基本的变量,流程控制,函数,数据结构等;
Make 脚本——能使程序更快一点。
为什么要写个虚拟机?
有以下原因:
想深入了解计算机工作原理。本文将帮助你了解计算机底层如何工作,虚拟机提供简洁的抽象层,这不就是一个最好的学习它们原理的方法吗?
更深入了解一些编程语言是如何工作。例如,当下多种经常使用那些语言的虚拟机。包括JVM,Lua VM,FaceBook 的 Hip—Hop VM(PHP/Hack) 等。
只是因为有兴趣学习虚拟机。
指令集
我们将要实现一种非常简单的自定义的指令集。我不会讲一些高级的如位移寄存器等,希望在读过这篇文章后掌握这些。
我们的虚拟机具有一组寄存器,A,B,C,D,E, 和F。这些是通用寄存器,也就是说,它们可以用于存储任何东西。一个程序将会是一个只读指令序列。这个虚拟机是一个基于堆栈的虚拟机,也就是说它有一个可以让我们压入和弹出值的堆栈,同时还有少量可用的寄存器。这要比实现一个基于寄存器的虚拟机简单的多。
言归正传,下面是我们将要实现的指令集:
PSH 5 ; pushes 5 to the stack
PSH 10 ; pushes 10 to the stack
ADD ; pops two values on top of the stack, adds them pushes to stack
POP ; pops the value on the stack, will also print it for debugging
SET A 0 ; sets register A to 0
HLT ; stop the program
这就是我们的指令集,注意,POP 指令将会打印我们弹出的指令,这样我们就能够看到 ADD 指令工作了。我还加入了一个 SET 指令,主要是让你理解寄存器是可以访问和写入的。你也可以自己实现像MOV A B(将A的值移动到B)这样的指令。HTL 指令是为了告诉我们程序已经运行结束。
虚拟机是如何工作的呢?
现在我们已经到了本文最关键的部分,虚拟机比你想象的简单,它们遵循一个简单的模式:读取;解码;执行。首先,我们从指令集合或代码中读取下一条指令,然后将指令解码并执行解码后的指令。为简单起见,我们忽略了虚拟机的编码部分,典型的虚拟机将会把一个指令(操作码和它的操作数)打包成一个数字,然后再解码这个指令。
项目结构
开始编程之前,我们需要设置好我们的项目。第一,你需要一个C编译器(我使用 clang 3.4)。还需要一个文件夹来放置我们的项目,我喜欢将我的项目放置于~/Dev:
$cd ~/Dev/
mkdir mac
cd mac
mkdir src
如上,我们先 cd 进入~/Dev 目录,或者任何你想放置的位置,然后新建一个目录(我称这个虚拟机为"mac")。然后再 cd 进这个目录并新建我们 src 目录,这个目录用于放置代码。
Makefile
makefile 相对直接,我们不需要将什么东西分成多个文件,也不用包含任何东西,所以我们只需要用一些标志来编译文件:
SRC_FILES = main.c
CC_FLAGS = -Wall -Wextra