Linux-等待子进程

参考资料:《Linux环境编程:从应用到内核》

僵尸进程

进程退出时会进行内核清理,基本就是释放进程所有的资源,这些资源包括内存资源、文件资源、信号量资源、共享内存资源,或者引用计数减一,或者彻底释放。不过,进程的退出其实并没有将所有的资源完全释放,仍保留了少量的资源,比如进程的PID依然被占用着,不可被系统分配。此时的进程不可运行,事实上也没有地址空间让其运行,进程进入僵尸状态。

僵尸进程依然保留的资源有进程控制块task_struct、内核栈等。这些资源不释放是为了提供一些重要的信息,比如进程为何退出,是收到信号退出还是正常退出,进程退出码是多少,进程一共消耗了多少系统CPU时间,多少用户CPU时间,收到了多少信号,发生了多少次上下文切换,最大内存驻留集是多少,产生多少缺页中断?等等。

父进程通过fork()函数创建子进程后,子进程退出,但父进程没有调用wait()或waitpid()回收子进程的资源的话,这个时候子进程变成僵尸进程。

清除僵尸进程有以下两种方法:

  • 父进程调用wait函数或waitpid函数,为子进程“收尸”。
  • 父进程退出,init进程会为子进程“收尸”。

一般而言,系统不希望大量进程长期处于僵尸状态,因为会浪费系统资源。除了少量的内存资源外,比较重要的是进程ID。僵尸进程并没有将自己的进程ID归还给系统,而是依然占有这个进程ID,因此系统不能将该ID分配给其他进程。

如果我们不关心子进程的退出状态,就应该将父进程对SIGCHLD的处理函数设置为SIG_IGN,或者在调用sigaction函数时设置SA_NOCLDWAIT标志位。这两者都会明确告诉子进程,父进程很“绝情”,不会为子进程“收尸”。子进程退出的时候,内核会检查父进程的SIGCHLD信号处理结构体是否设置了SA_NOCLDWAIT标志位,或者是否将信号处理函数显式地设为SIG_IGN。如果是,则autoreap为true,子进程发现autoreap为true也就“死心”了,不会进入僵尸状态,而是调用release_task函数“自行了断”了。

等待子进程之wait()

Linux提供了wait()函数来获取子进程的退出状态:

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

成功时,返回已退出子进程的进程ID;失败时,则返回-1并设置errno,常见的errno。

在这里插入图片描述

注意父子进程是两个进程,子进程退出和父进程调用wait()函数获取子进程状态在时间上是独立的,所以会出现以下两种情况:

  • 子进程先退出,父进程后调用wait()函数
  • 父进程先调用wait()函数,子进程后退出

对于第一种情况,子进程执行完毕已经退出,只留下了少量的信息等待父进程回收。当父进程调用wait()函数时候,父进程获取到子进程的状态信息,wait函数立刻返回。

对于第二种情况,如果父进程先调用wait()函数,此时子进程还没退出,wait()函数就会阻塞在这里,直到某个子进程退出。

