夜雨聆风学习资料网

ARTICLE · 1189576

阿波罗 11 号源码考古:50 年前的代码,比段子还野

阿波罗 11 号源码考古:50 年前的代码,比段子还野

阿波罗 11 号源码考古:50 年前的代码,比段子还野

项目地址:<https://github.com/chrislgarry/Apollo-11> 内容:阿波罗 11 号制导计算机(AGC)的原始源码,指令舱(Comanche055)与登月舱(Luminary099)两部分。

1969 年 7 月 20 日,人类第一次踩上月球。四十年后,一群人不满足于看录像,跑去把当年那台计算机里跑的程序原样敲了出来——对着麻省理工学院博物馆里那叠发黄的打印纸,一行一行地抄,抄完放进 GitHub。

于是我们得到了一个相当诡异的阅读体验:一份把两个人送上月球、又送回来的软件,里面充满了拉丁文格言、莎士比亚台词、小熊维尼、狄俄尼索斯、以及一声"呜咽"。

这篇文章不打算讲算法。我们只讲那些注释、文件名和函数名——因为那才是这份代码真正的性情。


一、先看清楚:这是什么,又不是什么

是:两套 AGC(Apollo Guidance Computer)汇编程序,公共领域(public domain),用当年的汇编器 yaYUL 汇编。每一份文件的开头都长得一模一样:

# Copyright:	Public domain.# Filename:	BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc# Assembler:	yaYUL# Pages:	731-751

注意 Pages 那一行。它不是行号,是MIT 博物馆那叠纸的页码。这份代码在纸上有实体,第 731 页到第 751 页就是登月舱的点火程序。今天我们读到的每一个字符,都是有人对着扫描件一格一格校对出来的。

不是:这不是"现代意义上的软件工程"。它跑在一台 70 磅(32 公斤)重、时钟 2.048 MHz、内存周期 11.72 微秒的机器上。16 位字长里 15 位是数据位、1 位是奇偶校验位。可写的内存(磁芯)只有 2048 个字,程序则被"织"进 36864 个字的芯绳存储器(core rope memory)——那玩意儿是把导线从磁环里一根根穿过去、手工编出来的,所以改错一个 bit,等于重织一整根绳。

它也是最早使用硅集成电路的计算机之一,是人跟机器用一套"动词 + 名词"(Verb / Noun)的语言对话的起点。

而那句读起来最不像软件的元数据是这一行:

#	2021113-051.  10:28 APR. 1, 1969

1969 年 4 月 1 日,愚人节,上午 10:28。 指令舱的这套软件就是这么签发的。没有人刻意安排这件事,但它就那样留在了档案里。


二、两个目录,两艘飞船

仓库根目录下是两棵完全平行的树:Comanche055 和 Luminary099。很多人第一次打开会以为这是"同一份代码的两个版本",不是。这是两艘飞船、两台计算机、两套使命、两个团队。

Comanche055Luminary099
归属
指令舱(CM)
登月舱(LM)
程序全名
Colossus 2A(AGC 程序 Comanche)
Luminary 1A(AGC 程序 LMY99)
汇编修订
revision 055
revision 001
硬拷贝签署时间
1969-04-01 10:28
1969-07-14 16:27
负责的事
从地球到月球轨道、交会、返航再入
分离、下降、着陆、月面停留、上升

表格最后一行那个时间值得多看两眼:1969 年 7 月 14 日 16:27。阿波罗 11 号是 7 月 16 日发射的。也就是说,登月舱这套软件的这份修订版,是在发射前不到两天定稿的——那不是"提前三个月封版"的从容,那是最后一次改完、赶紧去装绳的紧张。

两个目录里也有大量同名文件,比如 EXECUTIVE.agc(操作系统内核)、WAITLIST.agc(任务调度)、ALARM_AND_ABORT.agc(报警与中止)、PINBALL_GAME_BUTTONS_AND_LIGHTS.agc(人机界面)。同一套骨架,装进两个不同的身体。

还有一个特别可爱的命名习惯:很多文件名就是宇航员要在键盘上按下的程序号。Luminary099/THE_LUNAR_LANDING.agc 里面开篇第一行注释就是:

#	*************************************#	P63: THE LUNAR LANDING, BRAKING PHASE#	*************************************

P63。宇航员在 DSKY 上敲 V37E63E,进入的就是这个文件。文件名,就是按钮。


