☰
Apollo-11制导计算机:4KB内存如何实现登月级实时控制
2026/10/15 1:11:37 网站建设 项目流程

1. 从一行代码到月球:Apollo-11 制导计算机到底有多“抠”

第一次看到 Apollo-11 制导计算机(AGC)的规格参数时,我的反应和大多数人一样——这玩意儿真的把人送上过月球?主频 0.043 MHz,内存 约 4KB(准确说是 2048 个 16 位字的 RAM 加上 36K 字的只读存储器),运算能力还不如现在一颗电子表里的芯片。但就是这套系统,在 1969 年完成了人类历史上最复杂的一次实时控制任务:从地球轨道转移到月球轨道、下降着陆、上升交会、再入返回,全程靠它算。

我之所以想聊这个话题,是因为 Apollo-11 不只是一个历史事件,它本质上是一个极端约束条件下的软件工程奇迹。今天我们在写业务代码时,内存不够加一条、CPU 不够升配置,但在 AGC 上,每一个字节都要靠人脑去“抠”。这种思维方式,对现在做嵌入式、做实时系统、甚至做前端性能优化的人,都有直接的借鉴意义。

这篇文章不会给你讲阿波罗计划的宏大叙事,那些你随便搜都能找到。我想做的是,把 AGC 这套系统拆开,从架构设计、指令系统、软件工程方法、故障处理逻辑这几个角度,讲清楚它为什么能在那么“寒酸”的硬件上跑出那么高的可靠性。如果你是对底层系统、实时控制、极限优化感兴趣的开发者,这篇内容应该能让你有不少收获。

2. AGC 的硬件底子:为什么它“慢”却不“卡”

2.1 核心参数与设计取舍

先把 AGC 的关键规格摆出来,方便后面展开分析:

项目参数备注
字长16 位其中 15 位数据 + 1 位奇偶校验
RAM2048 字磁芯存储器,易失性
ROM36864 字核心绳存储器,只读
主频约 2.048 MHz 晶振,指令周期约 11.7 微秒实际算力约 0.043 MIPS
指令数约 34 条基本指令含扩展指令后更多
重量约 32 公斤占整个导航系统重量的一小部分

看到这组数据,很多人第一反应是“这也能行?”但如果你仔细想,AGC 的设计目标从来不是“快”,而是确定性。在航天场景里,最怕的不是算得慢,而是算得不确定——同样的输入,这次 10 毫秒出结果,下次 50 毫秒,那整个制导回路就没法设计。

AGC 的指令周期是固定的,大部分指令都是 11.7 微秒,少数需要 23.4 微秒。这种固定时序的设计,让软件工程师可以精确计算每一段代码的执行时间,从而保证控制回路的稳定性。今天我们在做实时系统时,也会追求这种“最坏情况执行时间”(WCET)可预测性,而不是单纯追求平均性能。

2.2 磁芯内存与绳存储:两种存储介质的性格差异

AGC 用了两种存储:RAM 是磁芯存储器,ROM 是核心绳存储器。这两个东西的性格完全不同。

磁芯 RAM 可以读写,但断电就丢数据。它的读写速度相对快,但容量极小,只有 2048 个字。你可以把它理解成今天的内存,但容量大概只够存一段几百行的汇编代码。

核心绳存储器就更有意思了。它的原理是用铜线穿过磁环,有磁环代表 1,没磁环代表 0。程序在制造时就被“编织”进去,一旦编好就改不了。这东西的可靠性极高,不怕辐射、不怕断电,但代价是写程序等于改硬件。你想改一行代码?对不起,得重新穿线。

这种“写死”的存储方式,逼着当时的工程师必须在编程前把逻辑想得极其清楚。今天我们有 CI/CD、有热更新,改一行代码几秒钟的事,但 AGC 的软件迭代周期是以周甚至月计的。这种约束反而催生了一套非常严谨的软件工程方法,后面我会详细讲。

