(二十五)进程——wait与waitpid、僵尸进程与孤儿进程

来源:互联网 发布:淘宝店铺如何查排名 编辑:程序博客网 时间:2024/05/16 13:39

  僵尸进程: 子进程退出,父进程没有回收子进程资源(PCB),则子进程变成僵尸进程。
  孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为1号进程init进程,称为init进程领养孤儿进程。
  为了防止以上情况的出现,就需要使用wait或waitpid在父进程退出前回收子进程的资源,而父进程应当在回收完所有子进程的资源后才能退出。

#include <sys/types.h>#include <sys/wait.h>pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);status是一个传出参数。waitpid的pid参数选择:< -1 回收指定进程组内的任意子进程= -1 回收任意子进程= 0  回收和当前调用waitpid一个组的所有子进程> 0  回收指定ID的子进程


  一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程
  如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程。任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了,为了观察到僵尸进程,我们自己写一个不正常的程序,父进程fork出子进程,子进程终止,而父进程既不终止也不调用wait清理子进程:

#include <unistd.h>#include <stdlib.h>int main(void){    pid_t pid=fork();    if(pid<0) {        perror("fork");        exit(1);    }    if(pid>0) { /* parent */        while(1);    }    /* child */    return 0;}

  对于wait或waitpid函数若调用成功则返回清理掉的子进程id若调用出错则返回-1。父进程调用wait或waitpid时可能会出现一下的情况:

  1. 阻塞(如果它的所有子进程都还在运行)。
  2. 带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信息)。
  3. 出错立即返回(如果它没有任何子进程)。


  对于wait和waitpid两个函数,有所不同的是:

  1. 如果父进程的所有子进程都还在运行,调用wait将使父进程阻塞,而调用waitpid时如果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。
  2. wait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程。


  所以,调用wait和waitpid不仅可以获得子进程的终止信息,还可以使父进程阻塞等待子进程终止,起到进程间同步的作用。如果参数status不是空指针,则子进程的终止信息通过这个参数传出,如果只是为了同步而不关心子进程的终止信息,可以将status参数指定为NULL。
  



例子:

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>int main(void){    pid_t pid,pid_c;    int n = 10;    pid = fork();    if(pid > 0 )    {/* in parent */        while(1)        {            printf("I am parent %d\n",getpid());            //wait是一个阻塞函数,等待回收子进程资源,如果没有子进程,wait返回-1            pid_c = wait(NULL);            printf("wait for child %d\n",pid_c);            sleep(1);        }    }    else if(pid == 0)    {/* in child */        printf("I am child %d\n",getpid());        sleep(10);    }    return 0;}运行结果:I am parent 4797I am child 4798wait for child 4798I am parent 4797wait for child -1I am parent 4797wait for child -1I am parent 4797···

孤儿进程的例子:

#include <stdio.h>#include <unistd.h>#include <stdlib.h>int main(void){    pid_t pid;    int n=10;    pid = fork();    if(pid > 0)    {//创建完之后父进程就退出了        printf("I am parent\n");        exit(0);    }    else if(pid == 0)    {//此时父进程退出,子进程被init程序接管,该进程的父进程号变成1        while(n--)        {            printf("I am %d, my parent is %d\n",getpid(),getppid());            sleep(1);        }    }    else    {        perror("fork");        exit(-1);    }    return 0;}运行结果:I am 4813, my parent is 1I am 4813, my parent is 1I am 4813, my parent is 1I am 4813, my parent is 1I am 4813, my parent is 1I am 4813, my parent is 1

waitpid使用举例:

#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>int main(void){    pid_t pid;    pid = fork();    if (pid < 0)    {        perror("fork failed");        exit(1);    }    if (pid == 0)    {//in child        int i;        for (i = 3; i > 0; i--) {            printf("This is the child %d\n",getpid());            sleep(1);        }        exit(3);//返回3,运行时可以看到        //子进程睡眠3秒之后就退出了    }    else    {//in parent        int stat_val;        waitpid(pid, &stat_val, 0);//以阻塞方式等待回收子进程,第三个参数0,表示阻塞方式        if (WIFEXITED(stat_val))//正常退出            printf("Child exited with code %d\n", WEXITSTATUS(stat_val));        else if (WIFSIGNALED(stat_val))//查看被什么信号关闭    printf("Child terminated abnormally, signal %d\n", WTERMSIG(stat_val));    }    return 0;}
5 0