进程等待(wait和wait函数)【Linux】

简介: 进程等待(wait和wait函数)【Linux】


进程等待的概念

  进程等待就是通过 wait/waitpid的方式,让父进程对子进程进行等待子进程退出并且将子进程进行资源回收的过程。

进程等待的必要性

 为什么要进行进程等待?

   1. 因为父子进程的执行顺序是不确定的,可能父进程会比子进程先一步退出,此时就会让子进程成为僵尸进程,子进程已经退出,但是依旧占用着内存空间,造成内存泄漏。

   2. 由于一般来说,子进程的出现是为了协助父进程完成一些任务,子进程就像是父进程的手下小兵一样,因此子进程完成父进程交给的任务,退出后也一定要给父进程一个答复(子进程是否完成,是否出现异常等)。

返回值:

成功返回被等待进程pid,失败返回-1。

参数:

输出型参数,获取子进程退出状态,不关心则可以设置成为NULL 3. 父进程通过进程等待的方式,回收退出的子进程的资源,获取子进程的退出信息(也就是2)。

进程等待的方法

wait函数

#include <sys/types.h>

#inlclude <sys/wait.h>

pid_t wait(int* status);

返回值:

成功返回被等待进程pid,失败返回-1。

参数:

输出型参数,获取子进程退出状态,不关心则可以设置成为NULL,之后和waitpid一起介绍。

 如何进行等待?

  1. 调用wait函数,进程等待子进程的退出。
  2. 当子进程退出后,会变成一个僵尸进程(短暂的存在不会造成什么影响),然后通过wait函数,进程状态从僵尸状态(Z)变成死亡状态(X)。
  3. 如果子进程没有退出,父进程必须阻塞等待,直到子进程变成Z,wait自动回收返回。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(5);
        std::cout << "child: " << getpid() << std::endl;
        exit(0);
    }
    if (fd > 0)
    {
        sleep(1);
        std::cout << "parent: " << getpid() << std::endl;
    }
    sleep(50);
    return 0;
}

子进程退出后由于父进程没有等待回收,子进程变成僵尸进程:

调用wait后,子进程就会回收释放了:

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(5);
        std::cout << "child: " << getpid() << std::endl;
        exit(0);
    }
    if (fd > 0)
    {
        sleep(1);
        std::cout << "parent: " << getpid() << std::endl;
        wait(NULL);
    }
    sleep(50);
    return 0;
}

waitpid函数

#include <sys/types.h>

#inlclude <sys/wait.h>

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

  当正常返回的时候waitpid返回收集到的子进程的进程ID;

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

  如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

 pid:

  Pid=-1,等待任一个子进程。与wait等效。

  Pid>0.等待其进程ID与pid相等的子进程。

 status:

  WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

  WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

 options:

  WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进

程的ID。

  status其实就是个int型的输出型参数,一共32bit,但是我们只需要关注低位的16bit就行。下图是16bit, status的示意图:

使用方法:

  • 判断是否正常终止,可以提取低7位,如果全0就是正常终止:
    (status & 0x7F)
  • 提取退出状态:( (status >> 8) & 0xFF)
  • 提取终止状态:(status & 0x7F)
  • 提取core dump: ( (status >> 7) & 0x1)

除了使用上面的方法,也可以:

  • 判断是否正常终止:WIFEXITED(status);若为正常终止子进程返回的状态,则为真.
  • 提取子进程退出码:WEXITSTATUS(status); 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(5);
        std::cout << "child: " << getpid() << std::endl;
        exit(8);
    }
    if (fd > 0)
    {
        int st;
        int ret = waitpid(fd, &st, 0);
        if (ret > 0 && (st & 0x7F) == 0)
        {
            std::cout << "退出状态:" << ((st >> 8) & 0xFF) << std::endl;
        }
        else if (ret > 0)
        {
            std::cout << "终止信号:" << (st & 0x7F) << std::endl;
        }
        std::cout << "parent: " << getpid() << std::endl;
    }
    return 0;
}

