linux_wait()与僵尸进程

 
  在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)

他, 那么他将变成一个僵尸进程. 但是如果该进程的父进程已经先结束了,那么

该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统

中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果

是的话,就由Init 来接管他,成为他的父进程…… 

一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留

下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是 使进程退出,

但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁) 

 

2. 僵尸进程的危害 

由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底

什么时候结束. 那么不会因为父进程太忙来不及waid子进程,或者说不知道 子进程什么

时候结束,而丢失子进程结束时的状态信息呢? 不会.因为UNIX提供了一种机制可以保

证 只要父进程想知道子进程结束时的状态信息, 就可以得到. 这种机制就是: 在每个进

程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等. 但是仍然为

其保留一定的信息(包括进程号the process ID,退出状态the termination status of the

process,运行时间the amount of CPU time taken by the process等), 直到父进程通过

wait / waitpid来取时才释放. 但这样就导致了问题,如果你进程不调用wait / waitpid的话,

那么保留的那段信息就不会 释放,其进程号就会一定被占用,但是系统所能使用的进程号

是有限的,如果大量的产生 僵死进程,将因为没有可用的进程号而导致系统不能产生新的

进程. 此即为僵尸进程的危害,应当避免. 

 

3.僵尸进程的避免 

1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起

2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结

束后, 父进程会收到该信号,可以在handler中调用wait回收 

3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)

通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给

父进程发送信号 

4. 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork

一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程

的回收 还要自己做。 下面就是Stevens给的采用两次folk避免僵尸进程的示例.

 

3、僵尸进程的处理: 它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD

信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就

一直保持僵尸状态; 存在的问题:如果父进程是一个循环,不会结束,那么子进程就会

一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程,系统的性能可能会

收到影响。 ** 如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收

尸,它还是能被清除的。 4、子进程结束后为什么要进入僵尸状态? * 因为父进程可能

要取得子进程的退出状态等信息。 5、僵尸状态是每个子进程比经过的状态吗? 是的。

* 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸

进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。

如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状 态是“Z”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但

这并不等于子进程不经过僵尸状态。 * 如果父进程在子进程结束之前退出,则子进程将

由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。 6、如何查看僵尸

进程: $ ps -el 其中,有标记为Z的进程就是僵尸进程 S代表休眠状态;D代表不可

中断的休眠状态;R代表运行状态;Z代表僵死状态;T代表停止或跟踪状态

 

 

 

wait 系统调用

 

 

系统中的僵尸进程都要由wait系统调用来回收,下面就通过实战看一看wait的具体用法:

 

wait的函数原型是:

 

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经

退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,

并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有

一个出现为止。

 

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果

我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数

情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

 

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失

败,此时wait返回-1,同时errno被置为ECHILD。

 

下面就让我们用一个例子来实战应用一下wait调用:

 

 

#include <sys/types.h> 

#include <sys/wait.h> 

#include <unistd.h> 

#include <stdlib.h> 

#include <stdio.h> 

#include <errno.h> 

 

int main() 

pid_t pc, pr;   

pc = fork(); 

if ( pc < 0 )  

printf("create child prcocess error: %s\n", strerror(errno)); 

exit(1); 

else if ( pc == 0)  

printf("I am child process with pid %d \n", getpid()); 

sleep(3); 

exit(0); 

else  

printf("Now in parent process, pid = %d\n", getpid()); 

printf("I am waiting child process to exit.\n"); 

pr = wait(NULL);  

if ( pr > 0 )  

printf("I catched a child process with pid of %d\n", pr); 

else  

printf("error: %s\n.\n", strerror(errno)); 

exit(0); 

 

编译并运行:

 

$ gcc wait1.c -o wait1

$ ./wait1

I am child process with pid 2351

Now in parent process, pid = 2350

I am waiting child process to exit.

I catched a child process with pid of 2351

可以明显注意到,在第2行结果打印出来前有10秒钟的等待时间,这就是我们设定的让子

进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退 出,也就才能被父进程捕

捉到。其实这里我们不管设定子进程睡眠的时间有多长,父进程都会一直等待下去,读者如

果有兴趣的话,可以试着自己修改一下这个数 值,看看会出现怎样的结果。

 

如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一

个整数值(int),指出了子进程是正常退出还是 被非正常结束的(一个进程也可以被其他

进程用信号结束,我们将在以后的文章中介绍),以及正常结束时的返回值,或被哪一个信

号结束的等信息。由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法

读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习 一下其中最 常用的两个:

 

1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个

非零值。

 

(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针

status,而是那个指针所指向的整数,切记不要搞混了。)

 

2, WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进

程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程

调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就

是说, WIFEXITED返回0,这个值就毫无意义。

 

下面通过例子来实战一下我们刚刚学到的内容:

 

 

#include <sys/types.h> 

#include <sys/wait.h> 

#include <unistd.h> 

 

int main() 

int status; 

pid_t pc, pr; 

 

pc = fork(); 

if ( pc < 0)  

printf("error occured.\n"); 

else if ( pc == 0 )  

printf("This is child process with pid of %d.\n", getpid()); 

exit(3);  

else  

pr = wait(&status); 

if ( WIFEXITED(status) )    

printf("The child process %d exit normally.\n", pr); 

printf("the return code is %d.\n", WEXITSTATUS(status)); 

else  

printf("The child process %d exit abnormally.\n", pr); 

  

exit(0); 

 

编译并运行:

 

$ gcc wait2.c -o wait2

$ ./wait2

This is child process with pid of 1538.

the child process 1538 exit normally.

the return code is 3.

父进程准确捕捉到了子进程的返回值3,并把它打印了出来。

 

当然,处理进程退出状态的宏并不止这两个,但它们当中的绝大部分在平时的编程中很少用

到,就也不在这里浪费篇幅介绍了,有兴趣的读者可以自己参阅Linux man pages去了解它

们的用法。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值