关于linux:Linux-进程卡住了怎么办

52次阅读

共计 3883 个字符,预计需要花费 10 分钟才能阅读完成。

在咱们应用 Linux 零碎时,如果网络或者磁盘等 I/O 出问题,会发现过程卡住了,即应用 kill -9 也无奈杀掉过程,很多罕用的调试工具,比方 strace, pstack 等也都失灵了,是怎么回事?

此时,咱们应用 ps 查看过程列表,能够看到卡住的过程状态显示为 D。

man ps 中形容 D 状态是 Uninterruptible Sleep。

Linux 过程有两种睡眠状态:

  1. Interruptible Sleep,可中断睡眠,在 ps 命令中显示 S。处在这种睡眠状态的过程是能够通过给它发送信号来唤醒的。
  2. Uninterruptible Sleep,不可中断睡眠,在 ps 命令中显示 D。处在这种睡眠状态的过程无奈立刻解决任何发送给它的信号,这也是无奈用 kill 杀掉它的起因。

在 Stack Overflow 有一个解答:

kill -9 只是给过程发送了一个 SIGKILL 信号,当一个过程处于非凡状态时(信号处理,或者零碎调用中)会无奈解决任何信号,包含 SIGKILL 也不能被正确处理,导致过程不能被立刻杀掉,也就是咱们常说的 D 状态(不可中断的睡眠状态)。那些罕用的调试工具(比方 stracepstack 等)个别也是利用某个非凡的信号来实现的,在这种状态下也是无奈应用。

可见 D 状态的过程个别是处在某个内核态的零碎调用中,那怎么晓得是哪个零碎调用,又是在期待什么呢?幸好 Linux 下提供了 procfs(就是 Linux 下的 /proc 目录), 通过它就能够看到任何一个过程的以后内核调用栈。上面咱们用拜访 JuiceFS 的过程来模仿一下(因为 JuiceFS 客户端基于 FUSE,是用户态的文件系统,比拟容易模仿 I/O 故障)。

先将 JuiceFS 挂载到前台(在 ./juicefs mount 命令中加一个 -f 参数),而后用 Cltr+Z 把这个过程停掉,这时候用 ls /jfs 去拜访挂载点,会发现 ls 卡住了。

通过上面的命令能够看到 ls 卡在了 vfs_fstatat 调用上,它会给 FUSE 设施发送 getattr 申请,在期待回应。而 JuiceFS 客户端过程曾经被咱们停掉了,所以它就卡住了:

$ cat /proc/`pgrep ls`/stack
[<ffffffff813277c7>] request_wait_answer+0x197/0x280
[<ffffffff81327d07>] __fuse_request_send+0x67/0x90
[<ffffffff81327d57>] fuse_request_send+0x27/0x30
[<ffffffff8132b0ac>] fuse_simple_request+0xcc/0x1a0
[<ffffffff8132c0f0>] fuse_do_getattr+0x120/0x330
[<ffffffff8132df28>] fuse_update_attributes+0x68/0x70
[<ffffffff8132e33d>] fuse_getattr+0x3d/0x50
[<ffffffff81220c6f>] vfs_getattr_nosec+0x2f/0x40
[<ffffffff81220ee6>] vfs_getattr+0x26/0x30
[<ffffffff81220fc8>] vfs_fstatat+0x78/0xc0
[<ffffffff8122150e>] SYSC_newstat+0x2e/0x60
[<ffffffff8122169e>] SyS_newstat+0xe/0x10
[<ffffffff8186281b>] entry_SYSCALL_64_fastpath+0x22/0xcb
[<ffffffffffffffff>] 0xffffffffffffffff

这时候按 Ctrl+C 也不能退出。

root@localhost:~# ls /jfs
^C
^C^C^C^C^C

然而用 strace 却能唤醒它,并且开始解决之前的中断信号,而后就退出了。

root@localhost:~# strace -p `pgrep ls`
strace: Process 26469 attached
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Interrupted system call)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=13290, si_uid=0} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Interrupted system call)。。。tgkill(26469, 26469, SIGINT)            = 0
--- SIGINT {si_signo=SIGINT, si_code=SI_TKILL, si_pid=26469, si_uid=0} ---
+++ killed by SIGINT +++