// 示例#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>int main()
{pid_t pid = fork();if (pid < 0){std::cout << "子进程创建失败" << std::endl;return -1;}else if (pid == 0){// 这是子进程std::cout << "打印子进程的进程ID: " << getpid() << std::endl;sleep(10);}else{// 子进程还没退出,会阻塞在wait函数这里std::cout << "子进程还没退出" << std::endl;// 这是父进程pid_t pc = wait(nullptr);std::cout << "子进程退出, 进程ID: " << pc << std::endl;}return 0;
}[root@Zhn test4]# g++ test1.cpp -o test1
[root@Zhn test4]# ./test1
子进程还没退出
打印子进程的进程ID: 3400
子进程退出, 进程ID: 3400
[root@Zhn test4]# 

可以看到,父进程阻塞在wait()函数,等待子进程退出后执行。

wait()函数等待的是任意一个子进程,任何一个子进程退出都会让其立刻返回。当多个子进程都处于僵尸状态时,wait()函数获取到其中一个子进程的信息后立刻返回。由于wait()函数不会接收pid_t类型的参数,所以也不能明确等待某一个子进程的退出。

那么一个进程如何等待所有子进程都返回呢?

wait()函数返回有三种可能性:

  • 等到了子进程退出,获取其退出信息,返回值是该子进程的进程ID;
  • 等待过程中,收到了信号,信号打断了系统调用,并且注册信号处理函数时并没有设置SA_RESTART标志位,这样的话系统调用就不会重启wait()函数,wait()函数会返回-1,并且errno设置为EINTR;
  • 已经成功等待了所有子进程退出,没有子进程的退出信息需要接收,这种情况下,wait()函数返回-1并且errno设置为ECHILD。

《Linux/Unix系统编程手册》给出下面的代码来等待所有子进程的退出:

while((childPid = wait(NULL)) != -1)continue;if(errno !=ECHILD)errExit("wait");

但是这种方法忽略了wait()函数被信号中断这种情况,如果wait()函数被信号中断,上述代码就不能成功等待所有子进程退出。

所以我们需要把上面代码封装以下:

pid_t r_wait(int *stat_loc)
{int retval;// 如果被信号中断,表达式为真,重启wait()函数while(((retval = wait(stat_loc)) == -1 && (errno == EINTR));return retval;
}while((childPid = r_wait(NULL)) != -1)continue;If(errno != ECHILD)
{/*some error happened*/
}

由上面可以看出wait()函数具有一些局限性:

  • 不能等待特定的子进程:如果进程存在多个子进程,而它只想获取某一个子进程的退出状态,就需要一一等待,通过返回的进程ID判断是不是自己关心的子进程;
  • 如果不存在子进程退出,wait函数就会阻塞:有时候,只是想尝试获取子进程退出的状态,如果没有子进程退出就立刻返回,不需要阻塞等待;
  • wait()函数只能发现子进程的终止事件:如果某些子进程因某信号而停止,或者停止的子进程收到SIGCONT信号又恢复执行,这些事wait函数无法获知。

为了解决这三个缺点,引入了waitpid()函数。

等待子进程之waitpid()

waitpid()函数接口如下:

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

waitpid()与wait()相同的地方:

  • 返回值的含义相同,都是终止子进程或因信号停止或因信号恢复而执行的子进程的进程ID。
  • status的含义相同,都是用来记录子进程的相关事件,后面将会详细介绍。

接下来介绍waitpid()函数特有的功能:

第一个参数是pid_t类型,所以waitpid()可以明确指定要等待哪一个子进程的退出:

  • pid > 0:表示等待进程ID为pid的子进程;
  • pid = 0:表示等待与调用进程同一个进程组的任意子进程,因为子进程可以设置进程组,那么如果某些子进程和父进程不在同一个进程组,这样的进程就不关心它的退出状态;
  • pid = -1:表示等待任意子进程,同wait类似,waitpid(-1, &status, 0)与wait(&status)完全等价;
  • pid < -1:等待所有子进程中,进程组ID与pid绝对值相等的子进程。

第二个参数是int*类型:

无论是wait()还是waitpid(),都有一个status变量,这个变量是一个int类型指针。可以传递mullptr,表示不关心子进程退出状态,不为空就可以获得更多子进程的状态。

  1. 子进程是正常退出的:
    在这里插入图片描述

  2. 进程收到信号,导致退出:
    在这里插入图片描述

  3. 进程收到信号,被停止:
    在这里插入图片描述

  4. 子进程恢复执行
    在这里插入图片描述

第三个参数options是一个位掩码,可以同时存在多个标志位。如果options没有设置任何标志位,其行为与wait类似,即阻塞等待与pid匹配的进程退出。

options的标志位可以是如下标志位的组合:

  • WUNTRACED:除了关心终止子进程的信息,也关心那些因信号而停止的子进程信息。
  • WCONTINUED:除了关心终止子进程的信息,也关心那些因收到信号而恢复执行的子进程的状态信息。
  • WNOHANG:指定的子进程并未发生状态变化,立刻返回,不会阻塞。这种情况下返回值是0。如果调用进程并没有与pid匹配的子进程,则返回-1,并设置errno为ECHILD,根据返回值和errno可以区分这两种情况。

Linux提供了SIGSTOP(信号值19)和SIGCONT(信号值18)两个信号,来完成暂停和恢复的动作,可以通过执行kill-SIGSTOP或kill-19来暂停一个进程的执行,通过执行kill-SIGCONT或kill-18来让一个暂停的进程恢复执行。

// 示例// 等待子进程之waitpid#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{int status;pid_t pid = fork();if (pid < 0){std::cout << "子进程创建失败" << std::endl;return -1;}else if (pid == 0){std::cout << "这是子进程: " << getpid() << std::endl;sleep(3);exit(3);}else{std::cout << "这是父进程: " << getpid() << std::endl;pid_t pc = waitpid(0, &status, WNOHANG);if (pc == 0)std::cout << "此时没有子进程退出" << std::endl;else if (WIFEXITED(status))std::cout << "子进程: " << pc << "正常退出, 退出状态为" << WEXITSTATUS(status) << std::endl;elsestd::cout << "子进程: " << pc << "非正常退出" << std::endl;}return 0;
}[root@Zhn test4]# g++ test2.cpp -o test2
[root@Zhn test4]# ./test2
这是父进程: 4551
此时没有子进程退出
这是子进程: 4552
[root@Zhn test4]# 

