ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从Am29000模拟器到CPU指令集模拟:理解计算机底层原理的实践指南

从Am29000模拟器到CPU指令集模拟:理解计算机底层原理的实践指南 如果你是一位90年代的嵌入式系统开发者或者对计算机体系结构的历史充满好奇你可能会对一个名字感到熟悉又陌生Am29000。这不是一个今天的主流CPU但在上世纪90年代它曾是RISC处理器家族中一颗耀眼的明星尤其在嵌入式图形界面、网络设备和工控领域。然而随着时代变迁相关的开发工具和模拟环境早已消失在历史长河中。今天我们讨论一个听起来像“考古发现”的项目一个用机器码编写的、能在窗口化操作系统下运行的Am29000模拟器1996。这不仅仅是一个怀旧话题。对于现代开发者而言理解它意味着什么它能解决什么实际问题是仅仅为了复古跑一个老游戏还是对学习计算机底层原理有不可替代的价值这篇文章要给出的核心判断是这个项目是理解“从硬件到操作系统”完整链条的绝佳实践标本其价值远超简单的“模拟器使用”。它强迫你思考CPU如何取指、译码、执行操作系统如何管理窗口和内存以及如何用最原始的机器码去构建一个复杂的软件系统。对于从事编译器开发、操作系统内核研究、嵌入式虚拟化甚至只是想深入理解计算机如何工作的开发者来说这是一次难得的“穿越”之旅。我们将从“为什么需要关注它”开始逐步拆解Am29000的架构、模拟器的原理并提供一个从零开始的、可操作的实践路径。即使你没有真实的Am29000硬件也能通过本文的思路在现代环境中窥见那个时代的软件风貌。1. 这篇文章真正要解决的问题为什么要在2024年研究一个1996年的机器码模拟器你可能会问在拥有QEMU、VirtualBox、各种高性能硬件模拟器的今天为什么还要折腾一个近30年前、用机器码写的、针对特定老CPU的模拟器这听起来像是极客的复古玩具而非有实际价值的技术。这里的关键误解在于我们往往把“模拟器”等同于“运行老软件的工具”。而这个Am29000模拟器的真正价值在于它本身就是一个极其精简的“教学用CPU和操作系统实现”。它不是为了高效运行应用程序而是为了演示和教学。通过研究它你可以解决几个现代开发中依然存在的核心认知问题理解CPU的“灵魂”现代开发高度抽象我们写Java、Python甚至C都很少直接面对CPU指令。这个模拟器迫使你从机器码的视角看一条指令如何从内存加载如何被解码如何影响寄存器和状态。这是理解程序最终如何变成电信号的基础。窥探早期图形界面的实现“Windowed OS”在90年代中期的嵌入式环境里通常不是指Windows而是一个简单的、支持多窗口管理的图形框架。理解它是如何用有限的资源没有GPUCPU直接操作显存实现窗口绘制、事件分发和刷新对今天编写嵌入式UI或理解GUI框架底层仍有启发。掌握“自举”的思维用机器码编写意味着它极有可能是一段直接可执行的二进制映像或者包含极少的汇编引导。研究它如何初始化硬件、设置内存、建立执行环境是理解任何系统启动过程Bootloader的绝佳范例。逆向工程与系统考古的方法论面对一个没有源代码、文档稀缺的历史项目如何通过静态分析、动态调试和背景研究来理解它这套方法论在分析遗留系统、进行安全审计或维护老旧代码库时至关重要。因此本文的目标读者是计算机体系结构与编译器爱好者想深入理解指令集模拟(ISS)如何实现。嵌入式系统开发者对没有操作系统的“裸机”编程或轻量级GUI框架感兴趣。操作系统学习者想了解一个最简单的“窗口化”任务管理是如何运作的。技术历史研究者对90年代的RISC架构和软件开发环境好奇。如果你属于以上任何一类那么这篇文章将带你超越“如何使用模拟器”进入“如何理解和重现一个模拟器系统”的更深层次。2. 基础概念与核心原理Am29000、模拟器与窗口化OS在动手之前我们必须厘清几个核心概念否则后续的所有操作都将失去方向。2.1 Am29000被遗忘的RISC先驱Am29000是AMD公司在1987年推出的一款32位RISC微处理器。在90年代初它与ARM、MIPS、SPARC等一同竞争嵌入式市场。纯RISC设计采用加载-存储架构指令格式规整追求单周期执行。寄存器窗口一个重要的特性是支持寄存器窗口用于高效的过程调用这一设计思想源自SPARC旨在减少函数调用时的内存访问开销。应用领域广泛用于激光打印机、网络路由器、图形终端和工业控制器。它常运行一个简单的实时操作系统或专有图形环境。与现代ARM Cortex-M系列的类比你可以把Am29000想象成90年代的“Cortex-M3”。它性能足够驱动一个带图形界面的复杂设备但资源又远不如同时代的x86桌面CPU。理解它就理解了那个时代嵌入式高性能应用的硬件基础。2.2 模拟器Emulator vs. 模拟器Simulator在中文里我们都叫“模拟器”但在英文语境和实现上有细微差别Emulator仿真器目标是精确地模仿目标硬件的行为使得原始软件无需修改即可运行。它关心的是行为正确性。我们讨论的这个项目就是一个Emulator。Simulator模拟器/仿真器通常更关注系统模型和功能验证可能不追求周期精确或硬件细节的完全复制常用于算法验证或性能评估。本项目是一个用机器码编写的Am29000指令集仿真器。这意味着它是一段直接运行在宿主机器如x86 PC上的程序这段程序能够解析和执行Am29000的二进制指令。2.3 “Windowed OS in Machine Code”的含义这是最有趣也最令人困惑的部分。Windowed OS在1996年的语境下这几乎不可能是一个完整的像Windows 95那样的操作系统。更可能的是一个简单的多任务窗口管理内核。它可能提供基本的图形绘制原语画点、线、矩形、文本。窗口抽象创建、移动、重叠、关闭。简单的事件循环处理键盘、鼠标输入。可能非常原始的内存管理和任务调度。 它本质上是一个运行在Am29000上的图形化应用程序框架。In Machine Code这暗示了这个“OS”本身很可能是用Am29000汇编语言编写然后直接汇编成机器码二进制文件。这个模拟器项目则包含了这个“OS”的二进制映像并使其能在模拟的Am29000 CPU上运行。也可能“in machine code”指的是模拟器宿主程序本身是用低级别语言如x86汇编精心编写以求高效。一个合理的推测架构现代PC (x86/ARM) ↓ 运行 Am29000 模拟器程序 (用C或x86汇编写成) ↓ 模拟 虚拟的Am29000 CPU 内存 I/O ↓ 加载并执行 “Windowed OS” 二进制映像 (Am29000机器码) ↓ 提供 简单的图形窗口环境理解了这些我们就知道我们要对付的不是一个现成的.exe文件而是一个需要放在正确历史和技术语境中理解的系统。3. 环境准备与前置条件搭建你的“时间机器”由于原始项目1996的具体实现细节和发布形式已不可考可能是一个.zip包中的DOS可执行文件我们无法提供确切的安装步骤。但我们可以构建一个等效的、可实践的现代研究环境。我们的目标是创建一个能运行Am29000代码的模拟环境并尝试分析或运行类似的简单图形化系统。3.1 核心工具选择我们将使用一个现代、活跃、功能强大的开源处理器模拟框架QEMU。QEMU支持“用户模式模拟”可以像运行本地程序一样运行针对不同CPU架构的二进制文件。宿主机系统Linux (Ubuntu 22.04 LTS 或类似发行版) 是首选。macOS和Windows (通过WSL2) 也基本可行。QEMU用户模式我们需要安装针对特定架构的QEMU用户模式模拟器。但遗憾的是QEMU官方并未直接支持Am29000。这是我们面临的第一个现实挑战。备选方案使用一个支持Am29000的其他模拟器。经过搜索一个可能的选择是SimH或GXemul它们支持更多历史架构。但为了通用性和可操作性我们将调整目标使用QEMU模拟一个在历史上与Am29000有相似地位且QEMU支持的RISC架构来实践“模拟器简单OS”的研究方法。我们选择ARMv4T (如ARM920T)因为它有丰富的旧式嵌入式Linux资源且原理相通。3.2 环境搭建步骤以Ubuntu/Linux为例# 1. 更新系统并安装QEMU用户模式工具 sudo apt update sudo apt install qemu-user qemu-user-static # 2. 安装交叉编译工具链用于ARM sudo apt install gcc-arm-linux-gnueabi g-arm-linux-gnueabi # 3. 安装必要的分析工具 sudo apt install binutils-arm-linux-gnueabi # 包含objdump, readelf等 sudo apt install gdb-multiarch # 多架构调试器 sudo apt install xxd # 二进制文件查看工具 # 4. 验证安装 qemu-arm --version arm-linux-gnueabi-gcc --version这样我们就拥有了一个可以运行ARM32位二进制程序的环境以及编译和调试它的工具链。4. 核心流程拆解从零理解一个模拟器系统的构成即使没有原始的Am29000模拟器我们也可以通过构建一个“简化版”来理解其核心流程。这个过程分为两大步编写一个极简的CPU模拟器以及为它准备一个极简的“图形化”程序。4.1 第一步理解指令集模拟器的核心循环一个CPU模拟器的核心是一个无限循环通常被称为取指-译码-执行循环。// 文件simple_emu.c - 一个概念性的模拟器核心循环 #include stdint.h // 定义模拟的CPU状态 typedef struct { uint32_t regs[16]; // 通用寄存器例如R0-R15 uint32_t pc; // 程序计数器 uint32_t cpsr; // 当前程序状态寄存器条件标志位 uint8_t *memory; // 模拟的内存空间 uint32_t mem_size; } CPUState; // 从内存中读取指令假设小端序 uint32_t fetch_instruction(CPUState *cpu) { if (cpu-pc 4 cpu-mem_size) { uint32_t instr *(uint32_t*)(cpu-memory[cpu-pc]); cpu-pc 4; // 假设每条指令4字节 return instr; } return 0xFFFFFFFF; // 非法指令或终止信号 } // 译码和执行指令这里是极度简化的ARM-like示例 void decode_and_execute(CPUState *cpu, uint32_t instr) { // 提取操作码假设指令格式高4位是主要操作码 uint8_t opcode (instr 28) 0xF; switch(opcode) { case 0x0: // 例如数据处理指令 // 提取寄存器编号、操作数等 // 执行加法、减法等 // 更新cpu-regs和cpu-cpsr break; case 0x1: // 例如加载指令 // 从内存读数据到寄存器 break; case 0x2: // 例如存储指令 // 将寄存器数据写入内存 break; case 0x3: // 例如分支指令 // 修改cpu-pc break; default: // 遇到未知指令可以停止模拟或报错 printf(Unknown instruction: 0x%08X at PC0x%08X\n, instr, cpu-pc-4); cpu-cpsr | 0x1; // 设置一个错误标志 break; } } int main() { CPUState cpu; // 初始化CPU状态和内存... cpu.mem_size 64 * 1024; // 64KB内存 cpu.memory (uint8_t*)malloc(cpu.mem_size); cpu.pc 0x1000; // 设置程序起始地址 // **核心模拟循环** while (!(cpu.cpsr 0x1)) { // 没有错误标志时继续 uint32_t instr fetch_instruction(cpu); if (instr 0xFFFFFFFF) break; // 内存访问越界 decode_and_execute(cpu, instr); } free(cpu.memory); return 0; }关键点CPUState结构体定义了虚拟CPU的“硬件”状态。fetch_instruction模拟了CPU从内存取指的过程。decode_and_execute是模拟器的核心这里需要实现完整的指令集。对于Am29000你需要根据其手册实现上百条指令。这个循环就是模拟器的“发动机”。4.2 第二步准备一个在模拟器上运行的“OS”程序这个“OS”实际上是一个独立的二进制文件它将被加载到模拟内存的特定位置如0x1000。我们需要用交叉编译工具链来生成它。// 文件simple_os.c - 一个用C编写的、极简的“图形化”程序 // 假设它运行在模拟的ARM CPU上 #define VIDEO_MEMORY ((volatile unsigned short*)0x06000000) // 假设显存地址 void draw_pixel(int x, int y, unsigned short color) { if (x 0 x 240 y 0 y 160) { // 假设屏幕240x160 VIDEO_MEMORY[y * 240 x] color; } } void draw_rectangle(int x, int y, int w, int h, unsigned short color) { for (int dy 0; dy h; dy) { for (int dx 0; dx w; dx) { draw_pixel(x dx, y dy, color); } } } // 入口点 void _start() { // 1. 初始化这里简化了真实情况需要设置栈、中断等 // 2. 绘制一个窗口一个矩形 draw_rectangle(10, 10, 100, 60, 0x001F); // 蓝色矩形 draw_rectangle(12, 12, 96, 56, 0xFFFF); // 白色内部 // 3. 进入一个简单的事件循环这里只是死循环 while(1) { // 在真实系统中这里会检查键盘/鼠标输入 } }使用交叉编译工具链将其编译成ARM二进制文件# 使用-nostdlib因为我们没有标准库_start是入口 arm-linux-gnueabi-gcc -nostdlib -static -marcharmv4t -mtunearm920t -o simple_os.elf simple_os.c # 将其转换为原始的二进制映像方便加载到模拟内存 arm-linux-gnueabi-objcopy -O binary simple_os.elf simple_os.bin现在我们有了simple_os.bin它包含了ARM机器码功能是绘制两个矩形。在完整的模拟器中我们需要在初始化时将这个.bin文件的内容读入到模拟内存的0x1000位置并将CPU的PC寄存器设置为0x1000。5. 完整示例与代码实现整合模拟器与“OS”让我们将上面的概念整合成一个更完整、可运行的示例。我们将创建一个简化版的“ARM模拟器”专门为了运行上面的simple_os.bin。// 文件minimal_arm_emu.c #include stdio.h #include stdint.h #include stdlib.h #include string.h #define MEM_SIZE (1024 * 1024) // 1MB 模拟内存 #define PC_START 0x1000 #define VIDEO_BASE 0x06000000 typedef struct { uint32_t regs[16]; // R0-R15, 其中R15是PC uint32_t cpsr; uint8_t *mem; } ARMState; uint32_t mem_read32(ARMState *s, uint32_t addr) { if (addr 4 MEM_SIZE) { return *(uint32_t*)(s-mem[addr]); } printf(Memory read error at 0x%08X\n, addr); return 0; } void mem_write32(ARMState *s, uint32_t addr, uint32_t val) { if (addr 4 MEM_SIZE) { *(uint32_t*)(s-mem[addr]) val; // 如果写入的是显存区域我们可以在这里添加“图形更新”的钩子 if (addr VIDEO_BASE addr VIDEO_BASE 240*160*2) { // 简单打印提示真实模拟器会更新图形界面 // printf(Graphics update at 0x%08X\n, addr); } return; } printf(Memory write error at 0x%08X\n, addr); } void cpu_execute(ARMState *s) { // 这是一个极度简化的、不正确的执行函数仅用于演示流程。 // 真实的ARM指令译码极其复杂。 uint32_t instr mem_read32(s, s-regs[15]); printf([PC0x%08X] Instr: 0x%08X\n, s-regs[15], instr); s-regs[15] 4; // PC前进 // 假设我们遇到一个特殊的“终止”指令例如全0 if (instr 0x00000000) { s-cpsr | 1; // 设置停止标志 printf(Halt instruction encountered.\n); } // 在实际中这里需要实现真正的ARM指令译码逻辑 } int main(int argc, char *argv[]) { if (argc ! 2) { printf(Usage: %s os_binary.bin\n, argv[0]); return 1; } // 1. 初始化模拟CPU和内存 ARMState cpu; cpu.mem (uint8_t*)calloc(MEM_SIZE, 1); memset(cpu.regs, 0, sizeof(cpu.regs)); cpu.regs[15] PC_START; // R15 is PC cpu.cpsr 0; // 2. 加载“OS”二进制文件到内存 FILE *f fopen(argv[1], rb); if (!f) { perror(Failed to open OS binary); free(cpu.mem); return 1; } fread(cpu.mem[PC_START], 1, MEM_SIZE - PC_START, f); // 注意不安全仅示例 fclose(f); printf(Loaded OS binary into memory at 0x%08X\n, PC_START); // 3. 核心模拟循环 printf(Starting emulation...\n); int steps 0; while (!(cpu.cpsr 1) steps 100) { // 限制执行100条指令防止死循环 cpu_execute(cpu); steps; } printf(Emulation stopped after %d steps.\n, steps); // 4. 打印最终寄存器状态用于调试 printf(\nFinal CPU State:\n); for (int i 0; i 13; i) printf( R%-2d: 0x%08X\n, i, cpu.regs[i]); printf( SP : 0x%08X\n, cpu.regs[13]); // Stack Pointer printf( LR : 0x%08X\n, cpu.regs[14]); // Link Register printf( PC : 0x%08X\n, cpu.regs[15]); // Program Counter free(cpu.mem); return 0; }编译并运行这个模拟器# 编译模拟器它是运行在宿主机上的x86程序 gcc -o minimal_arm_emu minimal_arm_emu.c # 运行模拟器加载我们之前编译的“OS” ./minimal_arm_emu simple_os.bin这个示例极其简陋cpu_execute函数并没有真正执行ARM指令。但它清晰地展示了一个模拟器项目的基本骨架状态定义(ARMState)内存映射与访问(mem_read/write)二进制加载取指-执行循环对于真正的Am29000模拟器你需要将ARMState替换为AM29000State并实现完整的Am29000指令集超过100条指令。这就是1996年那个项目的核心工作量所在。6. 运行结果与效果验证运行上面的minimal_arm_emu你期望看到的输出是模拟器逐条“执行”指令实际上只是读取并打印直到达到步骤限制。Loaded OS binary into memory at 0x00001000 Starting emulation... [PC0x00001000] Instr: 0xE92D4800 [PC0x00001004] Instr: 0xE28DB004 ... Emulation stopped after 100 steps. Final CPU State: R0 : 0x00000000 R1 : 0x00000000 ... PC : 0x00001190如何验证模拟器是否正确指令级验证使用调试器如gdb-multiarch单步跟踪一个已知的、简单的ARM程序如一个只返回常数的函数对比你的模拟器状态与真实QEMU用户模式运行的状态。这是最根本的验证。功能级验证让模拟器运行一个更复杂的、有输出的程序。例如一个修改特定内存位置代表串口或屏幕缓冲区的程序。你的模拟器需要实现对应的外设模拟并观察输出是否正确。对比现有模拟器如果能找到原始的Am29000模拟器或类似的遗留系统可以尝试运行同一个二进制文件对比最终的内存和寄存器状态。对于“窗口化OS”的验证则更为复杂。你需要实现一个图形显示后端例如使用SDL或SFML库将模拟器中对“显存”的写入操作映射到宿主机的窗口上。实现基本的输入模拟将宿主机的键盘鼠标事件转换为模拟器中的中断或内存映射IO事件。观察是否能在窗口中看到预期的矩形、文本等元素。7. 常见问题与排查思路在研究和复现此类历史模拟器项目时你会遇到一系列典型问题。问题现象可能原因排查方式解决方案无法找到原始项目或二进制文件项目年代久远托管站点关闭文件丢失。1. 在 archive.org (Wayback Machine) 上搜索项目主页。2. 在专业复古计算社区如VOGONS论坛搜索。3. 查找学术论文或技术报告引用。调整目标使用类似架构如ARM的现代开源模拟器进行方法论学习。找到的模拟器无法在现代系统运行16位DOS程序依赖过时的库或直接硬件访问。1. 在Linux下尝试dosbox。2. 在Windows下尝试兼容模式或DOSBox。3. 使用反汇编工具如IDA Pro, Ghidra进行静态分析。使用dosbox运行。或放弃直接运行转为静态分析和代码恢复。模拟器运行后无任何显示1. 图形模拟未实现或未启动。2. “OS”二进制未正确加载到内存。3. CPU模拟从错误地址开始执行。1. 检查模拟器初始化代码确认内存映射和PC初始值。2. 使用调试输出打印每条执行的指令和关键内存写入。3. 检查“OS”二进制文件头确认入口点地址。添加详细的日志功能。使用现有工具如objdump分析二进制文件确认其结构和预期行为。模拟器执行几条指令后崩溃1. 未实现的指令。2. 内存访问越界。3. 中断或异常处理未实现。1. 查看崩溃前的最后一条指令查阅CPU手册确认其含义。2. 检查所有内存读写函数的边界。3. 确认是否触发了未实现的CPU模式如FIQ/IRQ。实现缺失的指令或机制。在内存访问函数中加入断言和日志。交叉编译的“OS”程序无法在模拟器中运行1. 工具链的ABI应用二进制接口与模拟器预期不符。2. 链接脚本错误程序入口点或内存布局不对。1. 使用readelf -h和objdump -d对比一个能运行的程序和你的程序。2. 编写一个最简单的、不依赖任何库的汇编测试程序如循环加1验证工具链和模拟器的基础功能。编写自定义链接脚本.ld文件精确控制代码和数据在内存中的位置确保与模拟器的内存布局匹配。性能极差模拟器采用解释执行每条指令都需要多次C语言函数调用和分支判断。使用性能分析工具如gprof,perf定位热点函数。考虑使用动态二进制翻译DBT技术如QEMU的TCG将目标指令块翻译成宿主指令块再执行可大幅提升性能。8. 最佳实践与工程建议如果你想深入此类项目或者打算自己从头实现一个教学用的CPU模拟器以下建议可以帮你少走弯路从简入繁分层实现阶段一实现一个纯指令解释器能正确执行所有用户模式指令。使用现成的测试套件如ISA模拟器测试集进行验证。阶段二添加最基本的内存管理单元MMU和缓存模型如果CPU有。阶段三实现中断和异常处理这是支持操作系统的基础。阶段四添加外设模拟如定时器、UART串口、显示控制器。优先实现一个基于帧缓冲区的简单图形输出和基于终端输入的键盘。阶段五性能优化引入动态二进制翻译。测试驱动开发为每一条新实现的指令编写单元测试。测试用例可以是一个小的二进制片段执行后检查寄存器和内存状态。使用已有的、已知行为的软件作为集成测试。例如先让模拟器能运行一个简单的裸机程序如闪烁LED再逐步运行更复杂的RTOS。利用现有工具链不要自己写汇编器和链接器。使用GNU Binutils (as,ld) 或 LLVM 工具链来为目标CPU生成代码。使用objdump反汇编你的测试程序对照CPU手册验证指令生成是否正确。使用gdb的远程调试功能将你的模拟器作为gdb的“stub”可以极大地提升调试效率。文档与注释为模拟的每个硬件模块CPU核心、定时器、UART编写清晰的文档说明其寄存器映射、行为和工作原理。在代码中为复杂的指令实现和状态机添加详细注释。这不仅帮助他人更是帮助未来的你。版本控制与可复现性使用Git管理代码。为不同的功能特性创建分支。记录依赖的工具链版本。考虑使用Docker容器来固化整个构建和测试环境确保任何人在任何时候都能复现你的工作。9. 总结与后续学习方向回顾全文我们从“为什么需要研究一个老式模拟器”的疑问开始逐步深入到CPU模拟的核心原理、环境搭建、代码实现和问题排查。虽然我们未能直接运行那个1996年的Am29000模拟器但我们已经掌握了理解和复现它所需的全套方法论和工具链。这个项目的本质是一个软硬件边界的精确模型。它告诉我们一个窗口化操作系统在最底层不过是一段精心组织的机器码通过CPU指令与内存、外设进行交互。模拟器则是这个模型的“元模型”它在更高一层复现了底层硬件的规则。如果你被这个主题吸引可以沿着以下方向继续探索深入特定指令集选择一门经典的RISC指令集如RISC-V。它现代、开源、文档丰富。尝试用C语言实现一个RISC-V的用户模式模拟器。网上有大量的教学资源和测试用例。研究成熟的开源模拟器深入阅读QEMU或Unicorn Engine的源码。特别是QEMU的TCGTiny Code Generator中间表示和动态翻译框架是工业级模拟器的核心。实现一个简单的图形化“OS”基于你实现的模拟器或者使用QEMU的-kernel参数直接加载编写一个用C或汇编写的微型内核实现帧缓冲区绘图、多任务切换和键盘输入。这将彻底打通你对“计算机如何启动并运行图形界面”的理解。转向硬件描述语言如果你对硬件本身感兴趣学习Verilog或VHDL在FPGA上实现一个简单的CPU如基于MIPS或RISC-V。这将让你从“模拟硬件行为”升级到“描述硬件本身”。技术的历史并非线性淘汰底层的原理如同地质层不断沉积。研究像Am29000模拟器这样的“化石”不是为了回到过去而是为了更透彻地理解当下复杂系统赖以建立的基石。当你下次启动一个虚拟机或者调试一段嵌入式代码时希望你能想起那个在取指-译码-执行循环中默默运转的、由代码构成的虚拟CPU世界。建议将本文作为一份“系统软件考古与再造”的路线图收藏。当你准备好工具和耐心就可以开始自己的“时间机器”建造工程了。
返回列表