当前位置: 首页 > Linux

Linux进程卡死怎么办?

时间:2023-04-06 23:29:35 Linux

我们在使用linux系统的时候,如果网络或者磁盘等I/O出现问题,我们会发现进程卡住了,即使我们使用kill-9杀掉进程,很多常用的调试工具,比如strace、pstack等,都出问题了,怎么回事?此时我们用ps查看进程列表,可以看到卡死进程的状态显示为D,manps中描述的D状态就是UninterruptibleSleep。Linux进程有两种休眠状态:InterruptibleSleep,可以中断休眠,在ps命令中显示S。处于睡眠状态的进程可以通过发送信号唤醒。UninterruptibleSleep,不可中断的睡眠,在ps命令中显示D。处于这种休眠状态的进程不能立即处理发送给它的任何信号,这就是它不能被kill杀死的原因。StackOverflow上有一个答案:kill-9只是向进程发送一个SIGKILL信号。当一个进程处于特殊状态(信号处理,或者系统调用)时,它会无法处理任何信号,包括SIGKILL无法正确处理,导致无法立即杀死进程,也就是我们常说的D状态(不间断睡眠状态)。那些常用的调试工具(如strace、pstack等)一般都是使用特殊信号实现的,不能在这种状态下使用。可见,处于D态的进程在内核态一般都处于系统调用中,那么怎么知道是哪个系统调用,还等什么呢?好在Linux下提供了procfs(即Linux下的/proc目录),通过它可以看到任何进程当前的内核调用栈。接下来,我们用访问JuiceFS的过程来模拟(因为JuiceFS客户端是基于FUSE的,是用户态文件系统,更容易模拟I/O故障)。首先将JuiceFS挂载到前台(在./juicefs挂载命令中添加一个-f参数),然后使用Cltr+Z停止进程。这时候用ls/jfs访问挂载点,会发现ls卡死了。通过以下命令,可以看到ls卡在了vfs_fstatat的调用上。它将向FUSE设备发送getattr请求并等待响应。而JuiceFS客户端进程已经被我们停止了,所以卡住了:$cat/proc/`pgrepls`/stack[]request_wait_answer+0x197/0x280[]__fuse_request_send+0x67/0x90[]fuse_request_send+0x27/0x30[]fuse_simple_request+0xcc/0x1a0[]fuse_do_getattr+0x120/0x330[]fuse_update_attributes+0x68/0x70[]fuse_getattr+0x3d/0x50[]vfs_getattr_nosec+0x2f/0x40[]vfs_getattr+0x26/0x30[]vfs_fstatat+0x78/0xc0[]SYSC_newstat+0x2e/0x60[]SyS_newstat+0xe/0x10[]entry_SYSCALL_64_fastpath+0x22/0xcb[]0xffffffffffffffff此时按Ctrl+C退出。root@localhost:~#ls/jfs^C^C^C^C^C^C但是可以用strace唤醒,开始处理之前的中断信号,然后退出。root@localhost:~#strace-p`pgrepls`strace:Process26469attached---SIGINT{si_signo=SIGINT,si_code=SI_KERNEL}---rt_sigreturn({mask=[]})=-1EINTR(中断系统call)---SIGTERM{si_signo=SIGTERM,si_code=SI_USER,si_pid=13290,si_uid=0}---rt_sigreturn({mask=[]})=-1EINTR(中断的系统调用)。..tgkill(26469,26469,SIGINT)=0---SIGINT{si_signo=SIGINT,si_code=SI_TKILL,si_pid=26469,si_uid=0}---+++被SIGINT杀死+++如果你使用kill-9at这个时候也可以杀掉: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/`pgrepcp`/stack[]request_wait_answer+0x197/0x280[]__fuse_request_send+0x67/0x90[]fuse_request_send+0x27/0x30[]fuse_flush+0x17f/0x200[]filp_close+0x32/0x80[]__close_fd+0xa3/0xd0[]SyS_close+0x23/0x50[]entry_SYSCALL_64_fastpath+0x22/0xcb[]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的client进程,让它不能再处理任何FUSE请求(包括中断请求),如果这个时候我尝试kill它,它是不会被kill的,包括kill-9,而且它可以用ps查看进程状态已经是D状态。root15920.10.0206121116pts/3D+12:450:00cpparity/jfs/aaa但是此时可以使用cat/proc/1592/stack查看其内核调用堆栈root@localhost:~#cat/proc/1592/stack[]request_wait_answer+0x12d/0x280[]__fuse_request_send+0x67/0x90[]fuse_request_send+0x27/0x30[]fuse_flush+0x17f/0x200[]filp_close+0x32/0x80[]__close_fd+0xa3/0xd0[]SyS_close+0x23/0x50[]entry_SYSCALL_64_fastpath+0x22/0xcb[]0xffffffffffffffff内核调用栈显示它卡在FUSE的flush调用,此时只要恢复JuiceFS客户端进程,就可以立即中断cp让它退出。close等涉及数据安全的操作是不可重启的,不能被SIGKILL随意打断。例如,FUSE的实现必须响应中断操作才能被中断。因此,只要JuiceFS客户端进程能够健康响应中断,就无需担心访问JuiceFS的应用卡住。或者杀死JuiceFS客户端进程也可以结束当前挂载点,中断所有访问当前挂载点的应用程序。如果对您有帮助,请关注我们的项目Juicedata/JuiceFS!(0?0?)