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

yo!这里是进程控制

目录

前言

进程创建

fork()函数

写时拷贝 

进程终止

退出场景

退出方法

进程等待 

等待原因

等待方法

1.wait函数

2.waitpid函数

等待结果(status介绍)

进程替换

替换原理

替换函数

进程替换例子

shell简易实现

后记


前言

        学习完操作系统中进程部分的入门介绍之后,大家应该进程有了个初步了解,那么,下面就可以很好地进军进程控制部分了,包括进程创建、进程终止、进程等待、进程替换等重点部分,其中的细节很多,也比较难以理解,但是没有关系,在介绍完进程控制之后,会简单实现一个shell程序,也就是类似Xshell的一个软件,也可以执行相关命令进行各种操作,来综合理解一下四个重点部分,快往下看吧!

进程创建

  • fork()函数

        在进程入门理解章节中,我们介绍到了fork()函数,可以创建一个新进程,此进程称为子进程,原进程称为父进程,函数信息如图所示

        还知道,fork失败时返回-1,成功时有两个返回值,给父进程返回子进程的pid,给子进程返回0,所以fork()之后由此分流,使得父子进程去做不同的事。

1)fork()深入理解

        由于进程=内核数据结构+进程的代码和数据,其中内核数据结构由os搞定,而进程的代码和数据一般从磁盘来,也就是c/c++运行的可执行文件。所以fork()之后,os创建子进程,为其分配对应内核数据结构(必须子进程独有,因为进程具有独立性),理论上,子进程也要有自己的代码和数据,这如何拥有?

        对于代码,都是不可写的,所以父子共享,对于数据,可写可读,所以不能共享,必须分离。这里先针对于代码,数据的分离会在下面的写时拷贝讲到。

        见上图想一下,fork()之后,父子进程是共享after的代码还是共享所有的代码?所有的!但子进程从after那里开始执行,而不是从头开始执行。下面先提一下两个认知:

①代码汇编以后会有很多行代码,在加载到内存之后,每行代码都有自己对应的地址;

②cpu中有一个寄存器叫做EIP,也叫做pc指针、程序计数器,记录当前正在执行代码的下一行代码的地址,属于进程的上下文数据。

        创建子进程时,EIP的值无需给子进程,因为父子进程各自调度,会修改EIP,就算给了子进程也用不到,在子进程中会将after的第一行代码的地址赋给EIP,进程就从after开始执行了。

        值得注意的是,fork()之后,父子进程两个执行流分别各自执行,谁先执行完全是由调度器决定的。

2)fork()常规用处

        ①一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

        ②一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec系列函数进行进程替换。

3)fork()调用失败的原因

        ①系统中有太多的进程;

        ②实际用户的进程数超过了限制。

  • 写时拷贝 

        在上文讲到,对于代码,都是不可写的,所以父子共享,对于数据,可写可读,所以不能共享,必须分离。那如何分离呢?直接拷贝一份然后修改?

        不行!这样的话会存在子进程不会用到的空间,造成内存浪费,即使有用到的空间,也可能只是读取,所以,数据分为不会被访问或指挥读取的数据和将来会被父或子进程写入的数据,但一般而言,os无法得知哪些数据不会被访问或者只会读取亦或会被写入,所以os选择了写时拷贝技术。

        也就是说,父子进程两方都没有数据写入操作时,数据也是共享的,当任意一方试图写入时,便以写时拷贝的方式重新分配内存并将原来内存上的内容拷贝到新内存上,再进行修改。

好处:

        ①使父子进程彻底分离,保证了进程的独立性;

        ②写时拷贝是一种延时申请技术,提高了整机内存的使用率。

进程终止

  • 退出场景

        在写c/c++程序时,我们写main函数都要返回一个int,且都返回0,这个操作到底是什么意思呢?实际上,main函数作为一个进程,在结束时是要返回一个结果给操作系统的。对于main函数,返回0代表成功返回,结果正确;而返回非0值代表结果不正确或异常,因为非0值有很多,所以错误结果或异常结果就对应很多,此时这个0或者非0值叫做进程退出码,返回给上一层评判进程的执行结果的。

        但是我们写完一个main函数,也不知道结果到底正不正确,进而也不知道该返回什么啊?其实是可以的,看看下面的例子,我们可以使用if语句判断是否为期望结果决定返回值,结果不正确返回1,正确返回0,当然也可以预判出其他错误返回不同的非0值。

        通过main函数可以总结出,一个进程退出有三种情况:

①正常退出,结果正确;

②正常退出,结果不正确;

③异常退出。

  • 退出方法

1)正常终止的方法

        ①在main函数中return;

        ②调用系统接口_exit函数;

        ③调用库函数exit函数,

注意:

        ①必须在main函数中返回才是终止进程,普通函数返回只是在返回调用结果;

        ②正常终止都会返回进程退出码给os,可以通过【echo $?】查看最近一次的进程退出码,同时可以通过函数【strerror(退出码)】查看对应的退出原因,比如

2)异常终止

        ①ctrl+c;

        ②通过信号终止。

注意:通过信号终止,在下面即将要学到的信号章节中讲解,这里重点讲上面的正常退出的方法。

exit、_exit介绍与对比: 

1)_exit函数

       参数status存储着进程的终止信息(包括进程退出码),父进程通过wait函数接收该值,这里在进程等待部分重点讲解。

2)exit函数

         这里的参数status与_exit函数中的一样。

3)对比

①exit函数与_exit函数在代码的任何地方调用都表示结束进程,无论在main中还是调用的某个函数中。

②其实,exit库函数是_exit系统接口的一个封装,在exit函数内部,也会调用_exit函数,但在这之前,还会执行清理函数,并且清理缓冲区,然后再调用_exit函数,如图。

注意:return结束进程更为常见,return n相当于exit(n)。

进程等待 

  • 等待原因

        在前面说过僵尸进程的问题,即子进程退出但父进程不管不顾,就会造成内存泄漏。按照正常情况,父进程创造出一个子进程肯定是要其完成一个任务,然后子进程去完成,父进程等待子进程终止以后返回的结果,这就是进程等待。通过这种方式,父进程回收子进程的资源及获取子进程退出信息(比如进程退出码)。

  • 等待方法

1.wait函数

返回值:成功接受到被等待进程返回该进程的pid,失败返回-1;

参数:status是一个输出型参数,即传进此参数,函数内会将进程信息放进这个指针中,函数返回后,父进程可通过此值查看子进程信息,若不想得到父进程的结束信息,就传入NULL,关于此参数的构成会在下文提到。

eg:

2.waitpid函数

参数:

        pid:①传入指定被等待的进程pid,②传-1,代表等待任一个进程,与wait等效;

        status:与wait函数一致;

        options:①传WNOHANG,代表若指定进程没有结束,则函数直接返回0,不再等待,若进程已经结束,则函数直接返回子进程pid,即非阻塞等待;②传0,代表当子进程没有结束,父进程处于阻塞状态去等待其结束,与wait等效。

注意:WNOHANG是一个宏定义,一般大写的标记位都是宏定义。

返回值:

        与wait一致,但要注意设置了WNOHANG选项的返回值。

eg:

  • 等待结果(status介绍)

        对于wait/waitpid函数,都有一个输出型参数status,os将子进程信息填入其中,带给父进程。status不能简单的当作一个整形来看,要分开看它的比特位(目前只关心status的低16个比特位),如图

        可以看到,低八位存放终止信号,此低八位存放退出码,对于异常终止时的core dump标志暂时不说明,后面信号章节会说到。明显地,当wait/waitpid函数接受完子进程退出结果之后,正常退出可以通过【(status>>8)&0xFF】获取退出码,异常退出可以通过【status&0x7F】获取终止信号,有点C语言地基础都可以看的懂,不多赘述。因为比较麻烦,所以Linux也提供了可以关于此的宏定义:

①WIFEXITED(status):查看进程是否正常退出,若正常退出返回真,否则返回假;

