编写你自己的虚拟机 (2022) 总结
本文介绍如何用 C 语言编写一个约250行的虚拟机,用于运行LC-3汇编程序。
什么是虚拟机?
虚拟机(VM)是模拟计算机硬件的程序,能执行机器语言。它可以重现特定硬件(如游戏机模拟器),或创建可移植的软件平台(如JVM)。VM在安全隔离执行(如垃圾回收、区块链智能合约)和提供可移植性方面有重要作用。
LC-3 架构
我们的虚拟机模拟教学用的LC-3计算机架构,包含以下核心组件:
- 内存:65,536个16位存储位置(128KB)。
- 寄存器:10个16位寄存器,包括8个通用寄存器(R0-R7)、程序计数器(PC)和条件标志寄存器(COND)。
- 指令集:16种操作码,包括加法(ADD)、加载(LD)、存储(ST)等基本操作。
- 条件标志:正(P)、零(Z)、负(N)三种标志,用于记录计算结果状态。
执行程序流程
虚拟机的主循环遵循“取指-解码-执行”步骤:
- 从PC指向的地址读取指令。
- 递增PC。
- 根据操作码(指令高4位)执行对应操作。
- 使用指令中的参数完成计算或内存访问。
- 返回步骤1。
指令执行涉及立即数符号扩展、条件标志更新等细节。
实现关键指令
以ADD和LDI为例:
- ADD:支持寄存器模式和立即数模式,将两个数相加并存入目标寄存器,最后更新条件标志。
- LDI:间接加载指令,通过两级地址访问将内存中的值加载到寄存器。
其他指令(如AND、NOT、BR、JMP等)实现方式类似,遵循LC-3规范。
陷阱例程
陷阱例程提供基本I/O功能,通过TRAP指令调用:
- GETC/OUT/IN:字符输入输出。
- PUTS/PUTSP:字符串输出。
- HALT:停止程序。
在虚拟机中,这些例程用C函数实现以提高效率和可移植性。
加载程序
程序文件包含起始地址(原点)和指令数据。加载时需处理字节序转换(大端转小端)。通过读取文件内容并复制到内存指定位置完成加载。
内存映射寄存器
LC-3通过内存映射地址访问键盘状态寄存器(KBSR)和键盘数据寄存器(KBDR),实现非阻塞输入检测。
平台特定处理
为实现终端输入,需要调整缓冲设置。不同操作系统(Unix/Windows)需使用对应API,程序中断时恢复原始设置。
运行与调试
编译后运行虚拟机,加载LC-3程序(如2048游戏)即可执行。调试时建议对照汇编源码单步执行,检查指令行为是否符合预期。
可选优化(C++)
文章提供C++模板元编程技巧,通过位标志组合处理步骤,减少代码重复,更接近硬件实现方式。
资源
最终代码约250行C代码,支持Unix和Windows。教程以“文学编程”形式组织,代码与讲解交织。社区贡献包括多语言实现和参考卡片。
通过本教程,读者可深入理解计算机底层运作和虚拟机原理,无需预先掌握汇编知识,只需熟悉基础C语言和二进制运算即可。