【Linux进程控制】进程终止和等待(waitpid回收僵尸进程)

在这里插入图片描述

前言

什么是写时拷贝

创建子进程,没必要将不会被访问
或只读的数据重新拷贝一份

只有将来会被父或子进程写入的数据才
值得拷贝,提前拷贝也并不会立马使用
一般而言即使是os,也无法提前知道
哪些空间可能被写入

所以os选择写时拷贝技术对父子进程
数据进行分离

写时拷贝本质是一种延时申请
当你想去修改数据的时候os再帮你去拷贝

写时拷贝实现图
在这里插入图片描述
为什么要使用写时拷贝

因为有写时拷贝技术的存在
父子进程得以彻底分离
完成进程独立性技术保证
写时拷贝是延时申请技术
可以提高整机内存使用率

一、EIP(pc指针)

进程 = 内核数据结构 + 进程代码和数据
内核数据结构是操作系统维护的
进程代码和数据一般从磁盘中来
也就是C/C++程序加载后的结果

父子进程代码共享是所有代码共享
代码汇编之后,会有多行代码
且每行代码加载到内存都有对应的地址
因进程可能随时被中断(可能没执行完)
下次回来还在之前的位置继续运行
就要求cpu随时记录当前进程执行的
位置,所以cpu内有个EIP
在教程上也叫pc指针(程序计数器)
pc指针永远记录当前正在执行代码
的下一行代码的地址

寄存器在cpu内只有一份
而寄存器内的数据则可以有多份
这些数据就叫做进程的上下文数据
在创建子进程时这些数据也要给子进程一份
虽然父子进程各自调度,各自修改EIP
但却已经不重要了,因为子进程已经认为
自己的EIP起始值就是fork之后的代码
但并不代表子进程看不到fork之前的代码

二、进程终止

当进程终止时,要释放进程申请的
内核数据结构和对应的数据和代码
本质就是释放系统资源

2.1 进程终止的常见方式?

  1. 代码跑完,结果正确
  2. 代码跑完,结果不正确
  3. 代码没跑完,程序崩溃

main函数是什么,返回值的意义?

main函数是进程退出码
main函数返回值并不总是0
也可以是其他的
返回值返回给上一级进程
用来评判给进程执行结果用的
返回0表示运行结果正确
非0标识的是运行结果的不正确
非零有无数个,不同非零值
标识不同错误原因

main函数返回值使用

int sum(int top) // 求1 + 到 99 的和
{
	int s = 0;
	for (int i = 1; i < top; i++)
	{
		s += i;
	}
	return s;
}

int main()
{
	int ret = 0;
	int res = sum(100);
	if (res != 4950)
	{
		// 代码将来的运行结果不正确
		ret = 1;
	}
	return ret;
}

退出码为0,代码运行结果正确
在这里插入图片描述

strerror() 函数
把数字转化成对应的错误码信息

echo $?
获取最近一个行程执行完毕的退出码

程序崩溃的时候,退出码是没有意义的
一般而言,退出码对应的return语句
没有被执行

2.2 如何用代码终止一个程序?

进程常见退出方法

  1. 从main返回
    main函数内return语句就是终止进程的
  2. 调用exit
    exit() 函数
    在代码的任何地方调用
    都表示直接终止进程

终止之前exit需要

  1. 执行用户通过 atexit或on_exit定义的清理函数
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit
  1. _exit
    跟exit一样
    不同的是_exit是直接终止
    并不会清理函数和写入缓冲区数据
    如图
    在这里插入图片描述

而缓冲区是C标准库维护的
在平时推荐用exit()函数

三、进程等待

3.1 进程等待必要性

  • 子进程退出父进程如果不管不顾就可能
    造成‘僵尸进程’问题,进而造成内存泄漏
  • 进程一旦变成僵尸状态,那就刀枪不入
    “杀人不眨眼”的kill -9 也无能为力,因为
    谁也没有办法杀死一个已经死去的进程
  • 父进程派给子进程的任务完成的如何
    我们需要知道。如,子进程运行完成
    结果对还是不对,或者是否正常退出
  • 父进程通过进程等待的方式,回收
    子进程资源,获取子进程退出信息

3.2 制造一个僵尸进程

