当前位置: 首页 > news >正文

【Linux】详解进程终止进程等待

一、页表&&写时拷贝的进一步理解

        页表中不仅仅只有虚拟地址到物理地址的映射,还包括了很多选项,其中就包括了映射条目的权限。当我们进程的代码和数据加载到内存并和进程地址空间建立映射关系时,如果数据的内容不允许被修改(比如说常量字符串),对应数据在页表中的映射条目的权限就会被设置为'r',表示该数据是只读的,不能被修改。这就是为什么当我们要对常量字符串的内容做修改程序运行阶段会报错的底层原因。

        通过页表的权限位,我们也可以很好地解释写时拷贝是如何做到的。当父进程创建子进程时会发生写时拷贝,写时拷贝会把大部分映射条目的权限都由‘rw’设置成‘r’。当子进程或者是父进程要对数据进行修改时,发现要修改数据对应的映射条目的权限位‘r’,无法进行修改,这时操作系统就会介入。操作系统发现子进程(假设是子进程要对数据进行修改)要对数据进行修改,且操作时合法的,这是就会在内存中申请一块空间重新建立映射关系,再将父子进程该对应的映射条目都改成‘rw’,这才算完成了一次写时拷贝。通过以上过程操作系统就可以按需进行写时拷贝。

二、进程终止

        main函数的返回值我们叫做进程的退出码。一般0表示进程执行成功,非0表示进程执行失败可以用非0的数字表示进程失败的原因。错误码可以转换成错误描述,可以使用语言和系统自带的方法进行转化,也可以自定义。其他函数错误码仅仅表示函数调用结束。

2.1、echo $?

        bash进程会记录最近一个进程退出的退出码,可以查看echo $?可以查看最近一个进程退出的退出码。

        调用函数我们通常想看到两种结果,一是函数的执行结果(比如说fopen打开文件,打开成功就返回文件指针,打开失败返回NULL,这叫函数的执行结果),二是函数的执行情况(比如说同样是fopen打开文件,我们函数的执行情况对应的数字会被保存在errno(错误码)这个变量中)。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{FILE* fp = fopen("./log.txt", "r");printf("%d %s\n", errno, strerror(errno));return 0;
}

 错误码和main函数的退出码本质上是一样的。 错误码只会记录系统提供的函数的执行情况

2.2、进程退出的场景

进程提出的场景与三种:

1、进程代码执行完了,结果是正确的。

2、进程代码执行完了,结果不正确。

3、进程代码没有执行完,进程出异常了。进程出异常时,进程的退出码是没有意义的。

         第三种情况进程出异常是进程收到了操作系统发出的异常信号,每个信号都有不同的编号,不同的信号编号表示异常的原因。

        任何进程最终的执行情况我们都可以用两个数字来表示。一个是进程的退出信号(exit_signal)(退出信号为0表示进程没有出异常),一个是进程的退出码(exit_code)进程的退出信号是由操作系统发送给进程,以指示进程应该终止或进行某种操作的信号,是上对下的操作。而进程的退出码则是当进程结束运行时返回给操作系统的一个整数值,用于表示进程的执行状态或结果,是下对上返回的结果。

2.3、exit

        exit就是用来终止进程的,exit括号中的内容就是进程的退出码。在我们的代码进程中,在任意地方调用exit都表示进程退出

三、进程等待

3.1、进程等待的必要性

1、之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

2、另外,进程一旦变成僵尸状态,那就刀枪不入,kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。

3、最后,父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果对还是不对,或者是否正常退出。

4、父进程通过进程等待(wait)的方式,回收子进程资源,获取子进程退出信息

 3.2、进程等待的方法

3.2.1、wait方法

        wait方法里的参数为输出型参数,可以设置为NULL。调用wait函数父进程默认进行阻塞等待,会等待任意一个子进程退出。等待成功,wait会返回子进程的pid,等待失败返回小于0的值。 

        fork之后父子进程谁先运行不确定,但fork之后一定是父进程后退出,因为父进程要回收子进程。

