APOLLO GUIDANCE COMPUTER · 1969 · PUBLIC DOMAIN

阿波罗 11 号制导计算机源码
逐层拆解

按「约束 → 设计」的顺序拆开这份真正飞上月球的软件。每条结论都附源码原文并标注到行号,外部资料单独标记,不与源码证据混排。

总行数 130,186源文件 175 个 .agc登月舱 / 指令舱 64,838 / 65,348版权 公有领域
目录
0先看清那个箱子Constraints113 万行怎么装进去Organization2谁来排班:ExecutiveScheduling3出错会怎样:从 1202 看容错Fault Tolerance4汇编不够用,就再铺一层Abstraction5人怎么指挥它:DSKYInteraction6它到底在算什么Mission7五十年后还剩下什么Lessons
怎么读这份讲义。先看约束,再看他们如何一步步回应。每一章都是对上一章提出的问题的回答。深色代码块是源码原文,紧贴其下的白底文字是解读。标着「外部资料」的条目来自 NASA 公开文档,仓库源码里查不到,单独区分以免混淆。
先说清楚单位:位、字、以及没有字节
这一篇里「位」和「字」是两个不同层级的单位,而且这台机器里根本没有字节。先花一分钟分清,后面就不会读错。
位(bit)
最小单位。一个开关只有 0 或 1 两种状态,就是一个 bit。
操作码 3 位、地址 12 位,说的都是 bit。
字(word)
这台机器一次能处理的完整一份数据。1 个字 = 15 位数据(最高位是符号)+ 1 位奇偶校验;程序按字寻址,不按 8 位字节,物理上占 16 位。校验位由硬件自动生成与检查,程序碰不到。
内存容量全用字:可写 2,048 字,只读 36,864 字。
字节(byte)
这台机器里没有字节这个概念。 今天说的 1 字节 = 8 位,是后来才通行的。所以看到「2,048 字」不要按字节去想。
按今天的换算:2,048 字 ≈ 4 KB,36,864 字 ≈ 72 KB。
换算一次就明白了。36,864 字 × 16 位 ÷ 8 ÷ 1,024 = 72 KB;2,048 字 × 16 位 ÷ 8 ÷ 1,024 = 4 KB。这就是常见说法里「阿波罗有 4 KB 内存、72 KB 只读存储」的来处——当年的人只说字,是后人按字节折算出来的。
四种内容,四种可信度
本册把事实和解释分开摆放。读到任何一句,先看清它属于哪一类。
源码实证可在仓库里按文件与行号直接核对的事实。引证固定在 commit 911e5c0。
外部资料来自 NASA / MIT / 公开史料。不能回仓库核对,单独标出并给出参考文献编号。
作者推导不是原文直接写明的,是我从源码数据算出来或推出的结论。会写明推导过程,读者可自行复核。
解释性类比为了帮助理解而打的比方,属于解释工具,不是证据。

0先看清那个箱子

Constraints
讲软件之前先讲硬件。后面每一个设计决定,几乎都能追溯到这台机器能给出的那点余量。
当时的处境
阿波罗制导计算机装在登月舱和指令舱里。它要扛住发射震动、真空、温差,还要在着陆这种一次定生死的事情上不出错。它的可用资源,按今天的标准看少得近乎荒谬。
可擦写内存的切分 依据 ERASABLE_ASSIGNMENTS.agc:374-380 / :432-437 的 ERASE 计数 核心集 CORE SETS 84 字 7 组 × 12 字,普通作业的工作位 JOB 0 12 字 JOB 1 JOB 2 … JOB 6 NO.CORES = DEC 7 VAC 区 220 字 5 区 × 44 字,解释型数学作业的加大工位 VAC1 44 字 VAC2 VAC3 … VAC5
图 1 可写内存与只读内存的容量对比。可写部分只有只读部分的约十八分之一,还要手工分配。
源码实证Luminary099/ERASABLE_ASSIGNMENTS.agc:374-380
# DYNAMICALLY ALLOCATED CORE SETS FOR JOBS	(84D)

MPAC		ERASE	+6		# MULTI-PURPOSE ACCUMULATOR.
MODE		ERASE			# +1 FOR TP, +0 FOR DP, OR -1 FOR VECTOR.
LOC		ERASE			# LOCATION ASSOCIATED WITH JOB.
BANKSET		ERASE			# USUALLY CONTAINS BBANK SETTING.
PUSHLOC		ERASE			# WORD OF PACKED INTERPRETIVE PARAMETERS.
一个作业(计算机正在处理的一项任务)的工作位由 MPAC(主累加器,解释器做算术的工作区——它是内存里划出来的一段,不是硬件寄存器)、MODE、LOC、BANKSET、PUSHLOC 等字段组成,加起来 12 个字。注释里的 84D 是这一整块的大小,单位是字。
源码实证Luminary099/ERASABLE_ASSIGNMENTS.agc:432-437
# VAC AREAS. -- BE CAREFUL OF PLACEMENT --	(220D)

VAC1USE		ERASE
VAC1		ERASE	+42D
VAC2USE		ERASE
VAC2		ERASE	+42D
BE CAREFUL OF PLACEMENT 是当年程序员留在注释里的原话。内存紧到程序员觉得需要专门写一句话,提醒后来的人摆放前先想清楚。
外部资料NASA 公开技术文档(本仓库未记载)
AGC 主要规格:
· 字长 15 位(bit)+ 1 位奇偶校验
· 固定内存(绳芯只读)约 36K 字(word)
· 可擦写内存约 2K 字
· 时钟约 2.048 MHz
这几条来自 NASA 公开资料,仓库源码里查不到,所以单独标成外部资料。凡是我没法在这份代码里指给你看的数字,都不会混进源码实证里。
●可擦写内存里有两块专门留出来动态分配的区域:84 字(word)的核心集区,220 字的 VAC 区。它们合计 304 字,占 2,048 字可写内存的约 14.8%——其余部分另有用途,不是只有这两块。
●内存按字手工分配,源码里每一条 ERASE 就是一次记账。
●后面会看到的调度、解释器、告警分级,全是为了在这点空间里把事情做完。

113 万行怎么装进去

