Linux系统编程——僵尸进程及wait/waitpid函数

来源:互联网 发布:如何增加福报 知乎 编辑:程序博客网 时间:2024/05/17 02:37

僵尸进程

一般来说,当父进程fork()了一个子进程之后,它有义务把子进程回收。但如果子进程运行完毕后,父进程尚未回收,此时的子进程残留信息(PCB)存放于内核中,变成僵尸进程。(若父进程先于子进程结束,则子进程成为孤儿进程。)
我们知道,子进程的结束和父进程的运行是异步的,那么,父进程会不会不知道子进程已经结束了呢?UNIX中存在一种机制,一个进程调用exit()来结束自己的生命,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但还保留了PCB存放于进程列表中,PCB主要记录了进程的退出状态和进程号。当父进程想要知道子进程的退出状态时,就可以随时查看,进而释放PCB。其实,每个进程都会变成僵尸进程。只是,当父进程快速的回收子进程的时候,我们察觉不到,而父进程还没回收的子进程,则残留在内核中成为了真正的僵尸进程。
通过top指令我们可以看到,僵尸进程并不占用内存资源,但这并不意味着它就没有危害。系统的进程号是有限的,如果产生大量的僵尸进程,则会占用大量的进程号,这就会导致新创建的进程无进程号可用。

僵尸进程代码如下:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){    pid_t pid;    pid = fork();    if (pid == 0)    {        printf("-----child, my parent = %d, going to sleep 10s\n", getppid());        sleep(10);        printf("-------------child die---------\n");    }    else if (pid > 0)    {        while (1)        {            printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);            sleep(1);        }    }    else    {        perror("fork");        return -1;    }    return 0;}

运行结果:
这里写图片描述

另外开一个shell,使用ps aux命令查看进程:
这里写图片描述
红框圈起来的就是僵尸进程,<defunct>的意思是死亡。

使用top -p 404 -p 405命令查看僵尸进程的内存占用情况:
这里写图片描述
S是状态栏,Z(zombie)代表僵尸进程,S(sleeping)代表父进程。在虚拟内存(VIRT)和物理内存(RES)一栏,父进程均有占用,而僵尸进程则占用为0。
既然僵尸进程对系统有一定的危害,那么有没有什么办法回收呢?这就要用到我们下面要讲到的wait和waitpid函数。

wait()函数

一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
父进程调用wait函数来回收子进程,该函数的作用主要表现在:

  • 阻塞等待子进程退出
  • 回收子进程残留资源
  • 获取子进程结束状态(退出原因)

wait函数的原型:pid_t wait(int *status)
成功:清理掉的子进程ID; 失败:-1(没有子进程)

注:参数 status 是一个整形指针传出参数,用来保存进程的退出(终止)状态。如果status不是一个空指针,则终止进程的终止状态将存储在该指针所指向的内存单元中。如果不关心终止状态,可以将 status参数设置为NULL。而子进程的进程ID将作为函数的返回值返回。

对僵尸进程的代码进行稍微修改如下:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){    pid_t pid, wpid;    pid = fork();    if (pid == 0)    {        printf("-----child, my parent = %d, going to sleep 10s\n", getppid());        sleep(10);        printf("-------------child die---------\n");    }    else if (pid > 0)    {        wpid = wait(NULL);        if (wpid == -1)        {            perror("wait error:");            exit(1);        }        while (1)        {            printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);            sleep(1);        }    }    else    {        perror("fork");        return -1;    }    return 0;}

运行结果:
这里写图片描述

使用ps aux命令查看进程列表:
这里写图片描述

可见,此时僵尸进程已被回收。

当参数status带着子进程的终止信息传出来时,因该指针指向的是整形,所以需要借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下两组:

  1. WIFEXITED(status) 为非0 → 进程正常结束
    WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
  2. WIFSIGNALED(status) 为非0 → 进程异常终止
    WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号

注:在Linux中,所有的进程异常退出都是由信号所导致的。

