Apollo-11 这个项目代号放到任何技术社区的标题栏里都会让人心里一紧登月、汇编、只读内存、没有第二次机会。我最初被它吸引不是因为在看航天纪录片而是偶然翻到那份公开的制导计算机源代码——本以为会是天书读进去才发现那是最早一批把“系统工程”真正写进代码里的作品。这篇文章不打算讲发射花絮或英雄故事而是想以同行的身份把这台计算机拆成四块来聊硬件底牌、实时调度与过载保护、验证链路、以及我从里面抄走的工程规矩。如果你是做嵌入式、后端、或者任何跟“可靠性”三个字脱不开关系的工程师这篇应该能给你一些可以直接带走的东西。1. 先看清 Apollo-11 的“电脑”2K字RAM、36K字只读存储、1.024MHz主频很多人想象中的登月舱计算机是一屋子嗡嗡作响的设备。实际这台制导计算机也就比一个行李箱大一点塞在航天员座位下方带着一块键盘和几位数字显示的仪表。它跑的代码总量放在今天是任何一个小程序都看不上的量级但它完成的任务今天也没几个系统敢打包票。先说结论这台机器在硬件上没有任何“余量”。没有硬盘没有虚拟内存没有图形界面没有通用操作系统。所有东西都是直接寻址、直接执行。理解它等于先理解“在什么都没有的前提下一群工程师怎么把不可能抠出来的资源一点点抠出来”。1.1 一张参数表看懂制导计算机我把核心参数整理成了下面这张表方便你对照着看参数数值换算成今天的说法字长15位 1位校验常被归类为“16位机”主频1.024 MHz约为普通手机主频的千分之一可写内存RAM2048字约4KB连一张高清照片都存不下只读存储ROM36864字约72KB全部程序、常量、导航表格都在这里功耗约55W比一盏节能灯高一点重量约32kg相当于两箱A4复印纸这里有个经常被误解的点那个“约4KB”的RAM并不是我们熟悉的DRAM而是磁芯存储。它同样靠电流改写但断电后数据不会丢这已经比很多现代终端设备“高级”了。真正限制它的不是技术可行性而是体积、重量和功耗预算——在飞船上每克重量都要用在刀口上所以可写内存只给了2K字剩下的大头全部做成只读。1.024MHz的主频意味着什么一条普通加法要走大约20微秒乘法可能到几十微秒。今天的CPU一微秒能做几千次运算而它一个加法的时间足够现代芯片完成几十万次操作。但这并不影响它完成制导任务原因在于它处理的物理过程本身变化不快真正稀缺的是“确定性”而不是“峰值性能”。1.2 “织”出来的程序铁芯绳只读存储的原理与代价Apollo-11 的程序存储在一种叫“铁芯绳”的只读存储里这个设计我每次想起来都觉得又原始又精巧。它的基本单元是一排铁氧体磁芯和一根根导线。导线穿过某个磁芯代表二进制“1”绕过这个磁芯代表“0”。成千上万根导线交错穿行被扎成一股股像绳索一样的模块所以叫 rope memory。读数据的时候脉冲电流从一端进入哪根线感应到了磁芯状态就输出对应的位。这个方案的直接好处非常硬核没有机械部件、不会掉电丢失、抗辐射能力强而且密度在当年能做到极致。三个成年人才能搬动的存储模块能装下整套登月制导程序在60年代已经算奇迹。代价也极其明显程序一旦“织”进铁丝里就物理上不可修改。想改一个1bit不是重新烧写而是要重新编织整个存储模块甚至重做一整条线束。用个不太严谨但好懂的类比——这就像把整本书刻在石碑上发现错别字的时候唯一的选择是把整块碑重新刻一遍。这个物理约束直接塑造了整个项目的软件管理风格代码冻结不是制度偏好而是硬件暴力逼出来的唯一选项。后面我会专门讲这套“冻结文化”它到今天都值得所有做固件、做交付型软件的人反复体会。1.3 DSKY一块数字按钮不是给人“操作电脑”的和这台计算机对话的唯一人机接口叫 DSKY其实就是一小组数字键盘和显示屏。上面没有图文界面没有鼠标只有两位的 Verb、两位的 Noun以及几行五位数字。航天员想启动某个程序先按动词码再按名词码比如“Verb 37Noun 01”机器会回显一串数字。这不是为了炫技而是在当时的技术约束下能做到的最可靠交互数字短、编码稳定、航天员戴着手套也能按。同时地面团队可以通过遥测看到同样一串数字双方用同一套编码沟通。这个设计给后来的我最大的启发是好的系统状态展示不是面板越花哨越好而是“任何一位训练过的人在危急时刻扫一眼就能明白当前在发生什么”。DSKY上每一个数字、每一个报警灯背后都有明确语义是为“人在回路”而设计的不是给工程师欣赏用的。2. 调度和过载1202 不是死机是它在故意“丢包袱”硬件只是骨架。真正让 Apollo-11 从一台“能做算术的机器”变成“能降落月球的系统”的是一套极简却相当成熟的实时调度机制。它没有我们熟悉的操作系统没有进程线程但它的设计思想放到今天做实时系统、做高并发服务的人身上依然能对得上号。这节我想重点聊一个被无数人误会的情节登月过程中出现的 1201 / 1202 报警。很多人把它理解成“电脑差点死机、航天员力挽狂澜”这话只对了一半。真相是那台计算机正在执行一种精心设计的“负载脱落”策略报警声说明它在保命而不是在崩溃。2.1 Executive、Waitlist、Interrupt一套极简的实时内核这套软件体系由三块核心构成中断管理机构、等待表、以及一个叫 Executive 的任务调度器。外部传感器惯性测量单元、雷达、键盘、遥测链路通过硬件中断把事件塞进系统等待表负责那些周期性发生的事比如每隔固定时间采样陀螺数据而 Executive 维护一个按优先级排列的任务队列总是挑最高优先级的任务先跑。你可以把它理解成一个急诊科的分诊台重症患者永远优先轻症患者在走廊等着护士台会根据病情严重程度决定谁先进抢救室。AGC 里的任务也分优先级导航、制导、姿态控制这些“重症”永远排在前显示刷新、遥测组帧之类的“轻症”让位。这套内核用掉的内存极小但逻辑已经相当完备有优先级、有任务调度、有时间片概念、有中断嵌套处理。放在今天很多小型 RTOS 的调度核心本质上还是这个套路。2.2 解读1201/1202调度器在说“我忙疯了但保命任务还活着”登月动力下降后期计算机突然响起了报警。历史记录里1201 和 1202 这两个报警码反复出现了好几回地面和航天员一度紧张。这两个码的意思是“Executive Overflow”。展开说就是某个瞬间涌入任务队列的请求太多Executive 连给新任务分配内存块的地方都不够了。按理说一个调度器碰到这种情况最直接的反映是“先堵住入口等有空间再接”但那样会让队列越积越长新到的数据永远是过期的。AGC 的设计者没有这么做。他们让调度器在队列塞满时主动“修剪”作业优先丢那些低优先级的、锦上添花的请求保住制导、导航、姿态这些致命任务继续运转。也就是说报警的时刻系统不是在死亡边缘挣扎而是在执行预设的求生策略——只保留能保住飞船的任务其他全扔。我当时看到这段原理真的起了一身鸡皮疙瘩。因为它不是“运行出错”而是“设计好的出错路径”。后来复盘普遍认为过载的诱因之一是雷达数据链路源源不断灌入额外的计算请求让本来就已经满负荷的调度器开始疯狂剪枝。下面是这两条报警的常见处置逻辑报警码含义处置原则1201Executive 溢出任务队列超出容量若在动力下降阶段且雷达数据正常按预案继续1202Executive 溢出且可用内存块被耗尽同上继续监视着陆程序是否在正常运行这里特别值得说的是地面团队的处置。地面上有不少工程师在模拟演练时把报警码表背得滚瓜烂熟看到1202的第一反应不是“完了”而是“这是溢出程序还在保命可以继续”。他们没有在飞行中翻手册因为手册早就在脑子里了。2.3 优雅降级的教材过载是设计参数不是边界情况我们做互联网服务的人天天把限流、熔断、降级挂在嘴边但真正在系统过载时能做到“主动丢任务”的其实不多。最常见的反应是让服务排队排到天荒地老用户看到无限转圈线程池堆满最后整个进程被拖死。AGC 给我们的启示是过载不是异常是系统运行的一个正常相位必须预先把“丢谁、保谁”的规则写好。它甚至把“丢弃决策”也做成了报警让人类决策者看到我丢了锦上添花的东西致命逻辑还在。判定标准也很干脆如果雷达数据是新鲜的导航计算还在按周期推进那就可以继续。这就是“在不确定中寻找确定性”的经典操作不是假装过载不存在也不是把全部希望寄托在“运气好不会过载”上而是让系统在过载时有一个明确的、可验证的存活状态。3. 没有第二次机会Apollo-11 的地面验证是怎么“过分认真”的软件工程里有个老生常谈测试只能证明错误存在不能证明错误不存在。放到 Apollo-11 身上这句话不是哲学而是生死问题。因为程序烧进铁芯绳之后飞上天就没有任何“打补丁”的可能。所以整个项目的验证策略不是“尽量多测”而是“在地面上把所有该错的、可能错的、不该错的全部错一遍”。3.1 全任务仿真在模拟器里先“登陆”一百遍当年没有云计算没有虚拟机但他们造了大型全任务模拟器把制导计算机的真实硬件接上用模拟的传感器数据去驱动它运行完整的飞行程序。模拟器能设置发动机推力偏差、雷达噪声、陀螺漂移甚至故意注入故障让飞行小组在模拟环境中“坠毁”好几次。这种硬件在环 系统联调的做法放到今天的嵌入式开发里依然是最高的验证级别。它的价值在于你测的不是“某一函数正确”而是“整套系统在面对异常输入时全体硬件、软件、操作人员的行为链是否正确”。我特别想说的一点是当年他们测试的“用户”不止是软件还包括操作它的人。模拟器里会突然冒出异常状态逼操作团队现场判断。这跟我们现在做混沌工程很像先把系统搞乱再看团队和系统怎么反应把反应中的漏洞填掉。3.2 代码冻结为什么改一行字要惊动整个项目组因为程序是织进线里的所以任何代码变更的本质是硬件变更。织一条新线束需要时间装进飞船需要时间重新验证更需要时间。于是项目管理只能走向一个极端越临近飞行变更门槛越高。到了后期一个细微改动不只要过技术评审还要评估重量、功耗、进度、风险所有环节都签字才允许动。这套“代码冻结”文化在今天的敏捷时代看起来很“反文化”但它背后有非常清醒的账本变更成本不等于改代码的成本而是从改代码到它真正可靠运行之间所有验证的总和。你想想我们现在的生产环境为什么这么怕半夜发版因为发布动作的验证成本几乎都转移到线上一但出问题回到测试环境重新复盘的代价远大于事前评审。Apollo-11 把这种成本量化得很清楚不是在口头上喊着“少改点”而是让每个人都知道“织好一条线要几周装回飞船又要几周”。当代价被看见人自然就谨慎了。3.3 测试的边界预演过才知道什么是“敢继续”前面提到 1202 报警时地面敢说“继续”这个决定的底气不是来自某个人的天才瞬间而是来自预演。在模拟训练中团队反复见过这个报警码知道它的含义边界也知道什么情况下可以继续、什么情况下必须中止。这就引出了测试哲学里至关重要的一条测试的真正产出不是一份“全部通过”的报告而是让决策者在压力下有可执行的判断依据。你可以不知道所有未来情况但你必须对已知的关键情况都排练过确保当它发生时第一反应不是愣住而是按预案走。当然这并不能消除所有风险。事实上后续飞行中依然出现过别的意外但他们把每种已知风险都变成了“有名字、有编号、有对策”的条目。工程上的成熟不是消灭不确定性而是把不确定性列成清单编好号提前想好对策。4. 从源码和工程实践里我抄走的六条规矩聊完原理和验证我想从一个普通开发者的角度说说这些年我从 Apollo-11 这个项目里真正“抄走”的东西。它们不是什么高深公式而是六条听起来朴素、做起来极难的规矩。4.1 代码是写给人看的机器只不过是顺便执行打开那份公开的制导计算机汇编源码你会发现里面注释非常多而且口吻一点都不“官方”更像是一群工程师在给后来的值班者留纸条。它时刻提醒我代码最宝贵的读者不是编译器而是半年后接手故障的那个人。所以我现在的习惯是注释里少写“在做什么”多写“为什么这么写”和“什么情况下这里会出事”。当年连数字面板都有人把注释写得那么有用我们守着IDE和文档工具更没有理由写出一堆“改了没人懂”的代码。4.2 把报警码变成肌肉记忆1202 事件里最让我后背发凉的不是机器报警而是报警来临时大家都能立刻反应。地面和航天员早就把关键报警码背熟了不需要现场查文档。我现在做系统也会给自己维护一份“报警手册”但更重要的是让值班的人真的练过而不只是文档躺在知识库里。所谓演练就是把“从看到报警到做出反应”的链路缩短到最短。4.3 过载降级要提前写进架构Apollo-11 教会我最关键的一句工程判断系统过载时保住什么是最高优先级必须提前定义好。它把“丢弃低优先级任务”写成了调度器的正常功能而不是事后补救。我现在设计中等规模的后端服务也会明确区分 must-run 和 nice-to-run用户核心链路是 must-run日志上报、非核心统计是 nice-to-run。一旦 resource 紧张nice-to-run 直接让位而且要让监控系统看得见“它在让位”而不是整个系统一起死掉。4.4 一切资源预算都被显式管理2K 字内存、36K 字存储、1MHz 主频每一项资源都有人在管预算。谁多占了一个字谁多耗了几百个周期都有人知道。这种“资源预算感”在今天的项目里基本消失了——几百MB内存随便花CPU 核数多到懒得数。但我要说一个现实你的系统或许不缺资源但缺预算感。没人知道核心链路到底能承受多大流量、哪个模块超了多少内存于是只能靠“扩容”来解决所有事。Apollo-11 的做法值得借鉴的不是“抠”而是“知道每一份资源被谁用掉了”。4.5 变更越晚代价越大这张表我是这么理解的它比任何PPT都直白阶段产物形态变更代价设计期图纸、文档低改一行字而已编码期纸带、磁带上的汇编源码中重新汇编后可生成新版本织带期铁芯绳只读模块高改1bit等于重织整捆线飞行中已烧录的运行系统无穷大无法修改它给我的直接教训是越靠近交付节点变更的代价不是线性增长而是指数爆炸。所以我在项目排期时会刻意把“需求冻结日”和“代码冻结日”全都写清楚让产品同学和开发同学都提前接受一个现实——没有所谓“顺手改一下”。4.6 把你最重要的代码和方案沉淀下来这套制导程序几十年前就已经解密并公开今天任何一个程序员都能在开源仓库里翻到那份汇编源码逐行去读当年工程师的思路。它的价值早就超出了登月本身变成了后来者学习实时系统、学习复杂调度、学习“极端约束下如何设计”的最佳教材。我自己的体会是你手头最重要的代码和方案如果只存在某个私有仓库里十年后很可能就没了但如果你把它整理成文章、复盘成文档、提炼成可复用的原则它的生命周期会远比任何一次发布都长。你的项目也许不会登月但你的记录可能帮下一个项目避开同一个坑。最后聊几句我个人的体会。做项目这些年我见过太多系统在流量洪峰里直接崩掉也见过团队在故障来临那一刻手忙脚乱地翻手册。每次遇到这种时刻我都会想起 Apollo-11 那个 1202 报警的瞬间它做到的其实不是“更聪明的代码”而是提前想清楚了哪些任务必须活、哪些任务可以丢、丢掉之后怎么让决策者看见。这套思路不挑行业、不挑语言从一台1MHz的计算机到今天的分布式系统底层逻辑几乎没变。找个周末把那份公开的制导源码翻出来跳到着陆程序那段注释读一读你大概率会和我一样在屏幕前安静地看很久。