Linux---进程(6)---进程控制

 

目录

 

进程创建

写时拷贝

代码共享原理

写时拷贝的设计

写时拷贝原理

进程终止

信号编号

进程退出码

exit函数/_exit函数解析

进程等待

等待接口

status

父进程等待方式

阻塞等待

非阻塞等待

进程替换

进程替换接口

Shell运行规则

环境变量与进程替换

su-/su指令与进程替换


进程创建

在Linux中,通过fork函数从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值:给子进程返回0,给父进程返回子进程的pid(这是为了让父进程找到子进程,而设计的。子进程通过ppid找父进程,父进程通过fork返回值找到子进程),出错则会返回-1。

调用fork函数后,操作系统内核做的工作:

1、分配新的内存块和内核数据结构给子进程

2、将父进程部分数据结构内容拷贝至子进程

3、添加子进程到系统进程列表当中

4、fork函数返回,由调度器调度

子进程会继承父进程PCB中的大部分属性,少量的pid、ppid等属性是自己的属性,代码也和父进程共享一份代码,需要修改时会发生写时拷贝。

fork之后,父子进程谁先执行完全由调度器决定

写时拷贝

fork返回给父子进程不同的值本质是由写时拷贝来完成的

程序被编译为二进制之后,所有的变量名、函数名都经过翻译变为物理地址,变量名、函数名只是为了方便我们用户去编程,提高代码可读性、可写性。

代码共享原理

父子进程的代码如何做到共享呢?

结论:父子进程各自的页表指向同一块区域(内存)中。

当父子进程不写入/修改数据时。

当父子进程任意一方进行写入时(这里以子进程写入为例)

写时拷贝的设计

为什么不在创建子进程时,直接将父进程的数据给子进程拷贝一份呢?

原因:因为如果在创建子进程的时候直接给子进程拷贝一份,如果子进程不执行写入/修改操作,或者执行一段时间之后再写入,那这部分空间操作系统就管理不了了,在子进程还存在的情况下,并且子进程写入的数据可能很少,这样就增加了fork的成本,操作系统效率也会变低,所以,操作系统通过写时拷贝按需分配内存。

为什么在写入的时候,申请出空间之后,还要将原数据给需要修改数据的进程拷贝一份呢?

原因:可能进程的修改要在原数据的基础之上修改,也可能只修改一部分,剩下的还用原来的数据。操作系统无法很清楚的判断出这些操作,所以,申请好空间之后,拷贝一份泛用性比较强。

写时拷贝原理

写实拷贝如何做到呢?

页表是用来帮助操作系统进行虚拟地址-物理地址相互转化的,同时也会做检测(缺页中断),其实,页表也有很多其他的属性,写时拷贝就是借助页表中的权限属性。

页表的虚拟-物理地址的转化工作也是受权限来控制的

比如:用户对某个虚拟地址的空间进行写入时,页表会判断该虚拟地址是否有w(写)权限,如果有写权限,就由虚拟地址转换为物理地址,如果没有写权限,就不能发生地址转换。就像之前C语言中的char*p="hello world","hello world"是字符常量,字符常量不能修改,就不能执行*p="haha"这样的指令。本质上是因为"hello world"在内存中存储,指针p在页表中记录的是地址,这个地址的权限是只读权限,所以在进行虚拟-物理地址转换中,由于权限问题,导致转换失败。

说明一下:这里也可以加const,const是在程序编译的时候检测出问题,将运行的错误提前在编译的时候就暴露出来,是一种防御性编程。

fork之前

fork创建子进程之后,操作系统会故意将父子进程的数据地址权限修改为只读。

等到父子进程某一方发生写入时,需要将虚拟地址转换为物理地址,但是此时权限是只读,所以就会转换失败,操作系统接收到转换失败的信息后,识别是此情况之后,发生缺页中断,由操作系统向内存申请空间,构建新的映射关系将其填入页表中,将权限恢复成可读可写,继续执行写入工作即可。

总而言之。写时拷贝就是由操作系统介入处理完成的。

