Linux程序替换
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 64bool 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;
}
相关文章:

Linux程序替换
Linux程序替换创建子进程的目的?程序替换如何实现程序替换?什么是程序替换?先见一见单进程版本的程序替换程序替换原理多进程版本的程序替换execl函数组简易版Shell创建子进程的目的? 目的:为了帮助父进程完成一些特定的任务&…...
@JsonFormat @DataTimeFormat 时间格式
省流:用JsonFormat即可有时候会看到入参dto里,在时间类型的变量上用DateTimeFormat,代码如下。public class XXXdto{DateTimeFormat(pattern "yyyy-MM-dd hh:mm:ss")private Date startDate; }这是为了入参传日期格式的值。即前端…...

带你玩转modbusTCP通信
modbus TCP Modbus TCP是一种基于TCP/IP协议的Modbus通信协议,它是Modbus协议的一种变体,用于在以太网上进行通信。Modbus TCP协议是一种开放的通信协议,它支持多种编程语言和操作系统,并且可以在不同的硬件和软件平台上进行通信…...
2021牛客OI赛前集训营-提高组(第三场)T2交替
2021牛客OI赛前集训营-提高组(第三场) 题目大意 一个长度为nnn的数组aaa,每秒都会变成一个长度为n−1n-1n−1的新数组a′aa′,其变化规则如下 如果当前数组aaa的大小nnn为偶数,则对于新数组a′aa′的每一个位置i(1≤…...

论文投稿指南——中文核心期刊推荐(金融)
【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…...
华为OD机试 - 不等式(C 语言解题)【独家】
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 使用说明本期题目:不等式题…...

90后老板用低代码整顿旅行社,创2000万年收,他是怎么做到的?(真实)
热爱旅游的92年成都小伙猴哥,大学毕业后开了一家旅行社,主要从事川藏、云南定制游服务。 从今年春节开始,国内各地旅游业开始复苏,向旅行社打电话咨询的人越来越多。 旅游的人多是好事,也是一种烦恼,因为…...

Apache Dubbo 存在反序列化漏洞(CVE-2023-23638)
漏洞描述 Apache Dubbo 是一款轻量级 Java RPC 框架 该项目受影响版本存在反序列化漏洞,由于Dubbo在序列化时检查不够全面,当攻击者可访问到dubbo服务时,可通过构造恶意请求绕过检查触发反序列化,执行恶意代码 漏洞名称Apache …...

【YOLO】YOLOv8训练自定义数据集(4种方式)
YOLOv8 出来一段时间了,继承了分类、检测、分割,本文主要实现自定义的数据集,使用 YOLOV8 进行检测模型的训练和使用 YOLOv8 此次将所有的配置参数全部解耦到配置文件 default.yaml,不再类似于 YOLOv5,一部分在配置文件…...

linux重置root用户密码
重置root密码 法一:rd.break 第 1 步:重启系统编辑内核参数 第 2 步:找到 linux 这行,在此行末尾空格后输入rd.break (End键也可直接进入行尾) 成功后显示页面为: 第 3 步:查看。…...

【DBC专题】-10-CAN DBC转换C语言代码Demo_接收Rx报文篇
案例背景(共15页精讲): 该篇博文将告诉您,CAN DBC转换C语言代码Demo,只需传递对应CAN信号关联参数,无需每个信号"左移"和"右移",并举例介绍:在CANoe/Canalyzer中CAPL中的应用ÿ…...
AtCoder292 E 思维
题意: 给定一副n(n≤3000)n(n\leq 3000)n(n≤3000)个顶点,mmm条有向边的图,可以在图中添加有向边,求添加的最少边数,使得这副图满足:如果顶点aaa到顶点bbb有边,顶点bbb到ccc右有边,…...

20230309英语学习
What Is Sleep Talking? We Look at the Science 为什么人睡觉会说梦话?来看看科学咋说 Nearly everyone has a story about people talking in their sleep.Though it tends to be more common in children, it can happen at any age:A 2010 study in the jour…...

CAD转换PDF格式怎么弄?教你几种方法轻松搞定!
CAD是从事与艺术创作相关等行业的打工人们必需的工作软件,可以用来完成建筑设计图、设计图纸等。在日常的工作中,一些伙伴经常需要传输图纸给合作方来完成探讨。但是CAD图纸需要使用专业软件才能打开,这就给文件传送带来了一定的困难。而且传…...
AtCoder 259E LCM
题意: 以唯一分解形式给出nnn个数: aipi,1ei,1pi,2ei,2...pi,tei,ta_{i}p_{i,1}^{e_{i,1}}p_{i,2}^{e_{i,2}}...p_{i,t}^{e_{i,t}} aipi,1ei,1pi,2ei,2...pi,tei,t 现在可以将某个数改为111,求所有改法中,有多少个…...

MQTT协议-取消订阅和取消订阅确认
MQTT协议-取消订阅和取消订阅确认 客户端向服务器取消订阅 取消订阅的前提是客户端已经通过CONNECT报文连接上服务器,并且订阅了一个主题 UNSUBSCRIBE—取消订阅 取消订阅的报文同样是由固定报头可变报头有效载荷组成 固定报头由两个字节组成,第一个…...

90后小伙,用低代码“整顿”旅游业,年入2000万,他是怎么做到的?
热爱旅游的92年成都小伙猴哥,大学毕业后开了一家旅行社,主要从事川藏、云南定制游服务。 从今年春节开始,国内各地旅游业开始复苏,向旅行社打电话咨询的人越来越多。 旅游的人多是好事,也是一种烦恼,因为…...

C51---PWM 脉冲宽度调制
1.PWM:脉冲宽度调制,它是通过一系列脉冲宽度进行调制,等效出所需要的波形(包含形状以及幅值)。对模拟信号电平进行数字编码。也就是说通过调节占空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于…...

毕业设计 基于51单片机WIFI智能家居系统设计
基于51单片机WIFI智能家居系统设计1、毕业设计选题原则说明(重点)2、项目资料2.1 系统框架2.2 系统功能3、部分电路设计3.1 STC89C52单片机最小系统电路设计3.2 ESP8266 WIFI电路设计3.3 DHT11温湿度传感器电路设计4、部分代码展示4.1 LCD12864显示字符串…...

Nginx服务优化措施与配置防盗链
目录 一.优化Nginx的相关措施 二.隐藏/查看版本号 三.修改用户与组 四.设置缓存时间 五.日志切割脚本 六.设置连接超时控制连接访问时间 七.开启多进程 八.配置网页压缩 九.配置防盗链 1.配置web源主机(192.168.79.210 www.zhuo.com) 1.1 安装…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...

【Axure高保真原型】图片列表添加和删除图片
今天和大家分享图片列表添加和删除图片的原型模板,效果包括: 点击图片列表的加号可以显示图片选择器,选择里面的图片; 选择图片后点击添加按钮,可以将该图片添加到图片列表; 鼠标移入图片列表的图片&…...
matlab实现DBR激光器计算
DBR激光器计算程序。非常值得参考的程序。DBR激光器程序 DBR计算/1.txt , 2056 DBR计算/4.asv , 22 DBR计算/4.txt , 32 DBR计算/GetDeviceEfficiency.asv , 2012 DBR计算/GetDeviceEfficiency.m , 2014 DBR计算/GetOneLayerArray.asv , 837 DBR计算/GetOneLayerArray.m , 836…...
慢慢欣赏linux 之 last = switch_to(prev, next)分析
last switch_to(prev, next); 为什么需要定义last作为调用switch_to之前的prev的引用 原因如下: struct task_struct * switch_to(struct task_struct *prev,struct task_struct *next) {... ...return cpu_switch_to(prev, next);> .global cpu_switch_tocpu_…...