程序员和工程师的民间传说(第1部分)





这是Internet上一些有关错误有时是如何令人难以置信的表现的故事的集合。也许您也有一个故事要讲。



汽车过敏香草冰淇淋



对于了解以下事实的工程师来说,这是一个故事,他们知道显而易见的问题并不总是解决方案,而且无论事实多么不可信,它们都是事实。通用汽车公司庞蒂亚克分部收到投诉:



, , , . : . , , , , . Pontiac, . , , , . , . , , : « Pontiac, - , , , ?».


可以想象,该部门总裁对此信表示怀疑。但是,以防万一,我派了工程师检查。一个居住在美丽地区的富裕,受过良好教育的人遇见了他,他感到惊讶。他们同意晚饭后见面,一起去冰淇淋店。那天晚上是香草,当他们回到车上时,它不会开始。



工程师又来了三个晚上。冰淇淋第一次是巧克力。汽车启动了。第二次是草莓冰淇淋。汽车启动了。第三天晚上,他要香草。汽车没有启动。



经过明智的推理,工程师拒绝相信这辆车对香草冰淇淋的过敏。因此,我同意车主的意见,他将继续访问直到找到解决问题的办法。在此过程中,他开始记笔记:写下所有信息,一天中的时间,汽油的种类,到达和从商店返回的时间等。



工程师很快意识到,车主花在购买香草冰淇淋上的时间减少了。原因是商店中产品的布局。香草冰淇淋是最受欢迎的冰淇淋,并存放在商店门前的单独冰箱中,以方便查找。所有其他品种都在商店的后面,花了更多的时间来找到合适的品种并付款。



现在的问题是工程师:如果从发动机关闭那一刻起经过的时间少了,为什么汽车不能启动?由于问题是时间,而不是香草冰淇淋,因此工程师很快找到了答案:这是一个气闸。每天晚上都会出现这种情况,但是当车主花更多的时间寻找冰淇淋时,引擎有时间冷却下来并安静地启动。当那个人买了香草冰淇淋时,发动机仍然很热,加油塞没有时间溶解。



道德:即使是完全疯狂的问题有时也可能是真实的。



崩溃班迪科特



经历这很痛苦。作为程序员,您习惯于将代码归咎于第一,第二,第三...甚至在十分之一的位置都归咎于编译器。甚至在列表的下面,您也已经责怪了设备。



这是我有关硬件错误的故事。



对于Crash Bandicoot游戏,我编写了代码以加载并保存到存储卡。对于这样一个自以为是的游戏开发者来说,这就像在公园散步一样:我认为这项工作需要几天的时间。但是,结果,我调试了六个星期的代码。我在此过程中解决了其他问题,但是每隔几天我就会回到此代码中几个小时。真是痛苦



症状如下所示:当您保存游戏的当前玩法并访问存储卡时,几乎所有事情都进展顺利……但是有时读或写操作是在没有明显原因的情况下通过超时完成的。简短的录音通常会损坏存储卡。玩家尝试保存时,他不仅无法保存,还破坏了地图。饼子。



不久之后,我们索尼的制片人康妮·巴斯(Connie Bus)开始感到恐慌。我们无法通过此错误发布游戏,六周后,我不明白导致此问题的原因。通过Connie,我们联系了其他PS1开发人员:有人遇到过类似的问题吗?没有。没有人对存储卡有任何问题。



如果您对调试没有任何想法,那么几乎唯一的方法就是“分而治之”:从错误的程序中删除越来越多的代码,直到出现一个相对较小的片段,仍然会导致问题。就是说,您逐步地从程序中断开,直到剩下包含该错误的部分为止。



但问题是,要从视频游戏中切出片段非常困难。如果删除了重力仿真代码,该如何运行?还是画人物?



因此,您必须用存根替换整个模块,这些存根假装正在做有用的事情,但实际上是在做非常简单的事情,不能包含错误。我们必须写这样的拐杖才能使游戏正常工作。这是一个缓慢而痛苦的过程。



简而言之,我做到了。我删除了越来越多的代码,直到有一个初始代码来设置系统以启动游戏,初始化要渲染的设备等。当然,在此阶段,我无法创建保存和加载菜单,因为我必须为所有图形代码创建一个存根。但是我可以假装自己是一个使用(不可见的)保存和加载屏幕并要求保存然后写入存储卡的用户。



结果,我剩下的一小段代码仍然存在上述问题-但到目前为止,它是随机发生的!在大多数情况下,一切工作正常,但偶尔会发生崩溃。我删除了几乎所有的游戏代码,但该错误仍然存​​在。这令人困惑:剩余的代码实际上没有做任何事情。



在某个时刻,大概是凌晨三点,我想到了一个想法。读和写(I / O)操作采用精确的时序。当使用硬盘,存储卡或蓝牙模块时,负责读写的底层代码会根据时钟脉冲进行操作。



在时钟的帮助下,未直接连接到处理器的设备将与处理器中的可执行代码同步。时钟确定波特率-波特率。如果对时序有混淆,那么硬件或软件或两者都将混淆。这非常糟糕,因为数据可能会损坏。