操作系统会将大部分数据都设置为只读属性,只要系统中触发写时拷贝,操作系统就会介入处理,按需触发写时拷贝。

fork常见用法是创建了子进程之后,通过返回值区分父子进程,利用if-else来使父子进程来执行不同的操作。

fork调用失败原因:

1、当前系统中有太多的进程,此时创建进程会被操作系统拦截。

2、当前用户创建了太多的进程(操作系统限制用户只能创建一定数量的进程)。

进程终止

进程退出时,一共有三种情况

1、代码没执行完,进程异常结束。

2、代码正常执行结束,结果不正确。

3、代码正常执行结束,结果正确。

任何进程最终执行的全部情况,由两个数字决定,分别是信号编号、进程退出码。

信号编号

进程退出码

意义
00代码正常执行结束且结果正确
01代码正常执行结束且结果不正确
10代码执行出现异常(退出码无意义)
11代码执行出现异常(退出码无意义)

信号编号

情况一中代码没有执行完成,进程就异常结束了。进程异常结束(比如,发生了野指针、数组越界、除0错误等)的本质是进程收到了信号,信号就是一个编号,每个编号对应一种错误信号,即我们可以通过信号编号来确定使程序异常的原因信息。

kill -l #查询当前系统的信号编号表

这些信号都是被定义好的宏常量,本质就是前面的数字。异常信号从1开始。

进程退出码

main函数的返回值,就是进程退出码。

进程退出码是用来反映进程在代码执行完成的情况下,结果是否正确的数据,0表示结果正确,非0表示结果不正确且具体的非0编号来表示失败的原因。

echo $? #记录最近一次进程退出码

不同的退出码,对应不同的退出信息,就形成了错误码表。

错误描述(错误码表)分为语言系统自带的和自定义的错误码表。

语言系统自带的错误码表

退出码可以设置退出码字符串,可以自定义。

main函数在退出时,表示进程退出,退出的值即为退出码

其他函数退出只表示函数调用完成。

函数(也叫子程序)退出之后,我们通过函数的返回值来获取函数执行的情况。我们通过函数返回值来获取函数执行结果。那函数执行的成功与失败我们该如何获取呢?

这时候,C标准库就为其中的函数提供了一个errno错误码,是一个全局变量,且只服务于标准库函数。自定义函数没有。

所以,库函数的返回值返回的是执行结果,errno变量返回的是函数出错的错误码。

exit函数/_exit函数解析

区别:

exit接口是用户调用接口、_exit函数是系统调用接口。

exit函数在退出时会刷新缓冲区,然后退出。_exit函数不刷新缓冲区就直接退出。

exit函数封装了_exit函数,两个函数底层都借助操作系统终止进程。

这里的缓冲区是语言库级别的缓冲区,不是操作系统中的缓冲区。

如果是操作系统中的缓冲区,_exit函数也会刷新缓冲区,所以此缓冲区是语言库级别的缓冲区。

进程在创建时,操作系统为其PCB属性申请内存空间,创建页表,将磁盘中的代码和数据拷贝至内存,构建页表映射关系,当进程退出时,释放PCB属性的资源,释放内存中的代码和数据,僵尸进程保留PCB至父进程等待回收资源。

进程等待

我们都知道,一个进程的终止都是从僵尸状态变为终止状态。

进程执行完成之后,首先变为僵尸状态,在这个状态下,该进程的代码和数据(可执行程序)、页表、PCB中大部分信息都会被操作系统释放掉,唯一保留下来的就是用来记录该进程执行情况的退出信息,这里的退出信息就是进程终止中的信号编号和进程退出码,通过获取这两个数字就可以获取该进程的执行情况。等该进程的父进程通过等待获取到该进程的退出信息之后,进程才会进入终止状态,PCB彻底被释放。进程也会彻底消失。

父进程通过等待获取该进程的退出信息。

父进程等待子进程是为了回收子进程的资源(释放资源)和获取子进程的退出信息。

如果进程的退出信息迟迟不被该进程的父进程等待获取,那么该进程的PCB就会一直存在,造成内存泄漏。

