【Linux进程】六、wait()函数——子进程回收

简介: 【Linux进程】六、wait()函数——子进程回收

1. 僵尸进程与孤儿进程

孤儿进程:父进程结束,子进程被init进程收养。

僵尸进程:子进程结束,父进程没有回收子进程的资源(PCB),这个资源必须要由父进程回收,否则就形成僵尸进程。

测试1: 孤儿进程测试

/************************************************************
  >File Name  : orphan.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月19日 星期四 20时53分41秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
  pid_t pid = fork();
  if(pid == 0)
  {
    while(1)
    {
      printf("child: %d, ppid: %d\n", getpid(), getppid());
      sleep(1);
    }
  }
  if(pid > 0)
  {
    printf("parent: %d\n", getpid());
    sleep(3); 
  }
  return 0;
}

我们看到,子进程的父进程ID在3秒后变成了1,这说明父进程结束后,它变成了孤儿进程,并被init进程收养,使用kill命令基于可以杀死孤儿进程。

测试2: 僵尸进程测试

/************************************************************
  >File Name  : zombie.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月19日 星期四 20时54分20秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
  pid_t pid = fork();
  if(pid == 0)
  {
    printf("child: %d, ppid: %d\n", getpid(), getppid()); 
    sleep(1);
  }
  if(pid > 0)
  {
    while(1)
    {
      printf("parent: %d\n", getpid()); 
      sleep(1);
    }
  }
  return 0;
}

我们可以通过ps命令查看僵尸进程

图中红色标出的三个地方Z+、[]、default都可以表明这是僵尸进程,另外Z+是进程类型的一个表示,可以通过 man ps 查看,我们可以通过 man ps 进入帮助手册,然后在命令行输入 /zombie 来搜索zombie相关的信息。

僵尸进程是不能用kill杀死的,因为kill命令是终止进程,而僵尸进程已经终止了。我们知道僵尸进程的资源需要由父进程去回收,那么我们在这种情况下如何回收僵尸进程的资源呢?方法就是杀死父进程,父进程被杀死后,由init接管子进程并回收子进程资源。

2. wait()函数与waitpid()函数

2.1 wait()函数

一个进程在终止的时候会关闭所有的文件描述符,释放在用户空间分配的内存,但是它的PID还保留着,内核在其中保存了一些信息:如果进程是正常终止则保存进程退出状态;如果进程是异常终止,则保存导致该进程终止的那个信号。这个进程的父进程可以调用wait()或者waitpid()来获取这些信息,然后彻底清除这个进程。我们知道,一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell进程是它的父进程,当它终止的时候shell调用wait()或waitpid()得到它的退出状态,同时彻底清除这个进程。父进程调用wait()函数可以回收子进程终止信息,wait()函数功能主要有三个:阻塞等待子进程退出;回收子进程残留资源;获取子进程退出状态(退出原因)。

  • 包含头文件及函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
/*
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
*/
  • 函数描述
    wait()函数用于回收子进程,获取子进程的终止原因,如果子进程没有终止,那么将会阻塞等待子进程的终止。
  • 函数参数
  • status:传出参数(C语言一级指针做输出)
WIFEXITED(status) /*wait if exited 等待是否退出*/
WEXITSTATUS(status) /*wait exit status 退出原因*/
WIFSIGNALED(status) /*wait if signaled 是否被信号杀死*/
WTERMSIG(status)  /*wait term sugnaled 被几号信号杀死的*/
WCOREDUMP(status)    
WIFSTOPPED(status)
WSTOPSIG(status)    
WIFCONTINUED(status)     
  • 根据status判断子进程终止原因
  • WIFEXITED(status)判断子进程是否正常退出;
  • WIFEXITED(status)为真表示正常退出,使用WEXITSTATUS(status)获取退出状态;
  • WIFEXITED(status)非真,表示非正常退出,使用WIFSIGNALED(status)判断是否被信号杀死;
  • WIFSIGNALED(status)为真,表示是被信号杀死,使用WTERMSIG(status) 获取杀死进程的信号;
  • 函数返回值
  • on success, returns the process ID of the terminated child; wait()函数成功返回终止的子进程的ID.
  • on error, -1 is returned. 失败返回-1.

