C语言控制进程之进程等待详解

目录
  • 进程等待的必要
  • 进程等待的方法
    • wait函数
    • waitpid函数
    • 获取子进程退出信息

进程等待的必要

当一个进程终止的时候,它的资源,比如说PCB,数据等不会被立马清理掉。它会保持在已经终止的状态,这种状态称为“僵尸状态”,直到被父进程确认。父进程wait,即父进程向内核确认子进程已经终止,可以为子进程“收尸”了,内核会把子进程的退出信息传给父进程,然后清理掉子进程的资源,这个时候子进程才算真正地终止了!

总结:

  • 父进程等待,可以获取子进程的退出信息,知道子进程的执行结果。
  • 父进程等待,可以释放子进程的资源,让子进程真正地退出,避免一直消耗系统的存储资源,造成“内存泄露”等危害。
  • 父进程等待,可以保证时序的问题,子进程先于父进程退出,避免让子进程变为孤儿进程。

进程等待的方法

wait函数

一个进程可以通过调用wait函数等待子进程。wait函数是系统调用函数。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

返回值:返回被等待进程的pid,如果等待失败,返回-1。

参数:输出型参数,可以获取子进程的退出状态,如果不需要获取子进程的退出状态,则设置为NULL。

测试:

 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <stdlib.h>
 int main(void)
 {
   pid_t id = fork(); //创建子进程
   if(id == 0)
   {
     //child
     //执行5秒
     int cnt = 5;
     while(cnt)
     {
       printf("child[%d] , cnt:%d\n", getpid(), cnt);
       sleep(1);
       cnt--;
     }
     exit(EXIT_SUCCESS);
   }
   sleep(10);
   pid_t ret = wait(NULL);
   if(ret > 0)
   {
     //wait success, ret is pid;
     printf("father wait child[%d] success\n", ret);
   }
   else{
     //wait failed.
     printf("father wait failed\n");
   }
   return 0;
 }

现象:子进程执行5秒后,终止了,但是内核没有立马清理掉它的资源,所以此时是僵尸状态,再过了5秒之后,父进程休眠完毕,然后等待子进程,确认子进程已经终止,返回子进程的pid,然后内核开始清理子进程资源,子进程真正地终止了,又过了5秒后父进程也终止了。

通过wait函数的输出型参数可以获得子进程的退出信息。

waitpid函数

waitpid函数也可以使得父进程等待子进程

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

先不关心第二个和第三个参数,第二个参数可以设置为NULL,第三个参数暂时设置为0。

第一个参数:

1、如果第一个参数pid传的是某个具体的进程的进程ID,表示等待该指定进程

2、如果第一个参数pid传的是-1,表示等待父进程的任意子进程。

第三个参数:

  • 传的是0,表示父进程是挂起等待子进程的(阻塞等待)。可以理解为“父进程在等待子进程的过程中,什么事情也没做,在干等”。
  • 传的是宏WNOHANG,表示父进程是非阻塞等待。若等待的子进程还没有终止,那么waitpid函数立即返回0,不予以等待。若等待的子进程已经正常结束,那么waitpid函数返回等待子进程的PID

wait(&status) 等价于 waitpid(-1, &status, 0)

【注意事项】

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,获得子进程退出信息,并且释放被等待子进程资源。
  • 如果在任意时刻调用wait/waitpid,子进程存在且还在正常运行,则父进程可能会发生阻塞。
  • 如果试图等待一个当前不存在的进程,wait/waitpid会调用出错,并立即返回。

获取子进程退出信息

在上述并没有具体解释参数status的作用。

  • 在wait和waitpid函数中,status的作用是一样的,它是输出型参数。
  • 如果给status传的是NULL,则表示不需要获取子进程的退出信息。
  • 如果给status传的是非NULL,则可以获取被等待进程的退出信息。

status是一个指向整形的指针。但是一个进程的退出信息那么多,怎么可能会那么简单地用一个整型就知道进程的退出信息了呢?实际上,并不是简单地看待status指向的整形,而是当作位图来看,一个整型有32位,这样就可以全面地描述被等待进程的退出信息了。

只用研究低16个比特位。

进程退出的情况有四种:

1、正常退出(自愿,代码执行完,结果正确)

2、错误退出(自愿,代码执行完,结果不正确)

3、异常退出(非自愿,代码未执行完,退出码无意义)

4、被其他进程终止(非自愿,代码未执行完,退出码无意义)

这四种情况,可以按照进程是否收到信号来分类,第一种和第二种进程未收到信号,第三和第四种进程收到信号。

当被等待进程不是被信号所终止时,低8位全是0,而次低8位则是被等待进程的退出码。

当被等待进程是被信号所终止时,低7位表示被等待进程收到的信号。

如果进程是正常终止,如何显示地知道退出码?

如果进程是收到信号而终止,如何知道它收到了什么信号?直接就是低7位表示的是进程收到的信号,如果是非法的信号,说明它没有收到信号,这个值是无效的。

测试:

 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <stdlib.h>

 int main(void)
 {
   pid_t id = fork(); //创建子进程
   if(id == 0)
   {
     //child
     //执行5秒
     int cnt =7;
     while(cnt)
     {
       printf("child[%d] , cnt:%d\n", getpid(), cnt);
       sleep(1);
       cnt--;
     }
     exit(12);
   }
   sleep(10);
   int status;
   pid_t ret = waitpid(id, &status, 0);
   if(ret > 0)
   {
     //wait success, ret is pid;
     printf("father wait child[%d] success\n", ret);
   }
   else{
     //wait failed.
   printf("father wait failed\n");
   }
   printf("get a exit num : %d\n, get a single:%d", (status >> 8) & 0XF    FFF, status & 0XFFFF);
   sleep(2);
   return 0;
 }

当然,还可以不需要进行位运算,系统提供了两个宏,可以得到退出信息

  • WIFEXITED(status):如果被等待进程正常退出则未真。
  • WEXITSTATUS(status):如果WFIEXITED(status)为真,提取被等待进程的退出码。
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <stdlib.h>
 int main(void)
 {
   pid_t id = fork(); //创建子进程
   if(id == 0)
   {
     //child
    //执行5秒
    printf("i am child precess\n");
    sleep(5);
    exit(12);
  }
  sleep(3);
  int status;
  printf("father begin wait\n");
  pid_t ret = waitpid(id, &status, 0);
  if(ret > 0)
 {
    //wait success, ret is pid;
    printf("father wait child[%d] success\n", ret);
  }
  else{
    //wait failed.
    printf("father wait failed\n");
  }
  if(WIFEXITED(status))
  {
    printf("exit code : %d\n", WEXITSTATUS(status));
  }
  else{
    printf("get a signal\n");
 }
  sleep(2);
  return 0;
}

执行结果