②WEXITSTATUS(status):查看进程的退出码。

eg(除了else部分,其他部分与上张截图一样):

eg(增加了子进程睡眠时间,中间通过kill指令杀掉进程): 

进程替换

  • 替换原理

        通过特定的接口,加载磁盘上的一个全新的程序(代码和数据)到内存中,并和当前进程的页表重新建立映射,这就叫做替换,而其中加载的方法就是使用exec系列函数。当进程调用一种exec函数时,该进程的代码和数据完全被新程序替换,从新程序的开头开始执行,原理图如下。

注意:

        ①调用exec函数并没有创建新进程,所以调用前后的进程id并没有变化;

        ②当子进程加载新数据时,代码和数据就会被替换,对于代码而言这正是一种写入,即写时拷贝,此时,父子进程彻底分离,虽然曾经并不冲突(之前说过,父子进程代码共享,数据采用写时拷贝的手段)。

  • 替换函数

        如图一,替换函数有6种,统称为exec函数,而图二的一个exec函数是系统调用函数,图一的6个函数都是基于这个系统调用函数封装的函数,以满足不同的需求,这里我们也是重点介绍上面6个函数。

参数:

        path参数是个指针,需要传入一个路径(字符串),

        arg参数也是个指针,需要传入一个指令,而后面的省略号是可变参数列表,可以传入指令的选项,

        file参数:指针变量,传入一个文件名,

        envp:指针数组,里面存放环境变量;

        argv参数:指针数组,里面存放命令行参数,即全部arg参数。

返回值:

        如果调用成功则加载新的程序从新程序的启动代码开始执行,不再返回,如果调用出错就返回-1。

注意:

        l(list) : 表示参数采用列表

        v(vector) : 参数用数组

        p(path) : 有p自动搜索环境变量PATH

        e(env) : 表示自己维护环境变量

eg:

 注意:可变参数列表列出所有的选项后要以NULL结尾,命令行参数数组也是如此。

进程替换例子

1.如何替换自己的c/c++程序

        自己通过vim或者其他编辑器编写一个c/c++程序,这里我编写了一个cmd.c的文件,其中main函数的参数可以传入命令行参数个数,命令行参数数组,及环境变量数组(这里我没有传入),函数体根据命令行参数传入所构成,具体如下图一所示。

        之前说过,我们可以创建一个子进程,让其执行其他的事,父进程等待回收以接受结果,图二就是在这样一个情况下,我们将子进程替换成自己写的如图一所示的子程序,如图二。

2.如何替换其他语言的程序

        替换其他语言的程序与c/c++语言的程序并无二质,都先编译成可执行程序,然后将子进程替换成自己写的程序就行,这里举例python程序和shell程序,具体如下图一

        如图二则是替换python程序的结果,替换shell程序一样。

shell简易实现

        在学习Linux的过程中,离不开xshell的帮助,这个软件可以远程访问服务器,通过指令管理服务器上的文件等,比如,ls、pwd等。思考一下,我们可不可以通过程序控制来简易实现一个shell程序,步骤如下:

①获取命令行;

②解析命令行;

③创建子进程;

④替换子进程;

⑤父进程等待接收子进程;

⑥循环以上步骤。

代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define CMD_LINE_SIZE 1024
#define ARGC_MAX 32char cmd_line[CMD_LINE_SIZE];
char* _argv[ARGC_MAX];int main()
{//程序不退出while(1){//用户名+主机+当前目录//[phan9@iZf8z8xmdh7b2erpqis8sxZ test_os_8_21]$printf("[root@localhost shell]# ");//初始化memset(cmd_line,'\0',sizeof(cmd_line));//获取用户输入的指令if(fgets(cmd_line,sizeof(cmd_line),stdin)==NULL)continue;cmd_line[strlen(cmd_line)-1]='\0';//将指令选项导入指令数组int index=0;_argv[0]=strtok(cmd_line," ");//将指令选项导入指令数组while(_argv[index]){index++;_argv[index]=strtok(NULL," ");}//创建进程pid_t id=fork();if(id==0){//子进程execvp(_argv[0],_argv);exit(1);}//父进程继续int status=0;int res=waitpid(-1,&status,0);//阻塞等待if(res>0)printf("退出码:%d\n",WEXITSTATUS(status));}return 0;
}