Organization
登月舱 64,838 行,指令舱 65,348 行,合计 130,186 行(GitHub chrislgarry/Apollo-11,固定版本 911e5c0)。这个项目当年不分文件编译,也没有使用链接器。
打个比方
像一本自己装订的活页夹:最前面贴一张手写的目录,写明哪几页是正文、哪几页是附录。要改其中一页,抽出来换一张就行,别的页都不用动。五十年过去了,那张手写的目录还夹在里面。
当时的处境
现代开发把源码拆成多个文件分别编译,再由链接器拼成可执行程序。1969 年的团队没有链接器,代码是打在卡片上的,复用别人的代码就是把自己的卡片插进对方卡片叠的对应位置。
源码实证Luminary099/README.md(仓库原文)
It may be reasonably asked why tens of thousands of lines of source are joined by means of inclusion, rather than simply assembling the source files individually and then linking them to form the executable. The answer is that the original development team had no linker.  The builds were monolithic just like this.
翻译过来:有人问为什么不分别编译再链接,答案是当年的开发团队没有链接器,构建本来就是单体式的。这不是偷懒,是当时工具链条件下唯一现实的选择——没有链接器可用。
源码实证Luminary099/MAIN.agc:1-7
$ASSEMBLY_AND_OPERATION_INFORMATION.agc		# pp. 1-27
$TAGS_FOR_RELATIVE_SETLOC.agc			# pp. 28-37
$CONTROLLED_CONSTANTS.agc			# pp. 38-53
$INPUT_OUTPUT_CHANNEL_BIT_DESCRIPTIONS.agc	# pp. 54-60
$FLAGWORD_ASSIGNMENTS.agc			# pp. 61-88
						# p.  89 is a GAP-generated table
$ERASABLE_ASSIGNMENTS.agc			# pp. 90-152
MAIN.agc 就是那本目录。$ 开头表示把另一个文件整个插进来,行尾的 pp. 1-27 是原始纸质装订本的页码,当年校对就是对着纸一页页数。
源码实证Luminary099/ALARM_AND_ABORT.agc:26-29
# Notations on the hardcopy document read, in part:
#
#	Assemble revision 001 of AGC program LMY99 by NASA 2021112-061
#	16:27 JULY 14, 1969
这份登月舱程序硬拷贝的打印装订时间是 1969 年 7 月 14 日 16 点 27 分,离 7 月 20 日着陆只剩 6 天——注意这是打印装订的日期,改不动的是早已织好的绳芯,程序员把装订记录当注释留在了源码里。
●MAIN.agc 是总目录,登月舱 89 个、指令舱 84 个源文件被 include 进来,两舱各另有 1 个 MAIN.agc(合计 90 个、85 个),两舱各另有 1 个 MAIN.agc(合计 90 个、85 个)。
●每个文件后面标着纸质本的页码,方便人工校对。
●没有链接器导致单体构建,反而让今天的人能从头到尾顺着读下去。

2谁来排班:Executive

Scheduling
飞船同时要算轨道、稳姿态、刷显示、读键盘、测雷达,计算机只有一台。谁先干,由 Executive 决定。
打个比方
一家只有 7 张桌子的小馆子,一个领班在门口看着。来的客人先按急不急排;桌子坐满了,后来的就在门口等;真的一张都不剩,领班按事先定好的办法处理,而不是直接关门歇业。
当时的处境
计算机同一时刻只真正算一件事:要干的活被拆成一件件「作业」——这个词后面会反复出现,指的是一项已经提交、正在算或排队等算的任务。多任务并不是操作系统普及之后才有的概念。在进程这个词还没流行的年代,他们已经做出了优先级抢占、作业休眠唤醒、上下文保存与恢复。
Executive 的优先级比较 新作业到来时,与当前最高优先级比一次,高就切换 着陆制导 优先级最高 姿态控制 DAP 次高 键盘显示 PINBALL 最低 EXECUTIVE NOVAC / FINDVAC PRIOCHNG / JOBSLEEP 7 个核心集 有空位就开工,没空位报 1202
图 2 Executive 分配核心集与判定优先级抢占的过程。
源码实证Luminary099/EXECUTIVE.agc:155-162
NOVAC2		CAF	ZERO		# NOVAC ENTERS HERE.  FIND A CORE SET.
		TS	LOCCTR
		CAF	NO.CORES	# SEVEN SETS OF ELEVEN REGISTERS EACH.
NOVAC3		TS	EXECTEM2
		INDEX	LOCCTR
		CCS	PRIORITY	# EACH PRIORITY REGISTER CONTAINS -0 IF
		TCF	NEXTCORE	# THE CORESPONDING CORE SET IS AVAILABLE.
NO.CORES	DEC	7
★ NO.CORES 的值就是 7,源码写得清清楚楚。常说的「8 个作业」在这份源码里找不到依据。但同一段注释里的 SEVEN SETS OF ELEVEN REGISTERS EACH(11 个),和同文件第 318 行的 COREINC DEC 12(12 个)对不上——这是原始文档自身的残留,不是我们的误读。验算见下一条。
作者推导Luminary099/EXECUTIVE.agc:157,162,318 / ERASABLE_ASSIGNMENTS.agc:374,383
# EXECUTIVE.agc:157
		CAF	NO.CORES	# SEVEN SETS OF ELEVEN REGISTERS EACH.
# EXECUTIVE.agc:162
NO.CORES	DEC	7
# EXECUTIVE.agc:318
COREINC		DEC	12		# 12 REGISTERS PER CORE SET.
# ERASABLE_ASSIGNMENTS.agc:374
# DYNAMICALLY ALLOCATED CORE SETS FOR JOBS	(84D)
# ERASABLE_ASSIGNMENTS.agc:383
		ERASE	+83D		# EIGHT SETS OF 12 REGISTERS EACH
★ 自己动手算一遍,比听任何一方转述都可靠。数据侧:核心集 7 个(NO.CORES DEC 7)、每集步长 12 字(COREINC DEC 12),7 × 12 = 84,正好对上批注里的 84D。批注侧:EIGHT SETS OF 12 是把 MPAC 起头那 12 个字也算作一集,12 + 84 = 96 = 8 × 12,同样自洽。唯一对不上的是第 157 行注释里的 ELEVEN(十一)。最合理的解释是改数之后忘记同步注释的残留,实际每集 12 字——但这只是基于三处对一处不一致的推断,没有当年的修订记录可以直接证明。这类不一致在老源码里很常见,读到时照实记下即可。
外部资料NASA NTRS 20230005922,Appendix F(p.288 / p.292)
Although technically a "general purpose computer," the AGC was customized
design essentially for GN&C functions operating with a priority driven
operating system with a very specialized I/O.

Component Description Performance Computer
  AGC (Block II): Priority-interrupt driven, parallel 16-bit, 1-MHz
  digital computer.
外部佐证:NASA 的技术文件把这套东西直接叫做 priority driven operating system,并注明是 priority-interrupt driven。——源码里的 Executive 不只是「有点像调度器」——在官方口径里,它就是操作系统层。
源码实证Luminary099/EXECUTIVE.agc:191-196
		DXCH	LOC
		INDEX	NEWJOB		# THIS INDEX INSTRUCTION INSURES THAT THE
		CS	PRIORITY	# HIGHEST ACTIVE PRIORITY WILL BE COMPARED
		AD	NEWPRIO		# WITH THE NEW PRIORITY TO SEE IF NEWJOB
		EXTEND			# SHOULD BE SET TO SIGNAL A SWITCH.
		BZMF	ENDFIND
