前言
之前一直在做深度学习方面的内容,主要是深度学习进行图像处理。之前的程序一直在服务器上正常运行,但昨天出现了一个让我很头疼的问题,先将其前因后果与处理结果进行记录,毕竟这样一个一弄弄一天的内容,值得足足一篇博文的纪念(狗头)。
基本情况
在改代码的时候,我错误修改了一个参数导致其在运行中出现了cuda out of memory 的错误,运行中的进程本该停止,但不知道问题出在了什么地方,该进程的状态变成了D,即disk sleep。该进程占用了大量的内存,疯狂挤占io,服务器出现没有宕机胜似宕机的局面。其余需要io的进程全部排队等待。
解决方法
重启
其实这种情况最好的解决方法是直接重启服务器,但由于原本系统也是我们几个学生自己装的,安装之时没有处理好开机引导,导致重启服务器等于服务器关机,而疫情又让我们进不了机房,所以,这个方法虽是最优解决方案,但我们无法采用。
利用linux内核源码改变D进程状态
进程存在几种状态,分别以R(running),S(sleeping),D(disk sleep),T(stopped),Z(zombies)来进行表示,其中以D与Z最难处理,前者就是我遇到的问题,kill -9无法直接将其杀掉,只能使用内核源码来处理。网上查了很多内容,国内的很多内容都是你“借鉴”我的我“借鉴”你的,基本都是以下方法:
// C代码,存存为stopd.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h> //for_each_process
MODULE_LICENSE("GPL v2");
static int pid = -1;
module_param(pid, int, S_IRUGO);
static int stopd_init(void){
struct task_struct * p;
printk(KERN_ALERT "stopd: force D status process to death\n");
printk(KERN_ALERT "stopd: pid=%d\n", pid);
//read_lock(&tasklist_lock);
for_each_process(p){
if(p->pid == pid){
printk("stopd: found\n");
set_task_state(p, TASK_STOPPED);
printk(KERN_ALERT "stopd: aha, dead already\n");
return 0;
}
}
printk("not found");
//read_unlock(&tasklist_lock);
return 0;
}
static void stopd_exit(void){
printk(KERN_ALERT "stopd: bye\n");
}
module_init(stopd_init);
module_exit(stopd_exit);
// 代码结束
在同文件夹下touch Makefile
// Makefile内容
obj-m := stopd.o
使用内核源码进行编译,查看/usr/src目录下带有linux的目录,找到自己服务器的yourkerneltree,随后在c所在目录运行make -C yourkerneltree M=`pwd` modules
进行编译,假设D进程的pid为1234,,则可以./insmod ./stopd.ko pid=1234
将1234变成T状态。
以上方法存在如下问题,首先是编译环节,一般来说是无法通过编译的,虽然网上也有人有同样的问题,但好像没有解答,我自己对程序进行了一定的修改,在头文件部分增加了一个include即#include <linux/sched/signal.h>
用以解决没有for_each_process函数的问题,同时将源代码里面的set_task_state(p, TASK_STOPPED);
修改为set_current_state(TASK_STOPPED);
,然后就能编译通过了。但是我使用了该方法,依旧无法实现D状态的变化,分析原因大概是我的程序里面读数据的部分留下了比较大的坑,导致状态改变后,进程还是去执行了代码里面的内容,没法直接停止,然后状态改变的瞬间就有被系统变成了状态D。
磁盘卸载
这个是在stackoverflow找到的方法,原文没有截图,只能把别人的文字直接粘贴了,Errors in underlying filesystem or disks might cause I/O bound processes. In this case try to "umount -f" the filesystem they depend upon - this will abort whatever outstanding I/O requests there are open.
简单来说就是如果因为数据读取等原因引起的io问题,可以使用将数据所在硬盘卸载后重新挂载的方式强制将进程停止。虽然不太理解内存占满与硬盘卸载的关系,但是也大概是一个可实验的方法吧,结果我的数据与根目录放在了同一块硬盘下(笑哭),最后也就没有尝试该方法了。
最后
最后导师申请进了学校,重启了服务器,这件事情才算是告一段落。(辛苦导师这么大太阳还跑学校一趟)
分析
最后我和同学一起分析了这次的问题,最后的结论是,这次的问题应该是由pytorch的dataloader引起的,dataloader里面有一个num_workers的参数,如果该参数不为零,则程序会开多个进程来实现数据读取与训练的工作,而这一次的问题出在负责数据读取的进程在硬盘里面读了大量数据在内存中,等待被杀进程的传数据请求,但负责训练的进程由于cuda溢出被系统杀死了,无法及时给到请求,所以进程就尬住了,接着就被系统捕获并标识为了D进程。
以后的问题
设置串行训练方式,可以自己写一个dataloader,自己去硬盘里面抓tensor出来训练,或者偷懒一点直接将num_workers置0,这样程序就不会出现训练与数据读取并行的情况,也就不太会出现上述问题了。