三、七个彩蛋

1. BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc

"烧吧,宝贝,烧!"——主点火程序。

这是全仓库最出名的一个文件名。它负责登月舱主发动机(DPS)和上升级发动机(APS)的点火,注释里写得很正式:

# THE MASTER IGNITION ROUTINE IS DESIGNED FOR USE BY THE FOLLOWING LEM PROGRAMS:# P12, P40, P42, P61, P63.# IT PERFORMS ALL FUNCTIONS IMMEDIATELY ASSOCIATED WITH APS OR DPS IGNITION

但紧接着,这份正式文件的深处埋着一段考据。整理者 Ron Burkey 在修改记录里写:"2009-07-23 RSB 加入了 Onno 关于这个函数命名来历的注释,他是从 Don Eyles 那里听来的。" 而 Don Eyles,正是这个程序的作者之一。

故事回到 1965 年的洛杉矶。电台老板、传奇 DJ Magnificent Montague 在放最热的新唱片时,会扯着嗓子喊一句 "Burn, baby! BURN!"。那是当年灵魂乐电台的标志性声音,后来也成了沃茨骚乱里被反复喊起的一句话。

点火 = burn。于是这个把登月舱发动机点着的程序,就叫了这个名字。

同一份文件里还有更多"不正经"的地方:

  • 程序里有一个标号写作 B*RNB*B*——代码自己给自己打了码。
  • 主程序开头有一句 # HELLO THERE.,结尾是 # GOODBYE. COME AGAIN SOON.("你好呀。""再见,欢迎下次光临。")
  • 有指令的注释是 # EXTIRPATE JUNK LEFT IN DVTOTAL("铲除 DVTOTAL 里的垃圾")、# ASSASSINATE CLOKTASK("暗杀倒计时任务")。
  • 作者署名带着不加掩饰的骄傲:THE MASTER IGNITION ROUTINE WAS CONCEIVED AND EXECUTED, AND (NOTA BENE) IS MAINTAINED BY ADLER AND EYLES.
  • 表格区上方压着两句拉丁文/法文格言:HONI SOIT QUI MAL Y PENSE("心怀邪念者蒙羞",嘉德骑士团座右铭)和 NOLI SE TANGERE("别碰我")。

一份点火程序,同时是家谱、墓碑和涂鸦墙。


2. PINBALL_GAME_BUTTONS_AND_LIGHTS.agc

"弹球机:按钮与灯。"

这是全仓库体量最大的文件之一(原始扫描件第 307–389 页),干的活是 AGC 与宇航员之间的全部交互:按下按键、点亮灯、刷新数字显示。也就是今天所谓"人机界面"。

它的日志区(LOG SECTION)名字就叫 PINBALL GAME BUTTONS AND LIGHTS。为什么叫弹球机?——因为 DSKY(Display and Keyboard,读作 DISS-kee)就是一块闪着灯、带数字键盘、还会"闪烁提示"的板子。你按一下,灯亮一下,数字跳一下。工程师抬头看着这东西,脑子里蹦出来的就是街角那台弹球机。

而这个文件真正让人愣住的地方,是它在严肃的技术说明中间,插了一段引文,还郑重写明"承蒙作者提供":

#	"IT WILL BE PROVED TO THY FACE THAT THOU HAST MEN ABOUT THEE THAT#	USUALLY TALK OF A NOUN AND A VERB, AND SUCH ABOMINABLE WORDS AS NO#	CHRISTIAN EAR CAN ENDURE TO HEAR."#					HENRY 6, ACT 2, SCENE 4

翻译过来大概是:"马上就会有人当面证明给你看:你身边这些人张口就是'名词'、'动词',还有一堆基督徒耳朵听不得的鬼话。"

莎士比亚《亨利六世》。为什么挑这一句?因为 AGC 的人机语言就叫 Noun 和 Verb——宇航员输入 V37E63E,意思是"动词 37:切换程序;名词 63:那是登月"。程序员们很清楚,自己发明的这套黑话在旁人听来有多像邪教切口,于是他们把这句嘲讽贴在了界面程序的正上方,当门神。

顺带一提,这个文件里还留着一句注释:THIS WORKS ONLY FOR INTERNAL PROGRAMS THAT USED ENDIDLE THROUGH **MARGARET'S DISPLAY SUBROUTINES.**——"玛格丽特的显示子程序"。玛格丽特·汉密尔顿(Margaret Hamilton),正是 CONTRACT_AND_APPROVALS.agc 上签字的"Colossus 编程负责人"。她的名字被同事写进了飞行代码的注释里,而那套子程序在月球轨道上正常工作过。


