【发布时间】:2016-04-03 23:20:44
【问题描述】:
最近我发现很多模拟器都很慢,因为它们不仅要模拟 CPU,还要模拟所模拟设备的内存。当设备具有内存映射 I/O、虚拟内存或只是未使用的地址空间时,必须在软件中模拟每个内存访问。
我觉得如果操作系统通过虚拟内存为我们这样做可能会快很多。为简单起见,我将使用 Game Boy 仿真作为示例,但显然这种方法更适合更新、更强大的机器。
Game Boy 的内存图大致如下:
- 0x0000 - 0x7FFF:映射到磁带 ROM
- 大多数磁带的 0x0000 - 0x3FFF 固定,0x4000 - 0x7FFF 可通过写入 0x2000 进行组切换
- 0x8000 - 0x9FFF:视频 RAM(仅在当前未渲染时可访问)
- 0xA000 - 0xBFFF:映射到盒式磁带(通常是电池供电的 RAM)
- 0xC000 - 0xDFFF:内部 RAM(0xD000 - 0xDFFF 在 GB 颜色上进行组切换)
- 0xE000 - 0xFDFF:内部 RAM 镜像
- 0xFE00 - 0xFE9F:对象属性内存(sprite RAM)
- 0xFEA0 - 0xFEFF:未映射(开放总线什么的,不确定)
- 0xFF00 - 0xFF7F:内存映射 I/O(音响系统、视频控制等)
- 0xFE80 - 0xFFFF:内部 RAM
因此,传统的模拟器必须翻译每个内存访问,例如:
if(addr < 0x4000) return rom[addr];
else if(addr < 0x8000) return rom[(addr - 0x4000) + (0x4000 * cur_rom_bank)];
else if(addr < 0xA000) {
if(vram_accessible) return vram[addr - 0x8000];
else return 0xFF;
}
else if(addr < 0xC000) return saveram[addr - 0xA000];
else if(addr < 0xE000) return ram[addr - 0xC000];
else if(addr < 0xFE00) return ram[addr - 0xE000];
else if(addr < 0xFE9F) return oam[addr - 0xFE00];
else if(addr < 0xFF00) return 0xFF; //or whatever should be here
else if(addr < 0xFF80) return handle_io_read(addr);
else return hram[addr - 0xFF80];
显然,这可以通过使用开关或表进行优化,但每次内存访问仍然需要运行大量代码。我们可以通过将一些页面映射到我们进程的内存映射中的这些地址来大大提高仿真速度:
- 0x0000 - 0x3FFF: R--(没有 Exec 标志,因为本机 CPU 不执行它)
- 0x4000 - 0x7FFF: R--
- 0x8000 - 0x9FFF:---
- 0xA000 - 0xBFFF: ---
- 0xC000 - 0xDFFF: RW-
- 0xE000 - 0xFDFF: RW-(并映射到与 0xC000 - 0xDFFF 相同的物理页面)
- 0xFE00 - 0xFE9F:---
- 0xFEA0 - 0xFEFF:---
- 0xFF00 - 0xFF7F:---
- 0xFF80 - 0xFFFF: RW-
然后处理我们在访问这些页面时获得的 SIGSEGV(或将生成的任何信号)。因此,从 ROM 读取或写入 RAM 可以直接执行,而对 ROM 的写入将引发我们可以处理的异常。我们可以将 VRAM (0x8000 - 0x9FFF) 的权限更改为 RW- 什么时候应该可以访问,而 --- 什么时候不应该。理论上它可能会更快,因为它不需要模拟器手动映射软件中的每个内存访问。
我知道我可以使用mmap() 映射具有各种权限的固定地址的页面。我不知道的是:
- 映射是否可以重叠,但权限不同?
- 我可以像这样将页面映射到任意地址,而不管系统的页面大小如何?我可以映射到地址 0 吗?
- 如何更改映射指向的内存? (例如当 ROM bank 改变时,我们可以切换 0x4000 - 0x7FFF 映射的内存,但是我该怎么做呢?)
- 在模拟系统具有 32 位或 64 位 CPU 的实际情况下,我可以映射整个前 4GB 或可能的整个内存空间吗?如何避免与已映射的内容(例如库、我的堆栈、内核)发生冲突?
- 这真的会更快吗?还是投掷和捕捉 SIGSEGV 会比传统方式产生更多开销?
- 如果无法在用户空间执行此操作,Linux 是否可能提供一种“接管”内核并在那里执行此操作的方法?所以我至少可以创建一个运行裸机的“模拟器操作系统”,同时仍然有一些 Linux 内核设施(如视频和文件系统驱动程序)可用?
【问题讨论】:
-
我能想到两种解决方案:动态重新编译和hyoervisor(如kvm)
标签: linux memory virtual-memory emulation memory-mapping