注释写得很直白:把当前最高优先级和新作业的优先级比一次,决定要不要切换。这就是抢占式调度的判定点。
源码实证Luminary099/EXECUTIVE.agc 中的例程名
NOVAC     新作业申请核心集
FINDVAC   申请 VAC 区,给解释型作业用
JOBSLEEP  作业休眠
JOBWAKE   作业唤醒
PRIOCHNG  修改优先级
ENDOFJOB  作业结束
要说的是它们背后的机制,不是这几个名字本身。NOVAC/FINDVAC 对应资源分配,JOBSLEEP/JOBWAKE 对应阻塞与唤醒,PRIOCHNG 对应优先级动态调整,ENDOFJOB 对应任务终止——这套机制今天仍是操作系统与实时系统的基本内容。但若说这些具体例程名「任何一本教材都还在用」,那是过头了:它们只是当年 MIT 团队自己的命名,不是通用术语。
源码实证Luminary099/DAPIDLER_PROGRAM.agc:479
DSPLYALT	EQUALS	BIT4		# 100 MS ALTERNATION BIT IN RCSFLAGS
连显示翻转这样的小作业都有节拍:DAPIDLER 的注释写着它每次被调用就翻转一次显示位,「大概每 100 毫秒」——原话带着 presumably,连这个周期都是当年的推测。这里的「实时」不是越快越好,而是指每个任务都有自己必须赶上的时间点。
CONTROL FLOW一次作业调度跑完的完整路径
从「要起一个作业」到「作业真正开始跑」,中间发生了什么
1
程序发起请求
普通作业走 NOVAC 申请核心集;解释型作业走 FINDVAC 申请 VAC 区。两者都要先关中断。
Luminary099/EXECUTIVE.agc:37 / :52
│
▼
2
扫描核心集找空位
以 NO.CORES(值为 7)为界,逐个检查核心集的 PRIORITY 寄存器,值为 -0 即空闲。
Luminary099/EXECUTIVE.agc:155-162
│
▼
3
找到就分配
NEXTCORE 用 COREINC(步长 12)算出下一个核心集的地址,把工作位交给新作业。
Luminary099/EXECUTIVE.agc:201-208
│
▼
4
比较优先级决定是否抢占
把当前最高优先级与新作业优先级相减,结果用于判断是否要切换——这就是抢占式调度的判定点。
Luminary099/EXECUTIVE.agc:191-196
│
▼
5
该等就睡
暂时跑不了的作业走 JOBSLEEP,让出处理机并保存现场。
Luminary099/EXECUTIVE.agc:90
│
▼
6
条件到了就唤醒
别的作业或中断走 JOBWAKE 把它重新挂回队列。
Luminary099/EXECUTIVE.agc:97
│
▼
7
跑完交还
ENDOFJOB 收尾,释放核心集,调度器接着挑下一个。
Luminary099/EXECUTIVE.agc:117
WALKTHROUGH核心集与 VAC 区的内存账,自己算一遍
调度器的容量不是凭感觉定的,是内存算出来的。下面这笔账的每一个数字都能在源码里找到出处,算完之后还能和源码自己的批注对上——如果对不上,说明其中一处是错的。
已知条件
·可写内存总量:2,048 字(源码批注)。
·核心集数量:NO.CORES DEC 7(EXECUTIVE.agc:162)。
·每个核心集步长:COREINC DEC 12,单位是字(word),见 EXECUTIVE.agc:318。
·VAC 区定义:VAC1USE … VAC5USE 共 5 个,每个区 VACxUSE 占 1 字、VACx 为 ERASE +42D 即 43 字(ERASABLE_ASSIGNMENTS.agc:432-443)。
步代码在算什么代入本例结果
1NO.CORES DEC 7
核心集有几个
NO.CORES = 77 个
2COREINC DEC 12
每个核心集占多少字
COREINC = 1212 字/集
37 x 12
核心集一共吃掉多少可写内存
7 x 1284 字
41 + 43
一个 VAC 区 = 使用标志字 + 数据区
1 + 4344 字/区
55 x 44
5 个 VAC 区一共多少字
5 x 44220 字
684 + 220
两块动态分配区合计
84 + 220304 字
7304 / 2048
占全部可写内存的比例
304 / 2048约 14.8%
用源码自己的批注独立验算
# 源码里的两处批注(ERASABLE_ASSIGNMENTS.agc)
# 374 行:DYNAMICALLY ALLOCATED CORE SETS FOR JOBS   (84D)
# 432 行:VAC AREAS. -- BE CAREFUL OF PLACEMENT --   (220D)

# 本册算出来的结果
核心集 7 x 12 = 84     <-- 与批注 (84D) 一致
VAC  5 x 44 = 220     <-- 与批注 (220D) 一致

# 再验一次每个核心集的 12 字是怎么来的(ERASABLE_ASSIGNMENTS.agc:375-380)
MPAC     ERASE  +6     ->  7 字(MPAC 到 MPAC+6)
MODE     ERASE         ->  1 字
LOC      ERASE         ->  1 字
BANKSET  ERASE         ->  1 字
PUSHLOC  ERASE         ->  1 字
PRIORITY ERASE         ->  1 字
                          合计 12 字   <-- 与 COREINC DEC 12 一致
两条独立路径给出同一个数:从 EXECUTIVE 里的数据算,得 84;源码在内存分配表里自己批注的也是 84D。VAC 区同理,算出来 220,批注也是 220D。这说明数据、批注、指令步长三者自洽。唯一对不上的是 EXECUTIVE.agc:157 那句 SEVEN SETS OF ELEVEN REGISTERS EACH——把 12 写成了 11。三处一致对一处不一致,可以判定是那句注释没跟着改。
第 8 个作业进来时会发生什么
扫描核心集的循环(EXECUTIVE.agc:155-208):
  LOCCTR 从 0 出发,每扫一个核心集加 COREINC(12)
  EXECTEM2 装入 NO.CORES(7),每轮由 CCS 减 1——CCS 这条指令本身就兼计数与判断

  第几轮扫描:        1     2     3     4     5     6     7   |(还想扫第 8 轮)
  LOCCTR:           0    12    24    36    48    60    72   |  84,已出核心集区
  CCS 后 EXECTEM2:  6     5     4     3     2     1     0   |  不再减

  CCS 碰到 0 时不再跳回 NOVAC3,直接落到
      TC  BAILOUT1
      OCT 1202        <-- 核心集耗尽

也就是说:7 个核心集全被占住时,第 8 个作业申请不到工作位,
触发的不是「等待」,而是走一条事先写好的恢复路径(详见下一章)。
●7 个核心集,源码里 NO.CORES 的值就是 7。
●新作业到来时比一次优先级,高就抢占,这是货真价实的抢占式调度。
●作业可以休眠、唤醒、改优先级,还能排队延时执行,具备实时操作系统的基本骨架。
●核心集 84 字 + VAC 区 220 字 = 304 字,占 2,048 字可写内存的约 14.8%,与源码两处批注完全对上。

3出错会怎样:从 1202 看容错