如果我们的代码中的某些东西使计时混乱了怎么办?我在测试程序的代码中检查了与此相关的所有内容,并注意到我们将PS1中的可编程计时器设置为1 kHz(每秒1000个周期)的频率。默认情况下,启动机顶盒时有很多东西,它以100 Hz的频率运行。大多数游戏都使用此频率。



游戏的开发者安迪(Andy)将计时器设置为1 kHz,以便更准确地计算运动。 Andy容易过度使用,如果我们模仿重力,那么我们会尽可能准确地做到这一点!



但是,如果加快计时器速度会以某种方式影响程序的整体时序,从而影响到调整存储卡波特率的时钟,那该怎么办呢?



我已注释掉计时器代码。该错误不再发生。但这并不意味着我们已修复它,因为崩溃是随机发生的。如果我很幸运怎么办?



几天后,我再次尝试了测试程序。该错误未重复。我回到完整的游戏代码库,更改了保存和加载代码,以便在访问存储卡之前将可编程计时器重置为其原始值(100Hz),然后再次返回1kHz。没有更多的崩溃。



但是为什么会这样呢?



我再次回到测试程序。我试图在使用1 kHz计时器的错误中找到一些规律性。最终,我注意到有人在玩PS1控制器时发生了错误。由于我很少自己做这个-为什么在测试保存和加载代码时需要控制器? -然后我没有注意到这种依赖性。但是有一天,我们的一位艺术家在等我完成测试-那时我可能正在诅咒-紧张地扭动了他手中的控制器。发生了错误。 “等等,什么?!好吧,再做一次!”



当我意识到这两个事件是相互关联的时,我能够轻松地重现该错误:我开始写入存储卡,移动了控制器,损坏了存储卡。对我来说,它看起来像是一个硬件错误。



我去了康妮并告诉了我发现的事。她将信息转发给了设计PS1的工程师之一。他回答说:“不可能,这不可能是硬件问题。”我请康妮与我们交谈。



工程师给我打电话,我们用他的英语和我的日语(极端)一起与他争论。最后我说:“让我发送30行测试程序,让控制器运动会导致错误。”他同意。他说这是浪费时间,他非常忙于一个新项目,但由于我们是索尼非常重要的开发商,他会放弃。我清理了测试程序并将其发送给他。



第二天晚上(我们在洛杉矶,他在东京),他给我打电话,不好意思地道歉。这是硬件问题。



我不知道错误的确切原因,但是根据我在索尼总部听到的消息,将计时器设置为足够高的值会干扰计时器晶体附近主板上的组件。其中之一是存储卡波特率控制器,该控制器还设置了控制器的波特率。我不是工程师,所以我可能有些困惑。



但关键是,主板上的组件之间存在干扰。而且,当使用以1 kHz频率运行的计时器通过控制器端口和存储卡端口同时传输数据时,这些位消失了,数据丢失了,并且卡被损坏了。



坏牛



在1980年代,我的导师谢尔盖(Sergei)为CM-1800(PDP-11的苏联克隆版)编写了软件。该微型计算机刚刚安装在苏联重要交通枢纽斯维尔德洛夫斯克附近的火车站。新系统是为货车和货运路线设计的。但是事实证明这是一个令人讨厌的错误,导致随机崩溃和崩溃。当有人晚上回家时,总是发生跌倒。但是,尽管第二天进行了仔细的调查,但计算机在所有手动和自动测试中均能正常工作。这通常表示在某些情况下会出现竞争状况或某些其他并发错误。厌倦了深夜的电话,Sergei决定深入研究此事,首先要了解编组场的情况是什么导致计算机故障。



首先,他收集了所有无法解释的瀑布的统计数据,并按日期和时间绘制了图表。模式很明显。经过几天的观察,Sergey意识到他可以轻松预测未来系统出现故障的时间。



他很快得知,只有在该站将来自乌克兰北部和俄罗斯西部的牛车运到附近的屠宰场时,才会发生干扰。这本身很奇怪,因为屠宰场是由哈萨克斯坦附近的农场提供的。



切尔诺贝利核电站于1986年爆炸,放射性尘埃使周围地区无法居住。乌克兰北部,白俄罗斯和俄罗斯西部的大部分地区都受到了污染。 Sergei怀疑到达的汽车中存在高水平的辐射,因此开发了一种测试该理论的方法。禁止携带剂量计的居民,因此谢尔盖(Sergei)在火车站放下了几名军人。喝了几杯伏特加酒后,他设法说服士兵测量一辆可疑汽车中的辐射水平。事实证明,该水平是通常值的几倍。



牛不仅发出强烈的辐射,而且辐射强度很高,以致意外丢失了CM-1800存储器中的碎片,该存储器位于车站附近的建筑物中。



苏联粮食短缺,当局决定将“切尔诺贝利”肉类与该国其他地区的肉类混合。这样就可以降低总体放射性水平,而不会浪费宝贵的资源。得知此事后,谢尔盖立即填写了移民文件。当辐射水平随着时间的推移而下降时,计算机的跌倒自行停止。



通过管道



