# VirtualMachine **Repository Path**: hanyeah/virtual-machine ## Basic Information - **Project Name**: VirtualMachine - **Description**: 实现一个虚拟机 - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2023-04-21 - **Last Updated**: 2023-04-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 显目简介 本项目是计算机体系结构相关实践,参考了2018年的一篇英文博客《Write your Own Virtual Machine》结合计算机体系结构的知识,运用`C++`编程语言实现了一个虚拟机,该虚拟机能够运行标准的`LC-3` 汇编程序,希望更深入地了解计算机的内部原理以及编程语言是如何工作的开发者朋友,本项目可能非常适合你。当然,你也可以通过下文中的参考资料阅读博客原文和源代码 # 项目结构 ``` ./ # 项目的根目录 VM |__ ./picture # 图片目录 |__ ./test # 测试文件目录,存放了两个测试文件 |__ ./include # 头文件目录 | |__ ./include/cpu.h # 虚拟机 cpu 类模板声明 | |__ ./include/cpu.hpp # 虚拟机 cpu 类模板的实现 | |__ ./include/dev.h # 虚拟机外部设备类头文件 | |__ ./include/disk.h # 虚拟机磁盘类模板 | |__ ./include/disk.hpp # 虚拟机磁盘类模板的实现 | |__ ./include/lc3.h # LC-3 指令集架构 | |__ ./include/mem.h # 虚拟机内存类模板 | |__ ./include/mem.hpp # 虚拟机内存类模板的实现 |__ ./dev.cpp # 虚拟机外部设备实现 |__ ./lc3.cpp # LC-3 指令集实现 |__ ./main.cpp # 虚拟机主文件 |__ ./Makefile # 编译脚本文件 ``` # 技术参数 本项目要求以下技术条件: + 熟悉 C++ 编程语言和泛型编程思想(类模板) + 熟悉 LC-3 指令集 + 了解 GCC 编译器、以 Makefile 编译脚本 > 注意: 本项目中包含中文注释,采用了`UTF-8`编码,为了防止乱码的产生,请编辑器使用`UTF-8`进行解码 # 项目运行 本项目的外部设备类及其实现(`./include/dev.h`,`./dev.cpp`)中包含少量平台相关的配置终端(terminal)和显示(display)的代码,需要在`Unix`系统(包括`macOS`)上执行,已在`Centos8`系统中运行成功在中断命令行下,进入到显目的根目录中,使用下面的命令来编译、运行项目 ``` make system=unix // 编译项目,指定系统平台,system默认值windows ./vm ./test/2048.obj // 在虚拟机中运行测试用例 ./test/2048.obj ./vm ./test/rogue.obj // 在虚拟机中运行测试用例 ./test/rogue.obj ``` 目前并没有实现虚拟机的关机操作,在虚拟机运行过程中,可通过`Ctrl+C`来终止程序,运行结果如下图所示: ![2048](./picture/m2048.jpg) ![rogue](./picture/rogue.jpg) # 虚拟机架构图 ![vm](./picture/vm.jpg) 架构的具体实现请参考下面的各节描述... # LC-3 指令集 ``` /** * LC-3 指令均为 16 位 * 操作码 op : 4bit * 目的操作数寄存器 rd : 3bit * 第一操作数寄存器 rs : 3bit * 操作数寻址模式 md : 1bit * 暂时保留未使用 __ : 2bit * 第二操作数寄存器 rt : 3bit */ class LC3 { private: uint16_t op : 4; // 操作码,4bit uint16_t rd : 3; // 目的操作数寄存器,3bit uint16_t rs : 3; // 第一操作数寄存器,3bit uint16_t md : 1; // 立即数模式,1bit(为 1 时表示后5bit为5位立即数) uint16_t __ : 2; // 无含义 uint16_t rt : 3; // 第二操作数寄存器,3bit public: LC3(); // 默认构造函数 LC3(uint16_t); // 带参构造函数 uint16_t bit(uint16_t); // 取出指令中的某一比特位 uint16_t group(); // 指令成分组合 void part(uint16_t &, uint16_t &, uint16_t &, uint16_t &); // 指令成分分离 void operator=(uint16_t); // 重载赋值运算符 void operator=(const LC3 &); // 重载赋值运算符 uint16_t operator&(uint16_t); // 重载按位与运算符 uint16_t operator<<(uint16_t); // 重载左移运算符 uint16_t operator>>(uint16_t); // 重载右移运算符 }; ``` LC-3 指令共 16bit,其中操作码占 4bit,最多有 16 条不同的指令;寄存器占 3bit,最多支持 8 个寄存器关于 LC-3 具指令集的具体实现,请参考`./lc3.cpp`源码 ![LC-3指令集](./picture/LC-3.jpg) > [参考资料](https://blog.csdn.net/weixin_44176696/article/details/105773303) # CPU设计 ``` /** * CPU类 * 主要包括含虚拟机的寄存器等硬件信息 * 由模板指定机器字长、指令集架构和内存信息 */ template< typename word_t, // 机器字长 typename arch_t, // 指令架构 typename mem_t // 内存型号 > class CPU { private: /** * 虚拟机寄存器组 * LC-3 共有8个通用寄存器 R0~R7 * 另外有一个程序计数器 PC 和一个标志寄存器 FLAG */ enum { R_R0 = 0, R_R1, R_R2, R_R3, R_R4, R_R5, R_R6, R_R7, R_PC, // 程序计数器 R_FLAG, // 程序状态字 R_COUNT }; word_t reg[R_COUNT]; /* CPU 需要执行的指令,由取指部件产生并 */ arch_t inst; /** * CPU运行信息,由译码器产生 * op : 操作码 * rd : 目标寄存器 * rs : 第一操作数寄存器 * rt : 第二操作数寄存器 * cond : 条件 * imm : 立即数 */ word_t op, rd, rs, rt, cond, imm; /* CPU 状态,零表示 CPU 停止运行,非零表示 CPU 正在运行 */ int status; /* 定义内存的应用,方便 CPU 直接访问内存 */ mem_t &mem; public: /* 构造函数 */ CPU(mem_t &mem); /* CPU 常规操作,包括 CPU 初始化、更新标记寄存器、符号扩展、中断等 */ void init(); // 启动 CPU int state(); // 获取CPU状态 void update_flags(word_t); // 更新标记寄存器 word_t sign_extend(word_t, word_t); // 符号位扩展 void trap(word_t); // CPU 中断 /* 指令的执行过程 */ void fetch(); // 取指 void decode(); // 译码 void run(); // 执行 }; ``` 虚拟机 CPU 的机器字长等于指令字长,其中包含8各通用寄存器组、一个 PC 寄存器、一个标记寄存器 FALG(也叫程序状态字 PSW) 关于 CPU 的具体实现,参考`./include/cpu.hpp`源码 # 内存设计 ``` template< typename word_t, // 机器字长 typename file_t // 文件类型 > class MEM { private: /** * 虚拟机内存 * 共 65536 个内存位置 * 每个位置可以存储一个 16-bit 的值 * 共可存储 128KB 数据 (64K*2Byte) */ word_t memory[UINT16_MAX]; /** * 内存映射寄存器(MMR) * 将I/O端口映射到内存 * 以方便将外设数据读入内存中 */ enum { MR_KBSR = 0xFE00, // 键盘状态端口 MR_KBDR = 0xFE02 // 键盘数据端口 }; public: /* 获取内存数组 */ word_t* get_memory(); /* 内存操作 */ void init( int (*)() ); // 内存初始化 word_t read(word_t); // 内存读 void write(word_t , word_t); // 内存写 word_t swap16(word_t); // 16 位数字节交换,用于大小端存储的转换 void load_image(file_t *); // 从磁盘中加载可执行文件映像入内存 /* 定义内存访问外设的钩子 */ int (*check_key)(); // 监听键盘 }; ``` 内存单元位 2 个字节,也就是一个机器字长,共有 65536 个内存单元,最多可存储 128KB 数据,此外,虚拟机的内存还映射了两个I/O端口寄存器,用来读取键盘的输入关于内存得具体实现,请参考`./include/mem.hpp`源码 # 磁盘设计 ``` template class DISK { private: file_t *file; // 文件类型 public: file_t* get_file(); // 获取文件流 int read_file(const char *); // 读文件 void close_file(); // 关闭文件 }; ``` 目前使用了一个文件流来模拟虚拟机得磁盘 # 外部设备 ``` class MDev { public: static int check_key(); // 键盘监听 static void disable_input_buffering(); // 禁用输入缓冲区 static void restore_input_buffering(); // 恢复输入缓冲区 }; ``` 虚拟机的外设部分主要是一个终端,这部分是与平台相关的,目前是采用在`Unix`的配置终端(terminal)和显示(display)关于虚拟机外设部分的实现,请参考`./dev.cpp`,这部分代码直接来源于博客中提供的源代码 # 平台相关性 该虚拟机的外部设备部分主要是关于终端的I/O,与平台相关。笔者添加的`Windows`平台的支持,可通过`make system=windows`命令编译`Windows`平台的可执行程序。但是,在`Windows`平台运行`./test/`中的测试用例,存在严重闪屏问题,待解决 # 参考 + 博客原文: https://justinmeiners.github.io/lc3-vm/ + 博客译文: https://arthurchiao.art/blog/write-your-own-virtual-machine-zh/ + 源代码: https://justinmeiners.github.io/lc3-vm/src/lc3.c