【Linux系统编程(进程编程)】进程的退出:父进程等待子进程的退出之僵尸进程与孤儿进程

文章目录

  • 一、进程退出
    • 1.1、进程正常退出方式
    • 1.2、异常退出
  • 二、父进程等待子进程退出(一)
    • 2.1、为什么要等待子进程退出
    • 2.2、(1)父进程等待子进程退出并收集子进程的退出状态
      • 如何等待
      • wstatus空
      • wstatus非空
    • 2.3、(2)子进程退出状态不被收集,变成僵尸进程(僵死进程)。
      • vfork没有使用wait,退出状态不被收集,子进程变成僵尸进程
      • fork没有使用wait,退出状态不被收集,子进程变成僵尸进程
  • 三、父进程等待子进程退出(二)
    • 3.1、相关函数
      • 阻塞等待
      • 非阻塞等待
    • 3.2、孤儿进程(面s)

一、进程退出

vfork保证子进程先运行,当子进程退出后,父进程才运行。

1.1、进程正常退出方式

共5种退出方式

  1. main函数调用return
  2. 进程调用exit(),标准c库
  3. 进程调用_exit()或者_Exit(),属于系统调用

补充

  1. 进程最后一个线程返回
  2. 最后一个线程调用pthread_exit

1.2、异常退出

  1. 主动调用abort(),放弃当前进程
  2. 当进程收到某些信号时,如ctrl+C【ctrl+Z???挂起】
  3. 最后一个进程对取消(cancellation)请求做出相应

不管进程如何终止,最后都会执行内核中同一段代码。这段代码为响应进程关闭素有打开的描述符,释放它所使用的存储器。

对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit、_Exit),实现这一点的方法是,将其退出状态传递(exit status)作为参数传递给函数。

在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的一个状态(termination status)。在任意一种情况下,该终止进程的父进程都能调用wait或waitpid函数取得其终止状态。

那么收集退出状态无非就是给exit函数传递一个值。
image.png
image.png
建议使用exit,是对_exit或_Exit的封装,会对进程的缓冲区做些处理,而_exit或_Exit系统调用是直接退出。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;pid = vfork();if(pid > 0){while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 3){_Exit(0);//break;}}}return 0;
}

二、父进程等待子进程退出(一)

2.1、为什么要等待子进程退出

问:创建子进程的目的是什么?

创建子进程的目的是为了让子进程干活,那么子进程不一定能把交给它的活顺利都做完。所以我们比较关心子进程干没干完活,干的怎么样。所以我们要等待子进程的退出,并且收集退出的状态。
我不仅要等待子进程的退出,而且还要检测你这个活干的怎么样。 你正常退出,你活干到什么程度,根据exit的退出码的不同进程判断。你没干完是异常退出,你是不想干了还是其它原因ctrl+C了等等。这是父进程为什么要等待子进程的退出的原因。

2.2、(1)父进程等待子进程退出并收集子进程的退出状态

调用wait函数,解析返回状态码。子进程调用exit返回状态码。
调用wait可以防止子进程变成僵尸进程。
image.png


如何等待

使用wait函数
image.png
函数参数是一个地址
wstatus参数:是一个整型数指针
wstatus非空:子进程退出状态放在它所指向的地址中。
wstatus空:不关心退出状态。

wstatus空

父进程中使用了wait函数,就不会出现子进程变成僵尸进程的情况。
image.png

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;pid = fork();if(pid > 0){wait(NULL);while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 5){exit(0);}}}return 0;
}

wstatus非空

非空:检查退出状态,存在status里面
image.png

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;int status = 10;pid = fork();if(pid > 0){wait(&status);printf("chlid quit, chlid status:%d\n", WEXITSTATUS(status));while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 5){exit(3);}}}return 0;
}

2.3、(2)子进程退出状态不被收集,变成僵尸进程(僵死进程)。

vfork没有使用wait,退出状态不被收集,子进程变成僵尸进程

没有使用wait,退出状态不被收集

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;pid = vfork();if(pid > 0)//父进程{while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 3){exit(0);//break;}}}return 0;
}

使用ps查看进程
image.png
S+ 是正在运行的状态
Z+ 是僵尸进程 zombie


fork没有使用wait,退出状态不被收集,子进程变成僵尸进程

fork 父进程没有wait
同样,子进程执行结束,父进程没收集其退出状态,变为了僵尸进程
image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;pid = fork();if(pid > 0){while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 5){exit(0);}}}return 0;
}

三、父进程等待子进程退出(二)

3.1、相关函数

