实现 8086 模拟器 - 准备工作

之前在公司内部的 JavaScript Weekly 邮件上看到有用 JavaScript 实现的 8086 模拟器,Google 了一番看到了 StackExchage 上的原题,假设与目标都很明确,正好这部分也不熟,那就来试试吧,前后折腾了差不多一个星期,实现有些纠结,方案换了三次,还好是越来越好,最后一个方案也是相对而言代码量最小的一个,最终的实现看这里

这一系列的日志记录于代码完成的一个月后,主要聊聊开发思路,全当复习啦~

8086

这货的历史各位看官估计都比我清楚,就不多表了。选择这货来模拟一个是其有代表性,另外一点也是比较简单,指令集并不复杂(相对而言啦,实际感觉还是多怪异的),通过模拟能更进一步了解 CPU 逻辑层面的工作原理及程序的本质(对,这次真的只剩 0 和 1 了…)。

PS:以后文中提到 CPU 都是专指 8086 CPU 哟,部分描述对于现代的 CPU 可能并不适用哟

开始之前需要先做些准备功课,首要的就是了解下 8086 的基本组成(逻辑层面就好,物理层面还是放过我吧…),在 wikipedia 上有简单明了的介绍,实现模拟器需要关注的主要是以下这些东东(了解汇编的同学可以绕过这部分了,不了解会汇编的同学也不用担心,实际上呀,写模拟器不需要了解任何汇编知识 哇哈哈):

指令集

CPU 的操作指令,也就是我们日常使用的高级程序语言最终生成的机器码。指令由操作命令与操作数两部分组成,每一条指令确定一种对操作数的具体操作,操作数可以是立即数(也就是数值直接包含在指令中)、寄存器或者内存,所有复杂的操作最终都归结于对这三者所代表的数据进行的运算。

直接记忆这些由 0 与 1 组成的操作指令简直令人发指,所以大佬们想出了用一些字符符号来代替这些指令来帮助记忆,然后再由专门的程序将这些使用助记符书写的程序翻译成二进制操作指令让 CPU 执行,这就是汇编语言的产生啦~(所以没有学过汇编也没事儿,我们是直接略过汇编玩儿二进制指令呢)

模拟器的核心就是对操作指令的解析并正确的执行,90% 的工作都集中在这里了,后面会有章节详细描述操作指令滴。

寄存器

CPU 用于暂存数据的地方,功能和内存一样,不过访问速度比内存高很多个数量级,同时每个寄存器的容量是固定的,有多种不同类型的寄存器用于存放不同功能的数据,在进行模拟时每个寄存器其实就是一个个的变量,需要特别注意的是要严格控制变量(寄存器)的大小:8086 是 16 位的,16位的,16位的 …

寻址方式

这个是指使用何种方式来从内存中找到想要的数据。CPU 只访问寄存器显然没有任何意义,还需要能直接访问内存才能运行程序。内存显然比寄存器要大很多,想要访问其中的数据就需要对内存空间进行编号,让内存中的每个字节都有一个唯一确定的地址(address),然后通过确定的地址来进行内存的访问,而确定这个唯一地址的过程就叫寻址(addressing)。CPU 有多种寻址方式:

我们忽略了什么

虽然 8086 已经是很简单的 CPU 了,但这里的模拟我们只实现 8086 指令集中的一部分,并且还忽略了很多其它的东西。比如硬盘的访问,这个涉及 I/O 的访问也就是中断的处理;忽略了 CPU 的时钟问题,不用想也知道 8086 的运算速度肯定比现在的 CPU 慢很多,直接运行老程序的话可能会有飞一般的感觉(玩儿过老游戏的同学都深有体会吧~);忽略了段寄存器,这货是用来对内存进行分区的,在我们的模拟中这些寄存器都被初始化为了 0 并且不会对这些寄存器进行写操作;虽然省略了不少东西,但这些都不会妨碍我们模拟 8086 的核心行为,在完成第一期的雏型后可以来慢慢完善~,OK,准备工作到此结束,let’s go ~

To Be Continued …