Movietech Solutions曾经创建用于票务,票务和一般管理的电影院软件。旗舰应用程序的DOS版本在北美的中小型影院连锁店中非常受欢迎。因此,毫不奇怪,当Windows 95版本宣布与最新的触摸屏和自助服务亭集成在一起,并配备了各种报告工具后,它迅速流行起来。大多数时候,更新都很顺利。地面上的IT人员安装了新硬件,迁移了数据,并继续进行业务。除非没有继续。发生这种情况时,公司派出了绰号“清洁工”的詹姆斯。



尽管这个绰号暗示了邪恶的类型,但清洁工只是讲师,安装人员和所有行业的杰克的结合。 James可以在客户的地方花几天时间,将所有组件组装在一起,然后几天,他教员工如何使用新系统,解决可能出现的任何硬件问题,并在整个开发阶段帮助软件。



因此,在这个忙碌的时间内,James早上来到办公室,没有时间去他的办公桌,这是不足为奇的。当老板遇到他时,James装满了高于正常水平的咖啡因。



“恐怕您需要尽快前往新斯科舍省的安纳波利斯。他们的整个系统崩溃了,与他们的工程师一起工作了一整夜后,我们无法确定发生了什么。服务器似乎出现网络故障。但是只有在系统运行了几分钟之后。



-他们没有回到旧系统吗?詹姆斯非常认真地回答,尽管在他的脑海中他惊讶地睁大了眼睛。



-确实如此:他们的IT专家“改变了优先级”,他决定离开他们的旧服务器。詹姆斯(James),他们已经在六个站点安装了该系统,并且刚刚获得了高级支持,并且他们的业务现在在1950年代进行。



詹姆斯微微挺直。



-这是另一回事。好的,让我们开始吧。



当他到达安纳波利斯时,他所做的第一件事就是找到客户的第一个有问题的电影院。在机场拍摄的地图上,一切看起来都不错,但所需地址的周围却显得可疑。不是贫民窟,而是让人想起黑色电影。当詹姆斯停在中心的路边时,一名妓女走近他。考虑到安纳波利斯的大小,它很可能是整个城市中唯一的一个。她的出现立即使人想起了著名的角色,她在大银幕上以金钱为生。不,不是朱莉娅·罗伯茨(Julia Roberts),而是乔恩·沃伊特(Jon Voight)[电影《午夜牛仔》暗示-大约。每。 ]。



将妓女送回家后,詹姆斯去了电影院。周围的环境已经变好,但是仍然给人一种肮脏的印象。并不是说詹姆斯太担心了。他已经去过肮脏的地方。这是加拿大,即使是劫匪也有礼貌地在拿走钱包后说“谢谢”。



电影院的侧门在一个潮湿的小巷里。詹姆斯走到门前敲了敲门。不久,她吱吱作响,张开了一点。



-你是清洁工吗?嘶哑的声音从内部传来。



“是的,是我……我来修理所有东西。



詹姆斯走进电影院的大厅。也许别无选择,工作人员开始向游客发行纸质机票。这使财务报告变得困难,更不用说更有趣的细节了。但是工作人员向詹姆斯打招呼,然后立即带他到服务器室。



乍一看,一切都井井有条。詹姆斯登录服务器,并检查了通常可疑的地方。没问题。但是,为预防起见,James关闭了服务器,更换了NIC,并回滚了系统。她立即​​开始全职工作。工作人员再次开始售票。



詹姆斯打电话给马克并报告了情况。不难假设詹姆斯可能想在这里徘徊,看看是否发生了意外情况。他下楼梯,开始询问工作人员发生了什么事。显然,系统已停止工作。他们关闭并重新打开它,它起作用了。但是10分钟后,系统崩溃了。



就在那一刻,类似的事情发生了。突然,票务系统开始出现错误。工作人员叹了口气,拿起纸机票,詹姆斯赶紧到服务器室。服务器一切正常。



然后一名雇员进入。



-系统再次运行。



詹姆斯很困惑,因为他什么都没做。更确切地说,没有任何东西可以使系统正常工作。他注销,拿起电话,并致电了公司的支持团队。不久,同一位员工进入服务器机房。



-系统在说谎。



詹姆斯瞥了一眼服务器。屏幕上跳动着一种有趣而又熟悉的多色形状的图案-管子混乱地扭曲和缠绕在一起。我们都曾经看过这个屏幕保护程序。它被精美地渲染并被催眠。





詹姆斯按下按钮,图案消失了。他匆匆赶往售票处,在途中遇见了返回他的员工。



-系统再次运行。



如果您可以在头脑上做一个脸部护理,那正是James所做的。屏幕保护。它使用OpenGL。因此,在运行期间,它会消耗服务器处理器的所有资源。结果,对服务器的每个请求都超时。



詹姆斯回到服务器机房,登录并用空白屏幕替换了漂亮的管道屏幕保护程序。也就是说,我安装了另一个不消耗资源的屏幕保护程序,而不是消耗了100%处理器资源的屏幕保护程序。然后我等了10分钟检查我的猜测。



当詹姆斯到达下一个电影院时,他想知道如何向他的上司解释他刚刚飞行了800公里以关闭屏幕保护程序。



