模拟器如何工作以及它们是如何编写的?
模拟器如何工作? 当我看到 NES/SNES 或 C64 模拟器时,我感到很惊讶。
您是否必须通过以下方式模拟这些机器的处理器解释其特定的汇编指令? 还有什么内容呢? 它们通常是如何设计的?
您能为有兴趣编写模拟器(特别是游戏系统)的人提供任何建议吗?
How do emulators work? When I see NES/SNES or C64 emulators, it astounds me.
Do you have to emulate the processor of those machines by interpreting its particular assembly instructions? What else goes into it? How are they typically designed?
Can you give any advice for someone interested in writing an emulator (particularly a game system)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
仿真是一个多方面的领域。 以下是基本思想和功能组件。 我将把它分成几部分,然后通过编辑填写详细信息。 我要描述的许多内容都需要处理器内部工作原理的知识——组装知识是必要的。 如果我对某些事情有点太模糊,请提出问题,以便我可以继续改进这个答案。
基本思想:
仿真通过处理处理器和各个组件的行为来工作。 您构建系统的每个单独部分,然后将各个部分连接起来,就像硬件中的电线一样。
处理器仿真:
处理处理器仿真的方法有以下三种:
通过所有这些路径,您都有相同的总体目标:执行一段代码来修改处理器状态并与“硬件”交互。 处理器状态是给定处理器目标的处理器寄存器、中断处理程序等的集合。 对于 6502,您将有许多代表寄存器的 8 位整数:
A
、X
、Y
、P< /code> 和
S
; 您还有一个 16 位PC
寄存器。通过解释,您从
IP
(指令指针——也称为PC
,程序计数器)开始并从内存中读取指令。 您的代码解析此指令并使用此信息来更改处理器指定的处理器状态。 解释的核心问题是它非常慢; 每次处理给定的指令时,您都必须对其进行解码并执行必要的操作。通过动态重新编译,您可以像解释一样迭代代码,但您不只是执行操作码,而是构建操作列表。 一旦到达分支指令,您就可以将此操作列表编译为主机平台的机器代码,然后缓存此编译后的代码并执行它。 然后,当您再次命中给定的指令组时,只需执行缓存中的代码即可。 说一句,大多数人实际上并没有制作指令列表,而是将它们即时编译为机器代码——这使得优化变得更加困难,但这超出了这个答案的范围,除非有足够多的人感兴趣)
(顺便 静态重新编译,与动态重新编译相同,但遵循分支。 您最终会构建代表程序中所有代码的代码块,然后可以在没有进一步干扰的情况下执行该代码。 如果不是因为以下问题,这将是一个很好的机制:
这些因素结合在一起使得静态重新编译在 99% 的情况下完全不可行。 有关更多信息,Michael Steil 对静态重新编译做了一些出色的研究——这是我所见过的最好的研究。
处理器模拟的另一方面是与硬件交互的方式。 这实际上有两个方面:
处理器计时:
某些平台——尤其是较旧的控制台,如 NES、SNES 等——要求您的模拟器具有严格的计时才能完全兼容。 对于 NES,您拥有 PPU(像素处理单元),它要求 CPU 在精确的时刻将像素放入内存中。 如果您使用解释,您可以轻松计算周期并模拟正确的计时; 使用动态/静态重新编译,事情会变得更加复杂。
中断处理:
中断是CPU与硬件通信的主要机制。 一般来说,您的硬件组件会告诉 CPU 它关心什么中断。 这非常简单——当您的代码引发给定中断时,您将查看中断处理程序表并调用适当的回调。
硬件仿真:
仿真给定的硬件设备有两个方面:
以硬盘驱动器为例。 通过创建后备存储、读/写/格式化例程等来模拟该功能。这部分通常非常简单。
设备的实际接口有点复杂。 这通常是内存映射寄存器(例如,设备监视变化以执行信号发送的内存部分)和中断的某种组合。 对于硬盘驱动器,您可能有一个内存映射区域,您可以在其中放置读取命令、写入等,然后读回该数据。
我会更详细地介绍,但是您可以使用一百万种方法。 如果您在这里有任何具体问题,请随时询问,我将添加信息。
资源:
我想我已经在这里给出了很好的介绍,但是还有大量其他区域。 我非常乐意帮助解决任何问题; 由于极其复杂,我对其中大部分内容都非常模糊。
强制性维基百科链接:
一般仿真资源:
模拟器项目的机器架构的资源以供参考:
处理器重编译参考:
附录:
自从提交这个答案以来已经过去了一年多了,并且随着它所受到的关注,我认为是时候更新一些东西了。
也许现在仿真中最令人兴奋的事情是 libcpu,它是由前面提到的 Michael Steil 发起的。 它是一个旨在支持大量 CPU 内核的库,它使用 LLVM 进行重新编译(静态和动态!)。 它具有巨大的潜力,我认为它将为仿真做出巨大的贡献。
emu-docs 也引起了我的注意,它包含一个很棒的系统文档存储库,这对于仿真非常有用目的。 我在那里呆的时间不多,但看起来他们有很多很棒的资源。
我很高兴这篇文章对我有所帮助,我希望我能在今年年底/明年初完成我关于这个主题的书。
Emulation is a multi-faceted area. Here are the basic ideas and functional components. I'm going to break it into pieces and then fill in the details via edits. Many of the things I'm going to describe will require knowledge of the inner workings of processors -- assembly knowledge is necessary. If I'm a bit too vague on certain things, please ask questions so I can continue to improve this answer.
Basic idea:
Emulation works by handling the behavior of the processor and the individual components. You build each individual piece of the system and then connect the pieces much like wires do in hardware.
Processor emulation:
There are three ways of handling processor emulation:
With all of these paths, you have the same overall goal: execute a piece of code to modify processor state and interact with 'hardware'. Processor state is a conglomeration of the processor registers, interrupt handlers, etc for a given processor target. For the 6502, you'd have a number of 8-bit integers representing registers:
A
,X
,Y
,P
, andS
; you'd also have a 16-bitPC
register.With interpretation, you start at the
IP
(instruction pointer -- also calledPC
, program counter) and read the instruction from memory. Your code parses this instruction and uses this information to alter processor state as specified by your processor. The core problem with interpretation is that it's very slow; each time you handle a given instruction, you have to decode it and perform the requisite operation.With dynamic recompilation, you iterate over the code much like interpretation, but instead of just executing opcodes, you build up a list of operations. Once you reach a branch instruction, you compile this list of operations to machine code for your host platform, then you cache this compiled code and execute it. Then when you hit a given instruction group again, you only have to execute the code from the cache. (BTW, most people don't actually make a list of instructions but compile them to machine code on the fly -- this makes it more difficult to optimize, but that's out of the scope of this answer, unless enough people are interested)
With static recompilation, you do the same as in dynamic recompilation, but you follow branches. You end up building a chunk of code that represents all of the code in the program, which can then be executed with no further interference. This would be a great mechanism if it weren't for the following problems:
These combine to make static recompilation completely infeasible in 99% of cases. For more information, Michael Steil has done some great research into static recompilation -- the best I've seen.
The other side to processor emulation is the way in which you interact with hardware. This really has two sides:
Processor timing:
Certain platforms -- especially older consoles like the NES, SNES, etc -- require your emulator to have strict timing to be completely compatible. With the NES, you have the PPU (pixel processing unit) which requires that the CPU put pixels into its memory at precise moments. If you use interpretation, you can easily count cycles and emulate proper timing; with dynamic/static recompilation, things are a /lot/ more complex.
Interrupt handling:
Interrupts are the primary mechanism that the CPU communicates with hardware. Generally, your hardware components will tell the CPU what interrupts it cares about. This is pretty straightforward -- when your code throws a given interrupt, you look at the interrupt handler table and call the proper callback.
Hardware emulation:
There are two sides to emulating a given hardware device:
Take the case of a hard-drive. The functionality is emulated by creating the backing storage, read/write/format routines, etc. This part is generally very straightforward.
The actual interface of the device is a bit more complex. This is generally some combination of memory mapped registers (e.g. parts of memory that the device watches for changes to do signaling) and interrupts. For a hard-drive, you may have a memory mapped area where you place read commands, writes, etc, then read this data back.
I'd go into more detail, but there are a million ways you can go with it. If you have any specific questions here, feel free to ask and I'll add the info.
Resources:
I think I've given a pretty good intro here, but there are a ton of additional areas. I'm more than happy to help with any questions; I've been very vague in most of this simply due to the immense complexity.
Obligatory Wikipedia links:
General emulation resources:
Emulator projects to reference:
Processor recompilation references:
Addendum:
It's been well over a year since this answer was submitted and with all the attention it's been getting, I figured it's time to update some things.
Perhaps the most exciting thing in emulation right now is libcpu, started by the aforementioned Michael Steil. It's a library intended to support a large number of CPU cores, which use LLVM for recompilation (static and dynamic!). It's got huge potential, and I think it'll do great things for emulation.
emu-docs has also been brought to my attention, which houses a great repository of system documentation, which is very useful for emulation purposes. I haven't spent much time there, but it looks like they have a lot of great resources.
I'm glad this post has been helpful, and I'm hoping I can get off my arse and finish up my book on the subject by the end of the year/early next year.
一个名叫维克多·莫亚·德尔·巴里奥(Victor Moya del Barrio)的人就这个主题写了他的论文。 152 页有很多很好的信息。 您可以下载 PDF 此处。
如果您不想注册 scribd,您可以通过 google 搜索 PDF 标题,“模拟编程技术研究”。 PDF 有几个不同的来源。
A guy named Victor Moya del Barrio wrote his thesis on this topic. A lot of good information on 152 pages. You can download the PDF here.
If you don't want to register with scribd, you can google for the PDF title, "Study of the techniques for emulation programming". There are a couple of different sources for the PDF.
仿真可能看起来令人畏惧,但实际上比模拟容易得多。
任何处理器通常都有一个编写良好的规范来描述状态、交互等。
如果您根本不关心性能,那么您可以使用非常优雅的面向对象程序轻松模拟大多数旧处理器。 例如,X86 处理器需要一些东西来维护寄存器的状态(简单),一些东西来维护内存状态(简单),以及一些东西可以接受每个传入的命令并将其应用于机器的当前状态。 如果您确实想要准确性,您还可以模拟内存翻译、缓存等,但这是可行的。
事实上,许多微芯片和CPU制造商先针对芯片仿真器测试程序,然后再针对芯片本身进行测试,这有助于他们找出芯片规格或芯片在硬件中的实际实现中是否存在问题。 例如,编写可能导致死锁的芯片规范,并且当硬件中出现最后期限时,重要的是要查看它是否可以在规范中重现,因为这表明比芯片实现中的问题更大。
当然,视频游戏模拟器通常关心性能,因此它们不会使用幼稚的实现,并且它们还包含与主机系统操作系统交互的代码,例如使用绘图和声音。
考虑到旧视频游戏(NES/SNES 等)的性能非常慢,在现代系统上进行模拟非常容易。 事实上,更令人惊奇的是,您可以下载一套所有 SNES 游戏或任何 Atari 2600 游戏,考虑到当这些系统流行时,免费访问每个卡带将是梦想成真。
Emulation may seem daunting but is actually quite easier than simulating.
Any processor typically has a well-written specification that describes states, interactions, etc.
If you did not care about performance at all, then you could easily emulate most older processors using very elegant object oriented programs. For example, an X86 processor would need something to maintain the state of registers (easy), something to maintain the state of memory (easy), and something that would take each incoming command and apply it to the current state of the machine. If you really wanted accuracy, you would also emulate memory translations, caching, etc., but that is doable.
In fact, many microchip and CPU manufacturers test programs against an emulator of the chip and then against the chip itself, which helps them find out if there are issues in the specifications of the chip, or in the actual implementation of the chip in hardware. For example, it is possible to write a chip specification that would result in deadlocks, and when a deadline occurs in the hardware it's important to see if it could be reproduced in the specification since that indicates a greater problem than something in the chip implementation.
Of course, emulators for video games usually care about performance so they don't use naive implementations, and they also include code that interfaces with the host system's OS, for example to use drawing and sound.
Considering the very slow performance of old video games (NES/SNES, etc.), emulation is quite easy on modern systems. In fact, it's even more amazing that you could just download a set of every SNES game ever or any Atari 2600 game ever, considering that when these systems were popular having free access to every cartridge would have been a dream come true.
我知道这个问题有点老了,但我想在讨论中添加一些内容。 这里的大多数答案都围绕模拟器解释它们所模拟的系统的机器指令。
然而,有一个众所周知的例外,称为“UltraHLE”(WIKIpedia 文章)。 UltraHLE 是有史以来最著名的模拟器之一,它模拟了商业 Nintendo 64 游戏(在家用计算机上具有不错的性能),而当时人们普遍认为这是不可能的。 事实上,当 UltraHLE 创建时,任天堂仍在为 Nintendo 64 制作新游戏!
我第一次在印刷杂志上看到有关模拟器的文章,而之前我只在网络上看到过它们的讨论。
UltraHLE 的概念是通过模拟 C 库调用而不是机器级调用来使不可能成为可能。
I know that this question is a bit old, but I would like to add something to the discussion. Most of the answers here center around emulators interpreting the machine instructions of the systems they emulate.
However, there is a very well-known exception to this called "UltraHLE" (WIKIpedia article). UltraHLE, one of the most famous emulators ever created, emulated commercial Nintendo 64 games (with decent performance on home computers) at a time when it was widely considered impossible to do so. As a matter of fact, Nintendo was still producing new titles for the Nintendo 64 when UltraHLE was created!
For the first time, I saw articles about emulators in print magazines where before, I had only seen them discussed on the web.
The concept of UltraHLE was to make possible the impossible by emulating C library calls instead of machine level calls.
值得一看的是 Imran Nazar 尝试编写 Gameboy JavaScript 中的模拟器。
Something worth taking a look at is Imran Nazar's attempt at writing a Gameboy emulator in JavaScript.
创建了我自己的 80 年代 BBC 微型计算机模拟器(在 Google 中输入 VBeeb)后,有很多事情需要了解。
实际上,您通常希望为仿真的速度和保真度而编写。 这是因为目标系统上的软件(可能)比源系统上的原始硬件运行得更慢。 这可能会限制编程语言、编译器、目标系统等的选择。
除此之外,您必须限制您准备模拟的内容,例如,不必模拟微处理器中晶体管的电压状态,但可能有必要模拟微处理器的寄存器组的状态。
一般来说,仿真的细节级别越小,原始系统的保真度就越高。
最后,旧系统的信息可能不完整或不存在。 因此,掌握原始设备至关重要,或者至少欣赏其他人编写的另一个优秀模拟器!
Having created my own emulator of the BBC Microcomputer of the 80s (type VBeeb into Google), there are a number of things to know.
Practically speaking, you're generally looking to write for speed and fidelity of emulation. This is because software on the target system will (may) run more slowly than the original hardware on the source system. That may constrain the choice of programming language, compilers, target system etc.
Further to that you have to circumscribe what you're prepared to emulate, for example its not necessary to emulate the voltage state of transistors in a microprocessor, but its probably necessary to emulate the state of the register set of the microprocessor.
Generally speaking the smaller the level of detail of emulation, the more fidelity you'll get to the original system.
Finally, information for older systems may be incomplete or non-existent. So getting hold of original equipment is essential, or at least prising apart another good emulator that someone else has written!
是的,您必须“手动”解释整个二进制机器代码混乱。 不仅如此,大多数时候您还必须模拟一些在目标计算机上没有等效硬件的特殊硬件。
简单的方法是一一解释指令。 这效果很好,但速度很慢。 更快的方法是重新编译 - 将源机器代码转换为目标机器代码。 这更复杂,因为大多数指令不会一对一映射。 相反,您将必须制定涉及额外代码的复杂解决方法。 但最终速度要快得多。 大多数现代模拟器都这样做。
Yes, you have to interpret the whole binary machine code mess "by hand". Not only that, most of the time you also have to simulate some exotic hardware that doesn't have an equivalent on the target machine.
The simple approach is to interpret the instructions one-by-one. That works well, but it's slow. A faster approach is recompilation - translating the source machine code to target machine code. This is more complicated, as most instructions will not map one-to-one. Instead you will have to make elaborate work-arounds that involve additional code. But in the end it's much faster. Most modern emulators do this.
当您开发模拟器时,您正在解释系统正在运行的处理器组件(Z80、8080、PS CPU 等)。
您还需要模拟系统拥有的所有外围设备(视频输出、控制器)。
您应该开始为简单的系统编写模拟器,例如老式的 Game Boy(使用 Z80 处理器) ,我不是没有弄错吗)或 C64。
When you develop an emulator you are interpreting the processor assembly that the system is working on (Z80, 8080, PS CPU, etc.).
You also need to emulate all peripherals that the system has (video output, controller).
You should start writing emulators for the simpe systems like the good old Game Boy (that use a Z80 processor, am I not not mistaking) OR for C64.
有关示例,请参阅 http://queue.acm.org/detail.cfm? id=1755886。
这还将向您展示为什么“需要”一个多 GHz CPU 来模拟 1MHz CPU。
For an example of this, see http://queue.acm.org/detail.cfm?id=1755886.
That will also show you why you ‘need’ a multi-GHz CPU for emulating a 1MHz one.
另请查看 Darek Mihocka 的 Emulators.com,了解有关 JIT 指令级优化的重要建议,以及有关构建高效的许多其他好东西模拟器。
Also check out Darek Mihocka's Emulators.com for great advice on instruction-level optimization for JITs, and many other goodies on building efficient emulators.
我从来没有做过任何像模拟游戏机这样奇特的事情,但我确实参加过一次课程,其中的任务是为 Andrew Tanenbaums 结构化计算机组织。 这很有趣,给了我很多顿悟的时刻。 在开始编写真正的模拟器之前,您可能需要先阅读这本书。
I've never done anything so fancy as to emulate a game console but I did take a course once where the assignment was to write an emulator for the machine described in Andrew Tanenbaums Structured Computer Organization. That was fun an gave me a lot of aha moments. You might want to pick that book up before diving in to writing a real emulator.
关于模拟真实系统或您自己的系统的建议?
我可以说模拟器通过模拟整个硬件来工作。 也许不涉及电路(就像硬件一样移动位。移动字节是最终结果,因此复制字节就可以了)。 模拟器很难创建,因为需要模拟许多黑客行为(如不寻常的效果)、时序问题等。 如果一个(输入)部分错误,整个系统可能会崩溃,或者最多会出现错误/故障。
Advice on emulating a real system or your own thing?
I can say that emulators work by emulating the ENTIRE hardware. Maybe not down to the circuit (as moving bits around like the HW would do. Moving the byte is the end result so copying the byte is fine). Emulator are very hard to create since there are many hacks (as in unusual effects), timing issues, etc that you need to simulate. If one (input) piece is wrong the entire system can do down or at best have a bug/glitch.
共享源设备模拟器包含可构建的源代码PocketPC/智能手机模拟器(需要 Visual Studio,在 Windows 上运行)。 我参与了二进制版本的 V1 和 V2 工作。
它解决了许多仿真问题:
- 从客户虚拟地址到客户物理地址再到主机虚拟地址的高效地址转换
- 访客代码的 JIT 编译
- 模拟网络适配器、触摸屏和音频等外围设备
- UI集成,用于主机键盘和鼠标
- 保存/恢复状态,用于模拟从低功耗模式恢复
The Shared Source Device Emulator contains buildable source code to a PocketPC/Smartphone emulator (Requires Visual Studio, runs on Windows). I worked on V1 and V2 of the binary release.
It tackles many emulation issues:
- efficient address translation from guest virtual to guest physical to host virtual
- JIT compilation of guest code
- simulation of peripheral devices such as network adapters, touchscreen and audio
- UI integration, for host keyboard and mouse
- save/restore of state, for simulation of resume from low-power mode
添加@Cody Brocious 提供的答案
在虚拟化环境中,您正在向虚拟机模拟新系统(CPU、I/O 等),我们可以看到以下类别的模拟器。
解释:bochs是解释器的一个例子,它是一个x86 PC模拟器,它将来自客户系统的每条指令翻译成另一组指令(主机ISA)以产生预期的效果。是的,它非常慢,但它并不不缓存任何内容,因此每条指令都会经历相同的周期。
动态仿真器:Qemu 是一个动态仿真器。 它对客户指令进行动态翻译并缓存结果。最好的部分是直接在主机系统上执行尽可能多的指令,以便仿真速度更快。 另外,正如 Cody 所提到的,它将代码划分为块(1 个执行流程)。
静态模拟器:据我所知,没有静态模拟器可以对虚拟化有所帮助。
To add the answer provided by @Cody Brocious
In the context of virtualization where you are emulating a new system(CPU , I/O etc ) to a virtual machine we can see the following categories of emulators.
Interpretation: bochs is an example of interpreter , it is a x86 PC emulator,it takes each instruction from guest system translates it in another set of instruction( of the host ISA) to produce the intended effect.Yes it is very slow , it doesn't cache anything so every instruction goes through the same cycle.
Dynamic emalator: Qemu is a dynamic emulator. It does on the fly translation of guest instruction also caches results.The best part is that executes as many instructions as possible directly on the host system so that emulation is faster. Also as mentioned by Cody, it divides the code into blocks ( 1 single flow of execution).
Static emulator: As far I know there are no static emulator that can be helpful in virtualization.
我将如何开始仿真。
1.获取基于低级编程的书籍,您将需要它用于任天堂的“假装”操作系统...game boy...
2.专门获取有关仿真的书籍,也许还有操作系统开发的书籍。 (您不会制作操作系统,而是最接近它的操作系统。3
.查看一些开源模拟器,尤其是您想要为其制作模拟器的系统。4
.将更复杂的代码片段复制到您的IDE中/compliler.这将节省您编写长代码的时间,这就是我为操作系统开发所做的,使用linux的一个分区。
How I would start emulation.
1.Get books based around low level programming, you'll need it for the "pretend" operating system of the Nintendo...game boy...
2.Get books on emulation specifically, and maybe os development. (you won't be making an os, but the closest to it.
3.look at some open source emulators, especially ones of the system you want to make an emulator for.
4.copy snippets of the more complex code into your IDE/compliler. This will save you writing out long code. This is what I do for os development, use a district of linux
我写了一篇关于用 JavaScript 模拟 Chip-8 系统的文章。
这是一个很好的起点,因为系统不是很复杂,但您仍然可以了解操作码、堆栈、寄存器等如何工作。
我很快就会为 NES 写一篇更长的指南。
I wrote an article about emulating the Chip-8 system in JavaScript.
It's a great place to start as the system isn't very complicated, but you still learn how opcodes, the stack, registers, etc work.
I will be writing a longer guide soon for the NES.