在 linux 系统中,当进程由于网络或磁盘等 i/o 问题而卡住时,通常会进入不可中断睡眠状态(uninterruptible sleep),在 ps 命令中显示为 d 状态。这是因为这些进程正处于内核态的系统调用中,无法立即响应信号,包括 kill -9。

当使用 ps 查看进程列表时,可以看到卡住的进程状态显示为 D。

根据 man ps 的描述,D 状态表示进程处于不可中断睡眠状态。这种状态的进程无法立即处理任何发送给它的信号,因此无法通过 kill 命令终止。
Linux 进程有两种睡眠状态:
ps 命令中显示为 S。这种状态的进程可以通过发送信号来唤醒。ps 命令中显示为 D。这种状态的进程无法立即处理任何信号,这就是为什么 kill 命令无法终止它们的主要原因。在 Stack Overflow 上有一个相关的解答,指出 D 状态的进程通常是处于某个内核态的系统调用中。要确定是哪个系统调用以及在等待什么,可以通过 Linux 下的 procfs(即 /proc 目录)查看进程的当前内核调用栈。
通过模拟 JuiceFS 客户端进程(因为 JuiceFS 基于 FUSE,是用户态的文件系统,容易模拟 I/O 故障),我们可以看到 ls 命令卡在了 vfs_fstatat 调用上,这会向 FUSE 设备发送 getattr 请求,并等待回应。如果 JuiceFS 客户端进程被暂停,ls 命令就会卡住。
$ 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 无法退出,但使用 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,也可以终止进程,因为 vfs_lstatat() 等简单的系统调用并没有屏蔽 SIGKILL、SIGQUIT、SIGABRT 等信号。
在更复杂的 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
cp 卡在 close_fd() 是因为 JuiceFS 的写入操作是异步的,当 cp 调用 write() 时,数据会先缓存在 JuiceFS 客户端进程中,并异步写入后端存储。cp 完成写入后会调用 close 确保数据写入完成,对应 FUSE 的 flush 操作。如果后端存储写入失败,flush 操作会卡住,导致 cp 也卡住。
在这种情况下,按 Ctrl+C 或使用 kill 可以中断 cp 的运行,因为 JuiceFS 实现了文件系统操作的中断处理,让它放弃当前操作(如 flush),返回 EINTR,从而在遇到网络故障时中断访问 JuiceFS 的应用。
如果停止 JuiceFS 客户端进程,使其无法处理任何 FUSE 请求(包括中断请求),则无法通过 kill -9 终止进程,进程状态会变为 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 这种涉及数据安全性的操作是不可重启的(non-restartable),因此不能被 SIGKILL 等信号随意中断,必须由 FUSE 实现端响应中断操作才能中断。
因此,只要 JuiceFS 客户端进程能够健康响应中断,就不必担心访问 JuiceFS 的应用会卡死。或者,可以通过终止 JuiceFS 客户端进程来结束当前挂载点,中断所有正在访问该挂载点的应用。
以上就是Linux 进程卡住了怎么办?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号