在特定的月相时崩溃



真实的故事。曾经有一个取决于月相的软件错误。在各种MIT程序中都有一个通用的子例程来计算到月球真实相位的近似值。 GLS将此子例程构建到LISP程序中,该程序在写入文件时输出带时间戳的字符串,该字符串的长度将近80个字符。消息的第一行太长而无法继续到下一行,这是非常罕见的。当程序随后读取该文件时,它就会受到诅咒。第一行的长度取决于确切的日期和时间,以及在打印时间戳时的相位规范的长度。也就是说,虫子实际上取决于月亮的相位!行话档案的



第一版(Steele-1983)包含导致描述的bug的此类示例,但合成器“修复”了它。此后被描述为“月相错误”。



但是,请谨慎假设。几年前,欧洲核研究中心(CERN)的工程师在大型电子正电子对撞机进行的实验中遇到了错误。由于计算机在向科学家展示结果之前正在积极处理由该设备生成的大量数据,因此许多人认为该软件对月相具有某种程度的敏感性。几位绝望的工程师弄清了真相。由于月球通过期间地球的变形,由于27 km长的环的几何形状略有变化,因此发生了错误!这个故事以“牛顿对粒子物理学的报复”进入物理学家的视野,并以最简单和最古老的物理定律与最先进的科学概念之间的联系为例。



冲洗马桶使火车停下来



我听说过的最好的硬件错误是在法国的高速火车上。虫子导致火车紧急制动,但前提是车上有乘客。在每种情况下,火车都停止了运行,进行了检查,但是什么也没找到。然后,他被送回生产线,他立即紧急停止。



在其中一项检查中,火车上的一名工程师上厕所。很快,它就冲了过去,BOOM!紧急停止



工程师联系驾驶员并询问:



-刹车前您做了什么?



-好吧,我在下降时放慢了速度...



这很奇怪,因为在正常行驶过程中,火车在下降时放慢了数十次。火车继续行驶,在下一个下降时刻,驾驶员警告:



-我要减速。



什么都没有发生。



-您最近一次刹车是做什么的?-问司机。



-好吧...我在洗手间...-



好吧,然后去洗手间,当我们再次摔倒时,做你所做的事情!



工程师去了洗手间,当驾驶员警告:“我在刹车”时,他冲了水。当然,火车立即停了下来。



现在他们可以重现问题并需要找到原因。



两分钟后,他们注意到发动机制动的远程控制电缆(火车的两端有一个发动机)从电气柜的墙上断开,并躺在控制马桶插头电磁阀的继电器上。当继电器打开时,它干扰了制动电缆和系统碰撞保护仅包括紧急制动。



讨厌FORTRAN的网关



几个月前,我们注意到大陆(在夏威夷)的网络连接变得非常非常慢。它可能会持续10-15分钟,然后突然重新出现。过了一会儿,我的一个同事向我抱怨说在大陆的网络连接无法在所有工作。他有一些FORTRAN代码需要复制到大陆的计算机上,但由于“网络持续时间不足以完成FTP上传,因此无法正常工作”。



是的,事实证明,当一位同事尝试将FORTRAN源文件通过FTP传输到大陆上的计算机时,发生了网络故障。我们尝试存档文件:然后将其安静地复制(但目标计算机上没有解包程序,因此未解决问题)。最后,我们将FORTRAN代码“拆分”为非常小的块,并按顺序发货。复制的大多数片段都没有问题,但是其中一些片段没有用,或者经过多次尝试后才起作用



在检查了问题片段之后,我们发现它们有一些共同点:它们都包含注释块,这些注释块的开头和结尾均由大写的C字母组成(作为同事,更喜欢在FORTRAN上进行注释)。我们向内地的网络专家发送了电子邮件,并寻求帮助。当然,他们想查看无法通过FTP发送的文件示例...但是我们的来信没有到达。最后,我们对未转发文件的外观进行了简单描述。它有效:) [我是否在此处添加了一个有关FORTRAN的有问题评论之一的示例?可能不值得!]



最后,我们设法弄清楚了。最近在我们校园的一部分和大陆网络之间安装了一个新的网关。他在传输包含重复大写Cs的数据包时遇到了巨大的困难!这些数据包中只有少数可以占用网关的所有资源,并可以防止其他大多数数据包突破。我们向网关制造商投诉...他们告诉我们:“哦,是的,您遇到了重复的C错误!我们已经知道他了。” 最后,我们通过从另一家制造商那里购买新的网关来解决了这个问题(为保护前者,我会说无法为某人将程序传输到FORTRAN可能是一个优势!)。



困难时期



几年前,在研究旨在降低3期临床试验成本的Perl ETL系统时,我需要处理大约40,000个日期。其中两个没有通过测试。这并没有给我带来太大的麻烦,因为这些日期是从客户提供的数据中得出的,这常常使我们感到惊讶。但是,当我检查原始数据时,发现这些日期分别是2011年1月1日和2007年1月1日。我以为该错误存在于我刚编写的程序中,但事实证明它已经存在30年了。对于那些不熟悉软件生态系统的人来说,这听起来很神秘。由于另一家公司长期以来决定赚钱,我的客户付了我钱来修复一个公司意外引入的错误和另一个公司故意引入的错误。为了了解这是什么意思,我需要告诉您有关该公司添加了导致该错误的功能的公司以及其他一些导致我修复的神秘错误的奇怪事件的信息。



