前言一、僵尸进程
1.1 僵尸进程的危害1.2 僵尸进程解决方案1.2.1 kill杀死元凶父进程1.2.2 父进程用wait或waitpid去回收资源1.2.3 通过信号机制,在处理函数中调用wait,回收资源 二、孤儿进程参考 前言
在Unix/Linux系统中,正常情况下,子进程是通过父进程创建的,子进程可以再继续创建新的进程。在Linux中,除了进程0(即PID=0的进程,init进程)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的被其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。
fork函数包含在unistd.h库中,其最主要的特点是,调用一次,返回两次,当父进程fork()创建子进程失败时,fork()返回-1,当父进程fork()创建子进程成功时,此时,父进程会返回子进程的pid,而子进程返回的是0。所以可以根据返回值的不同让父进程和子进程执行不同的代码。
上图中,当fork()函数调用后,父进程中的变量pid赋值成子进程的pid(pid>0),所以父进程会执行else里的代码,打印出"This is the parent",而子进程的变量pid赋值成0,所以子进程执行if(pid == 0)里的代码,打印出"This is the child"子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。因此,主进程挂了之后对子进程是没有影响的,让我们继续往下看。
当一个子进程结束运行(一般是调用exit、运行时发生致命错误或收到终止信号所导致)时,子进程的退出状态(返回值)会回报给操作系统,系统则以SIGCHLD信号将子进程被结束的事件告知父进程,此时子进程的进程控制块(PCB)仍驻留在内存中。一般来说,收到SIGCHLD后,父进程会使用wait系统调用以获取子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的PCB;而如若父进程没有这么做的话,子进程的PCB就会一直驻留在内存中,也即成为僵尸进程。1.1 僵尸进程的危害
总的来说,僵尸进程可以理解为一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,此时子进程得不到释放就变成了僵尸进程。
僵尸进程是有危害的。进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,当一个进程一直处于Z状态,那么它的PCB也就一直都要被维护。因为PCB本身就是一个结构体会占用空间,僵尸进程也就会造成资源浪费,所以我们应该避免僵尸进程的产生。
Unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID、退出状态the termination status of the process、运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程、此即为僵尸进程的危害,应当避免。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
例如有个进程,它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,其中一种解决方案就是把产生大量僵死进程的那个元凶枪(产生僵尸进程的父进程)毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程就能处理了。
1.2 僵尸进程解决方案 1.2.1 kill杀死元凶父进程严格的说,僵尸进程并不是问题的根源,罪魁祸首是产生大量僵死进程的父进程。因此,我们可以直接除掉元凶,通过kill发送SIGTERM或者SIGKILL信号。元凶死后,僵尸进程进程变成孤儿进程,由init充当父进程,并回收资源。命令为:kill -9 父进程的pid值强制杀死父进程。
1.2.2 父进程用wait或waitpid去回收资源父进程通过wait或waitpid等函数去等待子进程结束,但是不好,会导致父进程一直等待被挂起,相当于一个进程在干活,没有起到多进程的作用。
1.2.3 通过信号机制,在处理函数中调用wait,回收资源通过信号机制,子进程退出时向父进程发送SIGCHLD信号,父进程调用signal(SIGCHLD,sig_child)去处理SIGCHLD信号,在信号处理函数sig_child()中调用wait进行处理僵尸进程。什么时候得到子进程信号,什么时候进行信号处理,父进程可以继续干其他活,不用去阻塞等待。
二、孤儿进程孤儿进程则是指当一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。孤儿进程由于有init进程循环的wait()回收资源,因此并没有什么危害。我们已经知道,孤儿进程是没有父进程的进程,孤儿进程的回收任务最终会落到了init进程身上,init进程就是所有进程的根进程,有点类似于二叉树的根节点。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程结束了其生命周期的时候,init处理孤儿进程。因此孤儿进程并不会有什么危害。 参考
1、进程3.0——进程状态与僵尸进程、孤儿进程
2、僵尸进程和孤儿进程区别