代码测试

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
         perror("fork");
         exit(1); // 标识进程运行完毕,结果不正确
     }
     else if (id == 0)
     {
         // 子进程
         int cnt = 5;
         while (cnt)
         {
             printf("cnt: %d, 我是子进程:pid: %d, ppid: %d\n", cnt, getpid(), getppid());
             sleep(1);
             cnt--;
         }
         exit(0); // 子进程执行完后退出
     }
     else
     {                                                                                                    
         // 父进程
         while (1)
         {
             printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
             sleep(1);
         }
     }
}

执行结果
在这里插入图片描述

开启另一个窗口检测命令
while :; do ps ajx | head -1 && ps ajx | grep myproc | grep -v grep; sleep 1; echo “-------------------------------”; done

循环完5次,子进程退出
而父进程还在执行
此时子进程变成Z僵尸状态
defunct表示他是无效的
在这里插入图片描述

3.3 wait回收僵尸状态的子进程

在这里插入图片描述

wait:等一个进程,直到这个进程状态发生变化
简而言之就是父进程等子进程死亡
如果不死就一直等,直到你的状态
由R或S状态变成Z状态然后回收他
成功返回子进程ID,不成功返回-1

代码测试

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

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
         perror("fork");
         exit(1); // 标识进程运行完毕,结果不正确
     }
     else if (id == 0)
     {
         // 子进程
         int cnt = 5;
         while (cnt)
         {
             printf("cnt: %d, 我是子进程:pid: %d, ppid: %d\n", cnt, getpid(), getppid());
             sleep(1);
             cnt--;
         }
         exit(0); // 子进程执行完后退出
     }
     else
     {                                                                                                    
         // 父进程
         printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
         // 子进程要循环5次死亡,而父进程在阻塞式等待
         pid_t ret = wait(NULL); 
         if (ret > 0)
         {
         	printf("等待子进程成功,ret: %d\n", ret);
         }
         while (1)
         {
             printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
             sleep(1);
         }
     }
}

执行结果
在这里插入图片描述
循环完5次,子进程死亡
父进程立马回收,最后只剩下
父进程一个状态
在这里插入图片描述
如果想看到Z状态可以让父进程
等7秒针,而子进程是循环5次
每秒一次。这样就有两秒空挡
可以看到两次Z状态

// 父进程
printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
sleep(7);
// 子进程要循环5次死亡,而父进程在阻塞式等待
pid_t ret = wait(NULL); 
if (ret > 0)
{
	printf("等待子进程成功,ret: %d\n", ret);
}

在这里插入图片描述

3.4 waitpid回收僵尸状态的子进程

在这里插入图片描述
waitpid的参数如图
在这里插入图片描述

代码测试

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

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
         perror("fork");
         exit(1); // 标识进程运行完毕,结果不正确
     }
     else if (id == 0)
     {
         // 子进程
         int cnt = 5;
         while (cnt)
         {
             printf("cnt: %d, 我是子进程:pid: %d, ppid: %d\n", cnt, getpid(), getppid());
             sleep(1);
             cnt--;
         }
         exit(0); // 子进程执行完后退出
     }
     else
     {                                                                                                    
         // 父进程
         printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
         sleep(7);
         // 子进程要循环5次死亡,而父进程在阻塞式等待
         pid_t ret = waitpid(id, NULL, 0); 
         if (ret > 0)
         {
         	printf("等待子进程成功,ret: %d\n", ret);
         }
         while (1)
         {
             printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
             sleep(1);
         }
     }
}

3.5 waitpid的参数status

status不关心子进程的退出状态信息
可以设为NULL
如果关心,操作系统会根据该参数
将子进程的退出信息反馈给父进程

status并不是按照整数来整体使用的
而是按照比特位的方式,将32个
比特位进行划分,我们只学低16位
在这里插入图片描述
次低8位表示子进程退出的退出码
最低7个比特位表示进程收到的信号
还有一个比特位代表core dump标志
这个学到后面在讲

(status >> 8) & 0xFF // 拿到次低8位退出码
status & 0x7F // 拿到最低7位收到的信号

代码测试

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
         perror("fork");
         exit(1); // 标识进程运行完毕,结果不正确
     }
     else if (id == 0)
     {
         // 子进程
         int cnt = 5;
         while (cnt)
         {
             printf("cnt: %d, 我是子进程:pid: %d, ppid: %d\n", cnt, getpid(), getppid());
             sleep(1);
             cnt--;
         }
         exit(99); // 子进程执行完后退出
     }
     else
     {                                                                                                    
         // 父进程
         printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
         // 子进程要循环5次死亡,而父进程在阻塞式等待
         int status = 0;
         pid_t ret = waitpid(id, &status, 0); 
         if (ret > 0)
         {
         	printf("等待子进程成功,ret: %d, 子进程收到的信号编号: %d, 子进程退出码: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF);
         }
     }
}

父进程可以通过waitpid的参数status
接收子进程退出码判断子进程代码
是否正确执行并退出

执行结果
子进程退出码为我们设置的99
在这里插入图片描述
子进程收到的信号编号为0说明
我们的进程是正常跑完的
为其他数字说明进程异常结束
异常退出,退出码则没有意义

进程异常退出或崩溃本质是操作系统
杀掉了你的进程
而操作系统则是通过发送信号的方式杀掉进程的

kill -l 查看所有信号
我们应用程序员只要学1~31个普通信号

在这里插入图片描述
当子进程死循环,父进程
等不到子进程死亡则无法回收
我们可以使用9号信号
主动杀掉子进程

kill -9 [子进程PID]
杀掉子进程命令

在这里插入图片描述
此时父进程立马收到子进程退出
是因为收到9号信号
程序异常不光是内部代码问题
也可能是外力直接杀掉

子进程宏处理
WIFEXITED(status): 若为正常终止子进程返回
的状态,则为真用于查看进程是否是正常退出
WEXITSTATUS(status): 若WIFEXITED非零
提取子进程退出码

// 还是上面的代码
else
{                                                                                                    
	// 父进程
    printf("我是父进程:pid: %d, ppid: %d\n", getpid(), getppid());
    // 子进程要循环5次死亡,而父进程在阻塞式等待
    int status = 0;
    pid_t ret = waitpid(id, &status, 0); 
    if (ret > 0)
    {
    	// 可以不这么检测
        // printf("等待子进程成功,ret: %d, 子进程收到的信号编号: %d, 子进程退出码: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF);
        if (WIFEXITED(status))
        {
        	// 子进程正常退出
        	printf("子进程执行完毕,子进程退出码:%d\n", WEXITSTATUS(status));
        }
        else
        {
        	printf("子进程异常退出:%d\n", WIFEXITED(status));
        }
    }
}

3.6 waitpid第三个参数option

前面说过option默认为0
表示阻塞等待,即父进程啥也不干
等待子进程死亡,然后回收子进程
option:WNOHANG选项
表示父进程非阻塞等待

WNOGANG是一个宏定义
全名wait no hang(夯住了)
也就是进度没有被cpu调度
hang表示悬挂也就是非阻塞

代码测试

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

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
         perror("fork");
         exit(1); // 标识进程运行完毕,结果不正确
     }
     else if (id == 0)
     {
         // 子进程
         int cnt = 5;
         while (cnt)
         {
             printf("cnt: %d, 我是子进程:pid: %d, ppid: %d\n", cnt, getpid(), getppid());
             sleep(1);
             cnt--;
         }
         exit(0); // 子进程执行完后退出
     }
     else
     {
		  	                                                                                                    
        int quit = 0;
        while (!quit)
        {
        	
            int status = 0;
            pid_t res = waitpid(-1, &status, WNOHANG); // 以非阻塞方式等待
            if (res > 0)
            {   
            	                                                                                                        
                // 等待成功 && 子进程退出
                printf("等待子进程退出成功,退出码:%d\n", WEXITSTATUS(status));
                quit = 1;
            }
            else if (res == 0)
            {
            	
                // 等待成功 && 但子进程并未退出
                printf("子进程还在运行中,父进程可以做其他事\n");
                sleep(1);
            }
            else
            {
                // 等待失败
                printf("wait失败\n");
                quit = 1;
            }
        }
     }
}

执行结果
在这里插入图片描述
这里的父进程只是简单的打印
表示非阻塞状态,我们也可以
让父进程调用其他函数等等