等待接口

wait在等待时,默认会进行阻塞等待,且等待任意一个子进程。

阻塞等待是指即使父进程执行完了,只要子进程没有执行完,父进程就不退出,一直等子进程退出获取退出信息。

阻塞等待返回值:>0:等待成功,具体的数字是被等待的子进程的pid

                             <0:等待失败

fork创建子进程之后,父子进程谁先执行是由调度器决定的。最终是父进程最后退出。

while :; do ps axj | head -1 && ps axj | grep mybin(可执行程序名称) | grep -v grep; sleep 1; done #每个1秒检测一次
#测试代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{pid_t id = fork();if(id==0){//child processint num=5;while(num!=0){printf("I am child process,pid:%d,ppid:%d\n",getpid(),getppid());sleep(1);num--;}exit(1);}//father processint status=0;pid_t rid=wait(&status);if(rid>0){printf("wait success\n");}else{printf("wait fail\n");}return 0;
}

status

下面了解一下status这个输出型参数。

我们知道,一个进程的退出信息由退出码和信号编号组成。信号编号表示该进程是否异常结束(0表示正常结束,非0表示异常结束,具体的数字表示具体的信号)、进程退出码表示该进程在正常执行完成之后的返回值(用于检验结果是否正确,0表示正确,非0表示错误且具体数字表示错误信息编号,之后可用strerror函数查看具体错误信息)。

status变量用来记录进程退出码和信号编号,status为int类型,4字节=32bit,Linux就是用这32个比特位的后16位采用二进制方式(将数值转化为二进制)记录进程退出码和信号编号的。

我们传入status变量的地址,父进程wait等待完成之后,status变量被赋予了值

获取信号编号   :status&&0x7F

获取进程退出码:(status>>8)&&0xFF

此外,Linux内核(C语言)还提供给我们两个宏来查看信号编号和退出码信息。

WIFEXITED(status)宏来判断程序是否正常退出,若程序正常退出,返回真,异常则为假。

WEXITSTATUS(status)宏返回进程退出码,此接口用于查看进程退出码。

WIFEXITED(status)、WEXITSTATUS(status)都是通过宏来获取系统调用。

为什么不用两个全局变量来拿到退出码和信号编号呢?

原因在于:定义的全局变量必然是属于父进程的,子进程退出时要修改代码和数据会发生写时拷贝,操作系统会为子进程单独开辟空间,所以子进程将自己的退出码和退出信息写入到了属于自己的数据段,子进程无法写入到父进程的数据中,因为进程具有独立性,父子进程无法直接互相修改对方的数据且双方都无法获取对方的数据,所以只能通过系统调用接口。

信号编号和退出码只属于操作系统内部进程PCB中的信息,用户无法访问。

读取子进程的退出信息,本质是读取内核数据。

父进程等待方式

父进程在等待子进程执行完的这段时间,有阻塞等待和非阻塞等待两种方式。

options参数:0表示阻塞等待,WNOHANG表示非阻塞等待。

阻塞等待

父进程在等待子进程时,发现子进程还在运行中,由于系统调用的原因,将自己的状态设置为非R状态(大多都是S状态),将自己链接到等待队列中进行等待子进程。

父进程在调用等待这个系统调用时,检测出子进程没有退出时,就一直等待子进程退出,在此期间,父进程只能等待无法执行别的指令,等子进程退出了,再返回。

非阻塞等待

父进程执行完等待时,检测到子进程还在运行,没有退出,就立马返回,不等子进程退出。

父进程在调用等待时,检测子进程状态,如果子进程没有退出就返回(返回0),则每隔一段时间再去检测(再去调用等待),等到某次调用时,子进程退出了,就不再检测了。在子进程运行的过程中,父进程返回再到下一次调用等待的过程中,这期间父进程是可以执行其他指令的,一直做这种轮询检测。

每单次检测称为非阻塞,这个过程称为基于非阻塞状态的轮询访问,轮询期间,父进程可以执行其他指令。

非阻塞返回值:>0:表示等待成功

                         =0:表示调用等待接口成功,但子进程还在运行没有退出

                         <0:表示出现了异常,接口调用失败。