在过去的好日子里,Apple计算机有时会自发地将日期重置为1904年1月1日。原因很简单:使用电池供电的“系统时钟”来跟踪日期和时间。电池没电了怎么办?自时代开始以来,计算机就开始通过秒数来跟踪日期。纪元是参考原始日期,对于Macintosh,是1904年1月1日。电池耗尽后,当前日期重置为指定的日期。但是为什么会这样呢?



以前,Apple使用32位存储从原始日期起的秒数。一位可以存储两个值之一-1或0.两位可以存储四个值之一:00、01、10、11。三位-八个值之一:000、001、010、011、100、101, 110、111等。并且32可以存储2 32个值之一,即4 294 967 296秒。对于苹果日期,大约有136年的历史,因此较旧的Macs无法处理2040年之后的日期。并且,如果系统电池用完了,则日期从时代开始便重置为0秒,并且每次打开计算机时(或直到购买新电池时)都必须手动设置日期。



但是,Apple决定将日期存储为从纪元开始的秒数,这意味着我们无法在纪元开始之前处理日期,这将产生深远的影响。苹果推出了一项功能,而不是错误。除其他外,这意味着Macintosh操作系统不受“千年错误”的影响(对于具有自己的日期系统来规避限制的许多Mac应用程序来说,这是不可以的)。



继续。我们使用Lotus 1-2-3,这是IBM开发的“杀手级应用程序”,它引发了PC革命,尽管Apple计算机具有VisiCalc,这使个人计算机获得了成功。公平地讲,如果1-2-3还未出现,那么PC几乎不可能起飞,而个人计算机的历史发展可能会大不相同。莲花1-2-3将1900年错误地视为a年。当Microsoft发布其第一个Multiplan电子表格时,它的市场份额很小。当我们启动Excel项目时,我们决定不仅要复制Lotus 1-2-3中行和列的命名方案,而且还要确保错误的兼容性,故意将1900年视为a年。这个问题一直持续到今天。也就是说,在1-2-3中,这是一个错误,而在Excel中,这是一个故意的决定,可以保证所有1-2-3用户都可以将其电子表格导入Excel中,而无需更改数据,即使他们输入错误也是如此。



但是还有另一个问题。 Microsoft首次发布了适用于Macintosh的Excel,直到1904年1月1日才识别日期。在Excel中,一个时代的开始是1900年1月1日。因此,开发人员进行了更改,以使他们的程序可以识别时代的类型,并根据所需的时代将数据存储在内部。微软甚至写了一篇解释性文章。这个决定导致了我的错误。



我的ETL系统从客户那里收到了Excel电子表格,这些表格是在Windows上创建的,但也可以在Mac上创建。因此,表中某个时代的开始可能是1900年1月1日,或者是1904年1月1日。如何找出? Excel文件格式显示了必要的信息,但是我使用的解析器未显示(现在显示了),并假定您知道特定表的时代。也许,我可以花更多的时间来理解Excel二进制文件并将修补程序提交给解析器的作者,但是我为客户端做了大量的工作,因此我迅速编写了一种启发式方法来确定时代。很简单。



在Excel中,日期可以用格式“ 07-05-98”(美国的无用系统),“ 98年7月5日”,“ 1998年7月5日”,“ 98年7月5日”或其他形式表示另一种无用的格式(具有讽刺意味的是,我的Excel版本不提供的一种格式是ISO 8601标准)。但是,在表格中,未格式化的日期存储为epoch-1900的“ 35981”或epoch-1904的“ 34519”(数字表示自该纪元开始以来的天数)。我只是使用一个简单的解析器从格式化日期中提取年份,然后使用Excel解析器从未格式化日期中提取年份。如果两个值相差4年,那么我就知道我使用的是带有epoch-1904的系统。



为什么我不只使用格式化日期?因为可以将1998年7月5日的格式设置为“ 98年7月”,而缺少当月的日期。我们收到了来自许多公司的表格,这些表格以不同的方式创建它们,因此我们(在本例中为我)不得不处理日期。另外,如果Excel正确,那么我们也应该这样做!



然后我遇到了39082。让我提醒您,Lotus 1-2-3认为1900年是a年,这在Excel中得到了忠实的重复。而且由于这增加了1900年的一天,因此许多日期函数在当天可能是错误的。也就是说,39082可能是2011年1月1日(在Mac上)或2006年12月31日(在Windows上)。如果我的“年份解析器”是从格式化值中提取2011,那么一切都很好。但是由于Excel解析器不知道正在使用哪个纪元,因此它默认为epoch-1900,返回2006。我的应用程序发现有5年的差异,认为是错误,记录并返回了未格式化的值。



为了解决这个问题,我编写了这个(伪代码):



diff = formatted_year - parsed_year
if 0 == diff
    assume 1900 date system
if 4 == diff
    assume 1904 date system
