Elytra
其他

4KB 内存曾支撑人类登月:从阿波罗计算机到 Game Boy

1969 年,阿波罗 11 号在登月前遭遇计算机过载,却依靠精巧的恢复机制继续下降。五十多年后,开发者又把 Vue 组件编译进只有几 KB 内存的 Game Boy。两个时代的工程故事,讲述有限资源里的创造力。

1969 年 7 月 20 日,阿波罗 11 号的登月舱正在接近月球表面。离着陆只剩几分钟,计算机亮起了一个报警代码。

1202

阿波罗飞船座舱中的仪表和计算机操作面板

阿波罗飞船座舱的历史照片,面板上布满仪表、开关和数字显示器。

地面控制中心给出的答复是:可以继续。

接下来,1201、1202 又出现了几次。登月舱仍在下降,计算机仍在工作。1

执行制导任务的这台机器,只有约 4 KB 可读写内存。

我最初是看了一个分析阿波罗源代码的视频,才开始了解这段故事。吸引我的倒不只是那个小得惊人的数字:计算机已经忙不过来了,它怎么还能让登月继续?

后来想到在 RustChinaConf 认识的 PocketJS 团队,我又觉得有趣。他们正在把 Vue 组件编译到 Game Boy 这样的老掌机上。从登月舱到老掌机,我好奇的都是同一个问题:这么点资源,到底怎么够用?

1报警以后,为什么还能继续下降?

阿波罗使用的计算机叫 Apollo Guidance Computer(AGC),由 MIT 仪器实验室主导研制。

Apollo Guidance Computer 主机与 DSKY 显示键盘

点亮状态下的 Apollo 11 登月舱 DSKY 显示键盘

左:AGC 主机与 DSKY 操作单元;右:点亮的 DSKY,可以看到 VERB、NOUN 和数字显示区。

先把“4 KB”说清楚:它指的是可读写内存,不是整个登月程序的大小。AGC 还有约 72 KiB 固定存储,用来保存程序与常量。它以字而非现代字节组织存储,这里的换算只是为了方便感受容量。2

内存虽小,机器可不轻:整台 AGC 约重 32 公斤。3

下降时的麻烦来自交会雷达的接口。这部雷达原本用于登月舱与指令舱重新会合;当时的检查程序要求把它设为自动跟踪模式。接口中的两组参考信号相位不一致,导致计数器不断收到没有实际意义的更新请求,最多吃掉约 15% 的计算时间。45

原本按时能完成的工作,现在来不及做完。上一轮还没结束,下一轮又到了,任务越积越多,最后连安排任务所需的工作区都不够用了。

这就是 1201、1202 报警所指的问题:AGC 的任务管理系统 Executive 分不出工作区了。1202 对应 Core Set 不足,1201 对应 VAC 工作区不足。6

接下来发生的事有点出乎直觉:计算机触发了软件重启,登月却继续了。

这里的重启会保留必要数据,重新建立关键任务的执行状态;部分不需要恢复的工作则被丢下,不再占着工作区。制导等重要工作因此能继续运行。6

如果想到的是电脑重启后慢慢出现登录界面的场景,就很难想象有人敢在下降途中这么做。AGC 的恢复机制,却正是为飞行中继续工作准备的。

安装在阿波罗座舱面板中的 DSKY 显示键盘

安装在座舱面板中的 DSKY,宇航员通过它读取计算机状态和报警。

当然,地面也得知道这个报警意味着什么,才能给出继续下降的指令。计算机恢复工作,地面判断能否继续,宇航员继续操作——1202 没有消失在故事里,却也没有让故事停在这里。1

2程序写完了,还得织出来

AGC 还有一件让我很难忘的事:它的固定程序存放在 Core Rope Memory(磁芯绳存储器) 里。

导线穿过还是绕过磁芯,是编码的一部分。成批的导线与磁芯组成存储模块,程序就这样被固定在里面。7

人工穿线制作磁芯绳存储器的工序

制造人员正在为磁芯绳存储器穿线。

