Linux程序替换

news2024/9/29 7:27:37

Linux程序替换

  • 创建子进程的目的?
  • 程序替换
    • 如何实现程序替换?
    • 什么是程序替换?
      • 先见一见单进程版本的程序替换
      • 程序替换原理
    • 多进程版本的程序替换
  • execl函数组
  • 简易版Shell

创建子进程的目的?

目的:为了帮助父进程完成一些特定的任务;
子进程帮助父进程完成任务的方式有那些?
1、执行一段父进程的代码;(这是我们初学者经常使用子进程的方式):
在这里插入图片描述
2、让子进程执行一段与父进程完全不一样、全新的代码;
那么如何做到让子进程执行一段全新的代码呢?
对子进程实现程序替换

程序替换

如何实现程序替换?

Linux给我们提供了7个接口:
#include <unistd.h>
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
int execle(const char *path, const char *arg, …, char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
这些函数叫做exec函数组,我们暂且先不详细讲解着些函数的具体用法,我们后文在重点讲解;
我们现在只需知道通过调用这7个函数中的任意一个就可以完成程序替换;

什么是程序替换?

先见一见单进程版本的程序替换

为此我们先从最简单的ececl()函数讲解着走:
execl函数第一个参数path,表示我们要替换的程序在哪里,第二个参数arg表示我们想用怎么样的方式运行我们的程序!写完记得在传个参数NULL结尾!
test代码:
在这里插入图片描述
运行结果如下:
在这里插入图片描述
现象:我们观察到了我们的进程执行了我们begin的打印,然后又立马执行了ls -a -l命令,随之整个进程就运行结束了,我们发现并没有像我们想象的那样接着运行我们所写的printf(“end…\n”);语句;
这是为什么?
想要弄清楚这些现象产生的原因我们就必须清楚execl()接口进行程序替换的原理;

程序替换原理

首先根据上面的代码,我们自己写的代码(比如:打印begin的语句)在运行起来的时候就已经是一个进程,那么这时候该进程就已经有了自己的内核数据结构了,比如:pcb、进程地址空间、页表等!现在当我们的进程运行到ececl语句的时候,会发生程序替换:
在这里插入图片描述
当我们自己写的程序运行到execl()语句时,就会根据ececl()的path参数,将ls命令从磁盘加载进物理内存!加载到物理内存的那个地方呢?加载到我们自己写的程序对应的物理内存上的位置!
也就是说用ls命令的数据和代码,替换原来老的程序的代码和数据,物理空间还是原来的物理空间,页表的映射关系也基本不变,如果ls命令数据和代码太多了,os会在页表增加一些映射关系!然后该进程开始重新运行一段新的程序!
在这里插入图片描述
明白了上面的原理,我们就能明白为什么我们的老程序在运行到execl过后,会执行一段新的程序!同时由于新程序的代码和数据替换了老程序的代码和数据,当新程序运行结束过后,并不会再去运行原来老的程序的剩下的语句,比如上面代码中的打印end语句,因为打印end语句属于老程序的代码,而老程序的代码在execl接口中就被替换了,新程序运行完毕,也就表示这个进程终止了!新程序的退出码也就是该进程的退出码了!
换而言之,就是当我们的程序利用execl完成程序替换过后,当前进程的退出码就由新程序决定了!
为此我们可以查看一下在程序替换过后,进程的退出码!
测试代码:
在这里插入图片描述
在这里插入图片描述
当然我们也可以让我们的程序执行一个错误的ls命令,再次来观察当前进程的退出码:
在这里插入图片描述
在这里插入图片描述
站在进程的角度的角度来看等待程序替换:
现在我是一个进程,我的代码段和数据段都在物理内存上有一份映射,现在我调用了execl接口,execl会将我在物理内存上的数据和代码用一个新的程序的数据和代码来替换!然后我(当前进程)开始重新运行这段新程序!并且我(当前进程)的退出码由这个新程序的main函数返回值来确定!在整个程序替换期间,我(当前进程)并没由被销毁,我依旧存在!在完成程序替换过后,依旧是我(当前进程)来运行这段新程序!并不会创建一个新的进程来运行新的代码
站在新程序的角度来看待程序替换:
我是一个程序,我安安静静的躺在磁盘看“电视”,突然某一天我被execl加载进内存,让后某个进程就要求我帮他办件事;那么我(新程序)被加载进内存这个动作是由谁完成的? execl!!!
execl就充当着这个加载器的角色!
既然我自己写的程序都能加载新的程序,那么OS?
当我们想要运行某段程序的时候,OS会首先为我们的程序建立pcb、进程地址空间、页表等内核数据结构,也就是说这时候进程已经创建好了,然后在让当前进程调用execl()接口将我们的程序加载进内存,然后再开始运行我们程序!而我们的程序是从main函数开始的,但是我们的进程是先调用的execl过后我们的程序才开始运行起来的,那么换而言之在execl内部,帮助我们完成程序替换过后,execl会调用该程序的main函数,然后让该程序成功运行起来!

多进程版本的程序替换

上面我们讲解了单进程版本的程序替换和程序替换的原理,接下来我们来尝试一下多进程版本的程序替换:
也就是说我们让我们的子进程去执行一段与父进程完全不一样的代码:
测试代码:
在这里插入图片描述
在这里插入图片描述
当然,我们也可以让子进程去运行我们自己写的程序,无论我们的程序是用什么语言写的!
比如,现在我用C语言写一个程序,去运行一个C++写的程序:
测试:
被子进程运行的程序:
在这里插入图片描述
主程序:
在这里插入图片描述
运行结果:
在这里插入图片描述

接下来我们来讲解一下多进程进行程序替换的原理
首先我们的父进程,也就是mytest利用fork函数创建了一个子进程对吧!
那么刚开使的时候,子进程会继承父进程的大多数信息,包括子进程会共享着父进程的代码和数据,通过前面的学习我们知道,当我们的子进程想要修改与父进程共享的数据时,会发生写时拷贝!在物理内存中重新找一块新空间,让后将将需要修改的数据拷贝到新空间中去,然后修改子进程页表映射到该物理内存的映射关系,然后再让子进程去修改数据!以此达到进程之间的相互独立!
那么现在也是这样:刚开始的时候父子进程都共享着同一块物理内存的数据和代码:
在这里插入图片描述
当我们的子进程调用execl函数进行程序替换时,是会用程序的代码和数据来替换子进程原来数据段和代码段存的信息的!如果我们直接在“数据”和“代码”这块空间进行替换的话,我们就会将父进程的代码和数据也替换掉!从而影响到了进程的独立性!我们现在的目的是不想影想父进程,而让子进程执行一段全新的代码,为此os也会也会触发写时拷贝,当我们的子进程尝试修改代码段和数据段的信息时,os也会去重新找一物理内存中重新找一块空间来存储子进程的代码和数据,同时修改子进程代码段和数据段映射关系!
重新理解Shell运行原理
明白了上面的过程我们就能更好的理解Shell的运行原理了,首先shell从命令行接受到我们的命令后会创建一个子进程来执行我们的命令,然后在让该子进程调用execl函数来进程程序替换,替换掉子进程从Shell哪里继承下来的代码和数据!然后让子进程开始运行这段程序!

execl函数组

下面我们来正式介绍一下execl函数组:
int execl(const char *path, const char *arg, …)
参数: path//用于指定我们执行的命令在哪里
arg: 可变参数,可以传任意个参数,该参数的作用主要是告诉execl()你想怎样执行这段程序,你在命令行是怎么写的,在arg参数就怎么写,注意分割;比如:我们需要让execl按照ls -a -l的格式执行ls命令,那么我们喂给execl的参数就是(从第二个参数起):“ls”、“-a”、“-l”,NULL;一个选项一个字符串,注意当我们确定完程序运行的格式过后,必须以再传递一个NULL结尾!表示我们已经传递完当前程序的执行的格式;
比如:
在这里插入图片描述
返回值:该函数只会返回-1;由于execl是进行程序替换,当execl完成程序替换那一刻开始,execl后续的代码都被替换成了新程序的代码和数据,根本就运行不到后续的代码和数据,因此也就无法返回程序替换成功的返回值;当我们的程序替换失败的时候,我们进程的老数据和代码并没由被替换掉,当前进程依旧按照顺序执行剩下的代码,同时才能向上面返回-1来表示程序替换失败!
也就是说,execl程序替换成功是没有返回值的!如果execl有返回值那么说明程序替换失败,当前进程就会执行execl后续的代码!
int execv(const char *path, char *const argv[]);
我们可以发现execv接口与execl接口十分相似,但是在参数上却并不是一样的!
execl的参数可以是任意个,而execv的参数只有2个;
同时execv的功能与execl的功能一样,只是在使用上有点区别!
我们可以看一看exec+l就表示execl,这个l(list)就代表着列表的意思!表示execl的程序运行格式以列表的形式传递!
exec+v表示execv,这个v(vector)表示数组的意思,就表示程序运行的格式以数组的形式传递;
具体演示:
在这里插入图片描述
程序运行结果:
在这里插入图片描述
程序依旧正常运行;
有了前面的理解后面我们在认识其他exec函数就轻松了:
int execlp(const char *file, const char *arg, …);
exec+l+p:l表示以列表的形式传递程序如何运行这个程序!
p:表示path,表示我们只需告诉execlp我们要运行的程序的名称也就是传递file参数,execlp会自动去PATH环境变量下搜索!
具体演示:
在这里插入图片描述
程序运行结果:
在这里插入图片描述
int execle(const char *path, const char *arg, …,char *const envp[]);
l:如何运行程序的参数以列表的形式传递;
e:env表示自己维护环境变量
比如:我们可以将当前进程的环境变量表传递给我们的新程序!
我们的新程序就可以使用这张环境变量表:
子进程去替换的程序:
在这里插入图片描述
主程序:
在这里插入图片描述
程序运行结果:
在这里插入图片描述
我们也可以向环境变量里面加一点东西进去:
这里我们就需要使用putenv()这个函数了,putenv()功能是向环境变量表中导入一个环境变量!
在这里插入图片描述
在这里插入图片描述
讲解到这里,其他的execl函数也就依此类推了;

只不过我们需要注意一下,在exec函数组中
只有int execve(const char *filename, char *const argv[])是真正的系统调用!
其他的exec函数是基于该系统调用进行的封装!

简易版Shell

#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<stdbool.h>

#define COMMOD_NUM 256
#define ARGV_NUM 64


bool Strtok(char*commod,char**argv)
{
  //先跳过空格
  size_t i=0;
  size_t len=strlen(commod);
  size_t k=0;
 while(i<len&&commod[i]==' ')
   i++;
 if(i>=len)
 return false;
 size_t begin=i;
 size_t end=begin;
 while(begin<len)
 {
   while(commod[end]!=' '&&commod[end]!='\0')
     end++;
   commod[end]='\0';
   argv[k++]=commod+begin;
   begin=end+1;
   end=begin;   
 }
 argv[k]=NULL;
 return true;
}
extern char**environ;
int main()
{

while(1)
{
  printf("[cxk@VM-12-16-centos myshell]$ ");
char commod[COMMOD_NUM]={0};//用于接受从命令行输入的命令
char*argv[ARGV_NUM]={NULL};//用于存储将commod切割成一个一个字符串的指针
fgets(commod,COMMOD_NUM,stdin);
commod[strlen(commod)-1]='\0';
  //分割字符串
   if(Strtok(commod,argv)==false)
     continue;
//创建子进程
pid_t id=fork();
if(id==0)
{
int n= execvp(argv[0],argv);
if(n==-1)
{
  printf("-bash: %s: command not found\n",argv[0]);
  exit(1);
}
}
//父进程
waitpid(-1,NULL,0);
}
  return 0;
}

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

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

相关文章

网络实时变更监控

网络变更监控 未经授权的配置变更会严重破坏业务连续性&#xff0c;这就是为什么检测和跟踪变更是网络管理员的一项关键任务。虽然可以手动跟踪变更&#xff0c;但此方法往往很耗时&#xff0c;并且经常会导致人为错误&#xff0c;例如在跟踪时遗漏了关键网络设备的配置。 要解…

JavaEE简单示例——Spring的控制反转

简单介绍&#xff1a; 在之前的入门程序中&#xff0c;我们简单的介绍了关于Spring框架中的控制反转的概念&#xff0c;这次我们就来详细的介绍和体验一下Spring中的控制反转的理论和实操。 使用方法&#xff1a; 控制反转&#xff08;IoC&#xff09;是面向对象编程中的一个…

HTML5 和 CSS3 的新特性

目标能够说出 3~5 个 HTML5 新增布局和表单标签能够说出 CSS3 的新增特性有哪些HTML5新特性概述HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。 这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&…

CentOS 部署rocketmq集群

一、rocketmq 概览 1.rocketmq简介 rocketmq是一个队列模型的消息中间件&#xff0c;具有高性能、高可靠、高实时、分布式特点。能够保证严格的消息顺序&#xff0c;提供丰富的消息拉取模式&#xff0c;高效的订阅者水平扩展能力&#xff0c;实时的消息订阅机制。 2.rocketmq结…

项目四:使用路由交换机构建园区网-任务三:配置路由交换机并进行通信测试

配置路由交换机并通信测试1、在RS-1上创建VLAN并配置Trunk接口2、测试通信结果3、配置RS-1的三层路由接口&#xff08;SVI&#xff09;1、在RS-1上创建VLAN并配置Trunk接口 进入系统视图&#xff0c;关闭信息中心&#xff0c;重命名为RS-1 system-view undo info-center enab…

day48第九章动态规划(二刷)

今日任务 198.打家劫舍213.打家劫舍II337.打家劫舍III 今天就是打家劫舍的一天&#xff0c;这个系列不算难&#xff0c;大家可以一口气拿下。 198.打家劫舍 题目链接&#xff1a; https://leetcode.cn/problems/house-robber/description/ 题目描述&#xff1a; 你是一个…

synchronized轻量级锁优化

synchronized优化轻量级锁 使用场景 如果一个对象虽然有多个线程访问&#xff0c;但多线程访问时间是错开的&#xff0c;也就是没有竞争&#xff0c;那么可以使用轻量级锁优化&#xff1b; 原理 1、每个线程的栈帧中有锁记录 包括&#xff1a;记录锁对象的地址Object refer…

一次有效沟通的组成

犹记得之前看过一篇文章当中写道 『Tech Lead 作为开发团队的技术负责人&#xff0c;对内对外都起到至关重要的作用。Tech Lead 对外是团队技术能力的展现窗口&#xff0c;需要将团队的技术能力呈现给客户或业务团队。对内他需要和各个角色紧密协作&#xff0c;给非技术角色技术…

50年前的程序员女神,用代码把人类送上月球

1969年7月20日&#xff0c;经过4天飞行的阿波罗11号终于来到了最关键的时刻&#xff1a;降落到月球表面。就在这个时候&#xff0c;飞船计算机突然开始闪烁报警。全球6亿人在观看电视直播&#xff0c;NASA飞行控制中心的气氛让人窒息。人类的首次登月计划面临着艰难的抉择&…

d3绘图笔记

D3绘图笔记 安装与引用 npm install d3 --save-dev import * as d3 from d3; 选择器 d3.select() 可直接接元素名&#xff0c;也可以接属性与类 添加svg标签 this.d3 d3.select(.myd3) let svg this.d3.append("svg") // 添加svg并设置好高宽 .attr("wid…

<Linux>system v通信

前言&#x1f603;&#x1f603;&#x1f603;进程间通信的方式管道 - Linux原生提供SystemV - 多线程单机通信posix - 多线程网络通信这里我们主要是介绍一下SystemV通信方式一、SystemV原理首先我们需要知道通信的本质&#xff1a;多个进程能看到同一个资源&#xff0c;即内存…

如何使用码匠连接 CouchDB

目录 在码匠中集成 CouchDB 在码匠中使用 CouchDB 关于码匠 CouchDB 是一种开源的 NoSQL 数据库服务&#xff0c;它使用基于文档的数据模型来存储数据。CouchDB 的数据源提供了高度可扩展性、高可用性和分布式性质。它支持跨多个节点的数据同步和复制&#xff0c;可以在多个…

mysql数据库之表级锁

表级锁&#xff0c;每次操作锁住整张表。锁定粒度大&#xff0c;发生所冲突的概率最高&#xff0c;并发度最低。应用在myisam、innodb、bdb等存储引擎中。 一、表级锁分类。 1、表锁 2、元数据锁&#xff08;meta data lock&#xff0c;MDL&#xff09; 3、意向锁 二、表锁…

yum保留安装包

一. 用downloadonly下载 1.1 处理依赖关系自动下载到/tmp/pages目录&#xff0c;pages这个目录会自动创建 yum install --downloadonly --downloaddir/tmp/pages ceph-deploy注意&#xff0c;如果下载的包包含了任何没有满足的依赖关系&#xff0c;yum将会把所有的依赖关系包下…

微信小程序的代码由哪些结构组成?

小程序官方建议把所有小程序的页面&#xff0c;都存放在pages 目录中&#xff0c;以单独的文件夹存在&#xff0c;如图所示&#xff1a; 其中&#xff0c;每个页面由4 个基本文件组成&#xff0c;它们分别是&#xff1a;js文件(页面的脚本文件&#xff0c;存放页面的数据、事件…

Bean三种实例化方式的底层原理

Bean实例化的三种方式 1&#xff0c;使用类构造器实例化&#xff08;无参构造函数&#xff09;2&#xff0c;使用静态工厂方法实例化&#xff08;简单工厂模式&#xff09;3&#xff0c;使用实例工厂方法实例化&#xff08;工厂方法模式&#xff09; 基于以上的三种方式&…

时间轮来优化定时器

在raft协议中&#xff0c; 会初始化三个计时器是和选举有关的&#xff1a; voteTimer&#xff1a;这个timer负责定期的检查&#xff0c;如果当前的state的状态是候选者&#xff08;STATE_CANDIDATE&#xff09;&#xff0c;那么就会发起选举 electionTimer&#xff1a;在一定时…

Photoshop如何安装ZXP扩展插件?

Photoshop如何安装ZXP扩展插件呢&#xff1f;有一些小伙伴不会安装&#xff0c;今天介绍两种安装ZXP扩展的方法&#xff0c;希望对能帮助到大家。方法一&#xff1a;手动安装方式1&#xff09;把下载好的.zxp扩展名改为.zip&#xff0c;然后解压。Windows系统&#xff1a;C:\Us…

参考文献去哪里查找,参考文献标准格式是什么

1、参考文献类型&#xff1a; 普通图书[M]、期刊文章[J]、报纸文章[N]、论文集[C]、学位论 文[D]、报告[R]、标准[s]、专利[P]、数据库[DB]、计算机程序[CP]、电 子公告[EB]、联机网络[OL]、网上期刊[J&#xff0f;OL]、网上电子公告[EB&#xff0f;OL]、其他未 说明文献[z]。…

I.MX6ULL_Linux_驱动篇(28) 字符设备驱动

字符设备驱动简介 字符设备是 Linux 驱动中最基本的一类设备驱动&#xff0c;字符设备就是一个一个字节&#xff0c;按照字节流进行读写操作的设备&#xff0c;读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI&#xff0c;LCD 等等都是字符设备&#xff0c;这…