3. POODOO、WHIMPER、ENEMA:当程序决定放弃

ALARM_AND_ABORT.agc 是负责"出事"的文件。它的函数名集体情绪失控:

名字
大概意思
它在代码里干什么
ALARM
报警
点亮报警灯,不显示
BAILOUT
跳伞逃生
中止程序、保存现场
WHIMPER呜咽
保命流程的收尾——真的就叫"哼唧一声"
POODOO
别细想
最严重的自毁式中止,报警码 77770
CCSHOLE
CCS 洞
掉进"理论上不可能"的坑,报警码 1103
CURTAINS落幕
报警码 00217
MR.KLEAN
清洁先生
收拾残局、擦干净可擦内存
ENEMA灌肠
让整机软件重启一遍

ENEMA 这个词在别的文件里被正式解释过:# 4. GO TO ENEMA (SOFTWARE RESTART).,注释还专门加一句 # CAUSE RESTART。软件级重启 = 灌肠。 这个比喻在 1969 年的麻省理工显然没有人提出异议。

CCSHOLE 尤其值得一提:CCS 是 AGC 的一条指令(比较并跳过)。凡是写下 CCS 的地方,理论上都会落入一个确定的分支——可万一落到了不该落的地方呢?那就是掉进了"洞"里。代码里到处是这种洞,谁掉进去,谁触发 1103 中止。这是最早的"不该发生却发生了"断言(assert),只不过写法是挖个洞。

还有个只有抄书人才会遇到的乐子。整理者在修改记录里写下过这样的更正:POODOO -> P00DOO,以及 GOTOPOOH -> GOTOPOOH——后者看起来一模一样,因为它真的就一模一样。这套汇编语言里 P00 就是"程序 00",而字母 O 和数字 0 在当年的打印纸和今天的屏幕上都长一个样,连"更正说明"本身都被这个坑绊了一跤。人类为了区分 O 和 0 已经痛苦了五十年,而且看样子还要继续。


4. FLAGORGY:狄俄尼索斯式的挥旗

Luminary099/THE_LUNAR_LANDING.agc 是登月舱下降段的主程序。它有一段初始化代码,要做的事情很枯燥:一口气清除、设置十几个标志位(NOTHROTL、REDFLAG、LRBYPASS、MUNFLAG、P25FLAG、RNDVZFLG……)。

这段代码的标号叫:

FLAGORGY	TC	INTPRET		# DIONYSIAN FLAG WAVING

FLAG-ORGY。"标志位狂欢"。而注释给它下的定义是:狄俄尼索斯式的挥旗。

狄俄尼索斯,希腊神话里的酒神。一段清除状态位、二十几个微秒就执行完的初始化代码,被写它的人形容成酒神节的狂欢。这就是 1969 年 MIT 仪表实验室的幽默浓度。

同一个文件里还有一串更贴近人话的注释,都是写给宇航员看的提示,被直接写进了代码:

  • 雷达天线没到位时,屏幕会提示:# ASTRONAUT: PLEASE CRANK THE SILLY THING AROUND——"宇航员:麻烦把那破玩意儿摇过去。"
  • 宇航员摇完之后,下一句注释是:# PROCEED SEE IF HE'S LYING——"(点)执行:看看他是不是在撒谎。"
  • 准备点火那一刻:TC POSTJUMP # OFF TO SEE THE WIZARD...——"去见巫师咯……"(《绿野仙踪》)
  • 落地之后:# ASTRONAUT: NOW LOOK WHERE YOU ENDED UP——"宇航员:好了,看看你落到哪儿了。"
  • 最后一句:# ASTRONAUT: PLEASE SELECT P57——"宇航员:请选择 P57。"

一个人刚刚在月球上停稳,登月舱的计算机蹦出来的第一句话是"看看你落到哪儿了"。


5. MYSUBS.agc:一份叫"我的子程序"的文件

Comanche055/MYSUBS.agc,两千多字节,是这批文件里最小的几个之一。它的名字直译过来就是——"我的子程序"。

