资讯动态

Apollo-11制导计算机源码解析:从AGC汇编到模拟器实战

发布时间:2026/10/18 0:45:15 来源:尧图企业网站定制
1. 项目缘起与整体设计思路1.1 为什么一个“老古董”代码库值得反复折腾第一次听说 Apollo-11 这个项目很多人会以为又是哪个航天科普的 PPT 或者纪录片合集。实际上它是一个把上世纪六十年代阿波罗计划中制导计算机AGC的飞行软件源码完整搬到现代开发环境里跑起来的开源工程。原始代码是用一种叫 AGC Assembly 的汇编语言写的当年存储这些代码的介质是一种叫“核心绳存储器”的东西——你可以把它想象成一张手工编织的、不可擦写的 ROM每一根线穿过或绕过磁环就代表一个 0 或 1。这种工艺放到今天连博物馆都未必能完整展示但 Apollo-11 这个项目做的事情就是让这些代码在现代电脑上重新“活”过来。这个项目能做什么简单说它提供了一个完整的模拟器可以加载阿波罗 11 号任务中指令舱和登月舱两套独立的飞行软件让你在自己的机器上运行、调试、甚至单步跟踪当年宇航员在关键时刻看到的那些指令。它解决的核心问题是原始代码虽然公开了但普通人根本没法运行——没有对应的硬件没有汇编器没有链接器连代码的注释都是几十年前的风格。Apollo-11 项目把这些坑全部填平了提供了一套可以在 Linux、macOS 甚至 Windows 的 WSL 环境下编译运行的完整工具链。适合谁来参考我觉得有三类人最值得花时间研究。第一类是对计算机体系结构、嵌入式系统感兴趣的学生和工程师因为 AGC 是一台真正的实时嵌入式计算机它的设计哲学和今天的单片机、RTOS 有太多可以对照的地方。第二类是对软件考古、历史代码修复有兴趣的开发者这个项目里充满了“在没有现代工具的情况下如何写出可靠代码”的智慧。第三类就是纯粹的好奇者想看看人类第一次登月时那台只有 72KB 内存的计算机到底是怎么工作的。1.2 整体架构从源码到可运行模拟器的三层结构Apollo-11 项目的整体设计可以拆成三层来看每一层都有明确的职责层与层之间的接口也定义得很清楚。这种分层思路本身就值得学习因为很多现代项目反而把边界搞得很乱。第一层是源码层。项目仓库里包含了从原始扫描件转录出来的汇编代码分为两个主要目录一个对应指令舱的 Comanche 055 软件另一个对应登月舱的 Luminary 099 软件。这些代码不是简单的文本复制而是经过了逐行校对修正了扫描件中模糊不清的字符并且保留了原始的注释和页码标记。源码文件的后缀通常是.agc里面全是类似TCF、CCS、INDEX这样的助记符第一次看会觉得很懵但看多了会发现它的指令集其实非常精简。第二层是工具链层。这一层负责把汇编代码变成模拟器能执行的二进制格式。项目里包含了一个用 Python 写的汇编器叫yaYULYet Another YUL assemblerYUL 是当年 AGC 汇编语言的一种叫法。这个汇编器会解析.agc文件处理符号表、宏定义、条件汇编最后生成一个.bin文件。同时还有一个链接器负责把多个目标文件合并成最终的内存映像。工具链里还有一堆辅助脚本比如用来校验校验和的、用来生成符号表的、用来把二进制转成十六进制格式的。这些工具大部分是用 Python 2 写的所以如果你在 Python 3 环境下跑需要做一些兼容性处理这是第一个坑后面会详细说。第三层是模拟器层。项目提供了一个用 C 语言写的 AGC 模拟器核心文件是agc.c和agc.h。这个模拟器实现了 AGC 的指令集、寄存器组、内存模型、中断系统甚至包括那个著名的“1201 和 1202 报警”——当年登月过程中因为雷达数据过载触发的执行溢出报警。模拟器还支持外设模拟比如 DSKY显示键盘单元、IMU惯性测量单元、雷达、发动机指令接口等。你可以通过命令行参数选择加载哪套软件、以什么速度运行、是否开启调试输出。这三层之间的数据流是这样的源码层经过工具链层编译链接生成一个二进制内存映像模拟器层加载这个映像然后按照 AGC 的时钟周期逐条执行指令。如果你想修改代码只需要改.agc文件重新跑一遍工具链再启动模拟器就行。整个流程和现代嵌入式开发非常像只不过工具链是几十年前风格的。1.3 方案选型背后的考量为什么不用现成的模拟器你可能会问既然已经有其他 AGC 模拟器了为什么还要折腾这个项目我实际用下来觉得 Apollo-11 项目有几个不可替代的优势。首先是代码的完整性。这个项目里的源码是从原始扫描件逐行转录的而且经过了多轮校对。有些其他模拟器只包含部分代码或者用的是反汇编出来的版本注释和符号名都丢失了。Apollo-11 项目保留了原始的符号名比如P63、P64、R02这些你在调试的时候能直接对应到任务手册里的描述这对理解代码逻辑帮助极大。其次是工具链的自包含性。很多模拟器要求你自己去搞汇编器或者依赖某个特定版本的商业工具。Apollo-11 项目把汇编器、链接器、模拟器全部打包在一起你只需要一个 Python 环境和 C 编译器就能跑起来。这种“开箱即用”的设计对于想快速上手的人来说非常友好。第三是社区维护的活跃度。这个项目在代码托管平台上有专门的仓库虽然核心代码已经稳定但社区还在不断提交修正和改进。比如有人发现了原始扫描件中的一处笔误提交了修正有人为 Python 3 做了兼容性补丁还有人增加了对更多外设的模拟支持。这种持续的维护意味着你遇到问题时大概率能找到解决方案或者已经修复的版本。当然这个方案也不是没有缺点。最大的问题是性能。用 Python 写的汇编器在处理大型源码文件时速度较慢完整编译一次可能需要几十秒到几分钟取决于你的机器性能。另外模拟器本身是单线程的如果你把时钟频率设得太高可能会占用大量 CPU 资源。不过对于学习和调试来说这些都不是大问题毕竟你不需要用它来跑实时任务。2. 核心细节解析与实操要点2.1 源码结构两套软件、四个模块、一个核心Apollo-11 项目的源码结构乍看很乱但理清之后会发现它的组织方式非常清晰。整个代码库分为两大块指令舱软件和登月舱软件。每一块又分为四个主要模块执行程序、中断处理、任务调度、外设驱动。执行程序是核心中的核心它负责解释和执行来自 DSKY 的指令管理导航、制导、控制等主要功能。在指令舱软件里这个模块叫Comanche在登月舱软件里叫Luminary。这两个名字都是当年项目的内部代号和功能没有直接关系但社区里一直沿用下来。中断处理模块负责响应各种硬件中断比如雷达数据就绪、键盘输入、定时器到期等。AGC 的中断系统设计得很有意思它没有现代处理器的中断向量表而是用一组固定的入口地址每个入口对应一个中断类型。中断处理程序必须非常短小因为 AGC 的内存和寄存器资源极其有限。任务调度模块是 AGC 的“操作系统”它用一个叫Executive的程序来管理多个任务的执行。每个任务有一个优先级调度器会根据优先级和当前状态决定下一个执行哪个任务。这种设计在当年是非常先进的可以看作现代 RTOS 的雏形。外设驱动模块负责和 DSKY、IMU、雷达等设备通信。这些驱动代码里充满了对硬件寄存器的直接操作读起来很像今天的单片机裸机编程。比如 DSKY 的显示刷新就是通过向特定的内存地址写入数据来实现的。注意源码文件里的注释是理解代码的关键。很多注释是当年程序员写的风格非常简洁甚至有些缩写需要查资料才能明白。建议配合任务手册一起看手册里会解释每个程序的输入输出和预期行为。2.2 工具链的安装与配置Python 2 到 Python 3 的迁移工具链的安装是第一个容易卡住的地方。项目原始代码是基于 Python 2 的而现代系统默认都是 Python 3。如果你直接运行python yaYUL.py大概率会报语法错误。我试过几种解决方案最后发现最稳妥的是用 Python 3 的兼容模式同时手动修改几个关键文件。首先你需要确认系统里有 Python 3 和 GCC。在 Ubuntu 或 Debian 上可以这样检查python3 --version gcc --version如果版本太低建议先升级。Python 3.6 以上、GCC 7 以上基本没问题。接下来克隆项目仓库。假设你已经有了仓库地址直接git clone到本地。进入目录后你会看到yaYUL文件夹和Simulator文件夹。yaYUL里是汇编器和链接器Simulator里是模拟器。Python 2 到 Python 3 的主要改动点有几个print语句要加括号dict.has_key()要改成inxrange要改成range字符串和字节串的区分要处理。项目社区已经有人提交了 Python 3 的补丁但可能不在主分支里。你可以先尝试直接运行如果报错根据错误信息逐个修改。我踩过的坑是yaYUL.py里有一处用了string.uppercase这个在 Python 3 里已经移除了需要改成string.ascii_uppercase。还有一处用了map()返回列表在 Python 3 里返回的是迭代器需要包一层list()。修改完 Python 文件后还需要编译模拟器。进入Simulator目录直接make就行。如果报错说找不到readline库需要安装libreadline-dev。编译成功后会生成一个叫yaAGC的可执行文件。提示如果你不想手动改 Python 代码可以试试用2to3工具自动转换。命令是2to3 -w yaYUL/它会自动修改目录下的所有 Python 文件。但自动转换不一定完美转换后还是要手动检查一遍。2.3 编译与链接从汇编源码到二进制映像编译过程分为两步先用yaYUL把.agc文件汇编成.bin再用链接器把多个.bin合并成一个完整的内存映像。这个过程和现代编译工具链很像但参数和文件命名规则需要熟悉一下。以指令舱软件为例源码文件在Comanche055目录下。你需要先设置一些环境变量告诉汇编器去哪里找包含文件。通常的做法是写一个简单的 shell 脚本把常用命令封装起来。我自己的脚本是这样的#!/bin/bash export AGC_ROOT$(pwd) python3 yaYUL/yaYUL.py --input Comanche055/MAIN.agc --output Comanche055.binMAIN.agc是主文件它会通过#include指令引入其他文件。汇编器会自动处理这些包含关系。如果一切顺利你会得到一个Comanche055.bin文件大小大约是 36KB 左右。这个大小对应 AGC 的内存容量指令舱软件占用大约 36K 个 15 位字登月舱软件稍微小一点。链接步骤通常不需要手动做因为yaYUL已经生成了完整的内存映像。但如果你要修改代码可能需要重新链接。链接器叫yaLINK用法和yaYUL类似但输入是多个.bin文件输出是一个合并后的.bin。注意汇编过程中如果出现“符号未定义”的错误通常是因为某个包含文件没有被正确引入。检查MAIN.agc里的#include列表确保所有依赖文件都在。另外有些符号是硬件定义的比如寄存器地址这些在Hardware.agc里定义必须包含。2.4 模拟器启动与基本操作让 DSKY 亮起来编译成功后就可以启动模拟器了。最基本的命令是./yaAGC --coreComanche055.bin这会启动模拟器加载指令舱软件然后等待你输入命令。模拟器默认会打开一个命令行界面你可以输入help查看所有可用命令。最常用的命令是run开始执行、step单步执行、break设置断点、examine查看内存、deposit修改内存。如果你想让 DSKY 显示出来需要额外启动一个 DSKY 模拟器。项目里提供了一个叫yaDSKY的程序它用图形界面模拟了 DSKY 的显示和键盘。启动方式是./yaDSKY --cfgComanche055.cfg这个程序会读取配置文件建立和yaAGC的通信。配置文件里定义了 DSKY 的端口号和通信协议。默认情况下yaAGC监听 19697 端口yaDSKY连接这个端口。如果端口被占用可以在配置文件里修改。启动后你会看到一个类似老式计算器的界面上面有两行数字显示分别是 PROG、VERB、NOUN 和 R1、R2、R3下面有一排按键。你可以按VERB和NOUN键输入指令比如VERB 06 NOUN 62是显示当前时间。如果模拟器运行正常你会看到数字开始跳动这说明 AGC 软件已经在执行了。提示第一次启动时DSKY 可能显示全零或者乱码这通常是正常的因为软件需要一段时间初始化。等几秒钟如果数字开始变化就说明一切正常。如果一直没反应检查yaAGC的输出看看有没有报错信息。3. 实操过程与核心环节实现3.1 完整编译流程从零到可运行模拟器我把自己从零开始编译 Apollo-11 的完整过程记录了一遍包括遇到的错误和解决方法。整个过程大约需要 30 分钟到 1 小时取决于你对命令行的熟悉程度。第一步准备环境。我用的是一台 Ubuntu 20.04 的虚拟机配置是 2 核 CPU、4GB 内存。这个配置足够跑模拟器了。先安装必要的依赖sudo apt update sudo apt install git build-essential python3 python3-pip libreadline-dev第二步获取源码。假设你已经有了仓库地址直接克隆git clone repository-url apollo-11 cd apollo-11第三步修复 Python 3 兼容性。我写了一个小脚本自动替换常见的 Python 2 语法find yaYUL -name *.py -exec sed -i s/print \(.*\)/print(\1)/g {} \; find yaYUL -name *.py -exec sed -i s/xrange/range/g {} \; find yaYUL -name *.py -exec sed -i s/string.uppercase/string.ascii_uppercase/g {} \;这个脚本只是处理了最常见的几个问题实际运行时可能还会遇到其他错误需要根据报错信息手动修改。第四步编译模拟器。进入Simulator目录运行makecd Simulator make如果一切顺利会生成yaAGC和yaDSKY两个可执行文件。如果报错检查错误信息通常是缺少某个头文件或库。我遇到过一次readline.h找不到的问题安装libreadline-dev后解决。第五步编译 AGC 软件。回到项目根目录运行汇编器python3 yaYUL/yaYUL.py --input Comanche055/MAIN.agc --output Comanche055.bin这一步可能会花几十秒因为汇编器要处理大量文件。如果报错仔细看错误信息通常是某个符号未定义或者语法错误。我遇到过一个错误是Invalid opcode: TCF后来发现是因为某个包含文件没有被正确引入导致汇编器不认识TCF指令。第六步启动模拟器。先启动yaAGC./Simulator/yaAGC --coreComanche055.bin然后在另一个终端启动yaDSKY./Simulator/yaDSKY --cfgComanche055.cfg如果 DSKY 窗口出现并且数字开始变化恭喜你成功了。3.2 调试实战跟踪一次 DSKY 输入的处理过程光看模拟器跑起来还不够真正有意思的是跟踪代码的执行过程。我选了一个简单的场景在 DSKY 上输入VERB 06 NOUN 62看看 AGC 是怎么处理的。首先在yaAGC的命令行里设置一个断点地址是P00或者P06这两个是 DSKY 输入处理程序的入口。你可以用break P06命令。然后按run开始执行。在 DSKY 上依次按下VERB、0、6、NOUN、6、2、ENTR。每按一个键yaAGC就会在断点处停下来。你可以用examine命令查看寄存器的值比如examine A查看累加器examine L查看链接寄存器。我跟踪下来的流程是这样的按键事件首先被中断处理程序捕获然后放入一个输入队列。主程序在空闲时从队列里取出按键根据当前状态决定是更新显示还是执行命令。VERB 06 NOUN 62是一个显示当前时间的命令所以程序会读取内部时钟把小时和分钟转换成十进制然后写到 DSKY 的显示寄存器。这个过程涉及好几个子程序比如KEYRUPT键盘中断、DISPLAY显示刷新、TIME2DEC时间转十进制。每个子程序都很短但逻辑非常紧凑。比如TIME2DEC里用了查表法来做除法因为 AGC 没有硬件除法指令所有除法都要用移位和减法来实现。注意调试时不要一次执行太多指令否则很容易跟丢。建议用step命令单步执行每执行几步就用examine查看关键寄存器和内存地址。另外AGC 的指令周期是 11.7 微秒模拟器默认会尽量接近这个速度但你可以用--speed参数调整。3.3 参数计算AGC 的时钟与指令周期AGC 的时钟频率是 2.048 MHz这个频率来自一个晶体振荡器然后经过分频得到各种时序信号。指令周期不是固定的取决于指令类型。大部分指令需要 1 到 2 个内存周期每个内存周期是 11.7 微秒。所以一条典型指令的执行时间大约是 11.7 到 23.4 微秒。这个速度放到今天来看慢得不可思议但在当年这是能造出来的最可靠的计算机之一。你可以算一下如果一条指令平均 20 微秒那么一秒钟能执行 5 万条指令。阿波罗 11 号的软件大约有 7 万条指令也就是说完整跑一遍所有代码只需要 1.4 秒左右。当然实际运行时大部分时间都在等待外设输入CPU 并不是一直在满负荷工作。模拟器里有一个--speed参数可以控制模拟速度。默认值是 1.0表示尽量接近真实速度。如果你把它设成 0模拟器会全速运行不等待时钟。设成 2.0 就是两倍速。我一般用 0 来快速验证功能用 1.0 来观察时序相关的行为。提示如果你想精确测量某段代码的执行时间可以在模拟器里打开周期计数功能。命令是--cycle-count它会在每次停止时输出已经执行的周期数。你可以用这个功能来验证你对指令周期的计算。3.4 外设模拟DSKY、IMU 和雷达的交互Apollo-11 项目模拟的外设不止 DSKY 一个。IMU惯性测量单元和雷达的模拟也很有价值尤其是对理解导航和制导逻辑的人来说。IMU 模拟器叫yaIMU它模拟了陀螺仪和加速度计的输出。你可以通过配置文件设置初始姿态和速度然后模拟器会根据 AGC 发出的控制指令更新状态。这个模拟器对于理解P52IMU 校准和P63登月制动等程序非常有帮助。雷达模拟器叫yaRADAR它模拟了交会雷达和着陆雷达的数据输出。阿波罗 11 号登月过程中著名的 1201 和 1202 报警就是因为雷达数据过载导致执行程序溢出。你可以在模拟器里重现这个场景启动雷达模拟器设置高数据速率然后观察 AGC 的反应。这些外设模拟器都通过 TCP 端口和yaAGC通信端口号在配置文件里定义。你可以同时启动多个模拟器让它们协同工作。比如同时启动 DSKY、IMU 和雷达就能模拟一个完整的任务场景。注意外设模拟器的配置文件格式比较古老用的是类似 INI 的格式。如果你要修改端口号或者初始状态直接编辑.cfg文件就行。但要注意yaAGC和模拟器之间的端口号必须一致否则连不上。4. 常见问题与排查技巧实录4.1 编译阶段的高频错误与解决方案编译阶段最容易出问题的地方是 Python 兼容性和符号解析。我整理了一个常见错误速查表覆盖了大部分我遇到过的情况。错误信息可能原因解决方法SyntaxError: invalid syntaxPython 2 语法在 Python 3 下不兼容用2to3转换或手动修改NameError: name xrange is not definedPython 3 移除了xrange替换为rangeAttributeError: module string has no attribute uppercasePython 3 移除了string.uppercase替换为string.ascii_uppercaseUndefined symbol: TCF包含文件缺失或路径错误检查MAIN.agc的#include列表make: *** No rule to make target yaAGC缺少 Makefile 或依赖检查Simulator目录下是否有 Makefilefatal error: readline/readline.h: No such file缺少 readline 开发库安装libreadline-dev除了这些还有一个比较隐蔽的问题汇编器在处理某些宏定义时会陷入死循环。我遇到过一次原因是某个宏在展开时递归调用了自己。解决方法是找到那个宏手动加上终止条件。这种情况很少见但一旦遇到很难排查因为汇编器不会报错只是卡住不动。提示如果你在编译时遇到莫名其妙的错误先试试用项目社区提供的最新补丁。很多时候问题已经被别人遇到并修复了你只需要同步一下代码。4.2 模拟器运行时的典型故障模拟器跑起来之后也可能出现各种奇怪的现象。我总结了几种最常见的情况。第一种是 DSKY 显示乱码或者不刷新。这通常是因为yaDSKY和yaAGC之间的通信出了问题。检查两个程序的配置文件确保端口号一致。另外防火墙可能会阻止本地连接可以临时关闭防火墙试试。第二种是模拟器突然停止响应。这可能是遇到了死循环或者非法指令。你可以在yaAGC的命令行里按CtrlC中断然后用examine查看当前指令地址看看卡在哪里。常见的原因是某个条件判断写错了导致程序一直在循环。第三种是时间显示不准确。AGC 的内部时钟是基于中断的如果模拟器的时钟频率设置不对时间就会走快或走慢。检查--speed参数确保它和你的预期一致。另外有些版本的模拟器在时钟中断处理上有 bug需要打补丁。第四种是外设模拟器连不上。这通常是端口冲突或者配置文件路径错误。用netstat命令检查端口是否被占用然后修改配置文件里的端口号。注意模拟器在运行时会输出大量调试信息如果你不需要可以用--quiet参数关闭。但排查问题时这些信息非常有用建议先打开看看有没有异常。4.3 独家避坑技巧从踩坑到填坑我在折腾 Apollo-11 的过程中积累了一些文档里不会写的经验这里分享几个最有用的。第一个技巧是用符号表来辅助调试。汇编器在编译时会生成一个符号表文件里面记录了每个符号对应的内存地址。你可以把这个文件加载到模拟器里这样在examine的时候就可以直接用符号名而不是记地址。命令是--symbolsComanche055.sym。这个功能极大提高了调试效率尤其是当你需要查看某个特定变量的值时。第二个技巧是用脚本自动化重复操作。模拟器的命令行支持脚本文件你可以把一系列命令写在一个文本文件里然后用--script参数加载。比如你想每次启动都自动设置几个断点、加载符号表、运行到某个地址就可以写一个脚本。我自己的启动脚本大概有十几行省去了每次手动输入命令的麻烦。第三个技巧是对比不同版本的代码。Apollo-11 项目包含了多个版本的软件比如指令舱有 Comanche 055 和 Comanche 056登月舱有 Luminary 099 和 Luminary 116。这些版本之间的差异往往很小但反映了当年程序员在任务前的最后修改。你可以用diff命令对比两个版本的源码看看改了哪些地方然后思考为什么改。这种对比学习的效果非常好。第四个技巧是关注社区的问题追踪。项目仓库的 issue 区里有很多有价值的讨论比如某个符号的定义有歧义、某个指令的行为和文档不一致等。这些讨论往往能帮你避开一些隐藏的坑。我遇到过一个问题是某个内存地址的初始值不对导致程序启动后行为异常后来在 issue 区找到了答案原始扫描件里那个地址的值印错了社区已经提交了修正。提示如果你发现了新的问题或者有改进建议不妨提交一个 issue 或者 pull request。这个项目的维护者很友好而且你的贡献可能会帮助到其他人。4.4 性能优化让模拟器跑得更流畅虽然 Apollo-11 模拟器对硬件要求不高但在一些老机器上或者运行复杂场景时还是可能卡顿。我试过几种优化方法效果比较明显。第一种是关闭不必要的调试输出。模拟器默认会输出很多信息比如每条指令的执行、每次内存访问等。这些输出会拖慢速度。用--quiet参数可以关闭大部分输出只保留错误信息。第二种是调整模拟速度。如果你不需要精确的时序可以把--speed设成 0让模拟器全速运行。这样 CPU 占用会高一些但响应更快。第三种是使用更快的 Python 解释器。汇编器是用 Python 写的如果你用 PyPy 来运行速度会比 CPython 快很多。我试过用 PyPy 跑yaYUL编译时间从 40 秒缩短到了 10 秒左右。第四种是把模拟器编译成优化版本。默认的make可能没有开启最高优化级别。你可以修改 Makefile把-O2改成-O3或者加上-marchnative让编译器针对你的 CPU 优化。这样模拟器的执行速度会有明显提升。注意优化编译可能会引入一些微妙的 bug尤其是涉及到浮点运算或者时序的地方。如果你发现模拟器行为异常先换回默认编译选项试试。5. 从 Apollo-11 项目中学到的工程智慧5.1 资源受限下的编程哲学AGC 只有 72KB 的内存其中还包括了操作系统和外设驱动。留给应用程序的空间非常有限。在这种条件下当年的程序员发展出了一套独特的编程哲学我觉得对今天的嵌入式开发仍然有借鉴意义。首先是代码复用。AGC 软件里有大量的小型子程序每个只做一件事比如加法、减法、移位、查表。这些子程序被反复调用而不是把功能内联到主程序里。这种设计节省了内存但也增加了调用开销。程序员需要在两者之间权衡。其次是数据驱动。很多逻辑不是用条件判断实现的而是用查表法。比如把角度转换成三角函数值不是实时计算而是查一个预先算好的表。这种方法的精度取决于表的大小但速度非常快。第三是中断驱动。AGC 的大部分时间都在等待中断而不是轮询外设。中断处理程序尽量短小只做最紧急的事情剩下的工作交给主程序。这种设计提高了响应速度也降低了功耗。这些哲学放到今天在物联网设备、可穿戴设备、汽车电子等领域仍然适用。资源永远是不够的如何在限制下写出可靠的代码是每个嵌入式工程师都要面对的问题。5.2 代码的可读性与可维护性Apollo-11 的代码是用汇编写的但可读性并不差。当年的程序员有一套命名约定和注释规范让代码即使过了几十年也能看懂。比如所有变量名都是大写的用下划线分隔单词。子程序名通常以功能开头比如DISPLAY、KEYRUPT、TIME2DEC。注释里会说明子程序的输入输出、使用的寄存器、可能修改的内存地址。这些注释虽然简短但信息量很大。另外代码里有很多“防御性编程”的痕迹。比如在除法之前先检查除数是否为零在访问数组之前先检查索引是否越界。这些检查在资源紧张的情况下可能会被省略但当年的程序员还是坚持做了因为一次错误就可能导致任务失败。这种对可读性和可维护性的重视值得今天的开发者学习。尤其是在团队协作中代码不仅是给机器执行的更是给人看的。5.3 测试与验证的方法Apollo-11 项目里包含了一些测试脚本和验证工具虽然不如现代测试框架那么完善但思路很清晰。比如有一个脚本用来校验汇编后的二进制文件的校验和。AGC 的内存里有一个校验和区域软件启动时会计算所有代码的校验和和预存的值比较。如果不一致就说明代码被篡改了或者内存出了问题。这个机制在当年非常重要因为核心绳存储器是不可擦写的一旦出错只能更换。还有一个脚本用来模拟各种输入场景比如键盘输入、雷达数据、IMU 数据然后检查程序的输出是否符合预期。这种“场景测试”的方法在今天仍然很常用尤其是在航天、医疗等对可靠性要求极高的领域。我自己的做法是在修改代码之后先跑一遍基本的启动测试确认 DSKY 能正常显示然后再跑几个关键场景比如 P63 和 P64看看有没有异常。这种分层测试的方法可以快速定位问题。5.4 社区协作与知识传承Apollo-11 项目之所以能持续维护离不开社区的协作。很多人贡献了自己的时间和专业知识修复 bug、改进工具、编写文档。这种协作模式对于历史代码的修复和传承非常重要。我注意到社区里有一些人是当年参与过阿波罗计划的工程师他们在 issue 区里分享了很多第一手的经验。比如有人解释了为什么某个子程序要那样写因为当年的硬件有某个限制。这些信息在官方文档里是找不到的只有亲历者才知道。另外社区还整理了一份详细的 Wiki包含了编译指南、调试技巧、常见问题等。这份 Wiki 是新手入门的最佳资料比看源码效率高得多。如果你对历史代码感兴趣我建议你加入这个社区哪怕只是提一个问题或者修正一个错别字都是对知识传承的贡献。6. 后续扩展与个人体会6.1 可以继续深挖的方向Apollo-11 项目本身已经比较完整了但还有很多可以扩展的方向。比如你可以尝试把 AGC 软件移植到现代的微控制器上用真实的硬件来运行。这需要写一个 AGC 指令集的解释器或者用 FPGA 来实现 AGC 的硬件逻辑。这种“复古硬件”项目在创客社区里很受欢迎。另一个方向是可视化调试。目前的模拟器主要是命令行界面如果你能做一个图形化的调试器把内存、寄存器、DSKY 显示、外设状态都可视化会大大降低学习门槛。已经有一些社区项目在做类似的事情但还有很大的改进空间。还有一个方向是任务重现。你可以用模拟器完整重现阿波罗 11 号的登月过程从发射到着陆每一步都按照历史记录来操作。这需要深入理解任务手册和飞行计划但完成后会非常有成就感。6.2 我个人的一些体会折腾 Apollo-11 项目的这段时间我最大的收获不是学会了怎么运行一个老软件而是对“软件工程”这四个字有了更深的理解。当年的程序员在资源极其有限、工具极其简陋的条件下写出了能把人送上月球的代码。他们的代码里没有花哨的设计模式没有复杂的框架但每一行都经过深思熟虑每一个字节都用在刀刃上。这种对代码的敬畏之心在今天的开发环境里反而少见了。我们有了更多的内存、更快的 CPU、更强大的工具但代码的质量并没有成比例提升。有时候我会想如果今天的项目也像 AGC 那样限制内存和速度我们还能不能写出可靠的软件另一个体会是历史代码是一座宝库。很多人觉得老代码没有学习价值但我觉得恰恰相反。老代码里蕴含的工程智慧是经过实践检验的比很多新潮的概念更可靠。只要你愿意花时间去读、去调试、去理解一定能学到东西。最后再分享一个小技巧如果你在调试时遇到一个很难的问题不妨暂时放下代码去读一读当年的任务手册或者宇航员的回忆录。很多时候理解了当时的场景和约束问题就迎刃而解了。技术不是孤立的它总是嵌入在具体的历史和人文背景中。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