2.3 指令系统:34 条指令怎么撑起登月

AGC 的指令集非常精简,基本指令只有 34 条左右,包括加载、存储、加法、转移、逻辑运算等。没有乘法指令,没有除法指令,所有乘除都要靠软件子程序实现。

这听起来很不可思议,但仔细想,这恰恰是RISC 思想的早期实践。指令少,硬件就简单,时序就好控制。乘除法虽然慢,但在制导计算里,很多运算可以用增量法或者查表法替代。比如计算三角函数,AGC 里就用了多项式近似,而不是实时算级数。

我实际看过一些 AGC 的汇编代码片段,那种“一个字节掰成两半用”的写法,今天看来依然让人头皮发麻。比如为了节省存储空间,工程师会把多个布尔标志打包进同一个字的不同位里,用位操作来读写。这种手法在今天嵌入式开发里依然常见,但在 AGC 上,它是生存技能。

3. 软件工程方法:在没有版本控制的年代怎么管代码

3.1 从“绳存储”倒逼出来的模块化设计

核心绳存储器的制造过程决定了,你不可能今天改一个模块、明天改另一个模块。每一次修改都意味着重新编织整条绳,成本极高。所以 AGC 的软件从一开始就被设计成高度模块化的结构。

整个软件被拆成多个“段”(segment),每个段负责一个相对独立的功能,比如姿态控制、导航计算、发动机指令等。段与段之间通过定义良好的接口通信,接口一旦确定就尽量不改。这种思路和今天微服务架构里的“契约优先”非常像——先把接口定死,内部实现可以各自演进。

更关键的是,AGC 的软件有一个“解释器”层。很多高层逻辑不是直接写成机器码,而是写成一种中间语言,由解释器在运行时解释执行。这样做的好处是,修改高层逻辑时只需要改解释器能识别的“数据”,而不需要重新编织整个程序。这其实就是虚拟机思想的雏形。

3.2 优先级调度:把 CPU 时间当金子花

AGC 的 CPU 时间极其宝贵,所以它用了一套基于优先级的协作式调度机制。每个任务有固定的执行周期和优先级,高优先级任务可以打断低优先级任务。比如姿态控制任务的优先级就比显示更新高得多,因为前者关系到生死,后者只是给宇航员看个数字。

这套调度机制的核心是“任务表”(executive),它维护了所有任务的执行周期、优先级和入口地址。调度器在每个时间片结束时检查有没有更高优先级的任务需要执行,如果有就切换过去。这种设计保证了关键任务总能按时执行,不会被低优先级任务拖累。

今天我们在做实时操作系统(RTOS)时,任务优先级调度是标配,但在 1960 年代,这套机制是 AGC 团队自己摸索出来的。我后来在一些工业控制项目里也用过类似的调度策略,实测下来,只要任务周期设计合理,系统的确定性确实比通用操作系统好很多。

3.3 代码审查与测试:没有单元测试框架怎么保证质量

AGC 的软件团队当时没有今天这些测试工具,但他们有一套非常严格的代码审查和仿真测试流程。每一段代码在编织进绳存储之前,都要经过多轮人工审查,并且在模拟器上跑大量的测试用例。

他们的模拟器可以模拟各种飞行场景,包括正常流程和异常情况。测试用例覆盖了从发射到返回的每一个阶段,甚至包括一些极端情况,比如雷达数据丢失、发动机推力异常等。这种“场景化测试”的思路,今天在自动驾驶和航天软件里依然是主流。

我印象很深的一点是,AGC 团队会专门安排人扮演“捣乱者”,故意在测试中注入错误数据,看系统能不能正确处理。这种对抗性测试的思路,比单纯跑通流程要有效得多。后来我在做支付系统时也借鉴过这个方法,让测试同学专门构造异常请求,确实能挖出不少隐藏问题。

4. 1201 和 1202 警报:一次真实的“线上故障”复盘

4.1 警报触发时的场景还原