以上是基本的shell框架,可以自行加入一些功能,比如【ls -l】指令简写成【ll】指令,文件名变色,如图地方加入

后记

        本篇文章的知识点加上上篇进程入门介绍文章的知识点,大家应该对操作系统中的进程所涉及的知识点有了比较全面的认识了,相信反复阅读两篇文章,再加上自己尝试实现一个简易的shell程序,可以更加的深入认识,两篇文章有不懂的地方可以私我或者发在评论区有大伙共同解答哦,加油,拜拜!


相关文章:

yo!这里是进程控制

目录 前言 进程创建 fork()函数 写时拷贝 进程终止 退出场景 退出方法 进程等待 等待原因 等待方法 1.wait函数 2.waitpid函数 等待结果&#xff08;status介绍&#xff09; 进程替换 替换原理 替换函数 进程替换例子 shell简易实现 后记 前言 学习完操作…...

多线程快速入门

线程与进程区别 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合&#xff0c;或者是程序的特殊段&#xff0c;它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程&#xff0c;它负责在单个程序里…...

Redis 7 第七讲 哨兵模式(sentinal)架构篇

哨兵模式 哨兵巡查监控后台master主机是否故障,如果出现故障根据投票时自动将某一个从库转换成新的主库,继续对外服务。 作用 1. 监控redis运行状态,包括master和slave 2. 当master down机,能自动将salve切换成新的master 应用场景 主从监控监控主从redis库运行的状态…...

laravel框架系列(一),Dcat Admin 安装

介绍 Laravel 是一个流行的 PHP 开发框架&#xff0c;它提供了一套简洁、优雅的语法和丰富的功能&#xff0c;用于快速构建高质量的 Web 应用程序。 以下是 Laravel 的一些主要特点和功能&#xff1a; MVC 架构&#xff1a;Laravel 使用经典的模型-视图-控制器&#xff08;MV…...

Linux:工具(vim,gcc/g++,make/Makefile,yum,git,gdb)

目录 ---工具功能 1. vim 1.1 vim的模式 1.2 vim常见指令 2. gcc/g 2.1 预备知识 2.2 gcc的使用 3.make,Makefile make.Makefile的使用 4.yum --yum三板斧 5.git --git三板斧 --Linux下提交代码到远程仓库 6.gdb 6.1 gdb的常用指令 学习目标&#xff1a; 1.知道…...

小节1:Python字符串打印

1、字符串拼接 用可以将两个字符串拼接成一个字符串 print("你好 " "这是一串代码") 输出&#xff1a; 2、单双引号转义 当打印的字符串中带有引号或双引号时&#xff0c;使用\或\"表示 print("He said \"Let\s go!\"") 输…...

2023国赛C题解题思路代码及图表:蔬菜类商品的自动定价与补货决策

2023国赛C题&#xff1a;蔬菜类商品的自动定价与补货决策 C题表面上看上去似乎很简单&#xff0c;实际上23题非常的难&#xff0c;编程难度非常的大&#xff0c;第二题它是一个典型的动态规划加仿真题目&#xff0c;我们首先要计算出销量与销售价格&#xff0c;批发价格之间的…...

数据可视化工具中的显眼包:奥威BI自带方案上阵

根据经验来看&#xff0c;BI数据可视化分析项目是由BI数据可视化工具和数据分析方案两大部分共同组成&#xff0c;且大多数时候方案都需从零开始&#xff0c;反复调整&#xff0c;会耗费大量时间精力成本。而奥威BI数据可视化工具别具匠心&#xff0c;将17年经验凝聚成标准化、…...

LeetCode算法心得——生成特殊数字的最少操作(贪心找规律)

大家好&#xff0c;我是晴天学长&#xff0c;这是一个简单贪心思维技巧题&#xff0c;主要考察的还是临场发挥的能力。需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。 2) .算法思路 0 00 50 25 75 末尾是这两个的才能被45整除 思路&#xff1a;分别找&#x…...

【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献

【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献 1 题目 1.1 问题背景 多波束测深系统是利用声波在水中的传播特性来测量水体深度的技术&#xff0c;是在单波束测深的基础上发展起来的&#xff0c;该系统在与航迹垂直的平面内一次能发射出数十个乃至上百个…...

如何处理异步编程中的回调地狱问题?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 解决回调地狱问题的方法⭐使用 Promise⭐使用 async/await⭐ 使用回调函数库⭐模块化⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端…...

什么是Lambda表达式?

Lambda表达式是Java 8引入的一个重要特性&#xff0c;用于简化函数式编程中的匿名函数的定义和使用。它可以被视为一种轻量级的匿名函数&#xff0c;可以作为参数传递给方法或存储在变量中。 Lambda表达式的语法形式如下&#xff1a; (parameters) -> expression 或 (para…...

公式trick备忘录

增大不同class feature之间的距离用hinge loss 相关&#xff0c; similarity learning, svm https://www.youtube.com/watch?vQtAYgtBnhws https://www.youtube.com/watch?vbM4_AstaBZo&t286s...

向量数据库Milvus Cloud核心组件再升级,主打就是一个低延迟、高准确度

支持 ScaNN 索引 Faiss 实现的 ScaNN,又名 FastScan,使用更小的 PQ 编码和相应的指令集可以更为友好地访问 CPU 寄存器,从而使其拥有优秀的索引性能。该索引在 Cohere 数据集,Recall 约 95% 的时候,Milvus 使用 Knowhere 2.x 版本端到端的 QPS 是 IVF_FLAT 的 7 倍,HN…...

ELK框架Logstash配合Filebeats和kafka使用

ELK框架Logstash配合Filebeats和kafka使用 本文目录 ELK框架Logstash配合Filebeats和kafka使用配置文件结构input为标准输入&#xff0c;output为标准输出input为log文件output为标准输出output为es input为tcpspringboot配置logstash配置 input为filebeatsfilebeats配置logsta…...

后端面试话术集锦第 十二 篇:java基础部分面试话术

这是后端面试集锦第十二篇博文——java基础部分面试话术❗❗❗ 1. String类中常用的方法 split():把字符串分割成字符串数组 indexOf():从指定字符提取索引位置 trim():去除字符串两端空格 replace():替换 hashCode():返回此字符串的哈希码 subString():截取字符串 equa…...

【广州华锐互动】电厂三维数字孪生大屏的功能和优势

在工业互联网的背景下&#xff0c;电厂三维数字孪生大屏系统正在逐渐成为电力行业的重要技术。通过创建电厂的虚拟模型&#xff0c;这个数字孪生系统可以实现对实际电厂的实时监控&#xff0c;预测维护需求&#xff0c;优化运营效率&#xff0c;甚至在某些情况下&#xff0c;能…...

es6解构用法

一: 解构数组 二&#xff1a;解构对象 一: 解构数组 原理&#xff1a;模式(结构匹配), 索引值相同的完成赋值 总结&#xff1a;位置对应 二&#xff1a;解构对象 原理&#xff1a;模式(结构匹配), 属性名相同的完成赋值 {}{} 对象结构赋值的应用 常用的就以上两种 &#…...

a_bogus 音 算法还原大赏

a_bogus算法还原大赏 hello&#xff0c;大家好呀&#xff0c;我是你的好兄弟&#xff0c;[星云牛马]&#xff0c;花了几天时间算法还原了这个参数的加密过程&#xff0c;一起看看吧&#xff0c;记得加入我们的学习群&#xff1a;529528142 天才第一步&#xff0c;F12你会不&am…...

【计算机网络】UDP协议详解