if 5 == diff and month is December and day is 31
    assume 1904 date system


然后正确解析了所有40,000个日期。



在大型印刷工作中



在1980年代初期,我父亲在存储技术公司工作,该公司已经停业,建立了磁带驱动器和气动系统,用于高速磁带输送。



他们重新设计了驱动器,以便可以将一个中央驱动器“ A”连接到七个驱动器“ B”,并且RAM中控制驱动器“ A”的小型OS可以将读写操作委托给所有驱动器“ B”。



每次启动驱动器“ A”时,都必须将一张软盘插入连接到“ A”的外围驱动器中,以将操作系统加载到其内存中。这是非常原始的:处理能力由一个8位微控制器提供。



这种设备的目标受众是拥有非常大的数据存储的公司-银行,零售连锁店等-需要打印许多地址标签或银行对帐单。



一位客户有问题。在打印作业的中间,一个特定的驱动器“ A”可能会停止工作,从而使整个作业启动。为了使驱动器恢复正常工作,工作人员必须重新启动所有设备。而且,如果这是在六个小时的任务中间发生的,那么就会浪费大量昂贵的计算机时间,并且会破坏整个操作的时间表。



存储技术派出技术人员。尽管尽了最大的努力,他们仍无法在测试条件下重现该错误:该错误似乎发生在大型打印作业的中间。问题不在于硬件,而是他们尽其所能:RAM,微控制器,软盘驱动器,磁带驱动器的每个可能的部件-问题仍然存在。



然后,技术人员致电总部,并致电专家。



考官抓住一把椅子和一杯咖啡,坐在计算机室里-那时那儿有专用的计算机室-看着工作人员排队做大量的打印工作。专家等待失败的发生-发生了。每个人都看着专家-他不知道为什么会这样。因此,他下令将任务重新排队,然后所有带技术人员的人员恢复工作。



专家再次坐在椅子上,等待失败。花了大约六个小时,失败才发生。专家再次没有任何想法,只是一切都发生在一个挤满了人的房间里。他下令重启任务,再次坐下等待。



在第三次故障时,专家注意到了一些东西。当人员更换外部驱动器中的皮带时发生故障。此外,当一名员工穿过地板上的某个瓷砖时发生了撞车事故。



高架地板由放置6-8英寸高的铝砖制成。高架地板下面布满了许多计算机电线,因此不会有人意外踩到重要的电缆。瓷砖放置得非常紧,以防止杂物进入高架地板。



专家意识到其中一张瓷砖变形了。当员工踩到角落时,瓷砖的边缘在相邻的瓷砖上摩擦。他们还摩擦了连接瓷砖的塑料部件,从而形成了静电微放电,从而产生了射频干扰。



如今,可以更好地保护RAM免受射频干扰。但是在那些年里并非如此。专家意识到,这些干扰会干扰内存以及操作系统的运行。他给护送服务打了电话,订购了新的瓷砖,亲自安装,问题消失了。



是潮流!



故事发生在朴次茅斯(我认为)办公室的四楼或五楼的服务器室,在码头区。



有一天,带有主数据库的Unix服务器崩溃了。他被重新引导,但他仍然快乐地一遍又一遍地摔倒。我们决定致电支持服务人员。



支持伙计...我认为他的名字叫Mark,但没关系...我不认为我认识他。没关系,真的。我们留在马克吧,好吗?优秀的。



因此,几个小时后,马克到达了(您知道从利兹到朴茨茅斯的路还很近),打开了服务器,一切正常。典型的他妈的支持,客户对此非常不满。马克浏览日志文件,发现没有异议。然后马克回到火车上(或者就他所采用的运输方式而言,据我所知可能是一头la脚的牛……好吧,没关系,好​​吗?)然后回到利兹,浪费了一天。



当天晚上服务器再次崩溃。故事是一样的...服务器没有上升。 Mark尝试提供远程帮助,但是客户端无法启动服务器。



另一列火车,公共汽车,柠檬蛋白甜饼或其他东西,马克回到朴次茅斯。您看,服务器启动时没有任何问题!奇迹。马克检查了几个小时,确认操作系统或软件一切正常,然后转到利兹。



在一天的中午左右,服务器崩溃了(请放心!)。这次,引入硬件支持人员来更换服务器似乎是明智的。但不会,大约10小时后,它也会掉落。



这种情况持续了好几天。服务器已启动,大约10个小时后崩溃,并且在接下来的2个小时内未启动。他们检查了散热情况,内存泄漏,检查了所有内容,但一无所获。然后崩溃停止了。



一周愉快地过去了……每个人都很高兴。开心,直到一切再次开始。图片是一样的。 10小时的工作时间,2-3小时的停机时间...



然后有人(我想我被告知这个人与IT无关)说:



“这是潮流!”



惊叫声中茫然的凝视着,也许有人的手在按钮上摇了晃以呼唤警卫。



“他不再顺应潮流。”



对于IT支持人员来说,这似乎是一个完全陌生的概念,他们在坐下来喝咖啡时几乎不阅读《潮汐年鉴》。他们解释说,这与潮流无关,因为服务器已经运行了一个星期没有任何故障。