正常终止:

被kill杀死:上面的代码中子进程存在的时间延长

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(50);
        std::cout << "child: " << getpid() << std::endl;
        exit(8);
    }
    if (fd > 0)
    {
        int st;
        int ret = waitpid(fd, &st, 0);
        if (ret > 0 && (st & 0x7F) == 0)
        {
            std::cout << "退出状态:" << ((st >> 8) & 0xFF) << std::endl;
        }
        else if (ret > 0)
        {
            std::cout << "终止信号:" << (st & 0x7F) << std::endl;
        }
        std::cout << "parent: " << getpid() << std::endl;
    }
    return 0;
}

  验证时通过while :; do ps axj | head -1 && ps axj | grep test11 | grep -v "grep"; sleep 1; echo "###############"; done的指令来过去子进程的pid。

非阻塞等待和阻塞等待的对比

阻塞等待:

#include <iostream>
#include <unistd.h>
#include <sys/wait.h
> 这里是引用
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(10);
        std::cout << "child: " << getpid() << std::endl;
        exit(8);
    }
    if (fd > 0)
    {
        int ret;
        int st;
        do
        {
            ret = waitpid(fd, &st, 0);
            std::cout << "如果是非阻塞等待,则指向这个打印" << std::endl;
        } while (ret == 0);
        if (ret > 0 && (st & 0x7F) == 0)
        {
            std::cout << "退出状态:" << ((st >> 8) & 0xFF) << std::endl;
        }
        else if (ret > 0)
        {
            std::cout << "终止信号:" << (st & 0x7F) << std::endl;
        }
    }
    return 0;
}

  可以看到由于waitpid函数是这样的:waitpid(fd, &st, 0);option也就是第三个参数为0,表示阻塞等待,所以如果是非阻塞等待,则指向这个打印这句话只执行了一次。

非阻塞等待

  非阻塞等待也叫非阻塞轮询式等待,既然是轮询,就需要使用while循环的进行waitpid,否则如果waitpid没有等到子进程,就会执行下面的指令,直接执行完然后退出父进程。就不能回收子进程资源了。使用while时,需要注意这句话:如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        sleep(10);
        std::cout << "child: " << getpid() << std::endl;
        exit(8);
    }
    if (fd > 0)
    {
        int ret;
        int st;
        do
        {
            ret = waitpid(fd, &st, WNOHANG);
            std::cout << "如果是非阻塞等待,则指向这个打印" << std::endl;
        } while (ret == 0);
        if (ret > 0 && (st & 0x7F) == 0)
        {
            std::cout << "退出状态:" << ((st >> 8) & 0xFF) << std::endl;
        }
        else if (ret > 0)
        {
            std::cout << "终止信号:" << (st & 0x7F) << std::endl;
        }
    }
    return 0;
}

  可以看到由于waitpid函数是这样的:waitpid(fd, &st, WNOHANG);option也就是第三个参数为WNOHANG,表示非阻塞等待,所以如果是非阻塞等待,则指向这个打印这句话只执行了数次。


   😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

相关文章
|
11月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
387 67
|
10月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
300 16
|
10月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
208 20
|
9月前
|
Shell Linux C语言
函数和进程之间的相似性
在一个C程序可以fork/exec另一个程序,其过程是先fork一个子进程,然后让子进程使用exec系列函数将子进程的代码和数据替换为另一个程序的代码和数据,之后子进程就用该程序的数据执行该程序的代码,从而达到程序之间相互调用的效果。在学了C语言、C++或是JAVA等高级语言,你会知道,在这些语言中的函数是可以相互进行见调用的,但是在学习了Linux的前面的知识后,你就会有意无意的认识到其实进程也是与函数有相同之处的,进程之间也是可以相互调用的。程序之间相互调用带来的好处之一。那么下面就将这部分内容扩展。
158 0
|
9月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
224 0
|
9月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
293 0
|
9月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
176 0
|
9月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
235 0
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
465 4
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。