在Linux下进程有多种状态以下就是我列举的一些状态:
R运行状态(running):并不意味着进程一定在运行中,它表明进程要么在运行中要么在运行队列里。
S睡眠状态(sleeping):意味着进程在等待事件的完成(这里的睡眠有时候也会叫可中断睡眠)(interruptible sleep)。
D磁盘休眠状态(Disk sleep):有时候也叫做不可中断睡眠(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表中看到这个状态。
僵尸状态
僵尸状态是一个比较特殊的状态,当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸状态。僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码,所以只要子进程退出,父进程没有读取子进程的状态,子进程进入了Z状态。
问题一:为什么进程会有僵尸状态?
unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 这个存在是十分有意义的,可以接受,因为我们需要了解自己创建的进程的运行情况,这样才可控,失控(《失控》这本书也不错)是很可怕的事儿,尤其是计算机;
问题二:为什么要尽量避免僵尸进程?
因为系统中的进程数量是有限的,虽然僵尸进程占用的资源和内存都比较少,但是它却占领着数字,可能会导致系统无法再创建新的进程,所以必须先清除僵尸进程。
问题三:如何避免僵尸进程?
(1)、父进程调用wait和waitpid来等待子进程的结束,但是这将导致父进程的挂起,浪费资源;
(2)、如果父进程很忙,则可以通过signal函数为SIGCHLD来安装handler,因为子进程结束后一定会向父进程发送一个消息信号,这样可以在handler里调用wait来接收;
(3)、如果父进程不关心子进程的状态,可以用signal(SIGCHLE,SIGIGN)来通知内核,告诉内核我对子进程没兴趣,你帮我回收,那么子进程结束后,就不会向父进程发送信号消息,直接被内核回收;
(4)、还有就是fork()两次,这样让子进程直接退出,孙进程结束后就成了孤儿进程,会被init接管,直接回收,不用祖父进程管了;
问题四:僵尸状态是每个子进程比经过的状态吗?
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构(它占用一点内存 资源,也就是进程表里还有一个记录),等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这 时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。所以不一定说每个进程一定是僵尸进程,但是如果子进程先结束,那么僵尸进程就是它们的必经之途!
僵尸进程危害场景:“擒贼先擒王”
例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程(父进程)之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。这就是守护进程的作用,如果发生大量的僵尸进程,守护进程就会查找其父进程,然后无情的kill掉!
我们接下来就来创建一个僵尸进程:
#include <stdio.h>
2 #include <stdlib.h>
1 #include <stdio.h>
2 #include <stdlib.h>
3 int main ()
4 {
6 if(id<0){
7 perror("fork"); //perror错误处理函数,在标准输出设备上输出一个函数
8 return 1;
9 }
10 else if(id>0){ //father
11 printf("parent[%d] is sleeping.... \n",getpid()); //
12 sleep(30);
13 }else{ //child
14 printf("child[%d} is begin z....\n",getpid());
15 sleep(5);
16 exit(EXIT_SUCCESS); //进程退出函数;EXIT_SUCESS是一个宏,表明进程执行成功
17 }
18 return 0;
19
20 }
运行的结果如下:
孤儿进程
顾名思义和我们显示中所说的孤儿有点类似,当一个进程的父进程结束时,但它自己还没有结束,那么这个进程将成为孤儿进程,最后孤儿进程会被init进程(init进程的pid为1)的进程收养,当然在子进程结束时也会有init进程完成对它的状态的收集工作,因此一般来说,孤儿进程并没有什么危害。
创建的思路:在main函数中创建子进程,然后让父进程睡眠1s,让子进程先运行打印出其进程id(pid)以及父进程id(pid),随后子进程睡眠5s(此时会调度到父进程运行直到结束),目的是让父进程先与子进程结束让子进程有个孤儿对的状态,最后子进程再打印出其进程id(pid)以及父进程的id(pid),观察两次打印id(pid)的区别。
运行结果如下: