wait() 函数和 waitpid() 函数

简介: wait() 函数和 waitpid() 函数

孤儿进程与僵尸进程中介绍了僵尸进程的产生,进程的僵尸态与死亡态区别在于是否回收资源,在Linux系统中应该避免僵尸进程的产生。产生僵尸进程的原因是子进程在退出时,其父进程没有退出,这时父进程并不会主动回收其资源,那么该进程则会成为僵尸进程。


同时,在笔记进程的创建中,创建子进程的示例代码中,可以看出当父子进程没有做任何延时或循环不退出时,则不会产生僵尸进程。这说明了两种可能性:

  1. 如果子进程先退出,父进程后退出,那么退出的父进程会将子进程的资源回收,那么不会产生僵尸进程
  2. 如果父进程先退出,子进程成为孤儿进程,孤儿进程退出,资源将会被init/systemd进程回收。


这个时候通常处理僵尸进程不能寄希望于将其父进程也退出,这可能会导致父进程不能拥有自由的生命周期。在 Linux 中,通常可以选择 wait() 函数及 waitpid() 函数用来完成对僵尸进程的资源的回收。


wait() 函数

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);点击复制复制失败已复制


wait() 函数被用来执行等待,直到其子进程终止。也就是说 wait() 函数可用于使父进程阻塞,等待子进程退出,一旦子进程退出,则 wait() 函数立即返回,并获得子进程的退出时的状态值,并回收子进程所使用的各种资源,以避免子进程称为僵尸进程。


以下示例展示 wait() 函数的使用:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define errlog(errmsg)                                                         \
  perror(errmsg);                                                              \
  printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);                \
  return -1;
int main(int argc, const char *argv[]) {
  pid_t pid;
  int i = 3;
  pid = fork();
  if (pid < 0) {
    errlog("fork error");
    return -1;
  } else if (pid == 0) {
    /* child */
    printf("The child precess, id = %d parent id = %d\n", getpid(), getppid());
    while (i > 0) {
      sleep(1);
      printf("child...\n");
      i--;
    }
  } else {
    /* parent */
    int status;
    wait(&status);
    printf("The parent process, id = %d\n", getpid());
    while (1) {
    }
  }
  return 0;
}点击复制复制失败已复制


编译运行,结果如下:

$ gcc main.c && ./a.out 
The child precess, id = 14765 parent id = 14764
child...
child...
child...
The parent process, id = 14764点击复制复制失败已复制


由运行结果可知,父进程使用 wait() 函数执行等到子进程执行 3 秒之后退出,此时 wait() 函数立刻返回,返回值为子进程的 ID ,变为非阻塞,并立即回收子进程的资源。


通过终端输入 $ ps axj 可以明显看到,僵尸进程并没有产生,同时父进程也并没有退出。


waitpid()函数

wait() 函数使用存在诸多限制,而设计 waitpid() 函数则可以突破这种限制。

如果父进程已经创建了多个子进程,使用 wait() 函数将无法等到某个特定的子进程的完成,只能按顺序等待下一个子进程的终止。


如果子进程没有退出,则 wait() 函数总是保持阻塞。故此,使用 wait() 函数只能发现那些已经终止的子进程,而 waitpid() 函数则突破了这种限制。

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);点击复制复制失败已复制


waitpid() 函数被用来关注子进程的状态是否发生变化。这些状态的变化包括子进程终止子进程被一个信号停止子进程被一个信号恢复。如果子进程的状态变化为子进程退出,那么 waitpid() 函数可以对子进程的资源进行回收,让子进程的资源得以释放。


waitpid() 函数的参数及返回值相对于 wait() 函数更加复杂,具体如下表所示:

参数 含义
pid 占位符
<-1, 用于等待进程组中的任意一个子进程(该进程组ID等于pid的绝对值)
-1, 用于等待调用进程的任意一个子进程
0, 用于等待进程组中的任意一个子进程(该进程组ID等于调用进程的ID),即调用进程是该进程组的组长
>0, 用于等待子进程ID等于pid的子进程
status 同wait()函数功能一致,用于接收子进程退出时的状态值
options 0,同wait()函数功能一致,使函数阻塞,等待子进程状态发生改变
WNOHANG,执行非阻塞,即如果子进程没有退出,函数本身不阻塞,直接获得返回值为0,此时子进程资源不会被回收。如果此时子进程已经退出,函数同样不阻塞,立刻返回,返回值为子进程的ID,并回收其资源


接下来展示 waitpid() 函数使用非阻塞的情况。代码的设计思路如下所示:

微信截图_20221209152404.png


该程序的代码具体实现如下所示:

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#define errlog(errmsg)                                                         \
  perror(errmsg);                                                              \
  printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);                \
  return -1;
int main(int argc, const char *argv[]) {
  pid_t pid;
  pid = fork();
  if (pid < 0) {
    errlog("fork error");
    return -1;
  } else if (pid == 0) {
    /* child */
    printf("The child precess, id = %d parent id = %d\n", getpid(), getppid());
    sleep(5);
    exit(0);
  } else {
    /* parent */
    int status;
    pid_t ret;
    while ((ret = waitpid(pid,&status,WNOHANG))==0)
    {
      sleep(1);
      printf("child has not been exited\n");
    }
    if(ret == pid){
      printf("child has been recycled\n");
    }
    printf("The parent process, id = %d\n",getpid());
    exit(0);
  }
  return 0;
}点击复制复制失败已复制


编译并运行,结果如下:

$ gcc main.c && ./a.out 
The child precess, id = 18891 parent id = 18890
child has not been exited
child has not been exited
child has not been exited
child has not been exited
child has not been exited
child has been recycled
The parent process, id = 18890点击复制复制失败已复制


根据运行结果,当子进程未退出时, waitpid() 函数不阻塞立即返回,获得的返回值为 05 秒之后,子进程退出,此时执行 waitpid() 函数,捕获子进程退出,并获得其 ID ,回收子进程的资源。

目录
相关文章
|
安全 Java 数据库连接
基于dataX实现多种数据源数据汇聚(二)
上一篇文章提到在数据中台项目实践过程中,基于dataX实现数据汇聚的一些使用心得,在众多项目中,发现一个趋势,国产数据库的发展趋势,越来越多的企业要求国产化保障核心资产的安全。本章节主要介绍国产数据的安装、连接、与归集的知识。涉及场景的国产数据库如下: 1、达梦 2、人大金仓(后续补充) 3、南大通用(后续补充)
2967 0
基于dataX实现多种数据源数据汇聚(二)
|
存储 安全 数据管理
python如何批量创建文件与文件夹
python如何批量创建文件与文件夹
591 0
|
算法 Java
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
531 22
|
7月前
|
缓存 Linux 云计算
OOM 杀进程 or 应用卡顿?该如何抉择
阿里云操作系统控制台推出了 FastOOM 功能,支持节点以及 Pod 级别的用户态 OOM 配置,通过提前介入杀进程的方式避 Near-OOM 导致的抖动夯机。
357 0
|
存储 缓存 Android开发
Android系统分区与升级
Android系统分区与升级
603 4
|
设计模式 消息中间件 存储
性能优化:关于缓存的一些思考
利用缓存做性能优化的案例非常多,从基础的操作系统到数据库、分布式缓存、本地缓存等。它们表现形式各异,却有着共同的朴素的本质:弥补CPU的高算力和IO的慢读写之间巨大的鸿沟。
性能优化:关于缓存的一些思考
|
存储 监控 Java
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(二)
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用
639 1
|
负载均衡 Java 数据处理
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(三)
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用
1044 2
|
中间件 Linux 芯片
一张图秒懂嵌入式Linux系统的启动流程
一张图秒懂嵌入式Linux系统的启动流程
1181 0
|
存储
ETCD系列之二:部署集群
ETCD系列之二:部署集群 1. 概述 想必很多人都知道ZooKeeper,通常用作配置共享和服务发现。和它类似,ETCD算是一个非常优秀的后起之秀了。本文重点不在描述他们之间的不同点。首先,看看其官网关于ETCD的描述[1]: A distributed, reliable key-va
20974 156

热门文章

最新文章