【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:Linux从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学更多操作系统知识
  🔝🔝


在这里插入图片描述

进程程序替换

  • 1. 前言
  • 2. exec系列函数的认识
  • 3. execl系列函数
  • 4. execv系列函数
  • 5. 程序替换的使用场景
  • 6. 自我实现一个bash解释器
  • 7. 内建命令的特殊性
  • 8. 总结以及拓展

1. 前言

本篇文章是进程控制的最后一篇文章
有时我们遇见这种场景:子进程被创建
出来后并不想执行父进程的代码,而是
想去执行其他程序的代码来完成任务,
于是在这种场景下,程序替换显得很重要!

本章重点:

本篇文章着重讲解进程程序替换
的exec系列函数的用法(一共六个),
并且自主实现一个bash解释器.
最后拓展如何使用C调用其他语言的程序


2. exec系列函数的认识

在fork之后如果子进程想要执行一个
全新的程序,就需要用到此系列函数!

在这里插入图片描述

这里一共有六个函数,但是它们都有
一定的规律,并不难记忆!它们都是失败
返回-1首先先介绍一个最简单的函数:
execl

它的参数分别代表:要执行的程序的路径
以及名字,和如何执行此程序

先使用再解释:

int main()    
{    
    printf("我要进行程序替换了...\n");    
    int n = execl("/usr/bin/ls","ls","-a","-l",NULL);                                                                                                                   
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
}    

现象:

在这里插入图片描述
可以发现,在打印完:我要进行程序替换
后,就去执行了ls程序了,并且执行完后
并没有打印"程序替换完成"!

表面execl后,会将当前进程的代码和数据
进行替换,包括还没执行的代码!


3. execl系列函数

首先,execl隶属于exec系列,加上l,
l就是list,相当于要把执行的程序的路径
给列举出来,execl系列中还有
execlp和execle,现在来讲解这两个

在这里插入图片描述

execlp函数解析:

int main()    
{    
    printf("我要进行程序替换了...\n");    
    int n = execlp("ls","-l",NULL);                                                                                                                                     
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
} 

在这里插入图片描述

可以发现,使用execlp函数即使不加上
路径也可以找到程序,并且允许它,但是
这是为什么呢?带上p,p也就是PATH
环境变量,所以系统会去环境变量PATH
中找路劲,若找到就直接执行它!

execle函数解析:

int main()    
{    
    const char* _env[]={"MY_ENV=666",NULL};    
    printf("我要进行程序替换了...\n");    
    int n = execle("/usr/bin/ls","ls","-l",NULL,_env);//自己定义一个环境变量MY_ENV=666传递给要去执行的程序                                                              
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
}  

execle可以在执行其他程序前,
传入自己定义的环境变量,方便后续
程序的执行!e也就是env的简写


4. execv系列函数

execv系列函数即为将l换成了v,
v就是vector,数组,也就是利用
数组来传参

在这里插入图片描述

execv函数解析:

int main()    
  {    
	  char* const set[]={"ls","-a","-l",NULL};  
      printf("我要进行程序替换了...\n");    
      int n = execv("/usr/bin/ls",set);                                                                             
      if(n==-1)    
      {    
          perror("execl");    
      }    
      printf("程序替换完毕!\n");    
      return 0;    
  } 

将我们要执行程序的方法用数组存起来
如何再把数组传过去!后面的execvp和
execvpe函数也就很好理解了,加上p无非
就是去环境变量PATH中找路径,加上e也
就是给要去执行的程序传入环境变量,仅此而已!

在这里插入图片描述


5. 程序替换的使用场景

其实一般情况下,程序替换都不是将
自己替换掉,而是创建子进程去替换,
让子进程去干活,而父进程当"监工"

在这种场景下,我们可以很自然的想到
bash解释器的工作原理可能就是创建
子进程去执行任务,而bash父进程本身
就需要获取指令,并传达命令即可!

首先,bash解释器一定是一个while
死循环,因为它会不断给我们打印信息:
当然这个消息你可以自定义处理

在这里插入图片描述

int main()
{
    while(1)
    {
        //打印提示信息
        printf("[kwy@localhost myshell]# ");
        fflush(stdout);
        ......
    }
    return 0;
}

我们先把bash的整体结构分析一下,
然后在一步一步的实现它:

  1. 首先我们需要定义两个数组A和B
    A用来存放用户输入的所有字符串
    B用来存放以空格打散后的字符串

  2. 第二步,获取用户输入的字符串后,
    将字符串以空格为分割打散

  3. 第三步,创建子进程使用exec系列
    函数去执行用户输入的指令
    而bash本身充当监工的角色等待子进程死亡


6. 自我实现一个bash解释器

首先先创建两个数组备用
然后再接收用户的输入

#define NUM 1000
#define SIZE 16
char cmd_line[NUM];//保存完整的命令行字符串
char* my_argv[SIZE];//保存打散后的字符串
if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)//用fgets将标准输入输入到数组中
    continue;