3.2.2、waitpid方法

        以上pid参数为要回收子进程的pid(pid如果为-1,表示等待任意一个子进程,与wait等效),wstatus参数同样为输出型参数,可以设置为NULL(也可以设置为一个int变量的地址,可以查看子进程的退出码), options参数设置为0表示阻塞等待,设置为宏 WNOHANG表示非阻塞等待采用非阻塞的方法等待,子进程退出成功返回子进程的pid,子进程还在继续自己的工作返回0,子进程出错返回小于0的数。阻塞等待时父进程会阻塞在waitpid这里一直等待子进程返回,非阻塞等待采用轮询的方法查看子进程的退出信息,在轮询的间隙父进程可以继续做别的工作。

3.2.3、wstatus参数详解

        *wstatus表示一个int整形变量,由三十二个比特位组成,其中前16个比特位我们不用,第17到第24个比特位用来表示进程退出时的退出码,第26到第32个比特位用来表示进程退出时收到的退出信号。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childint cnt = 5;while(cnt){printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);cnt--;}exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0); // 阻塞等待if(rid > 0){printf("wait success, rid: %d, status: %d\n", rid, status);}return 0;
}

 

        这就可以解释为什么上面的status变量为256了。因为子进程的退出码为1,status的第24个比特位被设置为1,没有收到退出信号,所以status后8个比特位都为0,所以status等于2的8次方等于256。 

3.2.4、使用位操作从status变量中提取出进程的退出信号和退出码。

int main()
{pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);cnt--;}exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0); // 阻塞等待if(rid > 0){printf("wait success, rid: %d, status: %d, exit_signal: %d, exit_code: %d\n", rid, status, status&0x7f, (status>>8)&0xff);}return 0;
}

 3.2.5、使用宏获取进程的退出码

        一般用户不是很关心进程的退出状态只想获取子进程的退出码就可以使用这种方法。

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)。

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)。

int main()
{pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);cnt--;}exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0); // 阻塞等待if(WIFEXITED(status)){printf("wait success, rid: %d, status: %d, exit_code: %d\n", rid, status,WEXITSTATUS(status));}return 0;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Qt5.14.2 全面解锁Qt5事件处理的精髓
  • Partisia Blockchain:如何做到兼顾隐私、高性能和可拓展?
  • PHP页面如何实现设置独立访问密码
  • tewa-707e光猫超级密码获取方法
  • Lua热更新(xlua)
  • 今日讲讲路由配置
  • Docker搭建LNMP环境实战(04):安装VMwareTools共享文件夹
  • Unity 计算两个日期的时间差(含绝对值计算方法)
  • Linux相关命令(1)
  • Flink 学习资料
  • NO9 蓝桥杯单片机实践之串口通信的使用
  • 《适配器模式(极简c++)》
  • 基于 Linux 的更新版 MaxPatrol VM 可扫描 Windows
  • 时序预测 | Matlab实现BiTCN-BiLSTM双向时间卷积神经网络结合双向长短期记忆神经网络时间序列预测
  • chatgpt正面案例合集
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • [译]如何构建服务器端web组件,为何要构建?
  • 【5+】跨webview多页面 触发事件(二)
  • 【React系列】如何构建React应用程序
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • Angular数据绑定机制
  • IDEA常用插件整理
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Java到底能干嘛?
  • Meteor的表单提交:Form
  • opencv python Meanshift 和 Camshift
  • PhantomJS 安装
  • spring-boot List转Page
  • springboot_database项目介绍
  • 安装python包到指定虚拟环境
  • 大主子表关联的性能优化方法
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 如何选择开源的机器学习框架?
  • 什么是Javascript函数节流?
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 怎么将电脑中的声音录制成WAV格式
  • 正则表达式小结
  • #pragma once
  • #VERDI# 关于如何查看FSM状态机的方法
  • (2)(2.10) LTM telemetry
  • (NSDate) 时间 (time )比较
  • (PADS学习)第二章:原理图绘制 第一部分
  • (苍穹外卖)day03菜品管理
  • (待修改)PyG安装步骤
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .NET 2.0中新增的一些TryGet,TryParse等方法
  • .NET C# 操作Neo4j图数据库
  • .NET C# 使用 iText 生成PDF
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET 反射 Reflect
  • .NET6 命令行启动及发布单个Exe文件
  • .NET轻量级ORM组件Dapper葵花宝典