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 安装…...

Java 某厂面试题真题合集
哈喽~大家好,这篇来看看Java 某厂面试题真题合集。 🥇个人主页:个人主页 🥈 系列专栏:【日常学习上的分享】 🥉与这篇相关的文章: Spr…...

很特别的5G市场,5.75亿部手机,却有11亿5G用户,这是怎么了?
中国在5G商用方面已取得了巨大的成绩,这是毋庸置疑的,不过近期公布的一份数据却相当特别,5G手机用户数为5.75亿,而开通了5G套餐的用户数却已超过11亿,这数据对比有点意思。中国在5G商用方面推进很快,建成的…...

go modules
文章目录1. 简介示例1. 示例——同一项目2. 示例——不同项目3. 示例——添加远程模块依赖库1. 简介 go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具。到今天Go1.14版本推出之后Go modu…...

Baklib客户故事:快递助手ERP
快递助手ERP以多平台多店铺订单管理为核心,集打单发货、商品、库存、采购、售后于一体,中小商家易上手的轻量级ERP,可以满足满足微商、自建商城、档口货源网、一件代发等不同类型客户的打单需求,通过开放平台API接口,自…...

MongoDB学习(java版)
MongoDB概述 结构化数据库 结构化数据库是一种使用结构化查询语言(SQL)进行管理和操作的数据库,它们的数据存储方式是基于表格和列的。结构化数据库要求数据预先定义数据模式和结构,然后才能存储和查询数据。结构化数据库通常…...

RK3568平台开发系列讲解(显示篇)什么是DRM
🚀返回专栏总目录 文章目录 一、DRM介绍二、DRM与framebuffer的区别沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇文章将介绍什么是DRM。 一、DRM介绍 DRM 是 Linux 目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。 比如FB原生不支…...

Python蓝桥杯训练:基本数据结构 [二叉树] 上
Python蓝桥杯训练:基本数据结构 [二叉树] 上 文章目录Python蓝桥杯训练:基本数据结构 [二叉树] 上一、前言二、有关二叉树理论基础1、二叉树的基本定义2、二叉树的常见类型3、二叉树的遍历方式三、有关二叉树的层序遍历的题目1、[二叉树的层序遍历](http…...

vuex基础之初始化功能、state、mutations、getters、模块化module的使用
vuex基础之初始化功能、state、mutations、getters、模块化module的使用一、Vuex的介绍二、初始化功能三、state3.1 定义state3.2 获取state3.2.1 原始形式获取3.2.2 辅助函数获取(mapState)四、mutations4.1 定义mutations4.2 调用mutations4.2.1 原始形式调用($store)4.2.2 辅…...

WebSphere中间件漏洞总结
WebSphere中间件漏洞总结 一、WebSphere简介 WebSphere为SOA(面向服务架构)环境提供软件,以实现动态的、互联的业务流程,为所有业务情形提供高度有效的应用程序基础架构。WebSphere是IBM的应用程序和集成软件平台,包含所有必要的中间件基础架构(包括服务器、服务和工具)…...

Unity之ASE实现影魔灵魂收集特效
前言 我们今天来实现一下Dota中的影魔死亡后,灵魂收集的特效。效果如下: 实现原理 1.先添加一张FlowMap图,这张图的UV是根据默认UV图,用PS按照我们希望的扭曲方向修改的如下图所示: 2.通过FlowMap图,我…...