“浪潮上周低,而本周高。”



没有许可证的人可以使用一些术语来操作游艇。潮汐取决于月球周期。随着地球自转,每12.5个小时,太阳和月亮的引力就会产生一个潮汐波。在12.5小时的周期开始时,有一个高潮,在周期的中间有一个退潮,而在高潮结束时又有一个涨潮。但是,随着月球轨道的变化,落潮与落差之间的差异也随之改变。当月球位于太阳和地球之间或地球的另一侧(满月或没有月球)时,我们会得到Syzygy潮汐-最高潮汐和最低潮汐潮。在半个月亮时,我们得到正交潮汐-最低潮汐。两种极端之间的差异大大减小了。农历周期持续28天:syzygy-正交-syzygy-正交。



当潮汐力量被告知技术人员时,他们立即想到要报警。这是很合逻辑的。但是事实证明那家伙是对的。两周前,一艘驱逐舰在办公室附近停靠。每次潮水将其抬高到一定高度时,船的雷达柱将位于服务器机房地板的高度。雷达(或电子战设备,或其他军事玩具)在计算机中造成混乱。



火箭飞行任务



我被指示移植一个大型(约40万行)导弹发射控制和监视系统,以使用新版本的操作系统,编译器和语言。更准确地说,从Solaris 7上的Solaris 2.5.1,以及从Ada 83编写的Verdix Ada开发系统(VADS)到Ada 95编写的Rational Apex Ada系统。尝试实现特定于VADS的软件包的兼容版本,以简化向Apex编译器的过渡。



三个人帮了我,目的只是为了获得干净编译的代码。花了两个星期。然后,我独自工作以使系统正常工作。简而言之,这是我所遇到的最糟糕的软件系统体系结构和实现,因此又花了两个月的时间来完成移植。然后将系统移交给测试,这花费了几个月的时间。我立即修复了在测试过程中发现的错误,但是它们的数量迅速减少(源代码是生产系统,因此它的功能相当可靠,我只需要删除在适应新编译器时出现的错误)。最后,当一切正常进行时,我被转到了另一个项目。



在感恩节之前的星期五,电话响了。



大约三周后,将对火箭的发射进行测试,并且在倒计时的实验室测试中,命令序列被阻止。在现实生活中,这将导致测试中断,并且如果在启动发动机后几秒钟内发生堵塞,则辅助系统中将发生一些不可逆的动作,这将花费很长时间(且昂贵)来重新准备火箭。它不会开始,但是很多人会因为失去时间和非常巨大的金钱而感到沮丧。不要让任何人告诉你国防部正在花钱-我还没有见过合同经理,他没有第一或第二个预算,也没有时间表。



在过去的几个月中,此倒数测试已经进行了数百次不同的测试,只有很少的故障。因此,发生这种情况的可能性很小,但是其后果却非常重大。将这两个因素相乘,您将了解到该消息预示着我和数十名工程师和经理的假期假期将被破坏。



作为移植系统的人,引起了我的注意。



与大多数对安全性至关重要的系统一样,此处记录了许多参数,因此很容易识别在系统崩溃之前执行的几行代码。当然,它们之间绝对没有什么不同,相同的表达式在同一运行中成功执行了数千次。



我们将Apex的人员称为Rational,因为他们已经开发了编译器,并且在可疑代码中调用了他们开发的某些例程。他们(以及其他所有人)对找出真正具有民族意义的问题的原因印象深刻。



由于日志中没有有趣的内容,因此我们决定尝试在本地实验室中重现该问题。这不是一件容易的事,因为该事件大约每运行1000次就会发生一次。可能的原因之一是对供应商开发的互斥函数的调用(VADS迁移批处理的一部分)Unlock没有导致解锁。调用线程处理了心跳消息,该消息通常每秒发送一次。我们将频率提高到10 Hz,即每秒10次,然后开始运行。大约一个小时后,系统被锁定。在日志中,我们看到记录的消息顺序与测试失败期间的顺序相同。我们进行了多次运行,开始后45-90分钟,系统稳定地阻塞,并且每次日志包含相同的轨道。尽管事实上我们现在在技术上运行不同的代码-消息速率不同-重复了系统行为,所以我们确保此加载方案导致了相同的问题。



现在有必要确切地找出表达式序列中发生阻塞的位置。



此实现使用了Ada任务系统,使用率极低。任务是Ada中的高级并发可执行结构,有点像执行线程,只是内置在语言本身中。当两个任务需要交互时,它们“会合”,交换必要的数据,然后停止会合并返回其独立的表演。但是,该系统的实施方式有所不同。在目标具有一个集合点之后,该目标将与另一个集合点,然后与第三个集合点,依此类推,直到完成某些处理为止。此后,所有这些集合点都结束了,每个任务都必须返回到执行状态。也就是说,我们正在处理世界上最昂贵的函数调用系统,这样在处理某些输入数据时就停止了整个“多任务”处理。过去,这并不会因为吞吐量非常低而导致问题。