Fault Tolerance
1969 年 7 月 20 日,鹰号下降过程中 DSKY 连续跳出 1202。它之所以不要命,是因为出错这件事在这套系统里被分成了三六九等。
打个比方
像做饭时遇到的三种小状况:最轻的是墙上的指示灯闪一下,你在便签上记一笔,锅还在火上继续炖;中等的是把火关小、把锅端下来看看,但汤和料都还在,看完了接着炖;最重的才是关火、整锅倒掉重来。1202 属于中间那一级。
当时的处境
很多系统把异常当成意外,写成一句打印就完事。这份代码把异常当成一条条设计好的路径:哪一种错只亮灯,哪一种要重启,哪一种必须彻底停下,都写在告警表里。
1202 之后的七步 EXECUTIVE.agc:207 → ALARM_AND_ABORT.agc:189 → FRESH_START_AND_RESTART.agc:281 1. 新作业申请 NOVAC 找核心集 2. 7 个全都占满 TC BAILOUT1 3. 抛 OCT 1202 NO CORE SETS 4. 写进 FAILREG 三级告警寄存器 5. PROG 灯亮 宇航员看到 1202 6. 软件重启 WHIMPER → ENEMA 7. 按相位表把作业接回原位 GOPROG3 校验 -PHASE1..6,制导继续 官方告警表把 1202 标为 *,含义是 results in a software restart
图 3 1202 告警从产生到恢复所经过的模块与写入路径。
1202 之后:重启,不用从头来 新作业进来 走 NOVAC 扫描 7 个核心集 全被占住 BAILOUT1 OCT 1202 写进 FAILREG 三级槽位 PROG 灯亮 宇航员看见 WHIMPER 等一会儿 RESUME 重新开始挑活 ENEMA 清掉低优先级的 GOPROG3 查相位表 恢复计算进度 下降继续 源码:EXECUTIVE.agc:155-208 ALARM_AND_ABORT.agc:66-80 FRESH_START_AND_RESTART.agc(相位表校验)
图 4 1202 告警从产生到恢复所经过的模块。注意后半段不是从头重来,而是按相位表(登记每个作业恢复点的表)把每项计算接回原来的进度。
源码实证Luminary099/ALARM_AND_ABORT.agc:32-38
# THE FOLLOWING SUBROUTINE MAY BE CALLED TO DISPLAY A NON-ABORTIVE ALARM CONDITION.  IT MAY BE CALLED
# EITHER IN INTERRUPT OR UNDER EXECUTIVE CONTROL.
#
# CALLING SEQUENCE IS AS FOLLOWS:
#		TC	ALARM
#		OCT	AAANN		# ALARM NO. NN IN GENERAL AREA AAA.
#					# (RETURNS HERE)
最轻的一级叫 ALARM,注释写明它是「非中止性」的——不打断正在跑的活:只点亮程序告警灯,不显示,调用后照常返回。编码规则按注释原话是五位八进制 AAANN——前三位区域、后两位编号:01202 读作区域 012 的第 02 号。
源码实证Luminary099/ALARM_AND_ABORT.agc:66-81
CHKFAIL1	CCS	FAILREG		# IS ANYTHING IN FAILREG
		TCF	CHKFAIL2	# YES TRY NEXT REG
		LXCH	FAILREG
		TCF	PROGLARM	# TURN ALARM LIGHT ON FOR FIRST ALARM

CHKFAIL2	CCS	FAILREG +1
		TCF	FAIL3
		LXCH	FAILREG +1
		TCF	MULTEXIT

FAIL3		CA	FAILREG +2
		MASK	POSMAX
		CCS	A
		TCF	MULTFAIL
		LXCH	FAILREG +2
		TCF	MULTEXIT
告警码不是只记一个,而是有三级缓冲——FAILREG、FAILREG+1、FAILREG+2 三个连着的寄存器:第一格占了就往第二格放,第二格占了就往第三格放。第三格由 MULTFAIL 例程写入并带上 BIT15 标记(多重告警状态),所以这三格不是简单的先进先出队列。
源码实证Luminary099/ERASABLE_ASSIGNMENTS.agc:430
FAILREG		ERASE	+2		# B(3)PRM 3 ALARM CODE REGISTERS
三个告警码寄存器,注释里的 B(3)PRM 按内存分配表的记法是:B=基本指令引用,3=三个寄存器,PRM=整个任务期间用途固定、不可挪作他享——「专用」不等于「重启时被特殊保护」。真正的清零发生在 FRESH START 初始化:源码把三个格都写成零;1202 触发的重启走的是另一条路,告警记录得以保留。
源码实证Luminary099/EXECUTIVE.agc:201-208
NEXTCORE	CAF	COREINC
		ADS	LOCCTR
		CCS	EXECTEM2
		TCF	NOVAC3
		LXCH	EXECTEM1
		CA	Q
		TC	BAILOUT1	# NO CORE SETS AVAILABLE.
		OCT	1202
1202 就是在这里发出的:程序想要一个核心集,NOVAC 把 7 个位置找了一圈,全被占着,就走「没有核心集」的固定出口 BAILOUT1;紧跟着的 OCT 1202,就是把 1202 这个号码作为告警发出去。登月夜阿姆斯特朗听到的报警,源头就是这几行。
源码实证Luminary099/ASSEMBLY_AND_OPERATION_INFORMATION.agc:966-967
# 01201	      *	EXECUTIVE OVERFLOW - NO VAC AREAS		EXEC
# 01202	      *	EXECUTIVE OVERFLOW - NO CORE SETS		EXEC
发出去的号码不是随便编的,NASA 有一张官方告警码表存在仓库里,1201 和 1202 就挨在一起:1201 是「给解释器用的暂存区满了」,1202 是「干活用的核心集满了」。表里每个号码后面还跟着一列记号——星号、双星号、字母 P——它们决定告警的后果,含义在下面这段注释里。
源码实证Luminary099/ASSEMBLY_AND_OPERATION_INFORMATION.agc:1014-1021
#	      *	INDICATES AN ABORT CODE THAT RESULTS IN A SOFTWARE RESTART.
#
#	     **	INDICATES A MORE SERIOUS ABORT CODE THAT RESULTS IN THE
#		PROGRAM GOING TO R00.
#
#	      P	INDICATES A PRIORITY ALARM.
#
#		ALL OTHERS ARE NON-ABORTIVE
这段注释就是那列记号的说明书:一颗星,代表会触发一次软件重启——机器把现场收拾好,从头再跑一遍;两颗星,更严重,按告警表的原话程序 going to R00;字母 P,代表高优先级告警;什么都不带,只是记一笔,流程照旧。注意这套记号描述的是处理类别:同一个号码在不同阶段可能走不同处理——源码备注里就有现成的例子,1406 在点火算法阶段是 POODOO,在实际制导阶段只是 ALARM。1202 带一颗星,所以它的后果是重启一次——登记过的作业从各自的恢复点接回来,误调度的低优先级作业被丢弃,而不是死机。从告警到恢复的完整路径,下面这张图串起来了。
源码实证Luminary099/ALARM_AND_ABORT.agc:144-149
WHIMPER		CA	TWO
		AD	Z
		TS	BRUPT
		RESUME
		TC	POSTJUMP	# RESUME SENDS CONTROL HERE
		CADR	ENEMA
