在勇气号和机遇号登陆火星并重新卷起一股火星热的时候,我找到这篇去年自己翻译的文章,看看当年的小插曲。自从1997年7月4日抵达火星表面后,火星探路者(The Mars Pathfinder)一直被大肆宣称为“完美的”。它确实包含了很多成功之处,从它那非传统的着陆方式――被巨大的气囊包裹着蹦蹦跳跳着陆,然后释放出火星探路者――开始,直到它收集并向地球发回的大量火星第一手数据,比如后来流传到Web上并让人叹为观止的各种火星表面全景图。但是在着陆后的第10天,也就是开始采集气象资料后不久,探路者开始犯傻――开始无规律地重启,每次重启都造成了数据丢失,在每天的记者招待会上这都是记者们不会放过的最热门的话题。NASA(美国国家航空航天局)的公共关系部门只是用一些术语比如“softwareglitches”来解释,他们告知外界,原因是计算机试图同时完成太多的事情所以造成了故障。
在上周的IEEE实时系统会议上,WindRiver CTO David Wilner给出了一个很有意思的新说法。当然,众所周知,该公司的实时嵌入式内核VxWorks,就用在火星探路者上。在该会议上,他详细解释了哪些真正的软件问题导致了探路者整个系统的重启,以及他们是如何诊断并解决该问题的,让我们分享一下这个故事。
先了解一些基本情况。VxWorks提供基于优先级的可强占调度方式。探路者上的任务均被赋予优先级,采用的是常规方式――按任务的紧急程度――划分优先级。
探路者有一个“informationbus”,可以看作是一块共享存储区,该区域用于在探路者的不同组件之间传递信息。当然,随之就有一个总线管理任务,该任务以高优先级运行,负责在总线上放入或者取出各种数据。它被设计为最重要的任务,并且要保证能够每隔一定的时间就可以操作总线。对总线的异步访问是通过互斥锁(mutexes)来保证的。
另有一个气象数据搜集任务,它的运行频度不高,也只有低优先级,它只向总线写数据。写的过程是,申请/获得总线互斥量,进行写操作,完成后释放互斥量。在互斥量被占有的情况下,如果总线管理任务被激活了,试图获得该互斥量,那么总线管理任务会被挂起,最终结果是或者一直等到气象任务释放该互斥量,或者总线任务等待超时。最后,探路者上还有一个以中等优先级运行的通信任务,通信任务和总线是没有什么瓜葛的。
开始的大多数时间,系统工作的很好,然而,还是发生了这样的情况:气象任务(低优先级)获得互斥量并写总线的时候,一个中断的发生导致了通信任务(中优先级)被调度并就绪,调度的时机正好是总线管理任务(高优先级)等待在总线访问互斥量上的时候。这种情况下,因为通信任务比气象任务优先级高,所以会抢占气象任务,当然,这也就更让总线管理任务失去了运行的机会。通信任务运行时间稍长,总线管理任务就会等待互斥量超时,返回错误,提示总线任务没有能够在一定的时间内完成总线操作,在探路者中,这种情况被当作严重错误处理,作为错误处理的结果就是――整个系统被重启。如何调试出这个问题的?
VxWorks可以配置为在一种tracing模式下运行,即记录所有感兴趣的系统事件,例如,上下文切换,同步对象的使用,发生的中断。探路者上出现了错误之后,JPL(美国国家航空航天局喷气推进实验室)的工程师们花了相当多的时间在实验室里的一台一摸一样的探路者上运行打开了tracing的系统,希望能够再现引起重启的情况。几天过去了,一个清晨,几乎所有的工程师都走了,只剩下最后一位Mr. So-So的时候,火星上那台探路者兄弟身上发生的重启情况终于被再现了。经过对trace数据的分析,得出了原因,大名鼎鼎的――或者臭名昭著的?――优先级翻转。如何纠正这个问题的?
什么是优先级翻转呢?以探路者的例子做一个一般性的描述就是:低优先级的任务(气象任务)占有互斥量,这时有高优先级的任务(总线任务)申请信号量,因为不能满足而被挂起了,即低优先级任务阻塞了高优先级任务的运行――这还不是最糟的――这时插进了一个中优先级任务(通信任务),它又把低优先级任务抢占了,高优先级任务能执行的机会就更少了,成了解不开的死结。在VxWorks里,当创建互斥量的时候,有一个bool参数,决定对该互斥量是不是采用优先级继承来防止发生优先级翻转。探路者的程序中创建的总线互斥量正好把这个参数off了。如果这个参数on的话,探路者的行为就是另一个样子了:首先,气象任务(优先级最低)获得互斥量,做写操作,在还没有完成写操作的时候,总线管理任务(优先级最高)申请信号量,所以互斥量的所有者,气象任务,将继承总线任务的高优先级,那么当中优先级的通信任务被调度的时候,是不可能影响无辜的已经具有高优先级的气象任务的。这样优先级翻转就被避免了。
VxWorks带有一个C语言解释器,允许开发者在调试时输入C表达式或者函数并且可以随意执行。JPL的工程师们很走运的在探路者上使能了这个功能。按照他们的编码约定,该互斥量的初始化参数都存在全局变量中,全局变量的地址在符号表中,引导部分的软件包含了该符号表,并且可以被C语言解释器使用。所以,工程师们上载了一段程序到探路者上,这段程序被解释后,把互斥量的参数改变为TRUE。结果是,再也没发生重启了。
首先,也是最重要的,以黑盒方式调试这个问题几乎是不可能的,只有当造成系统错误的真实运行情况被trace下来分析识别后这个问题才得益解决。
第二,在系统中保留调试功能会有用的,因为如果系统不能修改的话,问题是不可能得到解决的。
最后,工程师们的分析――“总线任务的执行频率非常高并且是时间关键的,所以不应该为了优先级继承而花费额外的时间”――是极为错误的。显然,在这样时间关键但正确性也是十分重要的场合,即使付出额外的性能代价也是必须的。
David告诉我们,JPL的工程师们后来承认,在飞行前测试阶段就发生过一两起这样的重启,但是相当不易重现所以也没有得到解释。工程师们――出于一种很自然的回避情绪――认为这个问题不重要,并且也找到了一个托词――可能是硬件上的小毛病引起的。
另外工程师们的工作重点也是个问题。他们花费了极大的精力用于保证着陆软件部分的质量,使其几乎达到了完美的程度。因为如果着陆失败的话,整个火星探测计划就完全失败了,所以,完全可以理解工程师们为什么会对相比之下次重要的陆地巡航软件的小问题不那么在意,特别地,只是把简单的重启当作错误处理的方法也可以看出在这部分没有花费很多的注意力。
[
本帖最后由 jorya_txj 于 2013-12-22 18:04 编辑 ]