yo!这里是进程控制

news2024/12/30 3:30:10

目录

前言

进程创建

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 32

char 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.coloradmin.cn/o/989360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【更新至2022年】2000-2022年全国31省市以2000年为基期的实际GDP、名义GDP、GDP平减指数数据(含原始数据+计算过程+计算结果)

2000-2022年31省市名义GDP 实际GDP GDP平减指数 1、时间&#xff1a;2000-2022 2、范围&#xff1a;31省市 3、来源&#xff1a;GJ统计J和统计NJ 4、指标&#xff1a;名义GDP、地区生产总值指数&#xff08;上年100&#xff09;、实际GDP&#xff08;以2000年为基期&#x…

【光伏系统】将电流从直流转换为交流电的太阳能逆变器、太阳能跟踪系统来提高系统的整体性能及集成电池解决方案(Simulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

(高阶)Redis 7 第10讲 单线程 与 多线程 入门篇

面试题 1.Redis 是单线程还是多线程 最早的版本3.x是单线程。 版本4.x,严格意义不是单线程。负责处理客户端请求的线程是单线程,开始加入异步删除。 6.0.x版本后明确使用全新的多线程来解决问题 2.说说IO多路复用3.Redis 为什么快IO多路复用+epoll函…

基于SSM的乡镇自来水收费系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【leetcode 力扣刷题】回文串相关题目(KMP、动态规划)

回文串相关题目 5. 最长回文子串动态规划中心扩展算法 214. 最短回文串336. 回文对 5. 最长回文子串 题目链接&#xff1a;5. 最长回文子串 题目内容&#xff1a; 题目就是要我们找s中的回文子串&#xff0c;还要是最长的。其实想想&#xff0c;暴力求解也行……就是遍历所有的…

系统移植MakefileREADME文件分析

Makefile # 指定交叉编译工具链前缀变量 CROSS_COMPILE arm-linux-gnueabihf- #指定文件名字变量 NAME interface ## #-g:编译时添加gdb调试信息 -marm: 将程序编译生成arm指令集 -Wall:编译时显示所有警告信息 #-O0:编译时添加优化等级 -O0:不优化 -O1:一级优化 #-f…

zabbix配置钉钉告警、和故障自愈、监控java

文章目录 1.配置钉钉告警server 配置web界面创建媒介给用户添加媒介测试告警 实现故障自愈功能监控Javazabbix server 安装java gateway配置 Zabbix Server 支持 Java gateway使用系统内置模板监控 tomcat 主机 1.配置钉钉告警 server 配置 钉钉告警python脚本 脚本1 cd /…

实相融、云启未来,智慧公厕让城市生活更美好

现代社会&#xff0c;随着科技的不断发展&#xff0c;人们对于城市生活的要求也在不断提升。在这个过程中&#xff0c;智慧公厕作为城市基础设施中的重要组成部分&#xff0c;正在发挥着越来越重要的作用。通过数字化、云管理、人工智能等未来的科技方式&#xff0c;智慧公厕为…

2024字节跳动校招面试真题汇总及其解答(一)

1. 【算法题】重排链表 给定一个单链表 L 的头节点 head ,单链表 L 表示为: L0 → L1 → … → Ln - 1 → Ln请将其重新排列后变为: L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 示例 1: 输入:hea…

嵌入式Linux驱动开发(同步与互斥专题)(二)

一、自旋锁spinlock的实现 自旋锁&#xff0c;顾名思义&#xff1a;自己在原地打转&#xff0c;等待资源可用&#xff0c;一旦可用就上锁霸占它。 ① 原地打转的是CPU x&#xff0c;以后CPU y会解锁&#xff1a;这涉及多个CPU&#xff0c;适用于SMP系统&#xff1b; ② 对于单…

【Vue】Router路由无法跳转问题整理

问题集 整理了部分Vue Router路由无法跳转问题&#xff1a; 顶层router-view只能被顶层路由配置内容使用&#xff1a;此问题异常表现在路由跳转但页面不变子路由跳转必需父路由对应的组件中存在router-view&#xff1a;此问题异常表现在路由跳转但页面不变 子路由配置路径会自…

uniapp项目实践总结(十三)封装文件操作方法

导语&#xff1a;在日常 APP 开发过程中&#xff0c;经常要进行文件的保存、读取列表以及查看和删除文件等操作&#xff0c;接下来就看一下具体的方法。 目录 原理分析方法实现实战演练案例展示 原理分析 主要是以下 API。 uni.saveFile&#xff1a;保存文件到本地缓存列表…

处理流程设计-系统设计-人机界面设计

处理流程设计-系统设计-人机界面设计 流程表示工具&#xff08;重点&#xff09; 流程表示工具&#xff08;重点&#xff09; 数据流图也是一种 IPO 图 NS图和PAD图

为什么说电子元器件采购如果不够专业就容易上当受骗

电子元器件采购如果不够专业就容易上当受骗的原因有多方面&#xff0c;主要包括以下几点&#xff1a; 众多供应商和产品&#xff1a;电子元器件市场涉及众多供应商和各种不同的产品。如果采购人员不具备足够的专业知识和经验&#xff0c;可能会难以识别哪些供应商和产品是可信赖…

flink 写入数据到 kafka 后,数据过一段时间自动删除

版本 flink 1.16.0kafka 2.3 流程描述&#xff1a; flink利用KafkaSource&#xff0c;读取kafka的数据&#xff0c;然后经过一系列的处理&#xff0c;通过KafkaSink&#xff0c;采用 EXACTLY_ONCE 的模式&#xff0c;将处理后的数据再写入到新的topic中。 问题描述&#xff1…

【SpringMVC]获取参数的六种方式

目录 1.通过ServletAPI获取 2.通过控制器方法的形参获取 3.RequestParam&#xff1a;将请求参数和控制器方法的形参绑定 4.RequestHeader&#xff1a;将请求头信息与控制器方法的形参的值进行绑定 5. CookieValue&#xff1a;将cookie数据和控制器方法的形参绑定 Cookie&…

入门人工智能 —— 学习一门编程语言 python 基础代码编写和运算符介绍(1)

入门人工智能 —— 学习一门编程语言 python&#xff08;1&#xff09; 入门流程1.安装pythonwindowslinux ubuntu 代码编写打印输出结果 基本加减法介绍基本运算符 随着人工智能技术的快速发展&#xff0c;越来越多的年轻人开始关注这个领域。作为入门者&#xff0c;学习人工智…

文心一言 VS 讯飞星火 VS chatgpt (88)-- 算法导论8.3 1题

一、用go语言&#xff0c;参照图 8-3 的方法&#xff0c;说明 RADIX-SORT在下列英文单词上的操作过程:COW&#xff0c;DOG&#xff0c;SEA&#xff0c;RUG&#xff0c;ROW&#xff0c;MOB&#xff0c; BOX&#xff0c; TAB&#xff0c; BAR&#xff0c; EAR&#xff0c;TAR&…

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

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

AI人工智能Mojo语言:AI的新编程语言

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 Mojo的主要功能包括&#xff1a; 类似Python的语法和动态类型使Python开发人员易于学习Mojo&#xff0c;因为Python是现代AI / ML开发背后的主要编程语言。使用Mojo&#xff0c;您可以导入和使用任何Python库&#xf…