父进程在进行非阻塞等待时,可以执行别的操作。

子进程退出信息由bash获取。

命令行参数在执行指令时,bash会创建子进程,让子进程去执行我们输入的命令,bash则会等待,等到子进程退出之后,bash调用等待接口释放子进程且拿到子进程的退出信息,并将子进程退出码覆盖写入到自己用来记录上一条指令的退出码字段中去,以此类推,所以,执行echo $?指令时,才可以拿到最近一次进程的退出信息。

进程替换

我们创建的进程,只能执行当前的代码,最多可以通过if-else分流让父子进程执行不同代码中的不同区域,但如果我们创建出子进程,想让子进程执行其他的代码和数据呢?---进程替换

CPU正在执行当前进程时,当这个进程的代码和数据被操作系统调换之后,CPU不关心替换,继续执行。

进程替换就是将新代码和新数据替换旧代码和数据,重新构建页表映射关系,程序分配调整虚拟内存,这些工作由操作系统完成,操作系统通过系统调用,在替换的过程中,还用的是之前程序的壳子,只是放入了新的内容罢了,没有创建子进程

旧进程的mm_struct、页表、对应的物理内存、新的代码和数据替换旧的代码和数据。重新在旧的属性中调整,整个过程中,没有创建新的进程,操作系统做的这一切,底层都采用系统调用。

进程替换接口

每个进程替换接口特有字母含义:

l :表示参数列表

p:表示系统会去PATH环境变量中查找,只需要传指令名,不需要带路径。

v:表示需要传命令行参数数组

e:表示可以传自定义的环境变量表

程序替换成功之后,程序替换函数之后的代码将不被执行,因为调用程序替换接口之后,内存中的代码和数据已经被替换了。

程序替换系列接口只有调用失败时才会返回-1,调用成功不返回(出错失败返回值,成功没有返回值)。

系统替换接口若失败,程序必定执行失败,失败就会执行之后的代码,所以直接退出即可。

一般程序替换接口之后接exit退出函数。

调用成功则不执行后续代码,调用失败则需要退出函数使其退出。

整个程序替换期间,不创建新的子进程。

创建新的进程时,首先创建PCB属性、页表等,再将程序加载至内存。

程序替换时,如果源程序有代码和数据,那么新程序就替换旧程序的代码和数据。如果原程序没有代码和数据,新程序就加载至内存。

程序替换的本质就是加载。写时拷贝的本质是申请空间。

加载就是将磁盘中的数据拷贝至内存,由操作系统来做,程序替换是系统调用接口。

Shell运行规则

1、bash创建子进程,先创建出PCB属性、页表,子进程会继承bash的代码和数据。

2、子进程发生程序替换,替换为我们输入的指令(指令也是程序),子进程发生写时拷贝,操作系统为子进程的mm_struct、页表等信息做调整。

3、bash等待子进程退出,释放子进程并获取退出信息。

在系统的角度看来,任何语言在运行之后都是进程,所以可用一种语言写的进程替换另一种语言写的进程。

环境变量与进程替换

子进程默认拿到的环境变量,默认可以通过进程地址空间继承父进程的方式,让所有子进程都拿到环境变量。

进程替换之后,不会替换环境变量中的数据,因为进程始终没变。

每个进程的命令行参数都是通过继承父进程的命令行参数表来的。要想修改或者添加,就会发生写时拷贝,操作系统为该处理进程申请空间一系列操作。

bash在启动时,会根据配置文件构建一张系统环境变量表,bash子进程会继承下来,所以,以后每个进程都会继承其父进程的系统环境变量表,所以子进程就算不传环境变量表也可以使用父进程的环境变量表访问环境变量。

如果当前进程要修改或者添加环境变量,就会发生写时拷贝,操作系统为其操作处理,将该进程新增的环境变量添加至该进程自己的环境变量表中(调用putenv(指定环境变量)接口)。所以进程替换带e参数是将指定的环境变量传给子进程。

