reference: https://felixangell.com/blog/implementing-a-virtual-machine-in-c/
介绍
这里写篇文章介绍一下用C语言实现虚拟机。我喜欢从事底层程序的工作, 比如编译器、解释器、解析器和虚拟机等。所以我写这篇文章来学习一下虚拟机是如何工作的,以此来带领自己进入底层编程领域。
前提
继续这篇文章之前你需要有:
- GCC/Clang/… — 我用的是 clang,但是你可以使用任何一种编译器。
- Text Editor — 我不建议使用ide (当写 C 的时候), 我用的是 Emacs。
- 基本的编程知识 — 仅仅基础就行: 变量, 流程控制, 方法, 结构体, 等等。
- GNU Make — 构建系统,有了构建系统,就不用一遍一遍地写同样等编译命令了。
为什么要写一个虚拟机
- 深入理解计算机如何工作。这篇文章带你通过底层理解计算机。虚拟机对底层抽象出了一个简单层。没有比亲手做一个更好的学习方法了。
- 学习虚拟机很有趣。
- 可以理解很多语言是如何工作的。很多编程语言都是运行在虚拟机上的,比如java的jvm, lua 的 vm。
指令集
我们先实现一个很简单的指令集,比如从一个寄存器上取值或者跳转到另外一个指令。
我们的虚拟机有A, B, C, D, E, 和 F 这几个寄存器。这些寄存器是通用寄存器,可以存储任何东西。x86机器上还有一些特殊通途的寄存器,比如 ip 寄存器,ds 寄存器。
一个程序其实质就是一连串的指令。虚拟机是个基于栈的机器,这样我们就可以在虚拟机里做入栈出栈操作,也可以使用一些寄存器。相对基于寄存器的虚拟机,基于栈的虚拟机要容易实现的多。
下面我们将要实现的指令:
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
虚拟机如何工作?
虚拟机比你想象的要简单的多。它遵循一个简单的模式;“指令循环”,其实就是获取,解码,执行。
工程目录
开始编程之前,先设置好目录结构。假设虚拟机的名字是 mac,那么建一个 mac 文件夹,在这个目录下建一个 src 目录,我们的代码放在这个目录里。
Makefile
Makefile 很简单,以为我们没有很多分散在不同文件里的代码。
SRC_FILES = main.c
CC_FLAGS = -Wall -Wextra -g -std=c11
CC = clang
all:
${CC} ${SRC_FILES} ${CC_FLAGS} -o mac
程序指令
我们使用一个 enum
来定义我们的程序指令:
typedef enum {
PSH,
ADD,
POP,
SET,
HLT
} InstructionSet;
下面写一个测试程序,输出 5 + 6
const int program[] = {
PSH, 5,
PSH, 6,
ADD,
POP,
HLT
};
接下来要做的就是所谓的“指令循环”。
获取指令
和x86机器一样,虚拟机里有一个 “Program Counter”,又或称 “Instruction Pointer”,就是ip寄存器。
先设置
int ip = 0;
获取一条指令:
int fetch() {
return program[ip];
}
连续获取:
int ip = 0;
int main() {
int x = fetch(); // PSH
ip++; // increment instruction pointer
int y =