1969 年 7 月 20 日,鹰号登月舱正在下降的最后阶段,距离月面大约 1800 米。突然,计算机发出了 1202 警报。这个警报的意思是“执行溢出”——CPU 的任务队列满了,有任务没能在规定时间内执行完。

当时地面控制中心的气氛可想而知。但 AGC 的设计里有一个关键机制:当溢出发生时,系统会自动丢弃低优先级任务,保证高优先级任务继续执行。具体来说,雷达数据处理任务的优先级较低,在溢出时被暂时搁置,而下降制导任务继续运行。

这个决策不是临时拍脑袋的,而是软件设计时就定好的策略。工程师们早就预想过,在下降最后阶段,雷达数据可能会大量涌入,导致 CPU 过载。他们的解决方案就是让系统在过载时“丢卒保车”,先保证飞行控制,雷达数据可以稍后处理。

4.2 为什么这个“故障”其实不是故障

从今天的视角看,1202 警报其实是一次设计内的降级。系统检测到过载,主动丢弃低优先级任务,并通知宇航员和地面。宇航员看到警报后,按照训练时的流程确认了情况,地面控制中心快速判断这是预期内的行为,于是决定继续下降。

这里有几个关键点值得注意。第一,警报的设计是可解释的——1202 有明确的含义,不是模糊的“出错了”。第二,系统有自动降级机制,不需要人工干预就能保住关键任务。第三,宇航员和地面都受过训练,知道这个警报意味着什么,不会慌乱。

这三点加起来,让一次潜在的灾难变成了一次有惊无险的小插曲。我在做线上系统时经常拿这个案例提醒自己:故障处理的设计,不只是写个 try-catch,而是要提前想清楚“哪些功能可以丢,哪些绝对不能丢”,并且让运维人员知道每个告警的含义。

4.3 对现代系统的启示:降级策略要提前设计

AGC 的溢出处理机制,本质上是一套优先级驱动的降级策略。今天我们在做高并发系统时,也会用到类似的思想,比如熔断、限流、降级。但很多团队是在系统上线后才开始考虑这些,而 AGC 团队是在设计阶段就把降级逻辑写进了代码。

我个人的经验是,降级策略一定要在正常时期就设计好、测试好,而不是等出问题了再临时加。因为故障发生时,你根本没有时间去想“哪些任务可以丢”。AGC 的工程师们显然深谙此道,他们在绳存储里“编织”进去的,不只是正常流程,还有各种异常情况下的应对逻辑。

5. 从 AGC 到现代嵌入式:哪些经验今天依然管用

5.1 资源约束下的编程思维

AGC 的编程环境是极端的资源约束:4KB RAM、36KB ROM、0.043 MIPS。今天即使是低端单片机,资源也比这丰富得多。但 AGC 的编程思维依然有价值,尤其是在做低功耗设备、实时控制系统、安全关键系统时。

比如在写中断服务程序时,AGC 的经验是“尽量短、尽量确定”。中断里不要做复杂计算,不要调用可能阻塞的函数,把该做的事标记一下,放到主循环里处理。这个原则今天在嵌入式开发里依然是金科玉律。

再比如内存管理,AGC 没有动态内存分配,所有变量都是静态分配的。这避免了内存碎片和分配失败的问题,代价是灵活性降低。今天在一些安全关键系统里,比如汽车电子,动态内存分配也是被严格限制甚至禁止的。AGC 的做法算是这个传统的早期实践。

5.2 确定性优于性能

AGC 的主频很低,但它的时序确定性极高。每条指令的执行时间都是固定的,任务调度也是周期性的。这种确定性让软件工程师可以精确计算控制回路的响应时间,从而保证系统稳定。

今天我们在做实时系统时,也会追求这种确定性。比如在电机控制里,PWM 中断的响应时间必须是确定的,不能因为操作系统调度而抖动。这也是为什么很多实时系统宁愿用裸机或者 RTOS,而不用通用操作系统。