程序替换的系统调用接口只有一个,其他程序替换接口都是对系统调用接口的封装。

su-/su指令与进程替换

在发生用户替换时,也是创建子进程,然后让子进程替换掉bash。

su -指令会切换至root的家目录,会有新的环境变量。

su指令之后,使用的还是之前bash的环境变量,工作目录不变。

最后,如有不足,清各位大佬指正!!!

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

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

相关文章

【Datawhale AI夏令营第四期】 浪潮源大模型应用开发方向笔记 Task04 RAG模型 人话八股文Bakwaan_Buddy项目创空间部署

【Datawhale AI夏令营第四期】 浪潮源大模型应用开发方向笔记 Task04 RAG模型 人话八股文Bakwaan_Buddy项目创空间部署 什么是RAG&#xff1a; 我能把这个过程理解为Kimi.ai每次都能列出的一大堆网页参考资料吗&#xff1f;Kimi学了这些资料以后&#xff0c;根据这里面的信息综…

C#发送邮件功能实现全面指南?有哪些方法?

C#发送邮件性能优化技巧&#xff1f;C#发信时如何设置邮件格式&#xff1f; 在现代应用程序开发中&#xff0c;发送电子邮件是一个常见的需求。无论是发送通知、警报&#xff0c;还是定期报告&#xff0c;邮件功能都是至关重要的。AokSend将详细探讨如何在C#中实现发送邮件的功…

SpringBoot教程(二十三) | SpringBoot实现分布式定时任务之xxl-job

SpringBoot教程&#xff08;二十三&#xff09; | SpringBoot实现分布式定时任务之xxl-job 简介一、前置条件&#xff1a;需要搭建调度中心1、先下载调度中心源码2、修改配置文件3、启动项目4、进行访问5、打包部署&#xff08;上正式&#xff09; 二、SpringBoot集成Xxl-Job1.…

stm32的UART重定向printf()

1配置好uart 2打开usart.c文件 3在此文件前面添加头文件 4在末尾添加重定向代码 添加的代码 /* USER CODE BEGIN 1 *///加入以下代码,支持printf函数,而不需要选择use MicroLIB //#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #if 1 //#pragma import(__use_n…

day01- Web开发介绍-HTML-CS

Web开发介绍 1 什么是web开发 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站。 所以Web开发说白了&#xff0c;就是开发网站的&#xff0c;例如下图所示的网站&#xff1a;淘宝&#xff0c;京东等等 那么我们…

JavaEE过滤器的创建与使用过滤器的使用场景

过滤器 Filter也称之为过滤器&#xff0c;过滤器是javaEE规范肿定义的一种技术,可以让请求到达目标servlet之前,先进入到过滤器中,在过滤器中统一进行一些拦截处理,当处理完成后,可以继续向后执行,到达目标servlet,如果配置了多个过滤器,也可以进入下一个过滤器 创建过滤器 创…

asyncua模块实现OPC UA通讯

asyncua是OPCUA的python实现&#xff0c;使用起来非常方便&#xff0c;其github地址是https://github.com/FreeOpcUa/opcua-asyncio UaExpert是OPC UA Client的GUI工具&#xff0c;当编写好server代码后并运行&#xff0c;我们可以使用UaExpert去和server进行通信。UaExpert使…

小程序使用微信支付

接入小程序需要后端进行注册&#xff0c;前端只需要调用API即可。 大概流程如下&#xff1a; 本例使用一个购买商品的例子&#xff0c;前端点击微信支付&#xff0c;后端生成一个商品订单&#xff0c;然后向微信支付系统发起请求&#xff0c;生成一个微信的预支付订单&#xff…

windows上传的文本在linux执行不了,格式转换

在windows编辑的文件脚本上传到linux里面执行不了 1.现象描述 比如在windows编辑简单的文本 2.上传到linux后执行无结果 无响应 3.编码问题 比普通文件多了with CRLF line terminators结尾格式。 cat -v 可以让隐藏的转义字符也打印中显示 4.原因windows和linux的换行符不…

数字化办公OA系统是如何运作的——办公物品领用功能拆解

