系统编程-回收子进程-孤儿和僵尸进程,wait和waitpid方法

1. 孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
危害:没什么危害…
产生孤儿进程的举例:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = fork();
if(pid == 0){// child
while(1){//强制不让子进程退出,变为孤儿进程
printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(1);
}
}else if(pid>0){// parent
printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(5);
printf("I am parent,I will die!\n");
}
return 0;
}
2. 僵尸进程
僵尸进程: 子进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,此子进程便成为僵尸(zombie)进程。
注意:僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
产生僵尸进程的举例:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = fork();
if(pid == 0){// child
printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(2);
printf("I am child,I will die!\n");
}
}else if(pid>0){// parent
while(1){//强制不让父进程退出,子进程变为僵尸进程
printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(5);
}
}
return 0;
}
危害:僵尸进程会占用系统资源,如果很多,则会严重影响服务器的性能;
如何解决僵尸进程:
①杀死他的父进程使其变成孤儿进程,进而被系统处理。
②wait函数
3. 子进程回收
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
3.1 wait
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
wait函数的原型是:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸进程的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个这样的进程出现为止。
参数status用来保存被回收进程退出时的一些状态,如果我们不想知道这个子进程是如何死掉的,只想把它消灭掉的话,那么我们可以设定这个参数为NULL,就像下面这样:
pid_t pid = wait(NULL);
如果成功,wait会返回被回收子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
返回的status只是一个整型变量,不能很精确的描述出状态,因此需要借助宏函数来进一步判断进程终止的具体原因。经常用到的宏函数为如下两组:
//正常死亡WIFEXITED
if(WIFEXITED(status)){
pfintf("%d:"WEXITSTATUS(status));
}
//非正常死亡
if(WIFSIGNALED(status)){
pfintf("%d:"WTERMSIG(status));
}
WIFEXITED(status)为非0→ 进程正常结束
WEXITSTATUS(status)如上宏为真,使用此宏 → 获取进程退出状态 (return/exit的参数)WIFSIGNALED(status)为非0→ 进程异常终止
WTERMSIG(status)如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
pid_t pid = fork();
if(pid==0){
printf("I am child,will die!\n");
sleep(2);
//while(1){
printf("I am child,Come and hit me!!\n");
sleep(1);
//}
return 101;
}
else if(pid>0){
printf("I am parent,wait for child die\n");
int status;
pid_t wpid = wait(&status);
printf("wait ok,wpid = %d,pid = %d\n",wpid,pid);
//正常死亡WIFEXITED
if(WIFEXITED(status)){
printf("child exit with %d\n:",WEXITSTATUS(status));
}
//非正常死亡
if(WIFSIGNALED(status)){
printf("child killed by %d:",WTERMSIG(status));
}
}
}
子进程正常死亡运行结果:
子进程一直运行时(while(1)),kill掉之后运行结果:
3.2 waitpid
作用同wait,但可指定pid进程清理,可以不阻塞。
waitpid函数的原型是:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
返回值:
- 成功:返回
清理掉的子进程ID; - 失败:
-1(出错,无子进程); - 返回
0值,参3传WNOHANG,且子进程正在运行。
pid:
从参数的名字pid和类型pid_t 就可以看出,这里需要的是一个进程ID。当pid取不同的值时,在这里有不同的意义。
- pid >0时,回收指定ID的子进程,只等待进程ID等于
pid的子进程,不管其他已经有多少个子进程运行结束退出了,只要指定的子进程还没结束,waitpid就会一直等下去; - pid = -1时,回收任意子进程(相当于
wait),等待任何一个子进程退出,没有 任何限制; - pid = 0时,回收和当前调用waitpid一个组的所有子进程,如果 子进程已经加入了别的进程组,waitpid不会对它做任何理睬;
- pid < -1时,回收指定进程组内的任意子进程,这个进程组的ID等于pid的绝对值;
options:
- 0:(相当于wait)阻塞回收
WNOHANG:非阻塞回收,用轮询结构回收。
3.3 wait和waitpid的区别
·在一个子进程终止前,wait使其调用者阻塞,而waitpid则提供了非阻塞版本;
·waitpid等待一个指定的子进程,而wait等待第一个终止的子进程;
·waitpid支持作业控制(以WUNTRACED选项,由pid指定的任一子进程状态,且其状态自暂停以来还未报告过,则返回其状态);
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
wait循环回收:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int n =5;
int i =0;
pid_t pid;
for(i=0;i<5;++i){
pid = fork();
if(pid==0){
printf("I am child,pid=%d\n",getpid());
break;
}
}
sleep(i);
if(i==5){
for(i=0;i<5;++i){
pid_t wpid = wait(NULL);
printf("wpid = %d\n",wpid);
}
while(1){
sleep(1);
}
}
return 0;
}
运行结果:
waitpid循环回收:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int n =5;
int i =0;
pid_t pid;
for(i=0;i<5;++i){
pid = fork();
if(pid==0){
break;
}
}
if(i==5){
// parent
printf("I am parent!\n");
// 如何使用waitpid回收? -1 代表子进程都死了,都收了
while(1){
pid_t wpid = waitpid(-1,NULL,WNOHANG);
if(wpid == -1){
break;
}else if(wpid > 0){
printf("wairpid wpid = %d\n",wpid);
}
}
while(1){
sleep(1);
}
}
if(i<5){
// sleep(i);
printf("I am child,i = %d,pid = %d\n",i,getpid());
}
return 0;
}
运行结果: