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 ,回收子进程的资源。

目录
相关文章
|
存储 安全 数据管理
python如何批量创建文件与文件夹
python如何批量创建文件与文件夹
556 0
|
6月前
|
缓存 Linux 云计算
OOM 杀进程 or 应用卡顿?该如何抉择
阿里云操作系统控制台推出了 FastOOM 功能,支持节点以及 Pod 级别的用户态 OOM 配置,通过提前介入杀进程的方式避 Near-OOM 导致的抖动夯机。
338 0
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
884 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
存储 缓存 Android开发
Android系统分区与升级
Android系统分区与升级
511 4
|
搜索推荐 数据挖掘 UED
分享一些利用商品详情数据挖掘潜在需求的成功案例
本文介绍了四个成功利用商品详情数据挖掘潜在需求的案例:亚马逊通过个性化推荐系统提升销售额;小米通过精准挖掘用户需求优化智能硬件生态链;星巴克推出定制化饮品服务满足用户多样化口味;美妆品牌利用数据改进产品配方和设计,制定针对性营销策略。这些案例展示了数据挖掘在提升用户体验和商业价值方面的巨大潜力。
|
设计模式 消息中间件 存储
性能优化:关于缓存的一些思考
利用缓存做性能优化的案例非常多,从基础的操作系统到数据库、分布式缓存、本地缓存等。它们表现形式各异,却有着共同的朴素的本质:弥补CPU的高算力和IO的慢读写之间巨大的鸿沟。
性能优化:关于缓存的一些思考
|
存储 监控 Java
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(二)
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用
627 1
|
中间件 Linux 芯片
一张图秒懂嵌入式Linux系统的启动流程
一张图秒懂嵌入式Linux系统的启动流程
1125 0
凭借左程云(左神)的这份 “程序员代码面试指南”我入职了字节
左程云,本科就读于华中科技大学、硕士毕业于在芝加哥大学。先后在IBM、百度、GrowingIO和亚马逊工作,是一个刷题7年的算法爱好者,也是马士兵教育的算法授课老师。2014年起专职做程序员算法和数据结构培训,代码面试培训,刷题交流等相关工作。
|
Android开发
你要了解的USB接口知识总结
最近项目中有设计到USB接口,把一些常用的USB名词、常识以及关系总结一下。
1497 0