“好奇号”火星车和它搭载的软件(来自Erlang程序员的观点)
我十分好奇,忍不住想推测一下“好奇号”火星车上的软件究竟是个什么样的构造。我们已经知道,好奇号上的软件大部分都是用C语言写成的,这些代码加起来大概有250万行。有人可能会感到诧异,这样复杂的系统怎么能写出来而且能让它好用?下面是来自Erlang程序员的观点。
首先来些基础的。“好奇号”火星车使用的是核动力,它能持续的受控的方式提供给火星车能量。这个能量源同时还要负责平时对火星车进行加热——这是在火星表面极端天气环境下对火星车的必要保障。
“好奇号”基本上是自主控制的。它发送一条信息可能要用几分钟到几小时的时间,你只能在火星上一天里的有限时间段内给它发送信息。“好奇号”自己可以和地球通话,但这条线路速度很慢。它也可以通过围绕火星飞行的人造卫星进行通信,把卫星作为上行线路中继,这样更快。这种情况表明:火星车必须要能自主行动。我们不能让一个人坐在地球上的某个椅子里拿着操纵杆来指导它。
“好奇号”火星车上安装有两个完全一样的计算机。我们注意到美国宇航局正是按照Joe Armstrong(Erlang编程语言的创造人)的话做的:“要想获得一个可信赖的系统,你需要两台计算机”。一个一直处于休眠状态,一旦另一个由于异常情况死机,它可以随时受命接管系统。这样的做法在Erlang语言系统里、在OpenBSD PF防火墙等其它软件里都是很典型的接管方案。“好奇号”上使用的计算机是BAE systems RAD750。处理器是PowerPC ISA,速度非常的快。200百万赫兹, 150或250纳米 的制造工艺,它工作时对能允许的温度范围的表现非常优秀。它是经过抗辐射加固的,能经受相当强的辐射侵袭。内存也是抗辐射的。“好奇号”上的计算机里的每个硬件都不是随随便便一个东西能胜任的。
“好奇号”的操作系统使用的是VxWorks。它属于标准的的微内核系统。保守的估计,它的内核代码应该少于1万行,而且经过了严格的测试。也就是说,这个内核接近零bug。它的一个主要特征就是隔离。火星车上的各个模块都是相互隔离的。有些子系统对火星车的生命起着至关重要的作用,而另外一些只是用于科学观察的设备。所以,我们可以肯定这样一个事实,“好奇号”上的250万行代码中,只有一部分代码是深度测试杜绝了bug的。车上的有些程序并不是生命必须的。
美国宇航局使用了各种办法来确保代码质量。例如,递归调用是要求避免的,这是这因为C语言编译器不能保证递归堆栈不被撑破。循环要确保有终止点,这通过一个静态分析器来发现这些问题。所有的内存使用都几乎是静态分配的,这样避免了突然的内存收集产生的混乱和不可预知的性能问题。我们还可以发现讯息传递(Message Passing)作为子系统间的消息传递方式在火星车是被当作了首选。不存在互斥,不存在软件事务性内存。同样,隔离概念也是编码指导原则上的一部分。通过对内存进行保护和数据的单一归属关系,子系统之间就很难影响对方。Erlang程序员都很习惯这样的做法。
当年的“探路者”号火星车的架构设计事实上也跟Erlang语言系统的理念相似。它有用于传递消息的“组件”。组件只在接收消息时才等待,发送消息的都是无返回值的函数。它们接受消息采用的是单事件循环,这跟Erlang语言中的gen_server 工作方式很相似。不同的模块间通过某种协议传递消息进行通信,你可以访问其它模块使用的内存,但按照JPL编码指导原则,这种做法是要避免的。这跟Erlang语言有所不同,Erlang语言完全禁止这样操作。火星探测漫游者(勇气号和机遇号)拥有更多的组件,但软件基础上相同的。“好奇号”也不例外。它本质上是在老的软件上改造出来的。系统中的线程数有大几百个,这完美的和一个类似的如此规模的Erlang语言系统中的线程数相匹配。
在“好奇号”上,他们增加了“组件”的概念,组件由一组组的模块构成,以此用来控制复杂度。因为有两台计算机做冗余,很多子系统为了系统的稳固也是冗余的,组件的概念也是处理这些情况需要的。有趣的是,Erlang语言的设计者也看到了这一点,只是在Erlang里被叫做Applictions。
对函数恒量的校验。输入参数必须要满足前置条件。后置条件约束返回值。各种恒量必须满足这些条件。Erlang程序员熟悉这种做法。有趣的是,“好奇号”上的每个函数的长度限制在60行以内,这样它们可以被打印到单张纸上。Erlang程序员也喜欢简短的函数体,但没有这种限制。但都是为了让代码简单。让代码易于理解。
还有另外一个有趣的事情,在过去,有个火星车发生过优先级颠倒的问题。他们在调试控制台里向火星车注入了一段纠正信息挽救了火星车。这也跟Erlang语言系统里经常使用的方法相似。我们可以对运行中的系统进行修改,随时对系统进行升级和改造。我们对运行中的系统进行监控,确保它的运行状态跟我们期望的一样。这种对系统进行热修复的能力非常的有用。当然,这种开发是配合了大量的跟踪和分析——例如使用Erlang QuickCheck/PropEr,错误记录以及跟踪工具。
很明显,Erlang语言系统的很多特征都跟火星车上的系统吻合。但我并不认为这是巧合。各种软件有自己不同的属性特征——火星车属于硬实时(hard realtime)环境,Erlang语言系统是软实时环境。但大体上,写出健壮系统的条件是你需要隔离系统中的各个部分。这值得思考,看起来这种方式好用。这些对于高可靠性系统来说都是的重要的特征。也许比静态类型校验还要重要。
总结来看,对于火星车上的所有代码,我们也许并不必保障所有代码都达到最高级别的安全。我们可以把不同的模块进行隔离测试,对它们实施不同等级的正确性检查。换句话说,我可以通过精心的设计来控制错误和管理风险。因此,对于某些模块,我们可以承认它们可能存在某些错误。如果上行通信中继坏了,我们可以重启机器,这样来恢复它。如果这样不行,我们还有一个冗余的上行通信通道直接和地球通信,只是速度慢些——但可以替代另外一个通道。这种架构意味着只有多个组件同时失败时才能导致任务无法完成。模块出错,重启,恢复,然后就可继续拍摄图片。这种设计的基本原理是非常可靠的,也许需要根据情况做一些小的调整。毕竟它是经过了另外3个火星车的严格考验上发展出来的。
跟Erlang语言理念不相同的部分跟所对应的硬实时和软实时环境有关。在Erlang语言系统中我们可以暂缓服务。虽然不好,但可以这么干。在火星车上,这会成为灾难。在飞行控制系统中尤其是这样。如果火箭启动晚了,你的麻烦就大了。这就是为什么“好奇号”上要使用静态分配内存和固定堆栈大小,而不是使用动态分配的原因。这同样也是他们不喜欢递归的原因。而在Erlang语言系统里,我们不鼓励通过手动管理内存。我们对tail调用做了革命性的优化,所以我们可以放心的使用它。
长话短说——“好奇号”火星车的软件在某些特征上跟Erlang语言系统在架构上非常是相似。这些特征是一个健壮的软件系统的基本特征吗?
[本文英文原文链接:On Curiosity and its software ]