image.png

  • 如果其所有子进程都在执行,则阻塞
  • 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
  • 如果它没有任何子进程,则立即出错返回

对于waitpid函数中几个参数解释如下:

  • pid == -1 等待任意子进程。就这一方面而言,waitpid与wait等效。
  • pid > 0 等待其进程ID与pid相等的子进程。
  • pid == 0 等待其组ID等于调用进程组ID的任一子进程。
  • pid < -1 等待其组ID等于pid绝对值的任一子进程。

status参数:是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空:不关心退出状态
image.png

阻塞等待

阻塞等待不会出现僵尸进程情况


#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;int status = 10;pid = fork();if(pid > 0){wait(&status);//阻塞等待printf("chlid quit, chlid status:%d\n", WEXITSTATUS(status));while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 3){exit(3);}}}return 0;
}

非阻塞等待

非阻塞(se)等待,子进程也会变成僵尸进程(实验证明)。而且waitpid马上打印,好像不关心子进程,所以使用不多。一般使用wait。

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;int status = 10;pid = fork();if(pid > 0){//wait(&status);waitpid(pid, &status, WNOHANG);//非阻塞等待printf("chlid quit, chlid status:%d\n", WEXITSTATUS(status));while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 3){exit(3);}}}return 0;
}

3.2、孤儿进程(面s)

父进程不等待子进程退出,在子进程结束之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
init进程ID为1
实验验证:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;int status = 10;pid = fork();if(pid > 0){printf("this is father process, pid:%d\n",getpid());}else if(pid == 0){while(1){printf("chlid pid:%d, my father pid:%d\n",getpid(), getppid());sleep(1);cnt++;if(cnt == 5){exit(3);}}}return 0;
}

image.png
这里父进程退出后,新的父进程的进程id为1468而不是1。
是因为:这里是在图形界面伪终端运行的,shell其实是init进程的一个子进程,故孤儿进程其实是被shell所收养
在字符化界面运行时,ppid就是1了。

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

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

相关文章

LeetCode---389周赛

题目列表 3083. 字符串及其反转中是否存在同一子字符串 3084. 统计以给定字符开头和结尾的子字符串总数 3085. 成为 K 特殊字符串需要删除的最少字符数 3086. 拾起 K 个 1 需要的最少行动次数 一、字符串及其反转中是否存在同一子字符串 直接暴力枚举即可&#xff0c;代码…

【PHP + 代码审计】数组函数

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

Redis 教程系列之Redis 数据备份与恢复(五)

Redis SAVE 命令用于创建当前数据库的备份。 语法 redis Save 命令基本语法如下&#xff1a; redis 127.0.0.1:6379> SAVE 实例 redis 127.0.0.1:6379> SAVE OK 该命令将在 redis 安装目录中创建dump.rdb文件。 恢复数据 如果需要恢复数据&#xff0c;只需将备份…

力扣面试150 x 的平方根 二分 换底法 牛顿迭代法 一题多解

Problem: 69. x 的平方根 思路 &#x1f468;‍&#x1f3eb; 参考题解 &#x1f496; 袖珍计算器算法 class Solution {public int mySqrt(int x){if (x 0)return 0; // Math.exp(3)&#xff1a;e的三次方int ans (int) Math.exp(0.5 * Math.log(x));return (long) (an…

【数据库】MySQL库与表的基本操作

目录 一、数据库概述 1. SQL分类 2. 存储引擎 二、库的操作 1. 创建数据库 2. 字符集与校验规则 3. 查看数据库 4. 修改数据库 5. 删除数据库 6. 查看连接情况 三、表的操作 1. 创建数据表 2. 查看数据表 3. 修改数据表 4. 删除数据表 一、数据库概述 1. SQL分…

PhpSpreadsheet 读取 excel 里面的图片

使用 phpSpreadSheet 插件去读取 excel 里面的图片时发现坑点很多&#xff0c;这里做一个总结 我使用的是 tp 框架 一、安装 phpSpreadSheet 插件 在composer.json配置 "require": {..."phpoffice/phpspreadsheet": "*" } composer安装 co…

配置环境安装包遇到的问题

1. error: could not install packages due to an oserror: [errno 28] no space left on device 说是磁盘占用过多&#xff0c;查看磁盘占用情况。 df -h 果然占比100% 清理了众多缓存.cache都不行&#xff0c;而且其他利用率很低&#xff0c;可能是pip install的过程中使…

代码随想录刷题随记4-链表

代码随想录刷题随记4 文章目录 代码随想录刷题随记424. 两两交换链表中的节点19.删除链表的倒数第N个节点面试题 02.07. 链表相交142.环形链表II 24. 两两交换链表中的节点 leetcode 链接 没什么技巧&#xff0c;主要考察链表操作的熟练程度 class Solution { public:ListNod…