WHIMPER 是重启路径上的主要分支:BAILOUT 处理经公共返回机制安排好续接地址,WHIMPER 改好返回地址后执行 RESUME,接着跳到 ENEMA——它是正常情况下的主路径,不是唯一入口。ENEMA 这个词本身就是医学上的灌肠,程序员的黑色幽默。
源码实证Luminary099/FRESH_START_AND_RESTART.agc:281-300
ENEMA		INHINT
		TC	STARTSB1
		TCF	GOPROG2A
GOPROG2		TC	STARTSB2
GOPROG2A	TC	LIGHTSET
		CS	RSFLGBTS	# CLEAR BITS 7 AND 14.
		MASK	FLGWRD10
		TS	FLGWRD10

GOPROG3		CAF	NUMGRPS		# VERIFY PHASE TABLE AGREEMENTS
PCLOOP		TS	MPAC +5
		DOUBLE
		EXTEND
		INDEX	A
		DCA	-PHASE1		# COMPLEMENT INTO A, DIRECT INTO L.
		EXTEND
		RXOR	LCHAN		# RESULT MUST BE -0 FOR AGREEMENT.
		CCS	A
		TCF	PTBAD		# RESTART FAILURE.
		TCF	PTBAD
重启之后分两步:GOPROG3 先逐组校验相位表的一致性——对不上就判 PTBAD,重启失败;校验通过后再由 RESTARTS 例程遍历各组,有活动的组才按相位接回,没有活动的组直接跳过。
CONTROL FLOW1202 从产生到恢复的完整路径
把「计算机超载了」这句话,换成一条能顺着代码走一遍的链
1
新作业申请核心集
某个程序要起一个作业,调用 NOVAC,需要一块空闲的核心集。
Luminary099/EXECUTIVE.agc:37
│
▼
2
NOVAC 扫描 7 个核心集
拿 NO.CORES 当计数器,逐个查每个核心集的 PRIORITY 寄存器是否为空闲(值为 -0)。
Luminary099/EXECUTIVE.agc:155-162
│
▼
3
全部占满,走到尽头
扫完 7 个都没有空闲,控制流落到 NEXTCORE 的出口分支。
Luminary099/EXECUTIVE.agc:201-206
│
▼
4
发出 1202
执行 TC BAILOUT1,紧跟其后的 OCT 1202 就是告警码本身——它不是打印出来的字符串,是跟在调用后面的一个参数字——告警处理程序用 INDEX Q 把它读走。
Luminary099/EXECUTIVE.agc:207-208
│
▼
5
进入告警处理
BAILOUT1 关中断,接手后续处理。
Luminary099/ALARM_AND_ABORT.agc:189
│
▼
6
写入三级 FAILREG 缓冲
告警码被写进 FAILREG 的三级缓冲:源码直接证明的是「有三个告警码寄存器」,因此多个待处理告警可以同时留在系统里,不必等前一个处理完才记下一个。至于整个告警生命周期里是否绝对不丢码,需要更完整的控制流证明,本册不往下断言。
Luminary099/ALARM_AND_ABORT.agc:66-80
│
▼
7
点亮 PROG 灯
DSKY 上的 PROG 指示灯亮起,宇航员这才看见出事了。
Luminary099/ALARM_AND_ABORT.agc:85
│
▼
8
转到重启入口
WHIMPER 改好返回地址,执行 RESUME 后跳到 ENEMA。
Luminary099/ALARM_AND_ABORT.agc:144-149
│
▼
9
ENEMA 执行软件重启
注意这里发生的不是关机重来,而是一次受控的软件重启。
Luminary099/FRESH_START_AND_RESTART.agc:281
│
▼
10
校验相位表后接回
GOPROG3 逐组校验六个重启组的相位表,对不上则判定重启失败;对得上,事先登记过恢复点的作业从各自的恢复点继续。
Luminary099/FRESH_START_AND_RESTART.agc:291-296
为什么重启之后还能接着降落
关键在于相位登记表。作业在跑到关键位置时,会用 PHASCHNG 之类的调用把自己登记进某个重启组,相当于告诉系统:如果我被中断,请从这一步重新把我拉起来。源码里这类调用非常多,例如 BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc:159 就写着 GROUP 4 RESTARTS HERE。重启发生时,GOPROG3 遍历六组相位表,校验通过就把登记过的作业按相位恢复。所以 1202 表现成一次闪烁的告警灯和短暂的显示停顿,而不是任务失败。
●告警分三级:ALARM 只亮灯并返回,BAILOUT 触发软件重启,POODOO 才彻底中止。
●1202 的含义是核心集耗尽,官方告警表给它标了星号,后果是软件重启。
●重启不等于从头来:六组相位表登记了每个作业的恢复点,GOPROG3 会逐组校验再接回去。
●可擦写内存里专门留了三个用途固定(PRM)的告警码寄存器,重启路径不清零。

4汇编不够用,就再铺一层

Abstraction
AGC 的算术指令只有加、减、移位,乘除都得拆成多步。可制导方程里全是向量和矩阵。于是他们在汇编之上又造了一层虚拟机。
打个比方
你手头只有一把螺丝刀,却要盖一间小木屋。硬拧当然也能拧,就是太费劲;更省事的做法是先用这把螺丝刀做出锤子、锯子这些顺手家伙,再用它们去盖。解释器就是那套自己做出来的工具——它让后面的活轻松了,但做它的时候,用的还是那把螺丝刀。
当时的处境
这一层不是凭空加的设计。正因为原生指令太弱、内存太紧,直接写数学会写出一大堆重复代码,才逼出一套伪指令集:一条伪指令能顶几条到几十条汇编,没有固定的替换倍率。
解释器:在汇编之上再铺一层 应用层:制导方程、导航、着陆 写的人只想写两个向量相加 解释器 INTERPRETER 派发表 INDJUMP 00=VLOAD 24=VAD 25=VSU 27=DOT 30=VXV 13=MXV 硬件:AGC 本机汇编,只有加、减、移位
图 5 解释器在机器指令之上再铺一层的结构,用伪指令表达向量与矩阵运算。
源码实证Luminary099/INTERPRETER.agc:321-325
		TCF	VSU		# 25 -- VECTOR SUBTRACT.
		TCF	BVSU		# 26 -- VECTOR SUBTRACT FROM.
		TCF	DOT		# 27 -- VECTOR DOT PRODUCT.

		TCF	VXV		# 30 -- VECTOR CROSS PRODUCT.
