在我們使用 Linux 系統時,如果網路或者磁盤等 I/O 出問題,會發現行程卡住了,即使用 kill -9 也無法殺掉行程,很多常用的除錯工具,比如 strace, pstack 等也都失靈了,是怎么回事?

此時,我們使用 ps 查看行程串列,可以看到卡住的行程狀態顯示為 D,

man ps 中描述 D 狀態是 Uninterruptible Sleep,
Linux 行程有兩種睡眠狀態:
- Interruptible Sleep,可中斷睡眠,在 ps 命令中顯示 S,處在這種睡眠狀態的行程是可以通過給它發送信號來喚醒的,
- Uninterruptible Sleep,不可中斷睡眠,在 ps 命令中顯示 D,處在這種睡眠狀態的行程無法立即處理任何發送給它的信號,這也是無法用 kill 殺掉它的原因,
在 Stack Overflow 有一個解答:
kill -9只是給行程發送了一個SIGKILL信號,當一個行程處于特殊狀態時(信號處理,或者系統呼叫中)會無法處理任何信號,包括SIGKILL也不能被正確處理,導致行程不能被立即殺掉,也就是我們常說的D狀態(不可中斷的睡眠狀態),那些常用的除錯工具 (比如strace、pstack等)一般也是利用某個特殊的信號來實作的,在這種狀態下也是無法使用,
可見 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() 這種簡單的系統呼叫并沒有 屏蔽 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
怎么卡在 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?)
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/404254.html
標籤:其他
