干嵌入式的,谁没跟"死机"较过劲。但今天这个案子特别的地方在于:设备死机了,本该兜底的看门狗却一点反应没有,安安稳稳地"睡着"了。这个教训,让我到现在想起来还后背发凉。
出事的是一批野外无人值守站点的RTU设备,分布在各个犄角旮旯。隔三差五就有站点报"设备死了"——网络ping不通,串口也不回话,只能派人跑到现场断电重启。站点又多又散,维护成本高得吓人,客户那边已经下了最后通牒:必须彻底解决。
死机时的现象很诡异。板卡电源灯明明亮着,说明供电正常;看门狗芯片也没触发复位,说明它觉得系统"一切正常"。可实际上程序已经死了。这就是最可怕的地方:你以为有最后一道防线,结果这道防线在关键时刻一声不吭。
排查先得复现。我们让设备长时间运行,再叠加特定的业务负载,终于逮到了一次死机。读死机现场,发现程序卡在了一个死循环里——CPU没完全挂,还在那儿空转,但整个主循环已经转不动了。接下来追看门狗,问题开始浮出水面。
第一层问题:看门狗的喂狗动作放在主循环里,而主循环因为某个子任务死等一个外设响应被阻塞了,一直轮不到喂狗。按理说,这种情况下看门狗应该超时复位才对。可它没有。继续深挖,发现了第二层、也是最致命的问题:看门狗定时器被配成了窗口喂狗模式,而喂狗代码又被一个优先级更高的定时器中断持续触发。中断里也喂了狗,而且喂得"很准时"。主循环虽然死了,中断却还活得好好的,照常喂狗。于是看门狗永远被"按时喂饱",它以为系统健康得很,根本不知道自己守护的程序已经成了一具空转的尸体。
根因一句话就能说清:喂狗放在了错误的位置。看门狗的正确用法,是只有在系统确认健康之后才能喂狗,喂狗动作要放在主循环的关键健康检查点,而且必须保证只有系统正常流转的时候才喂狗。我们这个案子,把喂狗塞进了高优先级中断里。中断独立于主循环运行,主循环死了,中断照样跑,喂狗信号完全不能代表系统健康。说白了,我们是在用一根假的健康心跳欺骗看门狗。
这里有几个知识点必须记牢。看门狗的原理很简单:超时没喂狗就复位。但喂狗位置有黄金法则——只能在确认系统健康后喂狗,绝不能在中断里无脑喂狗。窗口看门狗和独立看门狗也有区别,前者对喂狗时机有更严格的窗口要求,用错了更容易出事。多任务系统里,健康监测要做成"每个任务喂自己的任务狗",任一任务异常就触发复位,不能整个系统共用一条命。软件冗余也不是只有看门狗,还得配合程序流监控和关键数据CRC校验,多道防线才有意义。
改进措施我们是一套组合拳。第一,把喂狗动作彻底移出中断,改在主循环的健康检查点喂狗,主循环死了就没人喂狗,看门狗必然复位。第二,多任务系统给每个关键任务设置独立的看门狗计数器,任务正常运行才清零,任一任务异常立刻触发复位。第三,增加程序流监控,看门狗复位前把死机现场记录下来,方便事后排查。第四,关键外设通信全部加超时机制,绝不允许主循环被一个死等的子任务卡死。第五,看门狗复位之后做安全恢复——记录日志、恢复现场、主动上报异常,而不是偷偷重启了事。
改进后的验证非常直接。我们做故障注入,人为让某个任务陷入死循环,看门狗在设定超时内准确复位,设备自动恢复,全程不需要人工干预。现场部署半年,死机率从每月好几次降到了零。远程监控平台还能收到复位告警日志,哪台设备什么时候被看门狗救了一命,后台看得清清楚楚。
复盘这个案子,教训太深刻了。看门狗是软件可靠性的最后一道防线,可喂错位置,它就跟没有一样。喂狗信号必须能代表系统真的健康,否则就是在骗自己。中断里喂狗,是最常见也最危险的反面教材,不知道多少产品死在这一条上。多任务系统一定要让每个任务都喂自己的任务狗。说到底,死机不可怕,可怕的是你以为有保护,其实保护早就睡着了。
最后说句玩笑话也是真心话:看门狗不是用来喂的,是用来证明你还活着的。喂错了地方,等于给自己开了一张假病假条,等真出了事,没人来救你。 |