cmd_line[strlen(cmd_line)-1] = '\0';//将输入的换行符给清除掉

接下来就是将字符串以空格为分割打散了
在C语言的学习时有strtok函数可以帮助
我们解决这个问题,它的功能如下:

在这里插入图片描述

这里默认大家知道这个函数的用法了
所以我直接将分割字符串的代码写出来:

//命令行字符串解析:以空格为分割打散
my_argv[0]=strtok(cmd_line," ");//提出第一部分
int index=1;
while(my_argv[index++] = strtok(NULL," "));//第二次调用strtok时若还想解析第一次调用的字符串,则传NULL

这段代码写完后,字符串就已经被我们
分割成了几个小字符串了,比如用户输入
“ls -a -l"就转换成了"ls”,“-a”,"-l"了,接下来
只需创建子进程完成任务即可!

//shell运行原理:通过子进程执行命令,父进程等待&&解析命令
//命令行解释器是一个常驻程序
#define NUM 1000
#define SIZE 16
char cmd_line[NUM];//保存完整的命令行字符串
char* my_argv[SIZE];//保存打散后的字符串

int main()
{
    while(1)
    {
        //打印提示信息
        printf("[kwy@localhost myshell]# ");
        fflush(stdout);
        memset(cmd_line,'\0',sizeof cmd_line);
        //获取用户的键盘输入
        if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)
            continue;
        cmd_line[strlen(cmd_line)-1] = '\0';//将输入的换行符给清除掉
        //命令行字符串解析:以空格为分割打散
        my_argv[0]=strtok(cmd_line," ");//提出第一部分
        int index=1;
        while(my_argv[index++] = strtok(NULL," "));//第二次调用strtok时若还想解析第一次调用的字符串,则传NULL
        //fork后子进程去完成任务
        pid_t id=fork();
        if(id == 0)//子进程
        {
            printf("下面的功能让子进程执行\n");
            //当执行cd等命令时,改变的是子进程的路径,而父进程的路径没变
            execvp(my_argv[0],my_argv);
            exit(1);//执行失败就返回1
        }
        //父进程的代码,当监工
        int status = 0;
        pid_t ret = waitpid(-1,&status,0);
        if(ret>0) printf("exit code: %d\n",WEXITSTATUS(status));
    }
    return 0;
}

关于代码的解释都在注释中
如果你还有哪个地方不懂,欢迎私信


7. 内建命令的特殊性

在实现bash时,可能会遇见一个问题:
就是cd指令进入某个文件夹似乎没用

这一点其实很好理解,因为指令cd是
进入某个文件夹,而进入此文件夹当然
是当前进程进入了,如果创建了子进程
去进去文件夹,由于写时拷贝的原因,父
进程并不会进去,所以对于像cd这样的
指令我们称为内建命令,也就是不能让
子进程来完成的命令,只能父进程亲自动手!

if(strcmp(my_argv[0],"cd")==0)
{
   if(my_argv[1]!=NULL)
       chdir(my_argv[1]);
   continue;//直接跳到while(1)处
}

chdir即为切换当前的工作目录

内建命令不止cd,像export,kill
和history等等也是内建命令!


8. 总结以及拓展

进程程序替换可以帮助我们完成很多
任务,制作一个简易的bash解释器只是
众多应用中的一个,随着我们学习的深入
你还会发现新大陆!

对于程序替换的拓展:

在Linux下,C语言程序不仅可以替换成
C语言程序去执行,还可以替换成python
或Java甚至是bash等程序去执行它们
语言的代码:

比如python脚本:

#! /usr/bin/python3.6
print("hello Python/n")

运行命令: python test.py

将进程替换为Python程序:execlp("python", "python", "test.py", NULL);

这样就可以直接在C程序上执行python代码了!


🔎 下期预告:基础IO 🔍

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【仿真动画】双机器人协作完成一个任务(切割)

场景 动画 两个机器人协同工作完成一个任务需要解决以下几个关键问题: 通信:两个机器人需要能够相互通信,以共享信息,例如位置、姿态、状态等。规划:需要对两个机器人的运动轨迹进行规划,确保两个机器人不会…

RESTful API概述以及如何使用它构建 web 应用程序

REST(Representational State Transfer)是一种设计风格和架构原则,它是一种为 Web 应用程序提供简化和标准化的 API 的方式。RESTful API(RESTful Web Services)是符合 REST 架构风格的网络应用程序 API,它…

未来之路:大模型技术在自动驾驶的应用与影响

本文深入分析了大模型技术在自动驾驶领域的应用和影响,万字长文,慢慢观看~ 文中首先概述了大模型技术的发展历程,自动驾驶模型的迭代路径,以及大模型在自动驾驶行业中的作用。接着,详细介绍了大模型的基本定义、基础功…

关系查询处理和查询优化

关系数据库系统的查询处理 4 个阶段 查询分析查询检查【此时的完整性检查是初步的、静态的检查】查询优化【分为代数优化、物理优化】查询执行 关系数据库系统的查询优化 查询优化的优点不仅在于用户不必考虑如何最好地表达查询以获得较高地效率,而且在于系统可…

