【终南山.内核问道】Linux故障分析方法(中)
下面从技术的角度来看看如何查Linux故障。
大家面对Linux内核觉得很恼火的事情是什么呢?就是遇到死机。死机分为真死和假死,假死的时候键盘还会动,有时候还可以翻页,没有真的死机。如果没有串口或者键盘,可以Ping一下单板,如果能Ping通过,说明系统没有完全死掉。当然,在开启了中断线程化的时候,即使是假死,也可能Ping不通。此时还有一个小手段,就是看单板或者PC有没有reset键,轻轻按一下,如果有反映,说明也是假死。
这种情况下的问题是不大的,因为中断还可以正常的响应,要查假死的故障最重要的一点是需要善于利用我们的中断,要对整个中断处理流程熟悉,你可以很快的在我们函数里面添加调试信息,打印出当前的进程,具体的方法大家下来可以去摸索。总结一点是在假死故障的时候,要把中断这一点利用好。
内核也给我们提供了很好的调试工具sysrq,它能够看到什么东西,哪些东西来查什么样的故障,这是非常有用的。大家要把sysrq的原理搞明白,它是怎么样做的,怎么样把信息取出来,能不能把里面的故障找出来,要搞清楚sysrq,一个月时间应该够了。
死机故障里面的真死故障,无论输入什么都没有响应了,中断已经死掉了不响应了怎么办?其实大家也不用心慌,真死故障其实很好查,内核有几个很好的工具,如NMI。在ARM、MIPS架构中,Linux不支持NMI,但是我也带着团队的兄弟自己做了一个NMI,真的查出来了一些比较难缠的故障,其实ARM、MIPS硬件是支持NMI的。还有一个hard-lockup,我们可以把这个打开。
当然如果有条件可以用仿真器。有一些故障是在真实产品里面,就没有仿真器的接口,这个时候工作是不是就不能查了呢?没有仿真器,这时要查线上的故障,综合判断连蒙带猜。还有直接走查代码,先查自己的改动,因为内核的故障70%都是我们自己改出来的,通过走查出来的重要故障,基本上查出来都是我们自己的同事写的驱动或者改的内核,改出来的问题,把他们改的代码拿出来一蒙一个准,真的是这样。
我们在查看死机故障的时候,有时候是OOPS故障信息,非法指令产生的,在真实的产品里面就没有输出OOPS信息,而在实际产品里面可以实现一个黑匣子功能,当它异常的时候,将OOPS信息转储到内存或者通过网络转储到其他地方去。当热启动的时候转储信息不会丢失,把OOPS现场拿出来,这个怎么看就太具体了,我推荐大家看两本书,对查内核OOPS故障有用的,就是《Debug Hacker》、《Linux内核精髓》,这两本书讲了不少调试手段,其中不同的调试手段都有各自的应用范围。这两本书大家可以花一年时间去看。重点是需要动手实践。
可能我们在发布产品的时候已经有个决策,这个产品到底是用现有的调试工具,还是我们自己写的黑匣子?根据实际情况来决定即可。
性能问题,最主要的是搞清楚性能没有达到我们的预期,是CPU密集型还是IO密集型问题?首先你看整体的CPU利用率,如果初步判断是CPU密集型的,这种情况下我平常喜欢用perf找热点函数,把热点找到,甚至把热点的调用链找到,对症下药找到代码,调整他的算法,基本上这个问题就可以搞定了。有时候还要注意Cache对性能的影响,我发现Cache对性能影响非常大,做好以后性能至少可以提升数倍。
中断的影响,有时候驱动写的不是太好,里面有过多的延迟,通过top工具一找,看起来是CPU利用率高了,实际上是我们驱动写的不好,它把应用层的CPU占用了,表面现象和真实原因不一样了,这时候要怀疑中断的情况。这种情况下,有时候通过top能发现内核态占用率升高,但是有时间发现不了。在观察应用程序占用率的时候,要注意这一点。
IO密集型,CPU利用率不高,整体性能上不来,程序大量的在进行IO操作,CPU在这里干着急的情况下,我们应该提高并发度。大家看一下《并行编程》这本书,怎么样提高并发度,是不是把线程增加一些就行,不是这样的,这里面有很多学问,比如资源划分,怎么样让它形成一个软件的流水线,怎么样数据流动的更合理,怎么样更好的利用CPU的缓存,这里面的学问非常大,你可以花一年的时间来专门研究,但也不一定能看懂。提高并发度是非常深的一门学问。
梳理流程,你到时候可以随便拉一个兄弟来和你一起看代码,这是工作协调的问题,这个本不是很难,但要求在公司里面有协调的能力,这是很重要的。我平时在工作中,很少用这个。
性能问题最重要的是要重视内核对性能的影响,即我这里说的内核奇兵,很多人总结性能问题的时候只盯着应用程序来看,忽略了内核。因为我们做通信的,每秒钟一个中心节点,希望每秒种转发数据达到几百个G,这个时候对性能的要求是非常苛刻的。上层的代码已经用汇编在优化了,我们要统计一个计数,减少一个统计值就能够影响10%的性能。后来我们用到一个调测工具,在内核里面找热点发现,一个do_page_fault函数竟然达到了5%的CPU,我们觉得严重不合理。因为MIPS架构里面有一个非常重要的缺陷,它是用软件来管理TLB的,这个就导致一个问题,硬件的缺陷要通过软件去弥补,但是软件耗费了我们太多的CPU,后来我们就做了一个功能,修改了编译器程序的链接方法,改了内核,实现巨页功能(当时的内核并不支持巨页,并且目前新版本内核的巨页对这个项目的性能提升并不明显)。这个就是说内核在提升性能方面可以做很多的事情,当然还有很多其他例子我就不一一说了。我们不要仅仅盯着应用程序来优化我们的性能,要从系统整体的角度来优化它。
海森堡bug分析。这是所有搞内核人最头疼的问题,什么是海森堡bug?这个故障很难复现,可能一个月两个月才复现一次,你想加一点调试手段也不行,就别想用GDB了,你甚至把一个地方随便调一个空函数在那里,调了以后故障不出现了,这种问题一般是通过静态分析来解决问题,故障和程序运行的时序紧密相关,稍微在那里面加一点点东西故障不复现了。其实这样也可以查,首先看在哪一行代码里面加了什么东西后,故障就消失了,你把前后的代码分析一下,看有没有乱序的问题或者内存缓存的问题,走查代码你找不到原因的时候,增加延迟,在你那个地方稍微加一些延迟函数,有可能故障更快的复现。如果增加延迟以后故障能够更快复现,基本上就是和你相应的代码有关了,这时候查走查代码就可以查准。
增加负载强度。以前网络每秒钟发出10兆数据的时候,很长时间才出现,而加大负载强度以后故障可能就好复现了。
隔离可疑的子系统,这个系统可能里面有网络,有磁盘以及其他很多的模块,我们一个个的排除掉,如果把模块都已经去掉以后故障还在,肯定就是和他没有什么关系,或者去掉以后故障不复现了,一定是和模块有关系的,这样做的根本目的是缩小故障范围。只要你缩小了故障范围就可以通过走查代码的方式来查故障。
模拟不常见的事件。举个例子,正常情况下你一般是会调用内存分配函数的,模拟不常见的事件,就是把我那个模块里面内存的函数封装起来,模拟10%的几率或者5%的几率失败,如果走到失败流程的时候这个故障马上就复现了,这时候就要怀疑边界条件没有处理好,没有判断函数的返回值,异常返回时按照正常的流程在处理。
对有惊无险的事件计数。就像我们研发一个机器人,对机器人倒地就表示是失败的事件,有惊无险的状态就是机器人摇摇晃晃的走。一般来说可能一个月死一次机,这表示失败的事件,而摇摇晃晃的事情可能一天一次,这样进行对摇摇晃晃的事情进行跟踪,就会得到一些线索。我建议大家看一下《深入理解并行编程》第232页,讲的很清楚。

浙公网安备 33010602011771号