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程序,可以更加的深入认识,两篇文章有不懂的地方可以私我或者发在评论区有大伙共同解答哦,加油,拜拜!


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

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

相关文章

校园二手物品交易系统微信小程序设计

系统简介 本网最大的特点就功能全面&#xff0c;结构简单&#xff0c;角色功能明确。其不同角色实现以下基本功能。 服务端 后台首页&#xff1a;可以直接跳转到后台首页。 用户信息管理&#xff1a;管理所有申请通过的用户。 商品信息管理&#xff1a;管理校园二手物品中…

2023-9-8 求组合数(三)

题目链接&#xff1a;求组合数 III #include <iostream> #include <algorithm>using namespace std;typedef long long LL;int p;int qmi(int a, int k) {int res 1;while(k){if(k & 1) res (LL) res * a % p;k >> 1;a (LL) a * a % p;}return res; }…

Android MQTT:实现设备信息上报与远程控制

Android MQTT&#xff1a;实现设备信息上报与远程控制 1. 介绍 1.1 MQTT是什么&#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的消息传输协议&#xff0c;最初由IBM开发&#xff0c;用于连接远程设备与服务器之间的通信。它在物…

Linux目录结构和远程使用

目录名作用根目录 ‘/’文件系统结构的起始点/root系统管理员的工作目录/home普通用户工作目录/bin存放二进制可执行文件&#xff0c;存放最经常使用的命令/sbin系统管理员使用的系统管理程序/boot启动linux时使用的一些核心文件/dev设备文件&#xff0c;包括块设备和字符设备/…

CPU 伪共享是如何发生的?又该如何避免?

CPU 如何读写数据的&#xff1f; 先来认识一下 CPU 的架构 一个 CPU 里通常会有多个 CPU 核心&#xff0c;并且每个 CPU 核心都有自己的 L1 Cache 和 L2 Cache&#xff0c;而 L1 Cache 通常分为&#xff08;数据缓存&#xff09;和&#xff08;指令缓存&#xff09;&#xff0…

STM32 Nucleo-144开发板开箱bring-up

文章目录 1. 开篇2. 开发环境搭建2.1 下载官方例程2.2 ST-Link安装 3. STM32F446ZE demo工程3.1 STM32F446ZE简介3.2 跑个demo试一试 1. 开篇 最近做项目&#xff0c;用到STM32F446ZET6这款MCU&#xff0c;为了赶进度&#xff0c;前期软件需要提前开发&#xff0c;于是在某宝买…

SQL函数

函数 字符串函数数值函数日期函数流程函数 字符串函数 常用函数&#xff1a; 函数功能CONCAT(s1, s2, …, sn)字符串拼接&#xff0c;将s1, s2, …, sn拼接成一个字符串LOWER(str)将字符串全部转为小写UPPER(str)将字符串全部转为大写LPAD(str, n, pad)左填充&#xff0c;用…

NLP(3)--GAN

目录 一、概述 二、算法过程 三、WGAN 1、GAN的不足 2、JS散度、KL散度、Wasserstein距离 3、WGAN设计 四、Mode Collapse and Mode Dropping 1、Mode Collapse 2、Mode Dropping 3、FID 四、Conditional GAN 一、概述 GAN&#xff08;Generative Adversial Networ…

【笔试强训选择题】Day37.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01; 文章目录 前言一、Day…

Java基于 SpringBoot 的车辆充电桩系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1、效果演示效果图技术栈 2、 前言介绍&#xff08;完整源码请私聊&#xff09;3、主要技术3.4.1 …

计算机竞赛 基于深度学习的目标检测算法

文章目录 1 简介2 目标检测概念3 目标分类、定位、检测示例4 传统目标检测5 两类目标检测算法5.1 相关研究5.1.1 选择性搜索5.1.2 OverFeat 5.2 基于区域提名的方法5.2.1 R-CNN5.2.2 SPP-net5.2.3 Fast R-CNN 5.3 端到端的方法YOLOSSD 6 人体检测结果7 最后 1 简介 &#x1f5…

Java缓存理解

CPU占用&#xff1a;如果你有某些应用需要消耗大量的cpu去计算&#xff0c;比如正则表达式&#xff0c;如果你使用正则表达式比较频繁&#xff0c;而其又占用了很多CPU的话&#xff0c;那你就应该使用缓存将正则表达式的结果给缓存下来。 数据库IO性能&#xff1a;如果发现有大…