这是一份私人草稿性质的工具文件。里面只有两个真正的例程:

  • SMALLMP
    ,上面注释是 # ONE AND ONE HALF PRECISION MULTIPLICATION ROUTINE("一个半精度乘法"——注意,不是"双精度",是"一个半")
  • DPADD
    ,# SUBROUTINE FOR DOUBLE PRECISION ADDITIONS OF ANGLES(角度双精度加法)

还有几行单纯的别名声明(SPCOS1 EQUALS SPCOS、SPSIN2 EQUALS SPSIN),以及一句轻描淡写的说明:# Page 1001 (empty page)。

一位工程师把自己随手攒的小工具取名叫 "my subs",交了上去,通过了评审,织进了芯绳,然后跟着指令舱飞到了月球轨道,又飞回来。 今天你在 GitHub 上打开它,文件名仍然是"我的子程序"。这大概是这份代码里最没有仪式感、也最动人的一处。


6. CHKPOOH:请回到小熊维尼

AGC 的"程序 00"是一个什么都不做的空闲程序——航天器待命时待的地方。而在源码里,它被写成 POOH。

于是你能在 EXTENDED_VERBS.agc(扩展动词,也就是"更多功能"菜单)里看到这样的句子:

SYSTEST		TC	CHKPOOH...CHKPOOH		CA	MODREG		EXTEND		BZF	TCQ		TCF	ALM/END

CHKPOOH 的任务是检查"现在是不是在程序 00",它的注释注释得极为直白:# DEMAND PROGRAM 00. / # DEMAND P00("要求进入程序 00")。进不去就报操作错误。

同一个文件里还有 GOTOPOOH——"去吧,去 Pooh 那儿"。

至于 P00H 为什么读作 Pooh,最常见的说法是工程师们顺手玩了个小熊维尼的梗:P00H,读起来就是 Pooh。一个"什么都不做"的程序,配上一个"什么都不做"的名字,逻辑上严丝合缝。点火程序里那句 # STOPCLOK TC NULLCLOK # STOP CLOKTASK & TURN OFF ULLAGE ON THE WAY TO P00——"在去 P00 的路上顺手关掉推进剂沉底"——读起来就像一句出门前的叮嘱。


7. OCT 1201 与 OCT 1202:全篇最不起眼、也最要命的两行

现在收一下笑。这两行藏在 EXECUTIVE.agc 里——那是最不浪漫的文件,操作系统内核。

EXECUTIVE 负责给任务分配"核心组"(core set,一块寄存器工作区)和"VAC 区"(向量累加器,解释型程序的暂存空间)。它的分配逻辑是:从头扫到尾,找一块空的。

		CAF	NO.CORES	# SEVEN SETS OF ELEVEN REGISTERS EACH....NEXTCORE	CAF	COREINC		ADS	LOCCTR		CCS	EXECTEM2		TCF	NOVAC3		LXCH	EXECTEM1		CA	Q		TC	BAILOUT1	# NO CORE SETS AVAILABLE.		OCT	1202

只有 7 块核心组。 扫完 7 块都满了,程序就走到这一行,报出 1202,然后 BAILOUT1——中止、重启、把不重要的任务丢掉。

再往上看几行,是它的兄弟:

		TC	BAILOUT1		OCT	1201		# NO VAC AREAS.

"1202:没有核心组了。""1201:没有 VAC 区了。"

这两行,就是 1969 年 7 月 20 日让整个休斯顿控制中心集体停止呼吸的那两个数字。它们不是 bug,是设计好的求生机制:当算不过来的时候,宁可扔掉活儿重来,也绝不硬撑到死机。


四、两则紧张的故事

故事一:1202,和那 25 秒

下降点火五分钟,登月舱离月面约 6000 英尺(约 1800 米)。突然,DSKY 上跳出一串数字。

奥尔德林的声音传回休斯顿:

"Program alarm. It's a 1202."

阿姆斯特朗接了一句,语气平得像在念仪表读数:

"1202... give us a reading on the 1202 program alarm."

控制中心里,所有人都知道这两个数字意味着"计算机过载"。但没有人知道,过载之后会发生什么。

只有一个人知道。24 岁的计算机工程师 Jack Garman,之前把所有可能的报警码抄在一张便签上、背了下来。而 GUIDO(制导官)Steve Bales 必须在一两秒内给出"继续下降"或"中止"的答案。

Bales 说:Go。

