编程高手,不仅擅长写代码,调试代码也很容易发现bug。编写代码只是程序员的工作之一。调试代码甚至会比写代码花费更多的时间。系统、架构、编程等方面我已经给大家讲解了很多。本文将向您展示内核的方方面面。故障排除过程。发现问题后,某天公司服务器报了警。登录机器后发现进程已经“卡死”了。常规的GDB调试没有反应,找不到Log的线索。问题似乎无法解决。就在这时,岛国出现在了博主的脑海中。.是的,你猜错了,是岛国的一修哥和柯南哥,中国的包青天,国外的狄仁杰,夏洛克等等,他们瞬间如神一般,必有办法!是的!先分析一下问题仔细分析,由于进程好像卡住了,如果是卡在用户态,那么进程的CPU占用肯定很高(死循环之类的);如果卡在内核态,此时进程应该在做IO或者网络通信等,那么CPU使用率应该很低,现在可以查到进程ID。有了进程ID,运行top命令可以看到:注意CPU一栏,显示CPU使用率为0%。该进程几乎不占用CPU,这基本上告诉我们该进程卡在了内核态。如果进程进入内核态,是因为调用了阻塞系统调用,被操作系统挂起,那么怎么知道进程调用了什么系统调用呢?trace进程系统调用strace命令就是用来告诉你这个的。运行strace命令查看此时进程调用的是什么系统调用:哎呀!strace命令也卡住了,无奈,再想想还有什么办法。可以使用pstack命令打印出进程运行时的堆栈信息。这个命令虽然不能跟踪内核,但是可以看到用户态最后调用了什么函数,从而推断出调用了什么系统调用,运行一下:和strace一样,pstack也卡住了。我们现在还能在哪里寻找线索?古老的ps命令永远不会过时。我们可以使用ps命令查看进程的运行状态和WCHAN(waitingchannel)。WCHAN是什么意思?在Linux的世界里,有问题可以问人(man),这就是万能的man命令。下面用man命令看看ps显示的内容的含义:$manps运行man命令,搜索“WCHAN”,啊哈!终于在“STANDARDFORMATSPECIFIERS”部分找到了WCHAN的意思,是这样写的:这里写的很清楚,WCHAN指的是当前进程阻塞在哪个内核函数上。OK,我们运行ps命令:这里值得注意的是,因为ps只是在运行ps命令的瞬间打印对应进程的状态,也就是说运行一次ps就相当于一次采样,所以应该多次运行ps,确保运行的结果没有改变,否则只运行一次并且时间足够聪明,可能会得到错误的线索。从ps打印的结果可以看出两个进程阻塞状态。进程的运行状态为D,运行状态D是什么意思?我们再次询问man,发现了这样的信息:原来进程运行状态D表示不间断睡眠,不可用。interruptedsleep表示进程正在休眠,即使你拍打它也唤醒不了,即进程目前不响应任何外部信号,此时即使是kill命令也无法杀死进程(除非内核允许进程接收到kill信号),直观感受就是进程“卡死”了。不可打扰的睡眠与可打扰的睡眠是相反的。从上图可以看出,状态为S,此时进程处于阻塞状态,等待一个事件(如网络数据到达等),处于该状态的进程可以接收到Signal,直观感受是该过程仍然有响应。通过ps命令,我们可以看到进程状态为D,进一步验证进程确实“卡死”了。那么进程卡在哪里呢?幸运的是,WCHAN专栏可以告诉您答案。进程阻塞在哪个内核函数上?上面ps命令WCHAN一栏显示rpc_wa,嗯。.rpc_wa是什么?好像被截断了,不过没关系,我们可以从源码中找到wchan的完整输出。其实ps等命令也是在这个源上搜索信息并显示的。这个来源就是proc文件系统,proc文件系统记录了内核和各个进程的运行时信息。我们可以使用最简单的cat命令,使用proc后跟进程ID和wchan:啊哈,这时候终于找到进程卡在什么地方了!看起来进程在等待一个RPC调用,RPC实际上是一个进程通过网络与另一个进程通信。虽然我们知道进程卡在哪里了,但是我们仍然不知道为什么会卡在这里。此时线程似乎断了。..让我们再考虑一下。既然进程卡住了,那么此时进程一定不是用户态。如果不是用户态,那一定是内核态。进程如何进入内核态?显而易见的答案是系统调用被调用。那么我们怎么知道一个进程当前正在调用哪个系统调用呢?你是幸运狗,向/proc/***/syscall问好,我们也可以使用简单的cat命令在proc文件系统中搜索,只需使用/proc后跟进程ID+syscall。卧槽..这是什么鬼串!原来这串看似不可理解的东西就是系统调用。第一个数字代表系统调用ID,其余都是参数,我们不用关心。从上面的输出我们可以看到调用了262号系统调用。只有一个数字是没有意义的。这个数字代表哪个系统调用?根据内核源码查看系统调用。要知道这个数字的含义,我们需要参考内核代码。一般Linux系统中必要的内核头文件都位于/usr/include目录下。在博主的64位Linux机器上,找到了这个文件:Gotyou!!!我们可以看到调用了newfstatat系统调用,这个系统调用的作用是什么?让我们再次询问man(man命令):$mannewfstatat得到了这个信息:啊哈!原来是fstatat,它正在读取文件的元信息。现在我们知道调用什么系统调用了,但是又出现了一个新的问题,就是为什么我们调用了这个系统调用后,最后还是卡在等待rpc呢?显然我们需要调用栈信息来验证。跟踪内核运行时栈OOOOKey,是时候求一个重量级的工具了,这个就是/proc/PID/stack,只要看这个文件,我们就可以知道内核中对应进程的调用栈了!!!就问你linux这个设计很厉害吧!!!这个内核调用栈终于揭开了所有的秘密。事实很清楚。首先,让我们看一下调用堆栈的顶部。堆栈的顶部正是ps命令WCHAN打印的列。内核中的进程因为调用了这个函数而卡住了。接下来,我们查看调用栈的底部,我们找到了系统调用,这证实了是调用这个系统调用的进程导致了卡死。那么调用这个系统调用是怎么回事呢?让我们抬头注意这些行:终于!!!从调用栈中,我们可以看到一系列与NFS相关的函数。NFS的全称是NetworkFileSystem,即网络文件系统,我们通常挂载(mount)一个远程文件系统是通过NFS来实现的。导致rpc等待的是网络通信的NFS。从内核调用栈我们知道,进程在查询远程主机上某个文件的元数据时,有时会因为网络问题卡住。有了这个线索,我们终于锁定了有问题的代码。总结本文为您展示了一个完整的bug定位过程。可以看到Linux为我们提供了非常丰富的调试工具。当然,这离不开Linux系统本身优秀的设计思想,即进程和内核的运行时信息。通过文件系统提供,极大的方便了问题的排查和定位。希望本文能帮助大家了解Linux系统下调试的问题。本文转载自微信公众号《码农的荒岛求生》,可通过以下二维码关注。转载本文请联系码农荒岛求生公众号。