示例中,父进程waitpid函数的参数设置的意思是等待父进程同一进程组的任意子进程退出事件,如果没有子进程退出,则返回值为0,子进程中睡眠了3秒,这时父进程调用waitpid,发现没有子进程退出,所以返回值为0。

这是一个简单的小例子,其他例子都是举一反三。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/801092.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PANet网络

PANet&#xff08;Path Aggregation Network&#xff09;是一种用于语义分割任务的神经网络结构&#xff0c;旨在解决多尺度特征融合的问题。该网络结构由中国科学院计算技术研究所提出&#xff0c;在2018年的论文中首次提出。 PANet的主要目标是解决语义分割任务中多尺度信息…

电脑开机启动项设置

电脑开机启动项设置 一、Windows 系统&#xff1a; 1、Windows 系统&#xff0c;可以通过【系统配置实用程序】来设置开机启动项&#xff1a; 1&#xff09;、按【WinR】组合键&#xff0c;打开【运行】对话框。 2&#xff09;、输入【msconfig】&#xff0c;点击【确定】或…

Transformer 模型及其典型应用研究

摘要&#xff1a; Transformer 模型是一种基于自注意力机制的深度学习架构&#xff0c;在自然语言处理等领域取得了巨大成功。本文介绍了 Transformer 模型的原理和结构&#xff0c;并探讨了其在语言翻译、文本生成、对话系统、语言模型、图像处理和推荐系统等典型应用领域的研…

Java中的Stream流常用接口和方法

​TOC 第一章&#xff1a;Stream流是什么 1.1&#xff09;简单介绍 学习Stream流就绕不开Lambda表达式&#xff0c; 需要了解Lambda表达式可以看一下这篇–>&#xff1a;Lambda表达式学习 1.其实“流”是个抽象概念&#xff0c;我们把现实世界中与Stream流有相同特性的…

一条SQL查询语句的执行顺序

SQL常用字段书写顺序 SELECT&#xff1a;选择要查询的列。 FROM&#xff1a;指定数据来源&#xff0c;即表名。 JOIN&#xff1a;根据指定的连接条件将多个表连接在一起。 ON&#xff1a;指定连接条件&#xff0c;即哪些列的值匹配时&#xff0c;应该将两个表中的行组合在一起。…

【Angular】什么是Angular中的APP_BASE_HREF

1 概述: 在这篇文章中&#xff0c;我们将看到Angular 10中的APP_BASE_HREF是什么以及如何使用它。 APP_BASE_HREF为当前页面的基础href返回一个预定义的DI标记。 APP_BASE_HREF是应该被保留的URL前缀。 2 语法: provide: APP_BASE_HREF, useValue: /gfgapp3 步骤: 在app.m…

SAP ERP 公有云有哪些模块?

随着全球化竞争的加剧和企业管理需求的日益复杂化&#xff0c;越来越多的企业开始采用云端企业资源计划&#xff08;ERP&#xff09;系统来优化业务流程。SAP ERP 公有云&#xff08;SAP S/4HANA Cloud, public edition&#xff09;作为一款领先的云端ERP解决方案&#xff0c;为…

不要再使用 @Builder 注解了!有深坑呀!

曾经&#xff0c;我在《千万不要再随便使用 lombok 的 Builder 了&#xff01;》 一文中提到 Builder 注解的其中一个大坑会导致默认值失效&#xff01; 最近阅读了 《Oh !! Stop using Builder》 发现 Builder 的问题还不止一个&#xff0c;Builder 会让人误以为是遵循构建器…

掌握Linux虚拟网络设备:从基础到应用的全面指南

在现代计算环境中&#xff0c;尤其是云计算☁️、容器化&#x1f4e6;和微服务架构&#x1f3d7;️大行其道的时代&#xff0c;了解和掌握Linux虚拟网络设备变得极为重要。本文将深入探讨Linux虚拟网络设备的世界&#xff0c;带你了解它们是什么、包含哪些类型、为什么需要它们…