案例测试: wait()获取子进程退出原因

/************************************************************
  >File Name  : wait_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月19日 星期四 22时45分28秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
    pid_t pid = fork();
    if(pid == 0)
    {
        printf("child: %d, ppid: %d\n", getpid(), getppid());
        sleep(3); /*子进程睡眠3秒,那么父进程中的wait函数会阻塞3秒,一直等到子进程退出*/
        return 66; /*正常退出,这个值可以被WEXITSTATUS获取到,这个值是有范围的*/
        /*exit(66); 也表示正常退出*/
    }
    if(pid > 0)
    {
        int status;
        pid_t wpid = wait(&status);
        printf("wpid: %d, cpid: %d\n", wpid, pid);
        if(WIFEXITED(status)) /*进程正常退出,获取退出原因*/
        {
            printf("child exit because: %d\n", WEXITSTATUS(status));
        }
        else /*非正常退出*/
        {
            if(WIFSIGNALED(status)) /*为真表示被信号杀死*/
            {
                printf("signal is: %d", WTERMSIG(status));
            }
            else
            {
                printf("other...\n");
            }
        }
        while(1)
        {
            sleep(3);
        }
    }
    return 0;
}

我们首先演示一下子进程的正常退出,并获取退出状态,子进程的退出状态可以用return或者exit来传递。

下面我们在子进程中增加一个循环,然后用信号杀死子进程

if(pid == 0)
    {
        printf("child: %d, ppid: %d\n", getpid(), getppid());
        sleep(2); /*子进程睡眠3秒,那么父进程中的wait函数会阻塞3秒,一直等到子进程退出*/
        while(1)
        {
          printf("child: %d, ppid: %d\n", getpid(), getppid());
          sleep(1);
        }
    }

重新编译运行,并开启另一个shell,使用 kill -9 杀死子进程

获取到杀死进程的信号,正好是9号信号,如果直接使用 kill pid 默认使用的是15号信号。

2.2 waitpid()函数

  • 包含头文件及函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
  • 函数描述
    The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state.
  • 函数参数
  • pid:
  • 小于 -1:meaning wait for any child process whose process group ID is equal to the absolute value of pid. 回收一个组的子进程,使用时把组ID(一般是父进程ID)传给pid参数,就可以使用waitpid()回收这个进程组的所有子进程。
  • -1:meaning wait for any child process. 回收所有,任何子进程,这是最常用的取值,把所有子进程都回收。
  • 0:meaning wait for any child process whose process group ID is equal to that of the calling process. 回收和调用进程组ID相同的组内的子进程。
  • 大于0:meaning wait for the child whose process ID is equal to the value of pid. 回收指定的进程pid。
  • status:传出参数,同wait()函数
  • options:选项
  • WNOHANG: return immediately if no child has exited. wait no hang,如果子进程没有结束,立即返回,不会挂起等待(wait函数如果子进程没有退出会阻塞等待)。如果options参数填0,那么和wait()函数一样会挂起等待子进程结束。
  • WUNTRACED: also return if a child has stopped (but not traced via ptrace(2)). Status for traced children which have stopped is provided even if this option is not specified.
  • WCONTINUED: also return if a stopped child has been resumed by delivery of SIGCONT.
  • 函数返回值
  • on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned. 如果设置了WNOHANG选项,并且没有子进程退出则返回0,如果有子进程退出则返回退出子进程的pid。
  • On error, -1 is returned. 比如说没有子进程或子进程早就全部结束了,可能就会出错返回-1。

下面通过例子演示waitpid()函数的用法。