Springboot项目部署及多环境开发

一、项目部署 我们之前写的代码都是部署在本地的tomcat上,别人是无法访问我们写的程序的。在实际开发中,我们都要将开发完毕的项目部署到公司的服务器上。 我们的代码需要经过编译打包生成一个jar包,这个过程需要借助一个插件来实现。 创建sp…

2024最新基于物联网单片机毕业设计选题汇总(合集)

博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…

初始MySQL(四)(查询加强练习,多表查询)

目录 查询加强 where加强 order by加强 group by 分页查询 总结 多表查询(重点) 笛卡尔集及其过滤 自连接 子查询 子查询当作临时表 all/any 多列子查询 #先创建三张表 #第一张表 CREATE TABLE dept(deptno MEDIUMINT NOT NULL DEFAULT 0,dname VARCHAR(20) NOT …

2023-11-13 LeetCode每日一题(区域和检索 - 数组可修改)

2023-11-13每日一题 一、题目编号 307. 区域和检索 - 数组可修改二、题目链接 点击跳转到题目位置 三、题目描述 给你一个数组 nums ,请你完成两类查询。 其中一类查询要求 更新 数组 nums 下标对应的值另一类查询要求返回数组 nums 中索引 left 和索引 right…

Oracle主备切换,ogg恢复方法(经典模式)

前言: 文章主要介绍Oracle数据库物理ADG主备在发生切换时(switchover,failover),在主库、备库运行的ogg进程(经典模式)如何进行恢复。 测试恢复场景: 1 主备发生switchover切换,主库为ogg源端 2 主备发生switchover切换,备库为ogg源端 3 主备…

【Linux】Linux动态库和静态库

​ ​📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:Linux 🎯长路漫漫浩浩,万事皆有期待 上一篇博客:【Linux】…

AIOT数字孪生智慧工地一体化管理平台源码

智慧工地app基于物联网和移动互联网技术,利用各类传感器及终端设备通过与云端服务器的实时数据交互,为施工现场的管理人员提供环境监测、劳务实名制管理、物料管理、巡检记录、设备管理等一系列优质高效的行业解决方案。 一、智能工地应用价值 智慧工地…

Java+Spring Cloud +UniApp +MySql智慧工地综合管理云平台源码

智慧工地围绕工程现场人、机、料、法、环及施工过程中质量、安全、进度、成本等各项数据满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效. 智慧工地综合管理云平台源码,PC监管端、项目端;APP监管端、项目端、数据可视化大屏端源码&#xf…

springboot rocketmq 延时消息、延迟消息

rocketmq也有延迟消息,经典的应用场景:订单30分钟未支付,则取消的场景 其他博客提到从rocketmq5.0开始,支持自定义延迟时间,4.x只支持预定义延迟时间,安装rocketmq可参考RocketMq简介及安装、docker安装ro…

iOS OpenGL ES3.0入门实践

一、效果图 入门实践,做的东西比较简单,效果如下: 二、关于顶点坐标和纹理坐标 绘制图片需要设置顶点坐标和纹理坐标并加载像素数据,之所以要指定两组坐标是因为纹理和顶点使用不同的坐标系,就是告诉OpenGL&#xf…

9 个可以免费检索意外删除或丢失的文件的专业数据恢复软件

今天,我们将探索一些最佳数据恢复软件,它们可以帮助您从 Windows PC 或存储设备中检索意外删除或丢失的文件! 丢失数据或意外删除数据是一种令人不安的经历。值得庆幸的是,存在有效的解决方案来解决这种情况。今天,我…

CSS 滚动捕获 scroll-snap-type

scroll-snap-type 语法实例 捕获轴 y 捕获严格程度 mandatory捕获轴 y 捕获严格程度 proximity同理看下捕获轴 x 一些注意事项兼容性 scroll-snap-type 用来指定一个滚动容器(scroll container)是否是滚动捕获容器(scroll snap container)、捕获的严格程度以及在什么方向上执行…

2.8 CE修改器:寻找共享代码

本关我们将学习共享代码,在C语言中角色属性都是以结构体的方式进行存储的,而结构体所存储的信息都是连续性的,这一关我们将会解释如何处理游戏中的共用代码,这种代码是通用在除了自己以外的其他同类型对像上的常常你在修改游戏的时…

在Vue.js中,什么是Vuex?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用

💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用 Spring Cloud OpenFeign是一个声明式的服务调用框架,基于Feign并整合了Ribbon和…

Crypto | Affine password 第二届“奇安信”杯网络安全技能竞赛

题目描述: 明文经过仿射函数y3x9加密之后变为JYYHWVPIDCOZ,请对其进行解密,flag的格式为flag{明文的大写形式}。 密文: JYYHWVPIDCOZ解题思路: 1、使用在线网站直接破解或手工计算破解,获得flag。&#xf…
最新文章