关于Linux下的进程等待(进程篇)

目录

为什么存在进程等待?进程等待是在做什么?

怎样去执行进程等待?

status

options


为什么存在进程等待?进程等待是在做什么?

代码示例:模仿僵尸进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{pid_t id = fork();if(id == 0){//child processint cnt = 5;while(cnt--){printf("I am child process! cnt: %d ,pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}}else{//father processwhile(1){  printf("I am father process! pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}return 0;
}

运行输出:

  • 因为如果子进程退出,父进程不接收子进程的退出状态,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 进程一旦变成僵尸状态,发送信号 kill -9 也是不可以的,因为谁也没有办法杀死一个已经死去的进程。
  • 其次父进程派创建子进程,是需要子进程执行相关的程序,我们需要知道。子进程执行程序,结果对还是不对,或者是否正常退出。
  • 总结就是:父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

怎样去执行进程等待?

这里需要用到两个接口:
wait()
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程 pid ,失败返回 -1
参数:
输出型参数,获取子进程退出状态 , 不关心则可以设置成为 NULL
waitpid()
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候, waitpid 返回收集到的子进程的进程 ID
如果设置了选项 WNOHANG, 而调用中 waitpid 发现没有已退出的子进程可收集 , 则返回 0
如果调用中出错 , 则返回 -1, 这时 errno 会被设置成相应的值以指示错误所在;
参数:
pid
Pid=-1, 等待任一个子进程。与 wait 等效。
Pid>0. 等待其进程 ID pid 相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: pid 指定的子进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该子进程的ID
注意:这里有些细节需要明示一下:
waitpid的返回值:
  • 返回值 > 0 表示等待子进程成功,子进程运行已结束
  • 返回值 == 0 表示等待子进程成功,子进程正在运行
  • 返回值 < 0 表示等待子进程失败
waitpid的参数 pid:
  • pid  > 0 表示等待进程IDpid相等的子进程
  • pid  < 0  表示等待任意的子进程
示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{pid_t id = fork();if(id == 0){//child processint cnt = 5;while(cnt--){printf("I am child process! cnt: %d ,pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}}else{//father processwhile(1){  printf("I am father process! pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);pid_t ret = wait(NULL); //阻塞等待if(ret > 0) printf("wait child process success!  ret :%d\n", ret);}}return 0;
}

输出:
观察发现,这里的确没有存在僵尸进程的问题了。

waitpid()

示例:

     //pid_t ret = wait(NULL);  pid_t ret = waitpid(id, NULL, 0);//阻塞等待 

输出:

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。


status

  • 输出型参数,获取子进程退出状态
代码示例:子进程退出码设置为99,查看status是否能获得到
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{pid_t id = fork();if(id == 0){//child processint cnt = 5;while(cnt--){printf("I am child process! cnt: %d ,pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}exit(99);}else{//father process//pid_t ret = wait(NULL);int status = 0;pid_t ret = waitpid(id, &status, 0);//阻塞等待if(ret > 0) printf("waitpid child process success!  ret :%d, status:%d\n", ret, status);//  while(1)//  {  //     printf("I am father process! pid: %d, ppid: %d\n", getpid(), getppid());//     sleep(7);//  }}return 0;
}
输出:
[wxq@VM-4-9-centos code_4_10]$ ./test 
I am child process! cnt: 4 ,pid: 4736, ppid: 4735
I am child process! cnt: 3 ,pid: 4736, ppid: 4735
I am child process! cnt: 2 ,pid: 4736, ppid: 4735
I am child process! cnt: 1 ,pid: 4736, ppid: 4735
I am child process! cnt: 0 ,pid: 4736, ppid: 4735
waitpid child process success!  ret :4736, status:3840
[wxq@VM-4-9-centos code_4_10]$ 

为什么这里status的值是3840呢??而不是我们设置的99呢???

  • 因为status并不是按照整数来整体使用的! ! !
  • 而是按照比特位的方式,将32个比特位进行划分,退出码只占了次低8位
     
具体如下:

所以进程异常退出,或者崩溃,本质上是操作系统杀掉了进程(程序运行起来就是进程,此时与语言没有任何关系,只和操作系统有关)
那么?系统是如何得知这个进程有问题,又是如何杀掉这个进程的呢?---信号  (在这里就不过多赘述)
 
既然我们已经明白了退出码是在次低8位,那通过位操作符就可以得到

"&":有0就为0,同为0就为0,同为1就为1
0xFF : 0000 0000 0000 .... .... 1111 1111

0x7F:  0000 0000 0000 .... .... 0111 1111

if(ret > 0) printf("waitpid child process success!  ret :%d, status:%d\n", ret, (status >> 8) & 0XFF);
输出:
[wxq@VM-4-9-centos code_4_10]$ vim process_wait.c 
[wxq@VM-4-9-centos code_4_10]$ make
gcc -o test process_wait.c
[wxq@VM-4-9-centos code_4_10]$ ./test 
I am child process! cnt: 4 ,pid: 9576, ppid: 9575
I am child process! cnt: 3 ,pid: 9576, ppid: 9575
I am child process! cnt: 2 ,pid: 9576, ppid: 9575
I am child process! cnt: 1 ,pid: 9576, ppid: 9575
I am child process! cnt: 0 ,pid: 9576, ppid: 9575
waitpid child process success!  ret :9576, status:99

的确通过这个途径,我们可以得到退出码。当然,我们也可以通过status,得到子进程退出的信号标号:

printf("等待子进程退出成功:ret: %d\n,子进程的信号编号:%d\n 子进程的退出码:%d\n",ret, status & 0x7F, (status >> 8) & 0xFF);

输出:信号编号为0,退出成功

[wxq@VM-4-9-centos code_4_10]$ ./test 
I am child process! cnt: 4 ,pid: 14639, ppid: 14638
I am child process! cnt: 3 ,pid: 14639, ppid: 14638
I am child process! cnt: 2 ,pid: 14639, ppid: 14638
I am child process! cnt: 1 ,pid: 14639, ppid: 14638
I am child process! cnt: 0 ,pid: 14639, ppid: 14638
等待子进程退出成功:ret: 14639
,子进程的信号编号:0子进程的退出码:99
[wxq@VM-4-9-centos code_4_10]$ 

接下来可以对信号编号进行测试,看看是否准确:
测试1:
输出: 信号8:SIGFPE :浮点数错误(溢出)     程序错误,此时退出码无意义
测试2:
输出:
不正常退出,退出码无意义。
所以,程序异常,不光光是内部代码有问题,也可能是外力直接杀掉(子进程代码跑完了吗?﹖不确定)
所以经过上述测试,其实通过status拿到子进程的退出码和退出信号是没有问题的。
但是,有没有发现一个问题,难道我每一次获取子进程的退出码和信号,还需要位运算吗?这不是太麻烦了,所以status提供了 - 宏!
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): WIFEXITED 非零,提取子进程退出码。(查看进程的退出码
所以一般我们会这样来获取:
代码示例:
            //father process//pid_t ret = wait(NULL);int status = 0;pid_t ret = waitpid(id, &status, 0);//阻塞等待// printf("等待子进程退出成功:ret: %d\n,子进程的信号编号:%d\n 子进程的退出码:%d\n",//          ret, status & 0x7F, (status >> 8) & 0xFF); if(ret > 0){if(WIFEXITED(status)){printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));}else{printf("子进程异常退出:%d\n", WIFEXITED(status));}}
输出:
[wxq@VM-4-9-centos code_4_10]$ vim process_wait.c 
[wxq@VM-4-9-centos code_4_10]$ make
gcc -o test process_wait.c
[wxq@VM-4-9-centos code_4_10]$ ./test 
I am child process! cnt: 4 ,pid: 20268, ppid: 20267
I am child process! cnt: 3 ,pid: 20268, ppid: 20267
I am child process! cnt: 2 ,pid: 20268, ppid: 20267
I am child process! cnt: 1 ,pid: 20268, ppid: 20267
I am child process! cnt: 0 ,pid: 20268, ppid: 20267
子进程正常退出,退出码:99
[wxq@VM-4-9-centos code_4_10]$ 


options

pid_t ret = waitpid(id,&status,0 );   //默认是在阻塞状态去等待子进程状态变化–退出
只有子进程退出的时候,父进程才会调用waitpid函数,进行返回(注意,父进程依旧在运行)
waitpid/wait可以在存在多个子进程的情况下,让子进程退出具有一定的顺序性,将来让父进程进行更多的收尾工作
 
  • options参数默认为0 :代表阻塞等待
  • WNOHANG:非阻塞等待
WNOHANG到底是什么:
[wxq@VM-4-9-centos code_4_10]$ grep -ER 'WNOHANG' /usr/include/
/usr/include/sys/wait.h:   If the WNOHANG bit is set in OPTIONS, and that child
/usr/include/sys/wait.h:   If the WNOHANG bit is set in OPTIONS, and that child
/usr/include/bits/waitflags.h:#define	WNOHANG		1	/* Don't block waiting.  */
/usr/include/valgrind/vki/vki-linux.h:#define VKI_WNOHANG	0x00000001
/usr/include/linux/wait.h:#define WNOHANG		0x00000001
[wxq@VM-4-9-centos code_4_10]$ 

其实就是宏定义:  #define  WNOHANG   1  

waitpid(id, &status, 1);  这里也可以传1,但是怕长时间忘记了1的含义,所以设置了这个宏,这里也叫做魔术数字。

所以0就是阻塞等待, 1就是非阻塞等待,只不过1被设置成了宏
那么什么是阻塞等待,什么是非阻塞等待呢?
阻塞等待:一般都是在内核中阻塞,等待被唤醒(伴随着切换)
非阻塞的等待:父进程通过调用waitpid来进行等待,如果子进程没有退出,我们waitpid这个系统调用,立马返回!
示例:
  •         进程阻塞的本质,是进程阻塞在系统函数的内部!
  • 这也就意味着后面的代码不再向后继续执行
  • 当条件满足的时候,父进程被唤醒,从哪里唤醒?
  • 是waitpid重新调用,还是从if的后面,if
  • (为什么?因为挂起父进程的时候,pc指针会存储父进程下一步命令的地址,换言之,pc指针会指向这里)
  • 再继续向后执行父进程的代码
举个例子:
我给小美打电话,说,寒假作业借我抄一下
小美说,好啊,我快写完了,你要不要来我家等一下
①我一想,还有这好事,那我就去小美家等着吧,然后挂断电话  ---》 此时就是阻塞调用
②不行,我是一个圣人君子,我不去,你写完了我再去拿,然后转头和好兄弟去了网吧
过了一会,我问小美,写完没啊,小美说没写完,我挂断电话
又过了一会,我继续问小美,写完没啊,小美说没写完,我又挂断电话
再过了一会,我还是问小美,写完没啊,小美说没写完,我再次挂断电话
而每一次打电话 ---》就是非阻塞调用
每一次打电话的过程,就是基于非阻塞调用的轮询检测方案!
我(代表用户)    --- > 打电话 (代表系统调用)  --->   小美 (代表操作系统)
那么表现在代码上是什么样子呢???
示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{pid_t id = fork();if(id == 0){//child processint cnt = 5;while(cnt--){printf("I am child process! cnt: %d ,pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}exit(99);}else{//father processint quit = 0;while(!quit){int status = 0;pid_t result = waitpid(-1, &status, WNOHANG); //-1表示等待任意的子进程  WNOHANG:以非阻塞的方式等待if(result > 0){        printf("等待子进程退出成功,退出码:%d\n", WEXITSTATUS(status)); quit = 1;}else if(result == 0 ){printf("子进程仍在运行,暂时并未退出,父进程可以继续执行自己的相关代码\n");}else{printf("waitpid error\n");}sleep(1);printf("hello father process\n");}//pid_t ret = wait(NULL);//            int status = 0;
//            pid_t ret = waitpid(id, &status, 0);//阻塞等待
//
//          //  printf("等待子进程退出成功:ret: %d\n,子进程的信号编号:%d\n 子进程的退出码:%d\n",
//          //          ret, status & 0x7F, (status >> 8) & 0xFF); 
//
//        
//            if(ret > 0)
//            {
//                if(WIFEXITED(status))
//                {
//                    printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
//                }
//                else
//                {
//                    printf("子进程异常退出:%d\n", WIFEXITED(status));
//                }
//            }
//
////  while(1)//  {  //     printf("I am father process! pid: %d, ppid: %d\n", getpid(), getppid());//     sleep(7);//  }}return 0;
}

输出:
[wxq@VM-4-9-centos code_4_10]$ vim process_wait.c 
[wxq@VM-4-9-centos code_4_10]$ make
gcc -o test process_wait.c
[wxq@VM-4-9-centos code_4_10]$ ./test 
子进程仍在运行,暂时并未退出,父进程可以继续执行自己的相关代码
I am child process! cnt: 4 ,pid: 10576, ppid: 10575
hello father process
子进程仍在运行,暂时并未退出,父进程可以继续执行自己的相关代码
I am child process! cnt: 3 ,pid: 10576, ppid: 10575
hello father process
子进程仍在运行,暂时并未退出,父进程可以继续执行自己的相关代码
I am child process! cnt: 2 ,pid: 10576, ppid: 10575
hello father process
I am child process! cnt: 1 ,pid: 10576, ppid: 10575
子进程仍在运行,暂时并未退出,父进程可以继续执行自己的相关代码
hello father process
I am child process! cnt: 0 ,pid: 10576, ppid: 10575
子进程仍在运行,暂时并未退出,父进程可以继续执行自己的相关代码
hello father process
子进程仍在运行,暂时并未退出,父进程可以继续执行自己的相关代码
hello father process
等待子进程退出成功,退出码:99
hello father process
[wxq@VM-4-9-centos code_4_10]$ 


最后思考一下:既然进程是具有独立性的,进程退出码,不也是子进程的数据吗?父进程为什么可以拿到呢?? wait/waitpid究竟干了什么呢? ? ?  
  • 本质其实是读取子进程的task _struct结构 (int exit_code, exit_signal;)
  • 僵尸进程:至少要保留该进程的PCB信息! task_struct里面保留了任何进程退出时的退出结果信息!!
  • wait/waitpid有这个权利吗? 当然,这两个接口是系统调用!,不就是操作系统吗 !
  • task_struct是内核数据结构对象!! -- 是操作系统来维护的

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

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

相关文章

xss跨站脚本攻击笔记

1 XSS跨站脚本攻击 1.1 xss跨站脚本攻击介绍 跨站脚本攻击英文全称为(Cross site Script)缩写为CSS&#xff0c;但是为了和层叠样式表(CascadingStyle Sheet)CSS区分开来&#xff0c;所以在安全领域跨站脚本攻击叫做XSS 1.2 xss跨战脚本攻击分类 第一种类型:反射型XSS 反射…

java数据结构与算法刷题-----LeetCode684. 冗余连接

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 并查集 并查集 解题思路&#xff1a;时间复杂度O( n ∗ l o g 2…

ThinkPHP审计(2) Thinkphp反序列化链5.1.X原理分析从0编写POC

ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC 文章目录 ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC动态调试环境配置Thinkphp反序列化链5.1.X原理分析一.实现任意文件删除二.实现任意命令执行真正的难点 Thinkphp反序列化链5.1.…

JVM规范中的运行时数据区

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;每天一个知识点 ✨特色专栏&#xff1a…

SpringBoot 集成H2数据库,启动执行sql, 中文乱码

目录 H2数据库介绍 SpringBoot版本&#xff1a;SpringBoot 2.1.12.RELEASE 快速集成H2&#xff0c;maven依赖 快速集成H2&#xff0c;数据源及关键参数配置 spring.datasource.schema参数&#xff08;建表SQL脚本&#xff09; spring.datasource.data参数&#xff08;更新、…

Qt5 编译 Qt Creator 源码中的 linguist 模块

文章目录 下载 Qt Creator 源码手动翻译多语言自动翻译多语言 下载 Qt Creator 源码 Github: https://github.com/qt/qttools 笔记打算用 Qt 5.12.12 来编译 qt creator-linguist 所以笔者下载的是 tag - 5.12.12 &#xff0c;解压后如下&#xff0c;先删除多余的文件&#xf…

【Vue】Vue3中的OptionsAPI与CompositionAPI

文章目录 OptionsAPICompositionAPI对比总结 OptionsAPI 中文名:选项式API通过定义methods,computed,watch,data等属性方法&#xff0c;处理页面逻辑。以下是OptionsAPI代码结构 实例代码: <script lang"ts">// js或者tsimport { defineComponent } from vu…

【学习】软件测试需求分析要从哪些方面入手

软件测试需求分析是软件测试过程中非常重要的一个环节&#xff0c;它是为了明确软件测试的目标、范围、资源和时间等要素&#xff0c;以确保软件测试的有效性和全面性。本文将从以下几个方面对软件测试需求分析进行详细的阐述&#xff1a; 一、软件测试目标 软件测试目标是指…

读所罗门的密码笔记16_直通心智

1. 直通心智 1.1. 如今&#xff0c;科学家已经可以诱发触觉、压觉、痛觉和大约250种其他感觉 1.1.1. DARPA支持的触觉技术第一次让一位受伤的人能够用假肢和手指感知到被触碰的物体 1.1.2. 可以建立人工系统&#xff0c;来替换和弥补受损大脑的部分区域 1.1.3. 神经科学家能…

Nginx日志格式化和追踪

背景 Nginx是一款功能强大的Web服务器&#xff0c;对于网络环境中的日志记录和配置至关重要。定制化Nginx日志格式可以帮助管理员更好地监控服务器性能、分析用户行为并做出相应优化。在本文中&#xff0c;我们将深入探讨Nginx日志格式的高级定制化策略&#xff0c;包括理解基…

词频统计程序

使用Hadoop MapReduce处理文本文件&#xff0c;Mapper负责将文本分割为单词&#xff0c;然后Reducer对每个单词进行计数&#xff0c;最后将结果写入输出文件。 // 定义WordCount公共类 public class WordCount {// 主入口方法&#xff0c;处理命令行参数public static void m…

循环神经网络RNN

循环神经网络RNN是一种人工神经网络&#xff0c;旨在处理时间序列、语音和自然语言等序列数据。将RNN 想象成传送带&#xff0c;一次处理一个元素的信息&#xff0c;从而 "记住 "前一个元素的信息&#xff0c;对下一个元素做出预测。   想象一下&#xff0c;我们有…

【多线程】Thread的常见属性 | 终止线程 | 等待线程 | 休眠线程 | 线程安全

文章目录 一、Thread的方法Thread的常见属性后台线程&#xff08;守护线程&#xff09;设置后台线程是否存活 启动线程终止\打断一个线程1.创建标志位2.调用 interrupt() 方法 等待一个线程 join()t.join&#xff08;&#xff09;的工作过程&#xff1a; 休眠一个进程sleep 二、…

ppt从零基础到高手【办公】

第一章&#xff1a;文字排版篇01演示文稿内容基密02文字操作规范03文字排版处理04复习&作业解析第二章&#xff1a;图形图片图表篇05图形化表达06图片艺术化07轻松玩转图表08高效工具&母版统一管理09复习&作业解析10轻松一刻-文字图形小技巧速学第三章&#xff1a;…

vue模版字符串解析成vue模版对象

模版字符串 this.code <template><div style"width:100% ; height: 100% ;">{{resultData[0].name}}</div> </template> <script> export default {data() {return {resultData: [{ name: 图幅, value: 20 },]}},mounted(){},method…

JVM虚拟机(二)类加载器、双亲委派模型、类装载的执行过程

目录 一、类加载器1.1 什么是类加载器&#xff1f;1.2 类加载器的分类 二、双亲委派模型2.1 什么是双亲委派模型&#xff1f;1&#xff09; 示例一&#xff1a;加载自己创建的类2&#xff09;示例二&#xff1a;加载JDK原有的类 2.2 JVM 为什么采用双亲委派模型&#xff1f; 三…

第14届java A组蓝桥杯做题记录

A题 特殊日期 package Java14省赛.Java研究生组;import java.time.Year; //特殊判断一下2月份&#xff0c;leaf 为true 1 import java.util.*;import 蓝桥杯.dfs_n皇后; public class 特殊日期 {static int sum(int d){int res 0;while(d > 0){res d % 10;d / 10;}return…

备战蓝桥杯Day40 - 第11届python组真题 - C跑步锻炼

一、题目描述 二、思路 1、使用datetime库中的方法可以很好的解决这个问题。 2、定义起始时间和结束时间&#xff0c;判断是否是周一或者是1号&#xff0c;结果res加上相应的里程数。 3、最后输出 res 即为本题答案。 三、代码实现 import datetimestart datetime.date(2…

NzN的数据结构--选择排序

接上文&#xff0c;本章我们来介绍选择排序。先三连后看才是好习惯~~~ 目录 一、基本思想 二、直接选择排序 三、堆排序 一、基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待…

前端工程化理解 (2024 面试题)

最好介绍远古世界最好随性一点&#xff0c;不要太刻板 &#xff0c;不然像背书 什么是前端工程化&#xff1f; - 知乎 前端工程化的历史 互联网初期&#xff0c;09 年以前&#xff0c;页面只需要展示一些列表、表格、文章内容以及简单图片即可&#xff0c;其目的是为了传送信…