到此这篇关于C语言控制进程之进程等待详解的文章就介绍到这了,更多相关C语言进程等待内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言详解分析进程控制中进程终止的实现

    目录 进程退出的形式 进程退出的几种方法 进程退出的形式 进程退出的几种情况 正常退出(自愿,代码运行完其结果正确) 错误退出(自愿,代码运行完其结果不正确) 异常退出(非自愿,代码异常直接终止) 被其他进程终止(非自愿) 自愿退出会返回一个退出码,由父进程接收. 在Linux上可以使用命令echo $?显示最近一次退出的进程返回的退出码 //现有如下代码,源文件名为mycode.c # include <stdio.h> int main(void) { printf("i am

  • C语言控制进程之进程等待详解

    目录 进程等待的必要 进程等待的方法 wait函数 waitpid函数 获取子进程退出信息 进程等待的必要 当一个进程终止的时候,它的资源,比如说PCB,数据等不会被立马清理掉.它会保持在已经终止的状态,这种状态称为“僵尸状态”,直到被父进程确认.父进程wait,即父进程向内核确认子进程已经终止,可以为子进程“收尸”了,内核会把子进程的退出信息传给父进程,然后清理掉子进程的资源,这个时候子进程才算真正地终止了! 总结: 父进程等待,可以获取子进程的退出信息,知道子进程的执行结果. 父进程等待,可

  • Docker守护进程安全配置项目详解

    本文将为大家介绍docker守护进程的相关安全配置项目. 一.测试环境 1.1 安装 CentOS 7 CentOS Linux release 7.7.1908 (Core) 升级内核,重启 # yum update kernel [root@localhost docker]# uname -a Linux localhost 3.10.0-1062.12.1.el7.x86_64 #1 SMP Tue Feb 4 23:02:59 UTC 2020 x86_64 x86_64 x86_64

  • python 进程池pool使用详解

    和选用线程池来关系多线程类似,当程序中设置到多进程编程时,Python 提供了更好的管理多个进程的方式,就是使用进程池. 在利用 Python 进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间. 当被操作对象数目不大时,可以直接利用 multiprocessing 中的 Process 动态生成多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效. Pool可以提供指定数量的进程供用户调用,当

  • Go语言中的数据竞争模式详解

    目录 前言 Go在goroutine中通过引用来透明地捕获自由变量 切片会产生难以诊断的数据竞争 并发访问Go内置的.不安全的线程映射会导致频繁的数据竞争 Go开发人员常在pass-by-value时犯错并导致non-trivial的数据竞争 消息传递(通道)和共享内存的混合使用使代码变得复杂且易受数据竞争的影响 Add和Done方法的错误放置会导致数据竞争 并发运行测试会导致产品或测试代码中的数据竞争 小结 前言 本文主要基于在Uber的Go monorepo中发现的各种数据竞争模式,分析了其

  • Go语言并发编程基础上下文概念详解

    目录 前言 1 Go 中的 Context 2 Context 接口 3 Context Tree 4 创建上下文 4.1 上下文创建函数 4.2 Context 使用规范 4.3 Context 使用场景 5 总结 前言 相信大家以前在做阅读理解的时候,一定有从老师那里学一个技巧或者从参考答案看个:结合上下文.根据上下文我们能够找到有助于解题的相关信息,也能更加了解段落的思想. 在开发过程中,也有这个上下文(Context)的概念,而且上下文也必不可少,缺少上下文,就不能获取完整的程序信息.那

  • Go语言开发保证并发安全实例详解

    目录 什么是并发安全? Mutex 悲观锁 乐观锁 版本号机制 CAS 互斥锁 读写互斥锁 什么是并发安全? 在高并发场景下,进程.线程(协程)可能会发生资源竞争,导致数据脏读.脏写.死锁等问题,为了避免此类问题的发生,就有了并发安全. 这里举一个简单的例子: var data int go func() { data++ }() if data == 0 { fmt.Printf("the value is %v.\n", data) } 在这段代码中 第2行go关键字开启了一个新的

  • C语言数据输入与输出实例详解

    C语言数据输入与输出实例详解 1 概论 C语言提供了跨平台的数据输入输出函数scanf()和printf()函数,它们可以按照指定的格式来解析常见的数据类型,例如整数,浮点数,字符和字符串等等.数据输入的来源可以是文件,控制台以及网络,而输出的终端可以是控制台,文件甚至是网页. 2 数据输出 从第一个c语言程序中,就使用了跨平台的库函数printf实现将一段文字输出到控制台,而实际上,printf()不仅可以将数据按照指定的格式输出到控制台,还可以是网页或者是指定的文件中,printf()函数执

  • Android 控制ScrollView滚动的实例详解

    Android 控制ScrollView滚动的实例详解 在开发中,我们经常需要更新列表,并将列表拉倒最底部,比如发表微博,聊天界面等等, 这里有两种办法,第一种,使用scrollTo(): public static void scrollToBottom(final View scroll, final View inner) { Handler mHandler = new Handler(); mHandler.post(new Runnable() { public void run()

  • C语言实现冒泡排序算法的示例详解

    目录 1. 问题描述 2. 问题分析 3. 算法设计 动图演示 4. 程序设计 设计一 设计二 结论 5. 流程框架 6. 代码实现 7. 问题拓展 1. 问题描述 对N个整数(数据由键盘输入)进行升序排列. 2. 问题分析 对于N个数因其类型相同,我们可利用 数组 进行存储. 冒泡排序是在 两个相邻元素之间进行比较交换的过程将一个无序表变成有序表. 冒泡排序的思想:首先,从表头开始往后扫描数组,在扫描过程中逐对比较相邻两个元素的大小. 若相邻两个元素中,前面的元素大于后面的元素,则将它们互换,

  • C语言与C++中内存管理详解

    目录 内存分布 动态内存管理方式-堆区 C语言动态内存管理 C++动态内存管理 new和delete的用法 operator new与operator delete函数 new和delete的实现原理 定位new表达式 高频面试题 重点new/delete和malloc/free的区别 内存泄漏 内存分布 主要段及其分布 ​ 每个程序运行起来以后,它将拥有自己独立的虚拟地址空间.这个虚拟地址空间的大小与操作系统的位数有关系.32位硬件平台的虚拟地址空间的地址可以从0~2^32-1,即0x0000

随机推荐