数字化办公 OA 系统究竟是如何运作的呢&#xff1f;它的核心功能又是如何发挥作用的呢&#xff1f;这篇就以行政 OA 系统为例&#xff0c;从产品视角来深入探讨它是如何运作的。 简道云行政OA管理系统模板&#xff0c;可以直接查看和使用&#xff1a;https://www.jiandaoyun.co…

springboot纹理生成图片系统--论文源码调试讲解

第2章 程序开发技术 2.1 MySQL数据库 开发的程序面向用户的只是程序的功能界面&#xff0c;让用户操作程序界面的各个功能&#xff0c;那么很多人就会问&#xff0c;用户使用程序功能生成的数据信息放在哪里的&#xff1f;这个就需要涉及到数据库的知识了&#xff0c;一般来说…

地平线旭日X3开发板--使用Opencv显示CSI摄像头例程

调试过程发现旭日派无法直接用opencv的VideoCapture()调用CSI摄像头, 需要使用libsrcampy库来获取CSI摄像头图像, 但是从libsrcampy获取的摄像头图像格式是NV12,无法直接在opencv上显示或者处理, 为了能够使用opencv的API来处理图像, 需要增加NV12转化成bgr像素格式的…

springboot项目配置https安装ssl证书教程

1.将下载的ssl证书文件中的jks后缀文件放在/src/main/resource文件夹里面 2.在配置文件中&#xff08;yml后缀配置文件的格式不同&#xff09;添加如下配置即可

ensp小实验(ospf+dhcp+防火墙)

前言 今天给大家分享一个ensp的小实验&#xff0c;里面包含了ospf、dhcp、防火墙的内容&#xff0c;如果需要文件的可以私我。 一、拓扑图 二、实训需求 某学校新建一个分校区网络&#xff0c;经过与校领导和网络管理员的沟通&#xff0c;现通过了设备选型和组网解决方案&…

运维学习————nginx-入门及反向代理搭建

目录 一、简介 二、正向代理和反向代理 1、正向代理 作用 2、反向代理 作用 三、单机版nginx部署 1、查看环境 2、环境安装以及nginx安装 2.1、安装pcre 2.2、安装gzip模块需要 zlib 库 2.3、安装Nginx 3、启动测试 四、反向代理配置 一、简介 nginx [engine x] 是…

计算机Java项目|基于SpringBoot的大学生一体化服务平台的设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参…

C++入门——16C++11新特性

1.列表初始化 初始化列表时&#xff0c;可添加等号()&#xff0c;也可不添加。 struct Point {int _x;int _y; };int main() {int x1 1;int x2{ 2 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C11中列表初始化也可以适用于new表达式中int* pa …

POK´ELLMON:在宝可梦战斗中实现人类水平的人工智能

人工智能咨询培训老师叶梓 转载标明出处 最近&#xff0c;由美国乔治亚理工学院的Sihao Hu、Tiansheng Huang和Ling Liu发表的论文介绍了POKELLMON&#xff0c;这是一个开创性的基于大模型&#xff08;LLM&#xff09;的具身智能体&#xff0c;它在战术战斗游戏中&#xff0c;特…

海康VisionMaster使用学习笔记11-VisionMaster基本操作

VisionMaster基本操作 VM示例方案 1. 工具拖拽及使用方式 分别从采集和定位栏里拖拽图像源,快速匹配,Blob分析工具 2. 模块连线 依次连线 3.如何订阅 点击快速匹配,可以看到输入源已订阅了图像1的图像,Blob分析类似 4. 方案操作及全局触发 点击快速匹配,创建特征模版,框选…

【Hot100】LeetCode—2. 两数相加

目录 1- 思路思路 2- 实现⭐2. 两数相加——题解思路 3- ACM 实现 原题连接&#xff1a;2. 两数相加 1- 思路 思路 分为几个步骤 ①数据结构&#xff1a;遍历指针,进位符、②遍历两个链表、③处理最后的进位符 1- 数据结构 定义 curA 和 curB 用来遍历两个链表定义 carry 记…