我之所以描述这种任务机制,是因为当一个集合点被请求或期望完成时,可能会发生“任务切换”。即,处理器可以开始处理准备好要执行的另一任务。事实证明,当一个任务准备好与另一个任务会合时,可以开始执行完全不同的任务,并最终控制权返回到第一个交会处。其他事件也可能导致任务切换。这样的事件之一就是系统功能调用,例如打印或执行互斥。



为了了解引起问题的代码行,我需要找到一种方法来记录表达式序列的进度而无需触发任务切换,而这可以防止崩溃的发生。所以我不能利用Put_Line()避免执行I / O操作。可以设置一个计数器变量或类似的变量,但是如果我不能在屏幕上显示它,如何查看它的值?



同样,在检查日志时,事实证明,尽管冻结了心跳消息的处理,这阻塞了该进程的所有I / O操作,并且不允许执行其他处理,但仍继续执行其他独立任务。也就是说,工作并没有完全被阻塞,而只是(关键)任务链。



这是评估阻塞表达式所需的钩子。



我制作了一个Ada软件包,其中包含一个任务,一个枚举类型和该类型的全局变量。列举的文字被捆绑到特定的表达式有问题的序列(例如Incrementing_Buffer_IndexLocking_MutexMutex_Unlocked),然后将赋值表达式插入其中,该赋值表达式将相应的枚举分配给全局变量。由于所有这些目标代码仅在内存中保持恒定,因此执行任务切换的可能性极小。首先,我们怀疑表达式可能会切换任务,因为阻塞是在执行期间发生的,而在切换回任务时不会返回(出于多种原因)。



跟踪任务只是循环运行,并定期检查以查看全局变量的值是否已更改。每次更改后,该值都保存到文件中。然后稍等片刻,然后重新检查。我将变量写到文件中是因为仅当系统在问题区域中切换任务时系统选择了要执行的任务时才执行任务。此任务中发生的任何事情都不会影响其他不相关的锁定任务。



可以预期,当系统执行有问题的代码时,将在每个下一个表达式中重置全局变量。然后会发生某些事情,导致任务切换,并且由于其执行频率(10 Hz)低于监视任务的频率,因此监视器可以固定并写入全局变量的值。在正常情况下,我可以得到一个枚举子集的重复序列:任务切换时变量的最后一个值。挂起时,全局变量不应再更改,并且最后写入的值将显示未完成执行的表达式。



启动了跟踪代码。它是冷冻的。监视工作就像发条一样。



该日志以预期的序列结束,该序列被一个值中断,该值指示互斥体已被调用Unlock且任务处于待处理状态(以前有数千次调用)。



Apex工程师此时疯狂地分析了他们的代码,并在互斥锁中找到了理论上可能发生锁定的位置。但是它的可能性很小,因为在特定时间发生的特定事件序列可能导致阻塞。墨菲定律的孩子们,这是墨菲定律。



为了保护这段代码,我用一个小的本机Ada互斥包替换了对互斥函数的调用(基于OS互斥功能构建),以控制对该部分的互斥访问。



将其粘贴到代码中并运行测试。七个小时后,代码继续工作。



我的代码已移交给Rational,在这里对其进行编译,反汇编并验证,它没有使用与问题互斥函数中使用的方法相同的方法。



这是我职业生涯中最拥挤的代码审查:)和我一起在会议室里大约有十位工程师和经理,还有十几个人在电话会议上联系-他们都检查了大约20行代码。



审查了代码,构建了新的可执行文件并将其提交以进行正式的回归测试。几周后,倒计时测试成功,火箭起飞。



好的,这一切都是美好的,但是这个故事的意义是什么?



这是一个完全令人作呕的问题。数十万行代码,并行执行,十几个交互过程,不良的体系结构和不良的实现,嵌入式系统的接口以及数百万美元的花费。没有压力,对。



尽管不是我在进行移植时的焦点,但我不是唯一致力于此问题的人。但是,即使我做到了,这也不意味着我理解所有成千上万行代码,或者至少浏览了其中的几行。全国的工程师对代码和日志进行了分析,但是当他们告诉我他们关于故障原因的假设时,我花了半分钟的时间来驳斥它们。当我被要求分析理论时,我将其传递给其他人,因为对我来说,这些工程师显然走错了路。听起来放肆吗?是的,是的,但是出于不同的原因,我拒绝了假设和要求。



我了解问题的本质。我不知道确切的位置或原因,但我确切知道发生了什么。



多年来,我积累了很多知识和经验。我是使用Ada的先驱者之一,我了解它的优缺点。我知道Ada运行时库如何处理任务并处理并行执行。而且我擅长于内存,寄存器和汇编器级别的低级编程。换句话说,我在我的领域有很深的知识。我用它们来查找问题的原因。我不仅解决了该错误,而且想出了如何在一个非常敏感的执行环境中找到它。



对于那些不熟悉这种特性和条件的人来说,用代码进行斗争的故事并不是很有趣。但是这些故事有助于理解解决真正困难的问题需要采取的措施。



您不仅需要程序员,而且要解决真正的难题。您需要了解代码的“命运”,代码如何与环境交互以及环境本身如何工作。



然后,您就拥有了假期周。






未完待续。



All Articles