软件仍然要先设计、编码、汇编和测试,再转换成制造所需的布线数据。接下来的穿线与组装由制造人员完成,其中有大量女性技术工人。8

今天发现一条指令写错了,可以改文件、重新编译。对这块固定存储器来说,正式修改程序还意味着重新制造、验证模块。“这个版本就这么定了”,在这里有很实在的分量。

不过,绳子里的程序改不了,飞行中的机器仍有调整余地。阿波罗 14 号就遇到过异常的中止开关信号,地面工程师设计了绕过方案,再由宇航员向计算机输入指令。9

一边是织好后不能随手改动的程序,一边是要在飞行中处理意外的现实。看完这些故事,再回头看“4 KB 登月”这几个字,我反而觉得它省略了最有意思的部分。

3五十多年后,还能找到那行 1202

让我开始读这些故事的 Apollo-11 GitHub 仓库,收录了指令舱的 Comanche055 和登月舱的 Luminary099。这些源码来自历史汇编清单的数字化与校对。10

打开登月舱的 EXECUTIVE.agc,找不到可用 Core Set 时,程序会走到这里:

TC   BAILOUT1   # NO CORE SETS AVAILABLE.
OCT  1202

TC 是控制转移指令,OCT 表示后面的数以八进制写出。这段代码会进入 BAILOUT1 路径,以 1202 为报警代码。同一个文件里,也能找到工作区不足时的 1201。11

原来那个在登月故事里反复出现的数字,在源代码里真有这么一行。比起单纯知道它的内存有多小,我更喜欢这种感觉:隔了五十多年,还能顺着报警代码往下找。

想继续看,可以沿着 Executive、Alarm and Abort 和 Restart Routine 读下去。Virtual AGC 还提供了相关的汇编与模拟工具。

4再往后,有人在 Game Boy 上写 Vue

1989 年,任天堂推出初代 Game Boy。它有 8 KiB 工作内存、8 KiB 显存,屏幕分辨率是 160 × 144。12

2026 年 10 月,我在 RustChinaConf 听了半糖的演讲:“不需要 JavaScript 的 Vue:Rust 嵌入式 UI 新探索”。那天认识了 PocketJS 团队的雪碧、半糖和 Jimmy,后来还一起吃了火锅、在海边散步,聊旧设备、编译器和游戏开发。

这些见闻已经写在 《为什么要让 Vue 跑在 PSP 上?》 里。不过这张 Game Boy 的运行画面,我还是想再放一次。

PocketJS 团队展示的 Game Boy 运行画面

RustChinaConf 上看到的 PocketJS 团队展示画面,摄于大会期间。

在这样的机器上写 Vue,听起来很容易让人先想到一个问题:JavaScript 引擎放哪儿?

Pocket Vapor 的掌机实验走了另一条路:将受支持的 Vue 组件提前编译成 C 代码,再用目标平台的工具链构建 ROM。Game Boy 最后运行的程序里,不需要塞进完整浏览器或 Vue 运行时。13

Loading diagram…

Mermaid 源代码

flowchart LR
    A["Vue 组件"] --> B["提前分析状态与更新关系"]
    B --> C["生成 C 代码"]
    C --> D["平台工具链构建 ROM"]
    D --> E["Game Boy / NES 等设备"]

可选择文字复制;按 Esc 或点击空白处关闭。

Pocket Vapor 掌机编译路线的简化示意。

Vue 里好用的东西,有一部分留下来了:用组件描述界面,用响应式状态表达变化。但设备上不必照搬它们原来的实现。对于结构已知、能力受限的组件,哪些状态影响哪些元素,可以在编译时提前分析,再生成固定的数据布局和更新逻辑。

代价也很直接:只能使用它支持的子集。拿一个现成 Vue 网站过来,连着 DOM、CSS 和各种动态 JavaScript 一起导出成 Game Boy ROM,当然不在这个实验的能力范围内。

团队展示过同类 Vue 待办事项组件在几种掌机上的结果:Game Boy ROM 为 32 KB,NES ROM 为 40 KB。这是演示程序的 ROM 体积,不是运行时占用的内存。13

