1969 年 7 月 20 日,人类首次踏上月球。下面是当年的一张照片,站在一大摞打印的代码边上的这位是 Margaret Hamilton,她带领团队开发阿波罗计划所需要的软件。
不久前,我们已经可以在 Github 上下载到阿波罗 11 号制导计算机 (AGC) 中的指令模块和登月模块的代码,并且也看到了这个仓库里很多无营养的中文 issue,反正有这么一波人就是来凑个热闹。毕竟这是 1969 年的代码,当时这样的工程还不被叫做软件工程,C 语言也是到 1972 年才诞生的。
在 Google 搜索 “github agc” 会看到两个仓库,一个就是上述代码仓库,叫做 chrislgarry/Apollo-11,这位名为 Chris Garry 的 github 用户前几年是 NASA 的见习工程师;另一个是 virtualagc,我们稍后再介绍。
在 Apollo-11 代码仓库中,除了各种语言的 README 和 CONTRIBUTING 文件外,有两个文件夹,分别名为 Comanche055 和 Luminary099,其内部各自包含许多以 .agc
结尾的文件。之所以有两个文件夹,是因为阿波罗 11 号上有两个制导计算机。在看代码之前,先了解一下飞船的整体构造。
根据维基百科的描述:
装载着阿波罗11号的土星5号火箭于当地时间1969年7月16日9时32分(13时32分UTC)在肯尼迪航天中心发射升空,12分钟后进入地球轨道。环绕地球一圈半后,第三级子火箭点火,航天器开始向月球航行。30分钟后,指令/服务舱从土星5号分离,在转向后与登月转接器中的登月舱连接。此后航天器进入地月转移轨道,前往月球。
如上图所示,阿波罗 11 号装载于火箭最顶端。它主要由三部分构成:指令模块 Command Module (CM)、服务模块 Service Module (SM) 和登月模块 Lunar Module (LM 或 LEM)。其中 CM 是宇航员待在里边时间最长的地方,包括从发射开始到旅行全程及返回地球的整个过程。SM 是最大的一个区域,但也是最简单的,是阿波罗登月及返回的引擎,提供燃料,储存水、氧气和电能等供宇航员维持生命所需的东西。CM 和 SM 组合起来称之为 CSM (the command and service module)。LM 是实际登陆月球表面的部件,并会再次将宇航员带离月球表面。
简单描述一下整体流程:一开始三个部件是连在一起的,三位宇航员,Neil Armstrong、Michael Collins 和 Edwin (“Buzz”) Aldrin 都在 CM 里面。当到达月球轨道之后,Buzz Aldrin 和 Neil Armstrong 就到 LM 里边,LM 与其他部件分离并着陆于月球表面,而 Michael Collins 则留在 CSM 里。两位宇航员在月球上处理完事情之后,便回到 LM 内,LM 再次与阿波罗号剩余部件对接。待两名宇航员回到 CM 里,LM 就完成了所有工作,并被丢弃。在回地球的最后阶段,CM 会单独分离,成为最终着陆地球的唯一模块。
再回过头来看之前下载到的代码。里边的两个文件夹,Comanche055 是 CM 的代码,Luminary099 是 LM 的代码。CM 和 LM 内各带有一个制导计算机,而 SM 没有单独的制导计算机,它由 CM 来控制。两个文件夹内有许多相似的地方,且有许多相同的代码,但毕竟两者要做的事情还是有所差别,所以代码并不完全一致。
上述两个文件内含有许多扩展名为 .agc
的文本文件,用 Visual Studio Code 或者 Sublime Text 打开的话,都可以安装相应的扩展来获得代码高亮。原版的几千页代码并不是按照这个 git 仓库里的文件来组织的。现在这种文件拆分和命名方式只是为了方便我们浏览。
这些 .agc
文件里的代码所使用的语言是汇编 (assembly) 语言。撇开现在的高级编程语言不说,即使开发者有过汇编语言的开发经验,也并不代表能完全看懂 AGC 所使用的汇编语言。我们在开发 JavaScript 应用的时候,现在都流行一次编写,各端运行。但汇编语言的话,必须是针对某个硬件架构来定制开发的。
所以这里的指令会细到把某个值从内存的这个位置挪到那个位置,开发者也就必须了解硬件的内存映射 (memory mapping) 的工作原理,然后就牵扯到处理器的中的寄存器 (register) 了。因为 AGC 的处理器是直接连接到阿波罗飞船的诸如陀螺仪、平衡环、引擎等硬件上的,这意味着开发者可以编写一系列汇编语言指令把数据传递给寄存器,达到类似关闭引擎等效果。
就算开发者在 PC 或者 iPhone 上用过汇编语言,到这里也是看到完全不一样的内容了。
下面是一个超级简单的代码摘录解读,代码摘自 Comanche055/AGC_BLOCK_TWO_SELF-CHECK.agc
。
INHINT
告诉 AGC 关闭任何“打断”操作,即在处理错误时不要有任何其他操作进行干预;CA
代表 clear and add,把一个单值放到累加器 (accumulator)。在 AGC 处理中,这是一个 16 位累加寄存器。这一整行代码表示把累加寄存器中的值清除,再把 Q
放到累加寄存器中。这里 Q
是指 AGC 处理器的又一个寄存器,是导致这个错误发生的代码所返回的。在 JavaScript 里调试的时候,我们是能看到调用栈的,能知道是 A 方法调了 B 方法,B 方法又调了 C 方法,C 方法执行到某一行报错了这样的信息。而在 ACG 中并没有这个东西。所以在出错的时候得先手动存一下出错的位置,通过 label 跳到错误处理,完成后再手动跳回;TS
表示 transform storage。因为累加寄存器不是一个持久的存储,所以这一行指令把当前累加寄存器中的内容拷贝到另一叫做 SFAIL 的地方进行存储;TS
,继续把当前累加寄存器里的东西拷贝到 ALMCADR
所指向的位置。根据这段指令后面的注释,可以猜测这个位置所保存的东西可以在一个显示设备上输出;INCR
是 increment,累加 ERCOUNT
以追踪出错总量。所以我作为一个只用面向对象编程语言的开发者,这是一个让我感觉很不友好的语言。
当然,大多数的代码看不懂不要紧,这里还有很大一片比较好玩并且看得懂的东西,就是注释。
从 github 下载过来的每一个 .agc
文件顶部的注释是将纸质代码转为电脑版本的时候加上的,简要叙述了我们现在在网上下载到这些代码的来历,并提供了扫描版的线上地址。
以前面摘录代码的 Comanche055/AGC_BLOCK_TWO_SELF-CHECK.agc
文件为例
# Copyright: Public domain.
# Filename: AGC_BLOCK_TWO_SELF-CHECK.agc
# Purpose: Part of the source code for Colossus 2A, AKA Comanche 055.
# It is part of the source code for the Command Module's (CM)
# Apollo Guidance Computer (AGC), for Apollo 11.
# Assembler: yaYUL
# Contact: Ron Burkey <[email protected]>.
# Website: www.ibiblio.org/apollo.
# Pages: 1394-1403
# Mod history: 2009-05-10 SN (Sergio Navarro). Started adapting
# from the Colossus249/ file of the same
# name, using Comanche055 page images.
#
# This source code has been transcribed or otherwise adapted from digitized
# images of a hardcopy from the MIT Museum. The digitization was performed
# by Paul Fjeld, and arranged for by Deborah Douglas of the Museum. Many
# thanks to both. The images (with suitable reduction in storage size and
# consequent reduction in image quality as well) are available online at
# www.ibiblio.org/apollo. If for some reason you find that the images are
# illegible, contact me at [email protected] about getting access to the
# (much) higher-quality images which Paul actually created.
#
# Notations on the hardcopy document read, in part:
#
# Assemble revision 055 of AGC program Comanche by NASA
# 2021113-051. 10:28 APR. 1, 1969
#
# This AGC program shall also be referred to as
# Colossus 2A
# Page 1394
# PROGRAM DESCRIPTION DATE 20 DECEMBER 1967
# PROGRAM NAME - SELF-CHECK LOG SECTION AGC BLOCK TWO SELF-CHECK
# MOD NO - 1 ASSEMBLY SUBROUTINE UTILITYM REV 25
# MOD BY - GAUNTT
因为电脑上通常以字母表升序来排列文件,要快速将这里的文件和原版顺序对上号,可以打开 Comanche055/README.md
文件查阅。
这样我们就知道其实原版的开头应该是 CONTRACT_AND_APPROVALS.agc
文件里的内容,并且知道 Margaret Hamilton 和她的团队是来自 MIT 的,而不是 NASA 雇员。AGC 项目是与 NASA 签约后在 MIT 仪器实验室里开发的。
现在很多开发者喜欢在自己的代码里留一些有意思的注释,当年的项目也是如此。
例如 Luminary099/LUNAR_LANDING_GUIDANCE_EQUATIONS.agc
的这个地方,就跟现在在我自己好几年前的代码里发现 TODO 注释一样
再如 Luminary099/THE_LUNAR_LANDING.agc
,这是即将登月的代码,里边有如下内容
有没有看美国科幻电影时候那种感觉?
上面代码准备点火着陆的时候,跳转到的代码叫做 BURNBABY。上世纪 70 年代中期有一首歌叫 Disco Inferno,歌词开头就是 burn, baby, burn。不过阿波罗登月比这首歌早得多了。实际上打开 BURNBABY 所在的代码,即 Luminary099/BURN_BABY_BURN--MASTER_IGNITION_ROUTINE.agc
文件,在开头的注释里解释了这段背景,跟一个 DJ 曾经说过的话以及 1965 年发生在洛杉矶的黑人骚乱有关系。
# Page 731
## At the get-together of the AGC developers celebrating the 40th anniversary
## of the first moonwalk, Don Eyles (one of the authors of this routine along
## with Peter Adler) has related to us a little interesting history behind the
## naming of the routine.
##
## It traces back to 1965 and the Los Angeles riots, and was inspired
## by disc jockey extraordinaire and radio station owner Magnificent Montague.
## Magnificent Montague used the phrase "Burn, baby! BURN!" when spinning the
## hottest new records. Magnificent Montague was the charismatic voice of
## soul music in Chicago, New York, and Los Angeles from the mid-1950s to
## the mid-1960s.
# BURN, BABY, BURN -- MASTER IGNITION ROUTINE
在刚才的代码截图中,还有一个蓝色框标注的代码指令,转向 GOTOP00H
代码块。这里 00
是两个阿拉伯数字 0
,代表 Program 00,不过发音和 pooh 一样,即 /puː/。P00
的作用是让 AGC 进入空闲状态。因为不像现代操作系统,在 AGC 里操作完一段指令后,系统需要开发者明确告知进入空闲状态才行,所以在代码仓库中能搜到很多 P00
或者 P00H
。如果你还不知道 Pooh 是什么就自己去查字典吧。
这里还有更进一步的,如果是要完全重启,也就是要把 pooh 彻底清理干净,那就要灌肠 (enema) 了,哈哈哈。在 Comanche055/FRESH_START_AND_RESTART.agc
里,就能搜到 # ENEMA SOFTWARE RESTART
了,并且说得很形象。
好奇他们当时在 code review 时候的场景。
最后再了解下宇航员是怎么和 AGC 交互的。在 Comanche055/ASSEMBLY_AND_OPERATION_INFORMATION.agc
文件里,一开始罗列了许多子程序的名称,接着是动词 (VERB) 表,再后面试名词 (NOUN) 表。这些词都有相应的编码。例如
接近文件尾部的地方有一系列错误代码,例如
在阿波罗 11 号即将登月着落的时候,就发生了这两个错误,即执行溢出。在阿波罗号上有 2 个雷达,一个是着陆雷达 (Landing Radar),另一个是交会雷达 (Rendezvous Radar)。在着陆期间,只需要开启着陆雷达,但实际上当时交会雷达也处于开启状态。在着陆时,AGC 就因为数据量增大,又浪费了资源在监控不需要的交会雷达,便开始报错。不过 AGC 的程序设计就是考虑过这个情况的,会自动抛弃低优先级的任务,此时就会显示上面这两个错误码。而 MIT 的工程师在实验室模拟过登月操作,知道此时发生这个错误时正常的,所以在决定是否终止登月计划的关键时刻,地面的飞行控制指挥官下令继续登月,最终飞船成功在月球着陆。
这些操作代码和错误代码,都显示在一个 DSKY 的设备上,看起来容易念成 D Sky,正确发音是 /ˈdɪskiː/,即 display and keyboard。宇航员也通过 DSKY 来输入指令与 AGC 进行交互。
在阿波罗 11 号上有两个 DSKY,你可以在 Smithsonian 的站点上看到其整个内部结构,并且有 VR 设备的话还能体验 VR 效果。
在文章一开始提到的 virtualagc,就是在网页上模拟 DSKY 的。你可以打开这个网站来操作。这个模拟器是给予阿波罗 9 号飞船的,不过和我们现在看到这个仓库的说明代码也很接近的。
前面提到 VERB 35 是测试灯光的,我们可以在模拟器上依次点击“VERB”、“3”、“5”、“ENTR”来测试灯光。
有的指令是要动词和名词组合操作的,例如 VERB 16 MONITOR DECIMAL IN R1 OR R1,R2 OR R1,R2,R3
是用来显示某个名词所返回的值,我们依次点击“VERB”、“1”、“6”、“NOUN”、“6”、“5”、“ENTR” 就能看到 AGC 模拟器从启动到现在的时间。因为 NOUN 65 SAMPLED AGC TIME
会返回 AGC 运行的时间。
还有的操作是在输入并确认动词之后,等待一个参数的输入,例如之前让程序转到空闲 P00H 的指令,就可以依次点击“VERB”、“3”、“7”、“ENTR”、“0”、“0”、“ENTR”。因为 37 CHANGE PROGRAM (MAJOR MODE)
是用来切换程序的。
所有在 DSKY 上的操作,都会由一个名为 PINBALL 的程序来处理,它的代码在 Comanche055/PINBALL_GAME_BUTTONS_AND_LIGHTS.agc
文件。这和街机上的弹珠台游戏 (就是 Windows xp 里三维弹球那种游戏)没啥关系,人家就是这么命名的。
这篇文章并不是说大家应该都去学习登月飞船的代码,而是可以去发现一下里边很多有趣的内容,了解些许历史。
最后以阿波罗 11 号飞船上,宇航员写在飞船 CM 里的一段话作为结尾:
Spacecraft 107 - alias Apollo 11 - alias “Columbia”. The Best Ship to Come Down the Line. God Bless Her. – Michael Collins, CMP