✨✨✨✨✨✨✨✨
本篇博客完,感谢阅读🌹
如有错误之处可评论指出
博主会耐心听取每条意见
✨✨✨✨✨✨✨✨

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

程序员必读:Python 中如何完美处理日志记录?

日志记录在软件开发中扮演着至关重要的角色。它不仅可以帮助开发人员跟踪应用程序的状态和行为&#xff0c;还能提供有价值的诊断信息。Python 提供了内置的 logging 模块&#xff0c;为开发者提供了一个强大且灵活的日志记录工具。 日志的重要性 在软件开发中&#xff0c;对…

uni-app开发者必看:如何轻松适配微信小程序?

一、v-if避坑 看uniapp官方文档上&#xff0c;v-if是支持多端支持的。小程序上仅支持微信小程序&#xff0c;用hbuilderx运行到小程序后就会变成微信的指令语法wx:if。这里有个坑&#xff0c;要特别注意&#xff01; v-if指令表达式 如果指令表达式为json对象&#xff0c;而…

数据可视化免费化的双面影响探析

近年来数据可视化的免费化也越来越明显&#xff0c;今天就以我作为可视化设计师的经验来和大家分析一下&#xff0c;数据可视化工具免费化所带来的利与弊。 先从好处入手&#xff0c;最明显的就是免费化可以让数据可视化工具得到更广泛的使用。 免费数据可视化工具使得更多人可…

基于 Webpack5 Module Federation 的业务解耦实践

前言 本文中会提到很多目前数栈中使用的特定名词&#xff0c;统一做下解释描述 dt-common&#xff1a;每个子产品都会引入的公共包(类似 NPM 包) AppMenus&#xff1a;在子产品中快速进入到其他子产品的导航栏&#xff0c;统一维护在 dt-common 中&#xff0c;子产品从 dt-com…

06.CSS选择器

CSS选择器 1.选择器的优先级 介绍 通过不同的选择器&#xff0c;选中相同的元素 &#xff0c;并且为相同的样式名设置不同的值时&#xff0c;就发生了样式的冲突 到底应用哪个样式&#xff0c;此时就需要看优先级了 简单描述 行内样式 > ID选择器 > 类选择器 > …

用 SpringBoot+Redis 解决海量重复提交问题

1 前言 在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求&#xff0c;我们来解释一下幂等的概念&#xff1a;**任意多次执行所产生的影响均与一次执行的影响相同 。**按照这个含义&#xff0c;最终的含义就是 对数据库的影响只能是一次性的&#xff0c;不能重复处理…

一站式API解决方案:工程通API连接营销系统、CRM与用户运营,推动无代码集成

无代码开发的革命&#xff1a;简化电商与CRM系统集成 在信息技术迅猛发展的今天&#xff0c;电商企业迫切需要一个高效、便捷的系统来管理客户关系和销售流程。然而&#xff0c;传统的系统集成方式往往因复杂的API开发而令人望而却步。工程通API应运而生&#xff0c;它的无代码…

Halcon 模板匹配基于相关性

文章目录 基于相关性使用匹配助手 基于相关性 适用场景 模板匹配&#xff1a;当你需要在图像中找到一个已知的模板时&#xff0c;例如在工业生产线上检测产品的特定标识或零件的特征时&#xff0c;相关性匹配是一种简单而有效的方法。实时应用&#xff1a;相关性匹配通常具有较…

<软考高项备考>《论文专题 - 8 论文的项目背景之背景段的写法》

1 背景特征 比较好的背景特征&#xff1a; 语句通顺&#xff0c;简洁明了逻辑清晰、层次分明&#xff08;建议分1段-2段描写)项目内容描写清楚&#xff0c;和规模相符合&#xff08;从你写的项目内容和业务描述上&#xff0c;能够勾勒出项目的原型图)技术新颖、路线清晰、描写…

我的隐私计算学习——匿踪查询

笔记内容来自多本书籍、学术资料、白皮书及ChatGPT等工具&#xff0c;经由自己阅读后整理而成。 &#xff08;一&#xff09;PIR的介绍 ​ 匿踪查询&#xff0c;即隐私信息检索&#xff08;Private InformationRetrieval&#xff0c;PIR&#xff09;&#xff0c;是安全多方计算…