待办事项大概不是我买 Game Boy 时会想到的用途。但熟悉的 Vue 组件,最后能变成这台机器读得懂的程序,这件事本身就够好玩了。

5这么点资源,还能做什么?

我不太想拿“4 KB 登月”去嘲笑今天几百 MB 的应用。现在的软件要联网、加密、处理音视频,还要适配各种设备。只比体积,比较不出多少东西。

但这两个故事确实让我更想往里面看一点。AGC 忙不过来时,哪些工作可以放下,哪些必须恢复?Vue 组件变成掌机程序时,哪些便利能保留,哪些成本能提前交给编译器?

那些惊人的数字,只是入口。往下找,才会看到计数器、任务工作区、穿过磁芯的导线,以及最后生成的 C 代码。

至于 Game Boy——我以前只想过拿它玩游戏,确实没想过拿 Vue 给它写界面。

注释

  1. NASA Apollo Lunar Surface Journal,Apollo 11 Lunar Landing。包含下降阶段宇航员与地面控制中心对报警的实时通话。 ↩ ↩2

  2. NASA,Apollo Guidance Computer 相关历史资料。AGC 通常记为 2,048 字可读写存储、36,864 字固定存储;这里采用每字 16 位的物理容量口径,不能视为现代 8 位字节的直接等效数据容量。 ↩

  3. Lorenz Keller,Mondlandung mit Steinzeit-Technik,Blick,2019 年。报道介绍了 AGC 约 32 公斤的重量,并附有实物照片;其关于存储容量的单位与本文字数规格不一致,因此不以该报道作为内存容量依据。 ↩

  4. NASA,Apollo 11 Mission Report,1969 年,第 16.2.5 节。分析登月过程中 1201 / 1202 报警以及交会雷达引发的额外中断负载。 ↩

  5. George W. Cherry(MIT 仪器实验室),Exegesis of the 1201 and 1202 Alarms Which Occurred During the Mission G Lunar Landing,1969 年 8 月 4 日,尤其第 2、3、7、8 页。原始事故分析详细解释交会雷达的 800 Hz 相位差、计数器请求造成的约 15% 开销、两秒周期调度以及软件重启机制。 ↩ ↩2

  6. NASA Apollo Lunar Surface Journal,The 1201 and 1202 Program Alarms。收录参与者的技术回顾,解释 Executive 工作区、重启与恢复机制。 ↩ ↩2

  7. MIT,Apollo Guidance Computer 历史与资料。保存有关 AGC、固定存储器与软件制造过程的历史资料及源代码线索。 ↩

  8. Nancy Atkinson,The Story of the Apollo Guidance Computer, Part 2,Universe Today,2019 年。文中介绍了早期集成电路、AGC 的硬件取舍与磁芯绳存储器的制造过程。 ↩

  9. NASA Apollo Lunar Surface Journal,Apollo 14 Lunar Surface Journal。关于任务期间异常中止信号、地面编制的处理方案与飞行操作背景。 ↩

  10. Chris Garry 整理,Apollo-11 source code。历史 AGC 程序清单的数字化版本,不是现代意义上的重新实现。 ↩

  11. Apollo-11 GitHub 源代码,Luminary099/EXECUTIVE.agc。该历史汇编文件同时包含 OCT 1201(VAC 工作区用尽)和 OCT 1202(Core Set 用尽)的处理分支。 ↩

  12. Pan Docs,Game Boy Technical Reference。初代 Game Boy 内存映射可核对 8 KiB WRAM 与 8 KiB VRAM。 ↩

  13. PocketJS,Pocket Vapor,2026 年。开发团队对 Vue 子集编译、生成 C、多个掌机目标与 ROM 体积的第一手说明。 ↩ ↩2

  14. PocketJS,Architecture;MicroTS。官方说明 JS Guest / Rust UI 核心与提前编译为 Rust 的不同技术路线。 ↩

CONTINUE EXPLORING

沿着这篇文章继续

在地图中查看

欢迎补充、提问,或分享不同的看法。使用 GitHub 账号登录后即可评论。

On this page