qt 实现 轮播图效果,且还有 手动 上一页和下一页 已解决

QT中有 轮播图的需求&#xff0c;按照正常html版本 。只需要配置数组就能搞定&#xff0c;但是c qt版本 应该用什么了。 第一想到的是采用定时器。 // 定时器初始化{m_pTime new QTimer(this);m_pTime->start(4 * 1000);//启动定时器并设置播放时间间隔m_pAutoFlag true;/…

TikCloud天玑云微服务技术选型

单体多机负载均衡详见:PUSDN 技术选型 平台采用Java开发语言,具有可移植性、跨平台等特性。同时,平台提供跨平台技术解决方案、Windows、MacOS、Linux多端互通,pgz-serial串口、建行POS收银等,都是基于本平台衍生的跨平台案例。 功能介绍使用技术优势/特性开发语言Java主…

数据库查询所有表和字段

查询所有表和字段 介绍 有时候可能要展示所有的表以及所有的字段&#xff0c;下面介绍oracle和mysql的方法以及代码 oracle sql 查询所有表 select Table_NAME from all_tables where owner 表空间 查询字段 SELECT * FROM user_tab_columns where table_name upper(表名…

【python】python turtle绘制坤坤打球

一、效果图&#xff1a; 二、准备工作 &#xff08;1)、导入必要的模块&#xff1a; 代码首先导入了需要使用的模块&#xff1a;requests、lxml和csv。 import requests from lxml import etree import csv 如果出现模块报错 进入控制台输入&#xff1a;建议使用国内镜像源 p…

智慧公厕:跨界融合,打造智慧城市新名片

随着城市化进程的不断加快&#xff0c;公共厕所建设成为一个亟待解决的问题。传统的公厕存在着管理繁琐、卫生差、服务不到位等一系列问题&#xff0c;与城市发展的节奏不协调。为此&#xff0c;推进新型智慧公厕建设成为了一个重要的解决方案。智慧公厕的建设需要推进技术融合…

Day32 贪心算法 part02

Day32 贪心算法 part02 122.买卖股票的最佳时机 II 我的思路&#xff1a; 只有当后一天比前一天价格高时&#xff0c;才出售&#xff0c;profit才累加 解答&#xff1a; class Solution {public int maxProfit(int[] prices) {if(prices.length 0) {return 0;}int profit …

【论文精读】VIT:vision transformer论文

相关文章 【论文精读】Transformer&#xff1a;Attention Is All You Need 文章目录 相关文章一、文章概览&#xff08;一&#xff09;研究背景&#xff08;二&#xff09;核心思路&#xff08;三&#xff09;相关工作&#xff08;三&#xff09;文章结论 二、模型细节&#x…

数据库中什么时候使用自增id,什么时候不能使用

在数据库中&#xff0c;自增ID的使用场景主要有以下几个&#xff1a; 插入记录时不需要指定ID&#xff1a;使用自增ID可以避免手动指定ID&#xff0c;从而避免了因重复ID导致的错误数据库自动编号&#xff0c;速度快&#xff1a;数据库会按照预设的步长&#xff08;默认为1&am…

【MySQL系列】优化数据库时区设置,确保数据一致性与准确性

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Neo4J导入OWL文件

1.neosemantics版本分为4.0以下和4.0以上&#xff0c;两者区别大&#xff0c;我使用的是4.0以上版本&#xff0c;所以参考的是以下文章 owl导入Neo4j-CSDN博客 Neo4j图数据库简介、安装、使用、导入RDF文件_rdf导入neo4j-CSDN博客 2.不同版本的有些函数是不能通用的。 比如…

图论基础|深度优先dfs、广度优先bfs

dfs 与 bfs 区别 提到深度优先搜索&#xff08;dfs&#xff09;&#xff0c;就不得不说和广度优先搜索&#xff08;bfs&#xff09;有什么区别 先来了解dfs的过程&#xff0c;很多录友可能对dfs&#xff08;深度优先搜索&#xff09;&#xff0c;bfs&#xff08;广度优先搜索…

鸿蒙Harmony应用开发—ArkTS-@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

上文所述的装饰器仅能观察到第一层的变化&#xff0c;但是在实际应用开发中&#xff0c;应用会根据开发需要&#xff0c;封装自己的数据模型。对于多层嵌套的情况&#xff0c;比如二维数组&#xff0c;或者数组项class&#xff0c;或者class的属性是class&#xff0c;他们的第二…