/************************************************************
  >File Name  : waitpid_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月20日 星期五 16时31分35秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
    pid_t pid = fork();
    if(pid == 0)
    {
        printf("child: %d\n", getpid());
        sleep(2);
    }
    if(pid > 0)
    {
        printf("parent: %d\n", getpid());
        int ret = waitpid(-1, NULL, WNOHANG);
        printf("ret: %d\n", ret);
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

为什么使用了waitpid()函数还会产生僵尸进程呢,这是因为在waitpid()函数中使用了选项参数WNOHANG,而子进程中有一个睡眠函数,子进程睡眠的时候,父进程中waitpid()语句没有等到子进程结束就执行了,由于WNOHANG选项参数的存在,waitpid不会阻塞等待之进程结束,而是直接返回。当waitpid()返回父进程中后,子进程才结束,但是waitpid()已经执行完了,所以并没有回收子进程,子进程因此变成僵尸进程。

解决方法就是在一个循环中执行waitpid()函数,直到ret不等于0的时候说明子进程退出了,跳出循环。

3. 回收多个子进程

上面使用wait()函数和waitpid()函数举的例子都是回收一个子进程,有时候我们可能需要回收多个子进程,下面介绍回收多个子进程的方法。

3.1 使用wait()回收多个子进程

首先使用wait()函数来回收多个子进程,我们可以在一个for循环中等待子进程的结束,创建了几个子进程就for循环等待几次,代码如下。

/************************************************************
  >File Name  : mutipwait.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月20日 星期五 17时23分57秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
    int i = 0;
    pid_t pid;
    for(i = 0; i < 5; i++)
    {
        pid = fork();
        if(pid == 0)
        {
            printf("child: %d\n", getpid());
            break;
        }
    }
    sleep(i);
    if(i == 5) /*只有父进程可以执行到i=5*/
    {
        for(i = 0; i < 5; i++)
        {
            pid_t wpid = wait(NULL);
            printf("wpid: %d\n", wpid);
        }
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

编译运行,可以看到所有子进程都被回收。

3.2 使用waitpid()回收多个子进程

如果使用waitpid()函数,可以借助函数的参数和返回值去判断每个子进程是否回收成功。

/************************************************************
  >File Name  : mutipwaitpid.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月20日 星期五 17时45分39秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
    int i = 0;
    pid_t pid;
    for(i = 0; i < 5; i++)
    {
        pid = fork();
        if(pid == 0)
        {
            break;
        }
    }
    if(i == 5) /*只有父进程可以执行到i=5*/
    {
        printf("parent: %d\n", getpid());
        while(1) /*无限循环保证所有子进程全部回收*/
        {
            pid_t wpid = waitpid(-1/*回收任何子进程*/, NULL, WNOHANG);
            if(wpid == -1)
            {
                break; /*如果返回-1说明已经没有子进程了,退出循环*/
            }
            if(wpid > 0)
            {
                printf("wpid: %d\n", wpid); /*打印被回收的子进程的ID*/
            }
        }
        while(1)
        {
            sleep(1);
        }
    }
    if(i < 5) /*说明是子进程*/
    {
        printf("no. %d child: %d\n", i, getpid());
    }
    return 0;
}

编译执行,可以看到所有进程都被回收了

相关文章
|
10月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
296 16
|
9月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
219 0
|
9月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
288 0
|
9月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
175 0
|
9月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
235 0
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
10月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
204 20
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
458 4
|
存储 缓存 Linux
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
|
存储 Linux Shell
Linux进程概念(上)
冯·诺依曼体系结构概述,包括存储程序概念,程序控制及五大组件(运算器、控制器、存储器、输入设备、输出设备)。程序和数据混合存储,通过内存执行指令。现代计算机以此为基础,但面临速度瓶颈问题,如缓存层次结构解决内存访问速度问题。操作系统作为核心管理软件,负责资源分配,包括进程、内存、文件和驱动管理。进程是程序执行实例,拥有进程控制块(PCB),如Linux中的task_struct。创建和管理进程涉及系统调用,如fork()用于创建新进程。
401 3
Linux进程概念(上)