Linux - 进程控制(上篇)- 进程创建 和 进程终止
进程控制
进程创建
对于进程的创建,你肯定知道,在 C/C++ 当中使用 fork()函数,以当前可执行程序生成的进程为 父进程,创建这个父进程的 一个子进程,这个 子进程就是一个新的进程。
如上图所示:刚开始创建出子进程之时,甚至父子进程的 页表 ,数据 和 代码都可以是共用的,当 子进程 有修改操作之时,才会发生写时拷贝,拷贝子进程修改之后需要有用到的数据。
当父进程 执行到 fork()函数之时,就会在内核当中去找到 fork()函数定义,调用 fork()函数创建子进程,此后,就从fork()函数之后,父子进程就会各自执行,开始各自的“旅程”。
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
当然,fork()函数创建进程是有创建失败的情况的,比如系统当中进程数太多,内存爆满,或者是 实际用户的进程使用数超过了限制,都是可能会导致 fork()函数创建进程失败的。
除了可是使用 if-else 语句来判断当前是什么进程,把父进程和子进程用来执行不同的代码块之外,你还可以用 循环来 批量化 创建多个进程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>#define N 5int main()
{int i = 0;for (int i = 0; i < N; i++){int fork_ret = fork();if (fork_ret == 0){// 某一个子进程printf("child : PID: %d , PPID: %d\n", getpid(), getppid());exit(0);// 子进程执行完上述的代码就终止}}return 0;
}
这个写时拷贝不仅仅只是子进程会进行写时拷贝,父进程同样会进行写时拷贝,父子进程谁先写,谁就先进行写时拷贝。
如上所示,不管原本的父进程对应的页表项是 什么访问权限,就算是 可读可写,在最后都会修改成只读,会把全部可写的区域,全部改成只读,所以,子进程继承下来也是只读的。
所以,在此之后,不管是哪一个进程想在某一个 区域当中的写的话,都与触发权限方面的问题,此时,操作系统识别到了,是不会进行异常处理的,反而是做写时拷贝的 处理,是谁想修改的,就新开辟空间,拷贝一份数据,给这个 进程使用。然后,再把两个进程对应的页表项的 访问权限标签标成可读的。
而,写时拷贝本质上就是用一种,延迟申请,按需申请,不要盲目的给子进程,直接拷贝一份,开辟一个 和 父进程一样大的空间,给子进程使用,如果父进行当中有100个数据,但是 子进程只用修改一个数据,父进程也不修改数据的话,那么很大部分的子进程的空间就浪费了,因为这些不用修改数据,完全可以和父进程共同使用。所以,才要写时拷贝,需要的时候,再去拷贝数据,开辟新的空间。
进程终止
在上述用循环来创建 多个进程 这个例子当中,我们想提前终止掉 子进程 的运行,所以我们在函数体当中 就 使用 exit()这个函数来终止掉这个 子进程:
要讨论上述的 exit ()函数之前我们先来讨论一下,为什么我们使用 main(),总是喜欢 return 0 呢?那么 return 其他的数可以吗,比如 :return 1 ?
进程终止无外乎就三种终止的情况:
- 代码运行完毕,结果正确;
- 代码运行完毕,结果不正确;
- 代码异常终止;
如果是前两种情况,结果正确的情况,我们是不关心的,我们最关心的是结果为什么是不正确的。如果结果是不正确的,那么我们对程序的返回结果做分析。
所以,其实 return 0 是 进程的退出码,这个退出码表示进程的运行结果是否正确,我们一般用 0->sucess 。0 是我们默认的 程序程序运行的退出码,其他程序员也是这样认为的,如果这个程序返回的是0 ,不仅仅是告诉写这个程序的人,这个程序执行成功了,也告诉 其他程序员,这个程序是正常退出的。
同时,main()函数的返回值,返回给谁了?为什么要返回这个值?
上述所说的 return 0 ,这个退出码,返回给谁了呢?其实这个 退出码,会被这个进程的父进程所拿到,对应到 我们日常在 使用 命令行来运行程序的话,就是被 bash 所拿到。
我们可以在命令行使用 echo $? 命令来查看到最近一个 bash 的子进程的退出码是多少:
之所以父进程要接收子进程的程序退出码,是因为,一般而言,某一个进程的父进程是要关心子进程的运行情况的。
如果子进程返回的是0,那么说明这个程序运行是正确的,父进程知道了子进程返回的是 0 的退出码,他也就放心了,因为 子进程运行结果是正确的。
父进程最关心的是 子进程 的退出码不是 0 的情况,也就对应 程序运行结果不正确的情况;所以,我们可以用 return 不同的数字,来表示不同错误原因。
用这种方式来告知使用这个程序的用户,这个程序出没出错,或者出错了,出的是什么错误;因为一个程序是不一定 一定要在屏幕上打印什么内容的,肯定是有程序是没有打印内存的这个需求的,那么在这种没有任何打印来提示错误的程序当中,要想知道这个程序是否出错的话,使用 程序退出码来表示是最好的。
所以,我们之前在写代码,直接“无脑写” return 0 其实是不太正确的,因为,如果整个程序的运行环境是在多个进程同时运行的环境下,而且这些进程都互相之间有一定关联,或者整个程序就是在各自运行,没有任何的反馈,我们是不知道这个程序到底出没有出错的。
而且,退出码是一个一个的数字,他是比较适合让计算机去看的,不然的话,这个程序退出码是1 表示 内存出错,2 表示数组越界····· 这些报错的退出码,人是不可能每一个都记住的,我们需要去查文档等等的文献来知道,当前程序的退出码表示的是什么意思。
这是一种机械化的操作,不就是查文档吗?那么交给计算机去做不就行了,所以,返回这些不同的退出码,本质上也就是返回一些代表着 不同报错信息的数字,这些数字是给计算机去看的。
当计算机查到当前程序的退出码对应的错误信息,计算机就可以把这个退出码,转换成对应的错误信息的字符串,让我们知道这个程序当中是出现了什么错误。
在 Linux 当中,string.h 这个头文件当中有一个 strerror()这个函数接口,这个函数就可以传入一个 退出码,把这个退出码对应的 报错信息,转换成 字符串的形式作为这个函数的返回值:
所以,我们可以简单的使用这个函数来打印一些 退出码代表的 错误信息来查看:
输出:
所以,我们在日常当中,使用各个指令,使用错误的时候,就会报一些错误:
这些错误信息就可以对应得上了。 所以,错误码和错误码对应的描述是有对应关系的。
而,父进程之所以要关心这个 子进程的 这个程序的退出码,其实就是把报错信息打印出来,做一个收集工作。
其实父进程就是一个工具人,一个跑腿的,真正关心这个子进程的 结果对不对的,是使用这个程序的用户,用户才是最关心这个程序的执行结果是不是正确的。
用户根据 父进程接收到的,打印在外设当中的 错误信息,根据这个错误信息, 用户才能更好的使用这个软件。
就像刚刚我们在使用 ls 查看某一个目录的时候,报错了,因为我们输入的文件在当前目录是不存在的,所以它报错“找不到这个文件”。然后,我就去查看当前目录,发现确实没有这个文件,所以用户就去看,区查找它想要访问的文件在哪里,从而正确的使用 ls 这个命令。
由此可见,使用这种方式,用户可以知道是自己使用错了,还是程序的实现出错了。这一切都是为用户服务。
所以,一个进程运行得怎么样,用户如何知道,靠的就是父进程收集这个子进程的 退出码,退出信息,转交给用户,用户才知道 这个 子进程是否运行成功。
所以,一个代码成功运行之后,返回的结果正不正确,统一采用进程的退出码来判定;而这个退出码就是 main()函数的返回值。
自己设置错误码信息
系统当中给的错误码是参考的,你想用就可以用,不想用自己写一套错误码体系也是可以的。
而且实现也是非常的简单,只需要定义一个保存 错误码信息的 全局字符串数组即可。这个字符串数组每一个 元素是一个字符串,那么这个数组的下标代表的就是 每一个字符串代表的错误信息描述所对应的错误码:
C语言当中的 errno 全局变量
与上述的 退出码 类似的,在 C 语言当中会有一个 errno 的全局变量:
这个全局变量保存的是,最近一次执行的错误码。是什么执行的错误码呢?
在 C 当中有很多的库函数,但是我们调用库函数不是每一次都会调用成功的,比如 malloc ,realloc, fopen 这些函数,都是可能会调用失败的,当我们调用失败,这函数可能会返回 -1 或者是 NULL 的返回值。
像上述,如果我们只看这个函数的返回值的话,就只能知道这个函数究竟有没有调用成功;但是对于调用失败的情况,我们通过函数返回值只能知道 当前函数调用失败了,但是具体,为什么失败,是什么原因导致的,这个函数的返回值是不能体现的。
所以,一旦我们使用 C 当中的库函数,调用失败了,那么,就会把 这个 errno 全局变量设置为对应的数字,这里的每一个数字,和上述说过的 程序的退出码一样,是要各自的含义的。就是我们想要知道的为什么会报错的。
我们把这个数字称之为 错误码。
但是,不排除当前情况下有多个函数都报错的情况,比如:第一个函数报错了,errno 就被更新为这个函数的报错信息了;但是当 第二个报错函数出现的时候, errno 是会继续更新的。会被最新一个报错的函数的错误码覆盖。
所以,如果你想知道某一个C/C++ 程序当中,如果出错了,出错原因是什么,那么可以直接返回这个 errno 全局变量:
像上述的情况,就是,不仅仅 可以通过 p 变量知道当前 malloc 函数是否调用成功吗,还可以通过 ret 变量接受的 errno 的值,知道错误码是多少,然后通过main()函数返回这个错误码 给 父进程,父进程在 转交给用户,那么用户就可以知道 具体是什么原因发生的错误了。
代码异常终止的情况
代码如果出现异常终止了,那么这个代码可能就是 没有跑完(没有执行完毕)就被操作系统直接干掉了。
如果代码没有跑完,程序直接退出了,那么根本就没有执行main()函数最后一条语句(return 返回退出码),最后一步是没有执行的。
所以,退出码在此处是不是没有意义呢?
是的,退出码在此处是没用了;
就算你执意认为,就算发生异常,但是没有及时捕获,还是执行到了最后的return语句。但是,这个退出码你敢用码?程序发生异常可能在任何位置,任何时候都可能会发生异常,那么最后返回的这个 错误码一定是正确的吗?不一定吧。
就好比的是,你考试作弊考了90 分,回去你更你爸爸说,你考试考90 分,但是要被请家长,你爸爸问你为什么要被请家长,你说,在考试过程当中“发生了异常”,考试作弊比发现了;像这个例子,虽然最后你跟你爸爸说了你考试考了90 分(相当于return 90),但是 是作弊的,程序是异常退出的,你可以在考试期间作弊,也可以在 考试最后作弊,但是不管在那个时候作弊,就是作弊了,那么你爸爸会相信你考的90分吗?
所以,当一个进程以 异常退出的方式结束的话,程序的退出码就没有意义了,我们也就不关心这个退出码了。
如何知道程序是异常退出的?程序是为什么异常的?
进程出现异常,本质其实是进程收到了对应的信号!
使用 kill -l 可以查看所以的信号代表的是什么意义:
比如:野指针访问的错误,其实访问指针指向地址空间,这个地址其实是一个虚拟地址,这个虚拟地址由页表映射到内存当中的实际存储数据的物理地址。
如果访问一个野指针的话,也就是访问某一个虚拟地址,但是这个虚拟地址在页表当中不存在,找不到这个虚拟地址的映射关系。
还是就是除数为0的情况,其实是在cpu当中的 状态寄存器就会出现溢出的情况。
像出现上述的两个情况,运行程序就会发生下面异常报错:
分别对应 进程信号当中的 8 和 11。
使用 kill 信号,可以给某一个进程发送某一个信号,那么这个进程就会因为这个信号而发生一些改变,比如使用 8 号信号的话,就会直接抛出 野指针的异常:
在程序当中主动退出程序(使用 exit()函数 和 _exit())
所以,父进程想要关系某一个子进程运行状态的话,首先要关心,子进程退出之时有没有接收到对应的信号,如果没有,说明程序是正常退出的,代码是跑完的;那么再去看子进程的退出码。
所以,我们在使用 exit ()函数,我们在其中可能会写 0 等等数字,这些个数字代表的就是我们想要退出这个程序所使用 退出码。
exit()函数可以在程序代码的任意地址被调用,都可以表示在exit()调用处,进程直接按照所给退出码直接退出。
而 return 和 exit()函数的区别在于,return只表示当前函数返回,程序继续向后运行。
调用这个函数输出的是:
发现,最后四个的 end show 没有打印。而且,返回的是 exit()当中的13,而不是 main函数当中的 12。
上述现在使用的是 return返回,发现,返回的退出码不是 exit ()函数当中的 13,而且是在main函数当中的 12。
还有一个 _exit()函数,可以提前终止掉进程:
使用这个 函数,都可以终止进程,进程的退出码及时其中参数 status。
那么,exit() 和 _exit()有什么区别呢?
_exit()函数是纯正的 系统调用函数。当我们要终止进程的时候,是直接在操作系统内部直接终止掉这个进程,而,缓冲区当中的数据不做刷新。他会直接调用操作系统层面上的接口,直接关闭掉进程。
而 exit()函数,在终止程序之前,会先执行用户定义的清理函数,再冲刷缓冲区,关闭流等等,然后 exit() 调用 _exit()。
例子:printf()函数一定是先把数据写到缓冲区当中,在合适的时候,在进程刷新到屏幕上(这个合适的时候,比如是 遇到 "\n" 或者是 进程退出)
这个缓冲区,绝对不在 内核区当中。因为是在内核区当中(也就是上图当中的 kernel 当中),那么 _exit()函数,在结束进程之时,就可以在内核区当中对缓冲区当中的数据进行刷新,但是实际上是没有刷新的。所以操作系统只要维护缓冲区,那么就一定会刷新。
相关文章:

Linux - 进程控制(上篇)- 进程创建 和 进程终止
进程控制 进程创建 对于进程的创建,你肯定知道,在 C/C 当中使用 fork()函数,以当前可执行程序生成的进程为 父进程,创建这个父进程的 一个子进程,这个 子进程就是一个新的进程。 如上图所示&a…...

NiceGui:Python中的轻量级GUI框架初体验
目录 一、引言 二、NiceGui概述 三、NiceGui实战:一个简单的计算器应用 四、NiceGui与其他GUI框架的比较 五、注意事项 总结与展望 一、引言 Python作为一门功能强大且易于学习的编程语言,广泛应用于各种领域。在图形用户界面(GUI&…...

php 常用的接口和函数
ArrayAccess — interface to provide accessing to objects as arrays 提供以数组形式访问对象的接口。 interface synopsis 接口需要实现下面几个方法 interface ArrayAccess { /* Methods */ public offsetExists(mixed $offset): bool public offsetGet(mixed $offset):…...

【Flutter】Flutter 动画深入解析(2):掌握 AnimatedBuilder 将动画的逻辑和 UI 代码分离
【Flutter】Flutter 动画深入解析(2):掌握 AnimatedBuilder 将动画的逻辑和 UI 代码分离 文章目录 一、前言二、Flutter 动画简介三、什么是 AnimatedBuilder四、AnimatedBuilder 与其他动画小部件的比较五、如何使用 AnimatedBuilder六、实际业务中的应用场景七、完整示例八…...

Spring Boot中解决跨域问题(CORS)
1. 跨域介绍 首先解释什么是跨域,跨域就是前端和后端的端口号不同;会产生跨域问题,这里浏览器的保护机制(同源策略)。 同源策略:前端和后端的协议、域名、端口号三者都相同叫做同源。 我们看一下不同源&am…...

基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python 计算机竞赛
文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于生成对抗网络的照片上色动态算法设计与实现 该项目较为新颖&am…...

广州华锐互动:数字孪生可视化制作软件有哪些亮点?
由广州华锐互动开发的数字孪生可视化制作软件在当今的数字孪生领域中扮演着重要角色,它突破了许多传统数字孪生可视化制作软件的限制。以下是几个方面的突破: 无限自由度:传统的3D建模工具通常有限制编辑器的自由度,使用户难以进行…...

设计模式之工厂模式讲解与案例
工厂模式是一种创建对象的设计模式,它通过提供一个统一的接口来创建对象,隐藏了具体对象的实例化过程。Java中的工厂模式有多种实现方式,下面我将举两个常见的例子。 简单工厂模式(Simple Factory Pattern):…...

(免费领源码)php#MySQL软件测试文档管理系统28035-计算机毕业设计项目选题推荐
目 录 摘 要 Abstract 第1章 前 言 1.1 研究背景 1.2 开发意义 1.3 系统开发目标 第2章 系统开发环境 6 2.1 HTTP协议 6 2.2 HTML网页技术 6 2.3 B/S结构 6 2.4 PHP脚本语言 7 2.5 MySQL数据库 7 2.6 Apache简介 8 第3章 需求分析 3.1 需求分析 3.2 系统可…...

05.Oracle数据库对象
Oracle数据库对象 Oracle数据库对象是指在Oracle数据库中存储和管理数据的各种实体。以下是一些常见的Oracle数据库对象: 表(Table):用于存储数据的基本结构。表由一组列(Column)组成,每列定义了…...

某国产中间件企业:提升研发安全能力,助力数字化建设安全发展
某国产中间件企业是我国中间件领导者,国内领先的大安全及行业信息化解决方案提供商,为各个行业领域近万家企业客户提供先进的中间件、信息安全及行业数字化产品、解决方案及服务支撑,致力于构建安全科学的数字世界,帮助客户实现…...

Servlet中主要的内置对象
Servlet中有一些内置对象,它们提供了与 Web 容器和客户端交互的功能。这些对象在开发Servlet时可以直接使用,无需显式创建。 一些主要的内置对象包括: HttpServletRequest:代表客户端的HTTP请求,提供访问请求的内容&am…...

STL-set和map
目录 一、pair和make_pair 1. pair 2. make_pair 二、set (一)set的模板参数列表 (二)set的构造 (三)set的插入 1. 测试1 2. 测试2 (四)low_bound和upper_boundÿ…...

【WinForm详细教程四】WinForm中的ProgressBar 、ImageList和ListView控件
文章目录 1.ProgressBar2. ImageList3.ListView控件 1.ProgressBar 用于显示某个操作的进度。 属性: Value: 表示当前进度条的值,其范围由Min和Max决定。Step: 设置每次调用PerformStep()方法时增加的步长。MarqueeAnimationSpeed: 在Style设置为Marq…...

写一个简单实用的Excel工具类
我们在开发中经常要将数据导入成Excel表格供展示,也需要解析Excel中的数据,官方提供的api操作太麻烦,这边封装了一个Excel工具类,可以很轻松的实现Excel的操作 首先加入依赖 <dependencies><!-- EasyPoi 基本库依赖 --…...

C#中LINQtoObjects、LINQtoDataSet和LINQtoXML
目录 一、使用LINQ操作数组和集合 二、使用LINQ操作DataSet数据集 1.AsEnumerable()方法 2.CopyToDataTable()方法 3.AsDataView()方法 4.Take()方法 5.Sum()方法 6.示例 (1)源码 (2)生成效果 三、使用LINQ操作XML 1.…...

k8s中 RBAC中,clusterrole,serviceaccount , rolebinding 是什么关系谁先谁后
在Kubernetes的RBAC(Role-Based Access Control)中,ClusterRole、ServiceAccount和RoleBinding是三个关键的组件,它们之间的关系如下: ClusterRole:ClusterRole 是一种全局的权限规则,它定义了一…...

什么是文件安全
文件安全就是通过实施严格的访问控制措施和完美的权限卫生来保护您的业务关键信息不被窥探,除了启用和监控安全访问控制外,整理数据存储在保护文件方面也起着重要作用。通过清除旧的、过时的和其他垃圾文件来定期优化文件存储,以专注于关键业…...

maven的settings.xml和pom.xml配置文件详解
一、配置文件 maven的配置文件主要有 settings.xml 和pom.xml 两个文件。 其中在maven安装目录下的settings.xml,如:D:\Program Files\apache-maven-3.6.3\conf\settings.xml 是全局配置文件 用户目录的.m2子目录下的settings.xml,如&#…...

YB2503HV 100V 3A SOP8内置MOS 高效率降压IC(昱灿)
YB2503HV 100V 3A SOP8内置MOS 高效率降压IC 描述: YB2503HV是单片集成可设定输出电流的开关型降压恒压驱动器,可工作在宽输入电压范围具有优良的负载和线性调整度。安全保护机制包括每周期的峰值限流、软启动、过压保护和温度保护。YB2503HV需要非常少…...

Redis安装Linux
1、下载安装包 wget http://download.redis.io/releases/redis-3.0.0.tar.gz 2、安装 mkdir /usr/local/redis cp redis-3.0.0.tar.gz /usr/local/redis cd /usr/local/redis tar -zxvf redis-3.0.0.tar.gz cd /usr/local/redis/redis-3.0.0 make PREFIX/usr/local/red…...

PCL点云处理(007)-Ransac
随机抽样一致性算法RANSAC(Random sample consensus)是一种迭代的方法来从一系列包含有离异值的数据中计算数学模型参数的方法。 RANSAC算法本质上由两步组成,不断进行循环: 从输入数据中随机选出能组成数学模型的最小数目的元素,使用这些元素…...

有方N58 HTTP POST 请求连接 TDengine
串口调试软件:格西调试精灵 第一步先注册网络获取IP地址 建立PPP连接 ATXIIC1\r PPP链路建立成功,查询IP地址 ATXIIC?\r 设置网络APN ATCREG?\r 运行结果,红线处是获…...

基于Python+Pygame实现一个滑雪小游戏
目录 项目介绍Pygame介绍项目文件夹介绍演示视频代码免费领取 一、项目介绍 使用介绍:运行main.py文件后,通过左右按键可以控制小人的移动,如果经过旗杆那么10分,如果碰到树木那么减50分。 二、Pygame介绍 Pygame是一个用于游…...

【限制输入框值类型】自定义指令el-input输入类型限制,vue和html两个版本
前言 经常遇到输入框需要限制只能输入数字的, 因为用户很离谱,明显输入数字的地方他非要输入英文或者中文 但是用到UI框架或者自己写方法验证表单比较麻烦 为了一个输入框专门去弄一个验证很麻烦 所以这里就整合了两种自定义指令的方式,更加…...

对一个金融风控测额公式的理解(1)
目录 公式:(近3个月回款总额/过去3个月的FBA平均库存价值)*最近FBA的库存价值*过去13周FBA发货比例 详细讨论一下这个:(近3个月回款总额/过去3个月的FBA平均库存价值) 既然(近3个月回款总额/…...

【GEE】2、探索数据集
1简介 在本单元中,我们将讨论以下概念: Google 地球引擎中可用的潜在数据来源。 通过生态示例显示的数据集采样用例。 如何使用 Google 地球引擎访问重要的元数据。 2背景 要将遥感集成到您的研究和分析中,学习如何解析 Google 地球引擎上…...

开发一款直播弹幕游戏需要多少钱?
开发一款直播弹幕游戏需要多少钱?有好多朋友在咨询过弹幕游戏的开发价格后,都会比较吃惊,一款体量这么小的游戏为什么动辄就要几万块甚至十几万? 我来给你们说分析一下原因,这种游戏如果脱离开直播间,可以…...

STM32F103C8T6第一天:认识STM32 标准库与HAL库 GPIO口 推挽输出与开漏输出
1. 课程概述(297.1) 课程要求:C语言熟练,提前学完 C51 2. 开发软件Keil5的安装(298.2) 开发环境的安装 编程语言:C语言需要安装的软件有两个:Keil5 和 STM32CubeMX Keil5 的安装…...

selenium元素定位 —— 提高篇 CSS定位元素
CSS (Cascading Style Sheets) 是一种用于渲染 HTML 或者 XML 文档的语言,CSS 利用其选择器可以将样式属性绑定到文档中的指定元素。理论上说无论一个元素定位有多复杂都能够定位到元素。 因为不同的浏览器 XPath 引擎不同甚至没有自己的 Xpath 引擎,这…...