这个时候如果用 kill -9 的话,也是能够把它杀掉的:

root@localhost:~# ls /jfs
^C
^C^C^C^C^C
^C^CKilled

因为 vfs_lstatat() 这种简略的零碎调用并没有 屏蔽 SIGKILLSIGQUITSIGABRT 等信号,还能够对它做些惯例的解决。

咱们再来模仿一个更简单的 I/O 谬误,给 JuiceFS 配置一个无奈写入的存储类型,并挂载上,用 cp 尝试往里写入数据,这时候 cp 也会卡住:

root@localhost:~# cat /proc/`pgrep cp`/stack
[<ffffffff813277c7>] request_wait_answer+0x197/0x280
[<ffffffff81327d07>] __fuse_request_send+0x67/0x90
[<ffffffff81327d57>] fuse_request_send+0x27/0x30
[<ffffffff81331b3f>] fuse_flush+0x17f/0x200
[<ffffffff81218fd2>] filp_close+0x32/0x80
[<ffffffff8123ac53>] __close_fd+0xa3/0xd0
[<ffffffff81219043>] SyS_close+0x23/0x50
[<ffffffff8186281b>] entry_SYSCALL_64_fastpath+0x22/0xcb
[<ffffffffffffffff>] 0xffffffffffffffff

怎么卡在 close_fd()?这是因为往 JFS 写数据是异步的,当 cp 调用 write() 时,数据会先缓存在 JuiceFS 的客户端过程里同时会异步写入到后端存储,等 cp 写完数据,它会调用 close 来确保数据写入实现,对应 FUSE 的 flush 操作。JuiceFS 的客户端在遇到 flush 操作时,须要确保全副写入的数据都长久化到后端存储,而后端存储写入失败了,它就在多次重试的过程中,所以 flush 操作卡住了,还没有回复给 cp,所以 cp 也卡住了。

这个时候如果用 Cltr+C 或者 kill 是能够中断 cp 的运行,因 JuiceFS 实现了各种文件系统操作的中断解决,让它放弃以后操作(比方 flush), 返回 EINTR 这样在遇到各种网络故障时能够中断正在拜访 JuiceFS 的利用

这时如果我进行 JuiceFS 客户端过程,让它不能再解决任何 FUSE 申请(包含中断请求),这个时候如果尝试去杀它,就杀不掉了,包含 kill -9 也杀不掉,用 ps 查看过程状态,曾经是 D 状态了。

root      1592  0.1  0.0  20612  1116 pts/3    D+   12:45   0:00 cp parity /jfs/aaa

但这个时候是能够用 cat /proc/1592/stack 来看它的内核调用栈

root@localhost:~# cat /proc/1592/stack
[<ffffffff8132775d>] request_wait_answer+0x12d/0x280
[<ffffffff81327d07>] __fuse_request_send+0x67/0x90
[<ffffffff81327d57>] fuse_request_send+0x27/0x30
[<ffffffff81331b3f>] fuse_flush+0x17f/0x200
[<ffffffff81218fd2>] filp_close+0x32/0x80
[<ffffffff8123ac53>] __close_fd+0xa3/0xd0
[<ffffffff81219043>] SyS_close+0x23/0x50
[<ffffffff8186281b>] entry_SYSCALL_64_fastpath+0x22/0xcb
[<ffffffffffffffff>] 0xffffffffffffffff

内核调用栈显示它卡在 FUSE 的 flush 调用上,这个时候只有复原 JuiceFS 客户端过程,就能够立刻中断 cp 让它退出。

close 这种波及到数据安全性的操作,不是 restartable, 也就不能被 SIGKILL 等随便中断,比方要 FUSE 的实现端响应中断操作能力中断。

因而,只有 JuiceFS 的客户端过程可能衰弱的响应中断,就不必放心拜访 JuiceFS 的利用卡死。或者杀掉 JuiceFS 客户端过程也能够完结以后的挂载点,中断所有在拜访以后挂载点的利用

如有帮忙的话欢送关注咱们我的项目 Juicedata/JuiceFS 哟!(0ᴗ0✿)

正文完
 0