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

目录
相关文章
|
11月前
|
消息中间件 Unix Linux
进程通信 软中断 signal()解读
进程通信 软中断 signal()解读
|
1月前
【进程控制】超详细讲解wait和waitpid的原理(结合代码)
【进程控制】超详细讲解wait和waitpid的原理(结合代码)
|
1月前
|
存储 缓存 安全
C语言进程(第二章,wait,sleep,waitpid,pthread_mutex_lock,pthread_mutex_unlock)
C语言进程(第二章,wait,sleep,waitpid,pthread_mutex_lock,pthread_mutex_unlock)
64 0
pthread_detach函数
指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)进程结束后,线程也会结束。网络、多线程服务器常用
|
物联网 Linux 开发者
Waitpid 函数|学习笔记
快速学习 Waitpid 函数,“Waitpid 函数”,也是“等”。虽然功能和 “Wait” 相同,但是 “Waitpid” 实现的功能比 “Wait” 实现的功能更多。
131 0
Waitpid 函数|学习笔记
|
物联网 Linux C语言
Wait 函数|学习笔记
快速学习 Wait 函数
196 0
Wait 函数|学习笔记
|
物联网 Linux 开发者
signal 函数1|学习笔记
快速学习 signal 函数1
101 0
signal 函数1|学习笔记
|
Linux 调度
vfork() 函数
vfork() 函数
54 0
|
存储 物联网 Linux
Pthread_exit 线程退出|学习笔记
快速学习 Pthread_exit 线程退出
209 0