后来我们知道了原因。登月舱的交会雷达开关被留在了错误的位置——它本来只是开着预热,好在紧急中止时能立刻派上用场。但雷达的一次电气相位错配(这个硬件设计缺陷在 Apollo 5 无人测试时就出现过),让静止的天线在计算机眼里"抖动"起来,于是不停地去更新一个非自愿计数器(involuntary counter),白白抢走了本该属于制导计算的机时。下降雷达同时也在工作。两边数据一起灌进来,7 块核心组终于不够用了。

于是第 7 块满的时候,程序执行了那一行 OCT 1202:丢掉最低优先级的任务,重启,继续降落。

阿波罗 11 号上的软件,在那个下午反反复复地"过载—丢任务—重启",而两位宇航员只是看到屏幕上跳出一串数字,然后它自己好了。

玛格丽特·汉密尔顿后来写过一句很有名的话:

"把阿波罗 11 号的问题归咎于计算机,就像把发现火灾、打电话报警的人当成纵火犯。计算机不是差点导致中止,是它阻止了中止。"

着陆时刻是 20:17:40 UTC。剩余的可用推进剂 216 磅。 当时摆在宇航员和控制中心面前的数据是:只够再飞 25 秒。而事后的分析说,真实余量大概接近 50 秒——因为推进剂晃得比预期厉害,把燃料传感器露了出来,于是低油量告警提前响了。后面的任务为此加了防晃隔板。

阿姆斯特朗关掉发动机之后,通讯里传来 CAPCOM 查尔斯·杜克的声音,那大概是航天史上最诚实的一句官方发言:

"You got a bunch of guys about to turn blue. We're breathing again." ("你这儿有一帮人快憋成蓝的了。我们又开始呼吸了。")

故事二:一支毡头笔

月面作业结束,奥尔德林先钻回舱内。在狭窄的座舱里挪动身体时,他身上的装备撞上了控制面板——一个断路器的开关柄被撞断了。

那个断路器的作用是:给上升级主发动机解除保险。

从月球起飞只有一次机会。没有上升级,就没有返回,没有指令舱,没有地球。而在白宫,演讲稿撰写人 William Safire 早就写好了一份题为 In Event of Moon Disaster 的备用讲稿,开头是:"命运已经注定,那些为和平探索而登上月球的人,将留在月球上安息。"

地面上的人开始想办法。而奥尔德林自己找到了办法。

他手边有一支 Duro 毡头笔。他把这支笔伸进断掉的开关位置,用它顶住了那个触点——

电路接通了。

1969 年 7 月 21 日 17:54 UTC,登月舱上升级准时点火,从月面起飞。

顺便说一句,这是 BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc 负责的那一段程序。那里面写着 # HELLO THERE. 和 # GOODBYE. COME AGAIN SOON. 的文件,真的把人送回来了。

那支笔后来被拍卖,成交价超过 85 万美元。


五、尾声

这份代码最迷人的地方,不在于它有多精巧(虽然它确实精巧:一个只有 2K 字可写内存的机器,跑出了自主交会、月面着陆和再入)。

而在于它处处暴露着写它的人。

他们会在点火程序里为一句 1965 年的电台口号立传;会把莎士比亚贴在人机界面的门口;会在最要命的中止逻辑里写下"呜咽"和"灌肠";会把自己随手写的小工具命名为"我的子程序";会在初始化标志位的时候想起酒神。他们清楚地知道,自己做的这件事要么成功、要么成为人类史上最著名的失败,然后他们决定,在这堆要飞三十八万公里的代码里,先痛快地开几个玩笑。

五十年后,这个仓库的 README 已经被翻译成了四十来种语言,issue 模板里专门有一份叫 Humour.md——连"讲个笑话"都有专门的入口。

如果你也想自己去翻翻,可以从这几个文件开始:

  • Luminary099/BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc
  • Comanche055/PINBALL_GAME_BUTTONS_AND_LIGHTS.agc
  • Comanche055/ALARM_AND_ABORT.agc
  • Luminary099/THE_LUNAR_LANDING.agc
  • Comanche055/MYSUBS.agc
  • Luminary099/EXECUTIVE.agc

在 GitHub 上按文件名搜就行。它们全都是纯文本,全都是公共领域,全都可以直接打开。

而且——它们真的上过月球。


本文所述注释、文件名与函数名,均核对自 chrislgarry/Apollo-11 仓库中 Comanche055 与 Luminary099 的原始转录文本。

相关学习资料