我个人的体会是,性能可以慢慢优化,但确定性一旦丢失就很难找回来。在设计阶段就要想清楚哪些操作必须在确定的时间内完成,然后围绕这个约束来选硬件、选系统、写代码。

5.3 故障处理要“设计进去”而不是“补上去”

AGC 的 1202 警报处理逻辑,是在设计阶段就写进软件的。这意味着工程师们在写代码时,就已经预想到了过载的可能性,并且决定了应对策略。

今天很多团队的做法是,先把正常流程跑通,上线后再根据故障报告来补异常处理。这种做法在互联网业务里可能还行,但在安全关键系统里是危险的。因为故障发生时,你可能根本没有机会去补。

我在做支付系统时,有一个原则:每一笔交易的状态机,必须把所有可能的异常分支都画出来,并且每个分支都要有明确的处理逻辑。这个原则其实就来自 AGC 的启发——不要假设“正常情况会一直正常”,要把异常当成常态来设计。

6. 如果想自己动手复现 AGC 的逻辑,可以怎么玩

6.1 用现代工具模拟 AGC 的执行环境

如果你对 AGC 感兴趣,想自己动手体验一下,其实有不少现成的资源。网上有开源的 AGC 模拟器,可以加载原始的绳存储二进制文件,模拟指令执行。你可以写一段简单的汇编代码,看看它在 AGC 上是怎么跑的。

更硬核一点的做法是,用 FPGA 或者单片机来模拟 AGC 的指令集。我试过用一块低端 FPGA 实现 AGC 的基本指令,跑一个简单的加法循环。虽然性能比原版 AGC 快很多,但那种“每条指令都要精打细算”的感觉,确实能让人对底层系统有更深的理解。

6.2 用 AGC 的思路优化今天的代码

即使不玩模拟器,AGC 的优化思路也可以直接用在今天的项目里。比如:

  • 位打包:把多个布尔标志塞进一个整数的不同位,节省内存。这在嵌入式里很常见,在前端里也可以用类似思路优化数据结构。
  • 查表代替计算:AGC 用查表法算三角函数,今天我们在做图形渲染、信号处理时,查表法依然是常用的优化手段。
  • 固定周期调度:把任务按固定周期调度,而不是事件驱动,可以简化时序分析。这在工业控制里很常见。

我最近在一个物联网项目里就用了位打包的思路,把设备状态压缩到一个 32 位整数里,通过串口传输时节省了不少带宽。虽然现代硬件不缺这点资源,但这种“抠”的习惯,能让你对系统的理解更深入。

6.3 阅读原始代码的收获

AGC 的原始汇编代码在网上可以找到。我建议你找一段看看,不一定要全部看懂,但感受一下那种“每个字节都有用”的写法,对编程思维是很好的锻炼。

比如有一段代码是处理宇航员键盘输入的,工程师用了一个状态机来解析按键序列,每个状态只占几个位。这种写法在今天看来可能过于“抠门”,但它背后的思路——用最少的资源做最可靠的事——是永远不会过时的。

7. 我个人从 AGC 里学到的最重要的一件事

说了这么多技术细节,如果只让我总结一条最有价值的经验,那就是:约束不是敌人,而是设计的催化剂。

AGC 的硬件限制极其苛刻,但正是这些限制,逼着工程师们想出了优先级调度、解释器、降级策略这些今天看来依然先进的设计。如果当时给他们一台现代计算机,他们可能反而不会去思考这些问题。

我在实际工作中也有类似的体会。资源充足的时候,代码容易写得随意,系统容易变得臃肿。反而是资源紧张的时候,你会被迫去思考“什么才是真正重要的”,然后围绕这个核心去设计。

所以,下次当你觉得“硬件不够快、内存不够大”的时候,不妨想想 AGC。4KB 内存都能登月,你手里的资源其实比你以为的多得多。关键不在于你有多少资源,而在于你怎么用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询