Epicypher:CUTANA™ E. coli Spike-in DNA

来源于Escherichia coli&#xff08;E.coli&#xff09;的片段DNA可以用作核酸酶靶向切割和释放&#xff08;CUT&RUN&#xff09;的实验标准化的spike-in对照。产品CUTANA™ E. coli Spike-in DNA含有足够的材料&#xff0c;可用于100-200个CUT&RUN样本&#xff08;高丰…

Linux系统编程总结:进程间通信之网络编程

这一块内容放在进程间通信也没有问题&#xff0c;因为进程间通信研究的就是进程之间如何进行协同操作、如何交换数据进行对话的过程&#xff0c;上一篇文章介绍的几种机制都是用在一台机器上的进程互相进行通信的&#xff0c;而网络套接字这种机制则是用来为两台不同机器上的进…

使用apriori来挖掘关联规则

1、apriori最重要的三个概念&#xff1a; 1、支持度 支持度 (Support)&#xff1a;指某个商品组合出现的次数与总订单数之间的比例。 在这个例子中&#xff0c;我们可以看到“牛奶”出现了 4 次&#xff0c;那么这 5 笔订单中“牛奶”的支持度就是 4/50.8。 2、置信度 置信度…

Win10错误代码0x80070005的解决方法

在Win10电脑操作过程中&#xff0c;用户可能会遇到各种各样的问题&#xff0c;从而影响到自己正常使用电脑办公。现在&#xff0c;就有用户遇到了错误码0x80070005的提示&#xff0c;但不清楚具体的解决方法。下面小编给大家分享不同的解决方法&#xff0c;解决后Win10电脑就能…

IP属地变化背后的原因

随着互联网的普及和技术的不断发展&#xff0c;IP属地变化的现象越来越受到人们的关注。近日&#xff0c;有网友发现自己的IP属地发生了变化&#xff0c;引发了广泛讨论。那么&#xff0c;IP属地为什么会发生变化呢&#xff1f; 首先&#xff0c;网络环境的变化是导致IP属地变化…

如何设置自动通过好友申请+自动回复?

1. 提供即时服务 当用户发送微信消息时&#xff0c;微信可以自动回复相应的内容&#xff0c;如欢迎语、订单确认、问题解答等&#xff0c;提供即时服务&#xff0c;不需要用户等待或反复发送消息。这种自动回复可以在特定的时间内自动通过好友请求&#xff0c;发送预先设置的…

PFA气体吸收瓶耐强酸PFA洗气瓶PFA鼓泡瓶特点分析

洗气瓶的别称有多种&#xff0c;取决于不同的地区和行业。以下是一些常见的别称&#xff1a; 1. 清洗瓶&#xff1a;因其主要用途是清洗气体样品而得名。 2. 干燥瓶&#xff1a;用于干燥气体样品的容器。 3. 气体净化瓶&#xff1a;用于净化气体样品的设备。 4. 气体清洗器…

性能测试 —— 认识 jmeter (性能测试流程 性能测试通过标准)

性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。 1 性能测试技能树   性能测试是一项综合性的工作&#xff0c;致力于暴露性能问题&#xff0c;评估系统性能趋势。性能测试工作实质上是利用工具去模拟大量用户来验证系统…

docker-harbor的私有仓库

仓库 保存镜像 私有&#xff0c;自定义用户的形式登录仓库&#xff0c;拉取或者上传镜像&#xff08;内部管理的用户&#xff09; harbor:VMware公司开发的&#xff0c;开源的企业级的docker registry项目 帮助用户快速的搭建一个企业级的docker仓库的服务 支持中文 harbor…

磁盘空间分析工具你知道几个!8个易学易用的磁盘分析工具,让你对硬盘知根知底

以下是我对可用磁盘空间分析工具(有时称为存储分析器)的首选列表。在我的电脑上试用了其中几个应用程序后,我可以确认这里列出的应用程序是100%免费使用的,并且在弄清楚是什么填满了硬盘、闪存驱动器或外部驱动器中做了很多工作,其中一些甚至允许你直接从程序中删除文件。…