揭秘淘宝商品详情数据接口(Taobao.item_get)

淘宝商品详情数据接口&#xff08;Taobao.item_get&#xff09;是一种允许开发者通过API访问淘宝平台上的商品详情信息的接口。通过该接口&#xff0c;开发者可以获取到商品的标题、价格、销量、描述等详细信息&#xff0c;为商品展示和销售提供数据支持。 请求示例&#xff0…

K8s学习八(配置与存储_配置)

配置与存储 配置管理 ConfigMap ConfigMap的创建 一般用于去存储 Pod 中应用所需的一些配置信息&#xff0c;或者环境变量&#xff0c;将配置于 Pod 分开&#xff0c;避免应为修改配置导致还需要重新构建 镜像与容器。configmap缩写为cmkubectl create cm -h来查看创建命令…

三星:HBM4的16层堆叠技术验证成功

随着人工智能、大数据分析、云计算及高端图形处理等领域对高速、高带宽存储需求的激增&#xff0c;下一代高带宽内存&#xff08;High Bandwidth Memory, HBM&#xff09;——HBM4已成为全球存储芯片巨头三星、SK海力士和美光竞相追逐的技术高地。 随着AI、机器学习以及高性能…

【JavaWeb】Day38.MySQL概述——数据库设计-DQL

数据库设计——DQL 介绍 DQL英文全称是Data Query Language(数据查询语言)&#xff0c;用来查询数据库表中的记录。 查询关键字&#xff1a;SELECT 查询操作是所有SQL语句当中最为常见&#xff0c;也是最为重要的操作。在一个正常的业务系统中&#xff0c;查询操作的使用频次…

kafka(四)——生产者流程分析(c++)

前言 kafka生产者负责将数据发布到kafka集群的主题&#xff1b;kafka生产者消息发送方式有两种&#xff1a; 同步发送异步回调发送 流程 流程说明&#xff1a; Kafka Producer整体可看作是一个异步处理操作&#xff1b;消息发送过程中涉及两个线程&#xff1a;main线程和se…

Java变量详解

​ 这里写目录标题 第一章、Java中的变量分类1.1&#xff09;变量分类1.2&#xff09;成员变量分类1.3&#xff09;成员变量和局部变量的区别 第二章、成员变量详解2.1&#xff09;成员变量作用域/权限修饰符2.2&#xff09;成员变量和成员属性的区别2.3&#xff09;成员变量初…

为什么 GraphQL 是构建微服务的更好选择

关于使用REST还是GraphQL来构建微服务哪个更好&#xff0c;一直存在争论。这两种技术都有其支持者和批评者&#xff0c;但当涉及微服务架构的特定需求时&#xff0c;GraphQL 成为明显的领先者。原因如下。 了解 RESTful 的关注点 虽然 REST 多年来一直是首选 API 风格&#x…

蓝桥杯 历届真题 时间显示【第十二届】【省赛】【C组】

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s #include<bits/stdc.h> #define int long long using namespace std; const int N 1e510; int n,m,t,d; int a[2][N],b[N]; //…

数据库关系模式三元及以上分解无损连接判断(表格法)

例题 1.首先构造初始表&#xff0c;如下表所示。 A B C D E ABC a1 a2 a3 b14 b15 CD b21 b22 a3 a4 b15 DE b31 b32 b33 a4 a5 2.遍历函数依赖&#xff0c;对AB→C&#xff0c;因各元组的第一、二列没有相同的分量&#xff0c;所以表不改变。 3.由C→D…

chabot项目介绍

项目介绍 整体的目录如下所示&#xff1a; 上述的项目结构中出了model是必须的外&#xff0c;其他的都可以根据训练的代码参数传入进行调整&#xff0c;有些不需要一定存在data train.pkl:对原始训练语料进行tokenize之后的文件,存储一个list对象&#xff0c;list的每条数据表…

javaWeb物流信息网的设计与实现

摘要 本文讲述了基于JSP物流信息网的设计与实现。该系统使用java语言开发&#xff0c;使系统具有更好的平台性和可扩展性。 该系统实现了用户登录、注册、查询快递信息、快递公司注册成为合作伙伴以及系统管理员对信息进行管理等功能。系统的主要界面会将所有的服务排列好&…