这是一张跳转表:操作码 25 跳到向量减,27 跳到点积,30 跳到叉积。编号是八进制。这张表是指令派发的一部分——真正的取指、前缀和译码还在它前面。
源码实证Luminary099/INTERPRETER.agc:34,43
# ENTRY TO THE INTERPRETER.  INTPRET SETS LOC TO THE FIRST INSTRUCTION, BANKSET TO THE BBANK OF THE
INTPRET		RELINT
INTPRET 是解释器的入口。程序可以在本机汇编和解释模式之间来回切换,需要密集数学运算时进解释器,需要直接操作硬件时退回汇编——两种入口各有各的适合用途。
源码实证Luminary099/ERASABLE_ASSIGNMENTS.agc:377
MODE		ERASE			# +1 FOR TP, +0 FOR DP, OR -1 FOR VECTOR.
连当前算的是三精度、双精度还是向量,都得专门用一个字记下来——MODE 记的就是当前的算术状态。这是那套节省哲学最具体的样子。
源码实证Luminary099/AOTMARK.agc:62
		TC	BAILOUT1	# ALL VAC AREAS OCCUPIED -- ABORT.
解释器作业要占 VAC 区——AOTMARK 里五个都占满时,这个出口报的是 01207(不是 1201)。抽象层同样要受资源约束,这一点它没有藏起来。
●解释器是一层架在汇编之上的虚拟机,提供向量、矩阵、多精度运算。
●INDJUMP 跳转表是指令派发的核心,操作码用八进制编号。
●这层抽象是被资源限制逼出来的,不是为了好看。

5人怎么指挥它:DSKY

Interaction
宇航员面前有一块专用面板:数字显示、几盏灯、一排按键。要用这点东西指挥一台计算机,交互怎么做?答案是动词加宾语。
当时的处境
这个设计的好处是按键数量和数据量解耦:十几二十个按键,靠两组数字的组合覆盖它支持的操作。宇航员受训时背的是词组,不是代码。
DSKY:动词加宾语 VERB 动词 做什么 + NOUN 名词 对谁做 V06 N07 源码实证的配对 源码原句 PINBALL_GAME_BUTTONS_AND_LIGHTS.agc:47 THE VERB CODE INDICATES WHAT ACTION IS TO BE TAKEN, THE NOUN CODE INDICATES TO WHAT THIS ACTION IS APPLIED. 按一次键 = 一次 KEYRUPT1 中断,5 位键码进通道 15
图 6 DSKY 上 Verb(动作)与 Noun(对象)的组合方式与显示寄存器。
源码实证Luminary099/PINBALL_GAME_BUTTONS_AND_LIGHTS.agc:46-49
# KNOWN AS VERB AND NOUN.  EACH OF THESE IS REPRESENTED BY A 2 CHARACTER
# DECIMAL NUMBER.  THE VERB CODE INDICATES WHAT ACTION IS TO BE TAKEN, THE
# NOUN CODE INDICATES TO WHAT THIS ACTION IS APPLIED.  NOUNS USUALLY
# REFER TO A GROUP OF ERASABLE REGISTERS.
源码原句就是最好的定义:VERB 决定做什么动作,NOUN 决定这个动作作用在谁身上,两者都是两位十进制数。
源码实证Luminary099/PINBALL_GAME_BUTTONS_AND_LIGHTS.agc:97-99
# EACH DEPRESSION OF A KEYBOARD BUTTON ACTIVATES AN INTERRUPT KEYRUPT1
# AND PLACES THE 5 BIT KEY CODE INTO CHANNEL 15.  KEYRUPT1 PLACES THE KEY
# CODE INTO MPAC, ENTERS AN EXECUTIVE REQUEST FOR THE KEYBOARD AND DISPLAY
每按一次键就触发一次 KEYRUPT1 中断,五位键码送进通道 15,再由中断处理程序放进 MPAC 并向 Executive 提交请求。硬件中断和作业调度在这里接上了。
源码实证Luminary099/PINBALL_GAME_BUTTONS_AND_LIGHTS.agc:33,40
# PROGRAM NAME -- KEYBOARD AND DISPLAY PROGRAM
# THE KEYBOARD AND DISPLAY SYSTEM PROGRAM OPERATES UNDER EXECUTIVE
键盘显示程序本身也是跑在 Executive 上的一个作业——连人机交互也参与调度(中断和定时任务走的是另外的机制)。
源码实证Luminary099/DISPLAY_INTERFACE_ROUTINES.agc:121-123,132
#	GOPERF2 IS ENTERED WITH A VARIABLE NOUN AND V01 (V00 FOR N10 OR N11) IN A.  GOPERF2 WILL FIRST DISPLAY THE
# 	REQUESTED NOUN BY MEANS OF A V01NYY OR A V00NYY.  PLEASE PERFORM ON NOUN (V50 NYY) THEN BECOMES A FLASHING
#	DISPLAY.  NO IMMEDIATE RETURN IS MADE TO THE USER (SEE NO. 7 ABOVE).
#	DISPLAYS THE NO. BY MEANS OF A V06 N07 FOLLOWED BY A FLASHING V50 N07 FOR A PLEASE PERFORM.  NO IMMEDIATE RETURN
具体编号必须回源码核对。这份仓库里能查到的是 V06 N07 与 V50 N07,后者源码原词 PLEASE PERFORM,意思是请宇航员确认执行。GOPERF2 负责带变量的显示请求,V50 就是 PLEASE PERFORM 的操作请求;终止、继续、输入或重做各有专门的分支。
CONTROL FLOW按下一个键之后发生了什么
源码注释里写得很完整,几乎不用补充
1
按下按键
每一次按下都触发一个中断,叫 KEYRUPT1。
Luminary099/PINBALL_GAME_BUTTONS_AND_LIGHTS.agc:97-101
│
▼
2
键码进入通道 15(八进制)
5 位(bit)的键码被放进通道 15——普通键码都走这一条中断通道。
同上
│
▼
3
键码暂存 RUPTREG4
中断处理先把键码放进暂存寄存器 RUPTREG4。
同上
│
▼
4
以 CHRPRIO 优先级发起请求
向 Executive 发起 NOVAC 请求,指向键盘显示程序的 CHARIN 入口;键码则用 INDEX LOCCTR 写进 MPAC,留给 CHARIN 用。
同上
│
▼
5
RESUME 返回
中断处理执行 RESUME 退出,真正的处理交给被排进队列的那个作业去做。
Luminary099/KEYRUPT_UPRUPT.agc:55
●VERB 加 NOUN,两组两位十进制数,组合出它支持的操作(可用时机由程序状态决定)。
●按键走硬件中断进入 Executive 队列,键盘显示程序自己也是一个被调度的作业。
●要宇航员确认时显示器会闪烁,源码里叫 PLEASE PERFORM。

6它到底在算什么