使用第一组宏函数代码如下(正常结束):

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){    pid_t pid, wpid;    int status;    pid = fork();    if (pid == 0)    {        printf("-----child, my parent = %d, going to sleep 3s\n", getppid());        sleep(3);        printf("-------------child die---------\n");        exit(76);        //return 100;    }    else if (pid > 0)    {        wpid = wait(&status);        if (wpid == -1)        {            perror("wait error:");            exit(1);        }        if (WIFEXITED(status))        {            printf("child exit with %d\n", WEXITSTATUS(status));        }        while (1)        {            printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);            sleep(1);        }    }    else    {        perror("fork");        return -1;    }    return 0;}

运行结果:
这里写图片描述

使用第二组宏函数代码如下(异常结束):

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void){    pid_t pid, wpid;    int status;    pid = fork();    if (pid == 0)    {        printf("-----child, my parent = %d, going to sleep 30s\n", getppid());        sleep(30);        printf("-------------child die---------\n");        exit(76);        //return 100;    }    else if (pid > 0)    {        wpid = wait(&status);        if (wpid == -1)        {            perror("wait error:");            exit(1);        }        if (WIFEXITED(status))        {            printf("child exit with %d\n", WEXITSTATUS(status));        }        if (WIFSIGNALED(status))        {            printf("child killed by %d\n", WTERMSIG(status));        }        while (1)        {            printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);            sleep(1);        }    }    else    {        perror("fork");        return -1;    }    return 0;}

运行结果:
打开两个终端,其中一个终端运行该程序:

这里写图片描述

另外一个终端执行kill pidkill -9 pid命令。
这里写图片描述

这里写图片描述

程序返回:
这里写图片描述

从这些结果中我们可以看到,当kill pid时,返回的信号是15,而当kill -9 pid时返回的信号是9。这是什么原因?这就要从kill命令说起,从下图中我们可以看到,

这里写图片描述

这里写图片描述

kill pid对应的信号编号是15,而kill -9 pid对应的信号编号是9。

waitpid()函数

wait函数一次只能回收一个子进程,假设现在有5个子进程都死亡了,那么wait函数回收的子进程到底是哪一个呢?如果父进程独宠第5个子进程,并不想管其它子进程的死活呢?这就需要使用waitpid函数,它能回收指定的子进程,并可以不阻塞。
函数原型:
pid_t waitpid(pid_t pid, int *status, int options);
成功:返回回收的子进程ID; 失败:-1(无子进程)
参数options可以设置阻塞(0)或非阻塞(WNOHANG),如果设置成阻塞,则等同于wait;若设置成非阻塞,则父进程不会阻塞等待子进程结束,而是直接返回。若参数options为WNOHANG(非阻塞),且当前子进程正在运行,则函数返回0.
对于参数pid:

  1. >0 回收指定ID的子进程
  2. -1 回收任意子进程(相当于wait)
  3. 0 回收和当前调用waitpid一个组的所有子进程
  4. <-1回收指定进程组内的任意子进程

注:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

以下程序主要实现的功能是:创建5个子进程,并用waitpid函数来回收它们。

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>int main(void){    int n = 5, i;        //默认创建5个子进程    pid_t pid, wpid;    for(i = 0; i < n; i++)    //出口1,父进程专用出口    {        pid = fork();        if (pid == 0)        {            break;           //出口2,子进程出口        }    }    if (n == i)              //父进程执行    {        sleep(n);        printf("I am parent, pid = %d, gpid = %d\n", getpid(), getgid());        do        {            wpid = waitpid(-1, NULL, WNOHANG);            if (wpid > 0)            {                n--;            }            sleep(1);        }while(n > 0);        printf("wait finish\n");    }    else                    //子进程执行    {        sleep(i);        printf("I am %dth child, pid = %d, gpid = %d\n", i+1, getpid(), getgid());    }    return 0;}

输出结果:
这里写图片描述

ps aux命令查看进程,没有出现僵尸进程,回收成功。

阅读全文
0 0
原创粉丝点击