为了账号安全,请及时绑定邮箱和手机立即绑定

疑难杂症: 遇到一个杀不掉,追不到,找不着的进程怎么破?

标签:
Linux 运维

今天的这个案例特别的经典,我们讨论的是当一个进程彻底死锁还不可打断(Uninterruptable Sleeping),常用调试工具全都用不上的时候我们该怎么办。

Rsync进程僵死

之前我们使用Rsync工具在不同机房的服务器上,同步一个存放临时文件的目录。先简要介绍一下Rsync这个工具,这里先简要介绍一下Rsync的情况。

rsync是一款跨平台的(Windows、Linux、AIX、HP-UX均可使用)数据镜像备份、传输工具,使用特有的增量备份方法,也就是将文件分块计算哈希值,只传差异的部分,而且还带压缩功能,是一款比较常用的数据备份、传输工具。

一般来说在同一数据中心内的应用集群如果要共享文件的话会使用网络存储(NFS),但是跨中心的文件共享使用网络存储NFS的方式一般成本较高,如果没有极特殊的强同步需求,一般来说会使用Rsync来进行文件的共享与传输。
图片描述
但是有一套系统在使用rsync时没有按照规范加上-t参数,-t这个参数会让rsync在同步文件时连同文件的时间戳一起同步,如果不加这个参数,即使文件完全一致没有任何差异,目标端文件的时间戳也会被强制更新为rsync运行时的时间。

这在文件也细碎的情况下会造成inode文件节点信息的大量更新,造成一定的IO浪费,但是这还引发了一个其它的问题,就是rsync目标端的服务死锁了。
图片描述

Kill不掉,strace追踪不到,pstack找不着的奇怪进程

接下来对于这个rsync服务的排查真是令我大开眼界,首先用ps找到该进程的pid

ps -ef|grep rsyncd

root 19276 1 0 10:25 ? 00:00:00 rsync --daemon --config=/etc/rsyncd.conf

root 20491 18565 0 10:25 pts/0 00:00:00 grep --color=auto rsyncd

然后用kill -9 19276,结果发现这个rsync进程还在,根本没被杀掉。

接下来我尝试用strace命令来追踪一下进程的当前系统调用情况:

[root@test-go-python ~]# strace -cp 19276

strace: Process 19276 attached

结果完全没有输出,strace也死了…

恰好当天发生问题的机器上装了GDB,因此我又用pstack命令查询进程的调用栈信息,

[root@test-go-python 19276]# pstack 19276

结果pstack还不如strace,什么都没输出也就义了。这个rsync进程目前就是一个油盐不进的倔强孩子,一时之间还真不知道从哪下手看这个病了。

高级检查手段用不上,只能回归初心从ps入手了

其实定位到rsync的-t参数未按照规范配置的问题之后,这个进程死锁的问题已经可以规避解决了,不过我就是觉得这个事情很有深挖一下的价值。

所以与一般的死锁不同,rsync进程并没有使用很高的CPU,这其实也就提示这似乎并不是由于rsync代码陷入死循环或者存在缺陷造成的,top的时候系统整体IO都很低。

因为PS还是有输出的,因此我加上-l参数看了一下

[root@test-go-python etc]# ps -efl|grep rsync
 
5 D root     19276     1  0  80   0 - 28712 rpc_wa 10:25 ?        00:00:00 rsync --daemon --config=/etc/rsyncd.conf

进程状态线索显现:结果还真有惊喜发现,首先这个进程正处在D状态,这是一种特殊的睡眠态。先补充一下这方面的知识,睡眠态一般分为两种,一种是可打断的,另一种是不可打断的,具体说明如下:

  1. Interruptible Sleep(可中断睡眠,在ps命令中显示“S”)。处在这种睡眠状态的进程是可以通过给它发送signal来唤醒的,比如发HUP信号给nginx的master进程可以让nginx重新加载配置文件而不需要重新启动nginx进程;

  2. Uninterruptible Sleep(不可中断睡眠,在ps命令中显示“D”)。处在这种状态的进程一般正在等待系统调用的返回,处于内核态中运行,不接受外来的任何signal,这也是为什么之前我无法用kill杀掉这些处于D状态的进程,无论是“kill”, “kill -9”还是“kill -15”,因为它们压根儿就不受这些信号的支配。

进程等待线索出现:另外还有一个线索就是ps返回的wchan值代表了进程阻塞的系统调用,而这个系统调用是rpc_wa,当然这个被截断了,我们知道PS命令其实采集/proc/pid/目录下的信息进行汇总的,因此立刻到/proc/19276/wchan查看信息具体的wchan信息,

[root@test-go-python 19276]# cat /proc/19276/wchan
 
rpc_wait

也就是说现在这个rsync进程正在等着一个rpc调用的返回。

正如我们刚刚提到的处于不可打断睡眠状态下的进程处于内核态,正在等待系统调用的返回,那么我们就/proc/19276/syscall的信息来定位rsync进程目前正在执行的系统调用。

[root@test-go-python etc]# cat /proc/19276/syscall
 
5 0x6 0x7ffd0b9fdbc0 0x0 0x0 0x0 0x560886f954f0 0x7ffd0b9fdac8 0x7fd698ffa983

其中第一个数字5代表了系统调用号,而对于64位的Linux来说,系统调用号可以在/usr/include/asm/unistd_64.h文件中查到

[root@test-go-python etc]# cat /usr/include/asm/unistd_64.h|grep 5
 
#define __NR_fstat 5

这样我们可以确认了rsync正卡在fstat系统调用,那么fstat到底是什么来头呢?