Mission
前面都在讲怎么跑。这一章讲跑的是什么:把一个罐子从月球轨道稳稳放到月面,再送回来。
当时的处境
任务被拆成一个个 P 程序,宇航员在 DSKY 上敲编号进入对应模式。每个 P 程序背后是一整套制导方程和油门控制逻辑。
源码实证Luminary099 着陆相关模块(按装订页码)
THE_LUNAR_LANDING(785-792)              登月着陆主程序
LUNAR_LANDING_GUIDANCE_EQUATIONS(798-828) 着陆制导方程
THROTTLE_CONTROL_ROUTINES(793-797)        油门控制
P40-P47(752-784)                          动力飞行程序组
光是着陆这一件事就拆了四块。按 MAIN 目录排下来:动力飞行程序组 33 页(752-784)、着陆主程序 8 页(785-792)、油门控制 5 页(793-797)、着陆制导方程 31 页(798-828)——最大的一块是动力飞行,不是着陆本身。
源码实证Luminary099/BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc:159
BURNBABY	TC	PHASCHNG	# GROUP 4 RESTARTS HERE
点火例行程序在这里用 PHASCHNG 把自己登记进第 4 重启组。发生重启时,如果这条登记仍然有效,系统会从这一行把它拉起来继续;登记会随任务阶段更新,恢复到哪一步取决于当时有效的记录。文件名本身是当年程序员开的玩笑。
源码实证Luminary099/LUNAR_LANDING_GUIDANCE_EQUATIONS.agc:808
EXOVFLOW	TC	ALARM		# SOUND THE ALARM NON-ABORTIVELY
连制导方程算溢出都走非中止性告警。注释里的 NON-ABORTIVELY 和告警表的分级是对得上的,说明这套分类不是纸面规定,而是真的在用。而且告警之后制导并不瞎算:后续代码检查姿态保持状态,该停油门就停(STOPRATE),并跳过这一次的油门与 CDU 更新——错误结果不会被拿去用。
●任务是导航、制导、控制三件事的组合,对应到源码里是不同的程序组。
●P 程序是宇航员可选的任务模式,背后挂着制导方程与油门控制。
●关键动作都登记了重启组,中断之后能从指定位置续跑。

7五十年后还剩下什么

Lessons
这份代码最值钱的地方不是它登了月,而是它示范了一件事:资源少到那个份上时,工程思路本身能把复杂度压住。
当时的处境
下面这几条不是感想,是这份源码里能指出来的一条条做法。做后端、做嵌入式、做稳定性,都值得把这几条当成问题清单带回去。
源码实证Luminary099 全仓库统计
总行数:130,186(登月舱 64,838 + 指令舱 65,348)
源文件:90 + 85 = 175 个 .agc
版权:Public domain
它是公有领域,可以自由阅读、修改,甚至用 Virtual AGC 这类项目重新汇编后在模拟器里跑起来。真正飞过的一手工程样本,而且是能自己动手验证的那种。
方法论从这份代码里能直接搬走的做法
1 约束先于设计:不是资源不够才将就,而是资源不够让每一处设计都得给出理由。
2 异常是一等公民:告警分级写进表,出错走的是设计好的路径,不是意外。
3 抽象因限制而生:原生指令弱,才长出解释器这一层。
4 可观测性:告警码、页码、交叉引用表,让系统状态随时可读可查。
5 中断可续:关键动作登记恢复点,重启之后能接着跑。
这五条是问题清单,不是结论——换一个环境,每一条都要重新验证。
●约束决定设计,这句话在他们身上是字面意思。
●异常分级写进表里,出错就有确定的去处。
●抽象层是被限制逼出来的,不是提前规划出来的。
●关键动作登记恢复点,故障之后能续跑而不是重来。

实物照片

真实可查证,版权已标注
阿波罗制导计算机主机
阿波罗制导计算机主机 Wikimedia Commons · CC BY-SA 3.0
DSKY 显示与键盘单元
DSKY 显示与键盘单元 Wikimedia Commons · CC BY-SA 3.0
阿波罗 11 号登月舱上升段
阿波罗 11 号登月舱上升段 NASA · Public Domain
阿波罗 11 号登月舱
阿波罗 11 号登月舱 NASA · Public Domain

术语速览