目录 前言 端口号的拓展 端口号范围划分 netstat pidof UDP协议 UDP协议端格式 UDP的特点 面向数据报 UDP的缓冲区 UDP使用注意事项 基于UDP的应用层协议 前言 我们前面讲完了http和https协议&#xff0c;它们都属于应用层&#xff0c;按照TCP/IP五层模…...

2023-9-8 满足条件的01序列

题目链接&#xff1a;满足条件的01序列 #include <iostream> #include <algorithm>using namespace std;typedef long long LL;const int mod 1e9 7;int qmi(int a, int k, int p) {int res 1;while(k){if(k & 1) res (LL) res * a % p;a (LL) a * a % p;…...

获取街道、乡镇级的地图geoJson数据,使用echarts绘制地图

在此以泰州靖江市为例为例&#xff0c;记录一下实现过程 1、整体完成后实现的效果如下 2、获取数据 &#xff08;1&#xff09;DataV.GeoAtlas 第一个能想到的获取数据的网站就是它&#xff0c; 是阿里推出的一个用于获取全国、各省、各市以及个县级市详细地图信息的json文…...

DBMS_RESOURCE_MANAGER

参考文档&#xff1a; Database Administrator’s Guide 27 Managing Resources with Oracle Database Resource Manager 27.5.5 Creating a Resource Plan BEGINDBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA();DBMS_RESOURCE_MANAGER.CREATE_PLAN(PLAN > bugdb_plan,…...

通俗讲解傅里叶变换

参考:六一礼物:给孩子解释什么是傅里叶变换 牛!不看任何数学公式来讲解傅里叶变换 如何直观形象、生动有趣地给文科学生介绍傅里叶变换? - 知乎 从基说起…… 从数学的角度,提供一个形象有趣的解释。理解傅里叶变换的钥匙是理解基♂,它能让你重新认识世界。 1. 什么是…...

数据结构——带头双向循环链表

数据结构——带头双向循环链表 一、带头双向循环链表的定义二、带头双向循环链表的实现2.1初始化创建带头双向循环链表的节点2.2申请新节点2.3节点的初始化2.4带头双向循环链表的尾插2.5带头双向循环链表的头插2.6判空函数2.7带头双向循环链表的打印函数2.8带头双向循环链表的尾…...

MySQL大数据量高速迁移,500GB只需1个小时

在上篇「快、准、稳的实现亿级别MySQL大表迁移」的文章中&#xff0c;介绍了NineData在单张大表场景下的迁移性能和优势。但在大部分场景中&#xff0c;可能遇到的是多张表构成的大数据量场景下的数据搬迁问题。因为搬迁数据量较大&#xff0c;迁移的时长、稳定性及准确性都受到…...

kafka复习:(25)kafka stream

一、java代码&#xff1a; package com.cisdi.dsp.modules.metaAnalysis.rest.kafka2023;import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.KafkaStreams; import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.s…...

接口自动化测试总结

一、什么项目适合做自动化测试&#xff1f; 软件需求变动不频繁 测试脚本的稳定性决定了自动化测试的维护成本。如果软件需求变动过于频繁&#xff0c;测试人员需要根据变动的需求来更新测试用例以及相关的测试脚本&#xff0c;而脚本的维护本身就是一个代码开发的过程&#x…...

【Redis】Lua脚本在Redis中的基本使用及其原子性保证原理

文章目录 背景一、Eval二、EvalSHA三、Redis 对 Lua 脚本的管理3.1 script flush3.2 script exists3.3 script load3.4 script kill 四、Lua在Redis中原子性执行的原理 背景 Lua 本身是一种轻量小巧的脚本语言&#xff0c;在Redis2.6版本开始引入了对Lua脚本的支持。通过在服务…...

汇编--int指令

中断信息可以来自CPU的内部和外部&#xff0c; 当CPU的内部有需要处理的事情发生的时候&#xff0c;将产生需要马上处理的中断信息&#xff0c;引发中断过程。在http://t.csdn.cn/jihpG&#xff0c;我们讲解了中断过程和两种内中断的处理。 这一章中&#xff0c; 我们讲解另一种…...