下载fstat的源代码包:既然已经到了这个程度,干脆直接下载代码来看最直观,在Linux下我们只要which一下确认命令的位置,再用rpm -qif把包下来,再用rpm2cpio把包还原即可,具体步骤如下:

[root@test-go-python etc]# which stat
 
/usr/bin/stat
 
[root@test-go-python etc]# rpm -qif /usr/bin/stat
 
Name        : coreutils
 
Version     : 8.22
 
Release     : 24.el7
 
Architecture: x86_64
 
Install Date: Sat 31 Oct 2020 03:32:16 PM CST
 
Group       : System Environment/Base
 
Size        : 14593469
 
License     : GPLv3+
 
Signature   : RSA/SHA256, Fri 23 Aug 2019 05:21:30 AM CST, Key ID 24c6a8a7f4a80eb5
 
Source RPM  : coreutils-8.22-24.el7.src.rpm
 
Build Date  : Tue 20 Aug 2019 02:27:26 PM CST
 
Build Host  : x86-01.bsys.centos.org
 
Relocations : (not relocatable)
 
Packager    : CentOS BuildSystem <http://bugs.centos.org>
 
Vendor      : CentOS
 
URL         : http://www.gnu.org/software/coreutils/
 
Summary     : A set of basic GNU tools commonly used in shell scripts
 
Description :
 
These are the GNU core utilities.  This package is the combination of
 
the old GNU fileutils, sh-utils, and textutils packages.
 
[root@test-go-python etc]# yumdownloader --source coreutils
 
Enabling updates-source repository
 
Enabling base-source repository
 
Enabling extras-source repository
 
coreutils-8.22-24.el7_9.2.src.rpm                                                                                                | 5.3 MB  00:00:21    
 
[root@test-go-python kernelstudy]# ll
 
total 5416
 
-rw-r--r-- 1 root root 5537823 Nov 18 22:27 coreutils-8.22-24.el7_9.2.src.rpm
 
[root@test-go-python kernelstudy]#  rpm2cpio coreutils-8.22-24.el7_9.2.src.rpm | cpio -id
 
11429 blocks
 
[root@test-go-python kernelstudy]# xz -d coreutils-8.22.tar.xz
 
[root@test-go-python kernelstudy]#tar xvf coreutils-8.22.tar

查看fstat代码:

具体步骤如下:

[root@test-go-python kernelstudy]#cd coreutils-8.22
 
[root@test-go-python kernelstudy]#cd lib
 
[root@test-go-python kernelstudy]#vi fstat.c
 
 
 
rpl_fstat (int fd, struct stat *buf)
 
{
 
#if REPLACE_FCHDIR && REPLACE_OPEN_DIRECTORY
 
  /* Handle the case when rpl_open() used a dummy file descriptor to work
     around an open() that can't normally visit directories.  */
 
  const char *name = _gl_directory_name (fd);
 
  if (name != NULL)
 
    return stat (name, buf);//从这也可以看到fstat基本和stat人功能差不多
 
#endif
 
 
 
  return fstat_nothrow (fd, buf);
 
}

由此可以看到fstat和stat的功能一样,都是获得文件系统元数据的也就是struct stat,这个数据结构主要有以下部分组成:

File 文件名

Size 文件大小(字节)

Blocks 文件使用的数据块总数

IO Block 数据块的大小

regular file:文件类型(常规文件)

Device 设备编号

Inode 文件所在的Inode

Links 硬链接次数

Access 权限

Uid 属主id/用户

Gid 属组id/组名

Access Time:简写为atime,表示文件的访问时间。当文件内容被访问时,更新这个时间

Modify Time:简写为mtime,表示文件内容的修改时间,当文件的数据内容被修改时,更新这个时间。

Change Time:简写为ctime,表示文件的状态时间,当文件的状态被修改时,更新这个时间,例如文件的链接数,大小,权限,Blocks数。

也就是说我们可以基本把逻辑线串起来,具体的逻辑线是这样的

1.首先就是rsync使用时没有加-t参数,造成大量的元数据修改,

2.大量元数据修改又使网络存储(NFS)对于元数据读取的响应速度变得极慢

3.而Linux读取元数据时的fstat还使rsync进程处于了不可打断的睡眠状态,必须等待网络存储的RPC调用返回才可以恢复。

以上就是造成本次问题的原因。

内核调用栈的印证

当然另一方面的信息也印证了这样的虽然进程的函数调用栈pstack不工作了,但是进程对系统调用的调用栈信息即还是可以看的,

[root@test-go-python etc]# cat /proc/19276/stack
 
[] rpc wait bit killable+0x24/0x40 IsunrpcI
 
[] rpc execute+0xf5/0x1de [sunrpc]
 
[] rpc execute+0x43/0x50 [sunrpc]
 
[] rpc_run task+0x75/0x90 [sunrpc]
 
[] rpc_call- sync+0x42/0x70 [sunrpcI
[] nfs_revalidate_inode+0xcc/0x1fe [nfs]
 
[] nfs _revalidate_inode+0x36/0x60 [nfs]
 
[] nfs_getattr+0x5f/0x110 [nfs]
 
[] vfs getattr+0x4e/0x80
 
[] sys_ fstat+0x70/0x90
 
[] system_call_fastpath+0x16/0x1b
 
[] 0xffffffffffff

也就是说rsync调用了fstat读到文件元数据,接下来又通过了网络存储nfs的相关rpc调用,在一系列的rpc execute之后,最后rpc wait僵死了。这也印证了刚刚的结论。

因此总结下来遇到这种不可打断睡眠态的进程第一要务就是从内核调用中找到解决问题的线索。
————————————————

版权声明:本文为CSDN博主「beyondma」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/BEYONDMA/article/details/115065516

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
14
获赞与收藏
46

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消