读正文卡住的词,这里一句话讲清
AGC阿波罗制导计算机,登月飞船上那台机载计算机。
Executive实时调度内核,决定哪个作业先跑。
核心集 Core Set一个作业时占用的那组寄存器,相当于工位,全机 7 个,每个 12 字(word)。
VAC 区解释型数学作业的加大工位,全机 5 个,每个 44 字(word)。
解释器 Interpreter架在汇编之上的一层虚拟机,提供向量与矩阵运算的伪指令。
MPAC多用途累加器,解释器算数时的工作区。
DSKY飞船上那块显示加键盘的面板,宇航员与计算机对话的主要入口。
VERB / NOUN动词加宾语:VERB 说做什么,NOUN 说对谁做。
FAILREG告警码寄存器,共三个,重启时不清空。
BAILOUT / POODOO两级出错处理:前者触发软件重启,后者彻底中止。
相位表 PHASE登记作业恢复点的表,重启后据此把作业接回去。
绳存 Rope Memory手工穿线编织的只读存储器,写进去就改不了。
怎么复核本册的每一条
下面几条命令可以直接粘贴执行。原始源码属公有领域,任何人都能拿到同一份底本。
1. 把仓库拉到本地,并切到本册固定的 commit
git clone https://github.com/chrislgarry/Apollo-11 cd Apollo-11 git checkout 911e5c0283c629c50cb97666f34065e8c07d71a5
2. 核对 1202 告警的产生点
sed -n '201,209p' Luminary099/EXECUTIVE.agc
3. 核对核心集的数量与步长
grep -nE 'NO\.CORES|COREINC' Luminary099/EXECUTIVE.agc
4. 核对可写内存里核心集与 VAC 区的容量注记
grep -nE '84D|220D' Luminary099/ERASABLE_ASSIGNMENTS.agc
5. 核对重启初始化:三个告警格清零
sed -n '55,60p' Luminary099/FRESH_START_AND_RESTART.agc
6. 核对 PRM 记法的定义
sed -n '67,73p' Luminary099/ERASABLE_ASSIGNMENTS.agc
·所有「源码实证」条目都标了文件与行号。行号以 commit 911e5c0 为准;仓库后续若有变动,请先 git checkout 911e5c0 再核对。
·「外部资料」条目不能回仓库核对,都给了参考文献编号,按编号去原文查。
·「作者推导」条目给出了完整算式,你可以自己重算一遍。若你的结果与本册不同,以源码为准,并请指出错处。
·「解释性类比」只是解释工具,不是证据,不必去源码里找。
参考文献
全部列出,便于复现本册的研究路径。
[1]
Apollo-11 源码仓库(Luminary099 登月舱 / Comanche055 指令舱)
chrislgarry(整理),MIT Museum 藏硬拷贝数字化(Paul Fjeld 扫描) · 源码
https://github.com/chrislgarry/Apollo-11,commit 911e5c0283c629c50cb97666f34065e8c07d71a5。Public domain(公有领域)。源码自身的装订记时为 16:27 JULY 14, 1969;仓库 commit 的提交时间为 2026-07-15(该日期为仓库 commit 的提交时间,与源码内容无关,可自行用 git log -1 核实)。两者相差 57 年,不要混为一谈。
用于:全文所有「源码实证」条目的出处。
[2]
MIT Instrumentation Laboratory Report R-500:Apollo Guidance and Navigation — Part VI, Guidance Computer Design
MIT Instrumentation Laboratory · 一手技术报告
MIT,1960 年代原始设计报告(klabs.org 等处存有扫描件)。
用于:指令字格式(3 位操作码 + 12 位地址)、EXTEND 与 partial codes 两种扩充手法、Block I 指令清单。
[3]
NASA SP-8015:Guidance and Navigation for Entry Vehicles(原稿误引条目,已更正)
NASA · 一手技术报告
NASA NTRS 19690010164(1968)。本次对 NTRS 原页核验:该号实为再入飞行器导航专册,与 AGC 无关;原稿据其引述的 AGC「通用计算机」表述,已改由 [4] 的 Appendix F 承担(该附录取自 Draper Lab Report R-700,对页核验通过)。
用于:文献纠错记录。
[4]
Apollo GN&C System Components(Appendix F)
NASA · 一手技术报告
NASA NTRS 20230005922(NASA/TP-20230005922,2023)。Appendix F 取自 Draper Lab Report R-700;PDF p288 含 AGC「general purpose computer…priority driven operating system」原话,p292(印刷页 271)含 AGC 规格表——均已对页核验。
用于:机载制导导航控制系统的职责边界描述。
[5]
Apollo Onboard Navigation Techniques
NASA · 一手技术报告
NASA NTRS 20090016292。
用于:机载导航与地面测控的分工、滤波方法的工程化过程。
[6]
Apollo 11 Mission Report / 任务相关技术文件
NASA · 一手技术报告
NASA NTRS 19700008096(MSC-00171)。原稿误引的 19700014507 经 NTRS 原页核验为月球大地测量报告,与任务报告无关,已更正;表 5-I 为着陆事件时间线(含 1202/1201 序列)
用于:任务时间线与测控(MSFN)定轨职责的表述。
[7]
Apollo 11 Lunar Surface Journal — Program Alarms(1201 / 1202)
NASA 历史部(ALSJ) · 史料 / 综述
nasa.gov 历史档案,含当事软件人员对 1202/1201 的回顾。
用于:1202/1201 与 core set / VAC 耗尽的关联,及重启后恢复的口述佐证。
[8]
Core rope memory(含其中引用的 Ken Shirriff 等人资料)
Wikipedia 条目及其引用 · 史料 / 综述
公开条目。注意:该条目关于存储密度的部分自身带有 citation needed 标记。
用于:仅用于 LOL memory 这个行话外号。原条目给出的密度数字 72 KB/ft³ 因来源过弱已从正文删除。
[9]
A New Approach to Linear Filtering and Prediction Problems
R. E. Kalman · 史料 / 综述
Journal of Basic Engineering, 1960。
用于:最优估计的理论起点。
[10]
James Potter 讣闻(MIT News)
MIT News · 史料 / 综述
news.mit.edu,2005。
用于:平方根形式滤波的提出者及其在阿波罗上的作用。
[11]
Apollo 11 任务时间线
Planetary Society / 史密森尼国家航空航天博物馆 · 史料 / 综述
两处公开资料。
用于:第 4 章时间线。两源在地月转移点火时刻上存在约一分钟差异,正文已标明。
[12]
Apollo Guidance Computer 公开技术综述(Wikipedia 等)
Wikipedia 等 · 史料 / 综述
公开条目。其中「Minuteman II 混用多种逻辑族」一句自身标有 citation needed。
用于:Block I / Block II 芯片数与封装、整机外形尺寸。等级偏低,正文已单独标注;功耗重量与容量改用 NASA NTRS 20230005922 原文。
[13]
AGC Block II Instructions(Digital Development Memo #193)
Albert Hopkins,MIT Instrumentation Laboratory · 一手技术报告
MIT 仪器实验室内部备忘录,ibiblio.org/apollo/Documents/dd_memo_193.pdf。
用于:Block II 指令集扩充的两条机制(extension 与 partial codes)及 quarter code 的位来源。
[14]
Apollo Guidance Computer Block II — CMC Data Cards(基本指令与操作码表)
F. Bernardini 整理,基于 CMC/AGC 原始手册 · 一手技术报告
ibiblio.org/apollo/CMC_data_cards_15_Fabrizio_Bernardini.pdf(整理稿,非 NASA 原件)。
用于:基本指令的操作码八进制值(TC=00、CCS=01、CA=03、AD=06 等)与 EXTEND=0.0006。
[15]
Virtual AGC 项目:yaYUL 汇编器与 yaAGC 模拟器
Ron Burkey 等,开源项目 · 公开网页
github.com/virtualagc/virtualagc;项目网站 virtualagc.github.io/virtualagc/ 与 ibiblio.org/apollo。chrislgarry/Apollo-11 仓库 README 的 Attribution 表里,Assembler 一栏写的就是 yaYUL(2026-09-15 对其 raw README 逐字核对)。
用于:把本册的源码实证变成可执行证据:汇编与仿真复现的工具链出处。
[16]
chrislgarry/Apollo-11 仓库 README 的 Attribution 表(源码来历与装订记时)
chrislgarry/Apollo-11 仓库 README · 源码
Attribution 表在 chrislgarry/Apollo-11 的 README(不是 virtualagc 仓库的 README)。2026-09-15 逐字核对:Luminary099 一栏写明 「Assemble revision 001 of AGC program LMY99 by NASA」与「2021112-061. 16:27 JUL. 14, 1969」;Comanche055 一栏为「10:28 APR. 1, 1969」。
用于:装订记时、程序正式编号;汇编器为 yaYUL。
两个日期,不要混。源码自身的装订记时 16:27 JULY 14, 1969,写在源码头部,指这份程序当年装订的那个时刻;仓库 commit 911e5c0283c629c50cb97666f34065e8c07d71a5,提交时间 2026-07-15,只是仓库整理者的提交时间,与源码内容无关,可用 git log -1 911e5c0 自行核实。两者相差 57 年。若仓库后续变动,请以该 commit 为准核对行号。
证据来源。本地 Apollo-11 仓库(chrislgarry/Apollo-11,MIT 博物馆转录本,公有领域)。文中每条源码证据都标注了文件与行号,可回仓库逐条核对。硬件规格等来自 NASA 公开资料的条目已标为「外部资料」。
图片。Wikimedia Commons(CC BY-SA 3.0,已署名)/NASA(Public Domain)。本页自包含,无外部依赖,无脚本。