【Linux】:程序替换

 朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux程序替换的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 程序替换

2. 单进程的程序替换

2.1 单进程程序替换的原理

3. 多进程的程序替换

3.1 多进程程序替换原理

4. 剩余接口的介绍以及使用 

4.1 execl函数介绍

4.2 execlp函数介绍

4.3 execv函数介绍 

4.4 execvp函数介绍

4.5 替换别的程序 

5. 环境变量和程序替换

5.1 从bash开始继承 

5.2 从父进程开始继承 

5.3 环境变量默认继承

5.4 execle函数介绍 

5.5 execve系统调用 


1. 程序替换

我们创建的所有子进程,执行的代码都是父进程代码的一部分!那么如果我们想让子进程执行新的程序呢?执行全新的代码和访问全新的数据,不再和父进程有所瓜葛,那么就需要用到程序替换。

这么多程序替换的函数调用,到底该怎么样用呢?我们直接开始使用:

2. 单进程的程序替换

为了便于理解,我们先从单进程的程序替换开始:

int execl(const char *path, const char *arg, ...);

先来看最简单的程序替换接口,其中里面的三个点表示的意思就是可变参数,使用的方法和我们的printf函数中的可变参数一样;

我们程序要能运行起来,第一步先要找到这个程序,第二部如何运行。

  • const char* path表示:新程序的文件路径 + 文件名;
  • const char *arg表示:这个程序怎么运行(简单的说我们在命令行怎么输就怎么写,最终以NULL结尾)。

返回值:成功没有返回值,失败返回-1。

我们先来演示一遍:让ls命令替换掉我们写的可执行

#include <stdio.h>
#include <unistd.h>int main()
{printf("pid: %d, exec command begin\n", getpid());sleep(3);// 程序替换execl("/usr/bin/ls", "ls", "-a", "-l", NULL);printf("pid: %d, exec command begin\n", getpid());return 0;
}

通过结果可以发现,我们通过execl成功完成了程序替换,但是代码中的最后一行代码为什么没有打印出来呢?这个到后续再解释!

2.1 单进程程序替换的原理

我们已经完成了单进程的程序替换,那么它的原理是什么呢?

当一个进程运行起来时,OS会为它创建PCB、虚拟地址空间、页表,将它的代码、数据加载到物理内存中,并且然后通过页表建立起虚拟到物理的映射关系;当进行程序替换时,execl就将要替换的程序代码和数据直接在物理内存中替换覆盖掉,此时原来的代码和数据就会被新替换的代码和数据覆盖掉,继续向后执行也就执行的新程序的代码和数据,这个过程中并没有创建新的进程。

3. 多进程的程序替换

见识过单进程的程序替换以及程序替换的原理,接下来替换一下多进程:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childprintf("pid: %d, exec command begin\n", getpid());sleep(3);// 程序替换execl("/usr/bin/ls", "ls", "-a", "-l", NULL);printf("pid: %d, exec command begin\n", getpid());}// parentpid_t rid = waitpid(id, NULL, 0);if(rid == id){printf("wait success, rid: %d\n", rid);}return 0;
}

3.1 多进程程序替换原理

首先,进程具有独立性,其次我们都知道,父子进程代码共享,数据以写时拷贝的方式各自私有一份,那么在程序替换的时候,把子进程的数据替换这没问题,因为父子数据各自私有,但是将代码替换,这就有点不能忍了,父子进程代码共享,既然子进程都执行的是替换后的代码,为什么父进程在程序替换的时候还是继续执行他自己代码呢?

是因为在多进程的程序替换时,当子进程启动程序替换覆盖它们的共享代码时,也会发生写时拷贝的方式,重新开辟一块空间,然后进行替换写入,这样父子进程的代码就不共享,子进程继续执行它替换后的代码,父进程继续执行自己的代码。

在替换完之后,子进程怎么知道从新程序的哪里开始执行呢?

我们的代码在编译形成可执行程序的时候会有一个程序入口地址--entry地址;

CPU内部的eip寄存器可以记录我们程序当前运行到哪里了,所以当程序替换之后,新程序的eip程序计数器就被修改为entry地址,所以就可以重新开始运行了。 

接下来解决上面遗留的问题: 代码中的最后一行代码为什么没有打印出来呢?

是因为在程序替换以后,代码和数据都被覆盖了,所以execl函数替换成功后,后续的代码没有机会执行了,因为已经被替换掉了!

所以我们不用判断它的返回值,如果继续执行了后续代码,说明程序替换出错了。

4. 剩余接口的介绍以及使用 

要完成程序替换需要满足以下两点:

  • 1. 必须先要找到需要替换的程序。
  • 2. 必须告诉替换函数该怎么执行。

这些程序替换的函数前面的单词都相同,主要是后缀不同,代表的意思也不同。

4.1 execl函数介绍

execl中的l表示列表的意思,在我们传递使用方法的时候,就像一个列表一样,最后以NULL结尾。也就类似于一个list链表一样。

4.2 execlp函数介绍

execlp中的p表示要找的目标可执行程序会自动在环境变量PATH中根据file去查找。

execlp("ls", "ls", "-a", "-l", NULL);
// 两个ls表示的含义不一样
// 第一个表示要执行的文件
// 第二个表示如何执行

4.3 execv函数介绍 

execv中v表示的是数组传参的意思,可以将如何执行保存一个数组中,然后传递该数组即可。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childprintf("pid: %d, exec command begin\n", getpid());sleep(3);// 程序替换char *const argv[] = {"ls","-a","-l",NULL};execv("/usr/bin/ls", argv);printf("pid: %d, exec command begin\n", getpid());}// parentpid_t rid = waitpid(id, NULL, 0);if(rid == id){printf("wait success, rid: %d\n", rid);}return 0;
}

4.4 execvp函数介绍

execvp就是execv和execlp和结合,可以直接传递文件名,并且执行方式以数组传参的方式传递。

        char *const argv[] = {"ls","-a","-l",NULL};execvp("ls", argv);//execvp("argv[0]", argv);

4.5 替换别的程序 

程序替换不仅可以替换系统的命令,还可以替换我们自己写的任何程序:

用C++程序来替换C语言程序

#include <iostream>int main()
{std::cout << "hello C++" << std::endl;std::cout << "hello C++" << std::endl;std::cout << "hello C++" << std::endl;std::cout << "hello C++" << std::endl;return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childprintf("pid: %d, exec command begin\n", getpid());sleep(3);// 程序替换execl("./test", "test", NULL);printf("pid: %d, exec command begin\n", getpid());}// parentpid_t rid = waitpid(id, NULL, 0);if(rid == id){printf("wait success, rid: %d\n", rid);}return 0;
}

我们在命令行执行可执行程序时用的是./exe,那么为什么在传参时只传递exe,因为./表示的意思是先找到该程序,在运行,execl第一个参数已经找到了,所以只需要传递exe即可。

5. 环境变量和程序替换

在环境变量章节说过环境变量具有全局属性,那么当我们进行程序替换的时候,子进程对应的环境变量是可以直接从父进程中来的,父进程的环境变量从bash中来,我们可以通过代码来验证一下:

5.1 从bash开始继承 

同样的我们还是使用程序替换的方式,来看看子进程替换之后从bash继承下来的环境变量。

先在命令行自定义一些环境变量:

export MYENV1=1111111111111
export MYENV2=2222222222222

源test.cc

#include <iostream>int main(int argc, char *argv[], char *env[])
{int i = 0;for(; env[i]; i++){std::cout << env[i] << std::endl;  // 打印环境变量}return 0;
}

源myprocess.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){printf("pid: %d, exec command begin\n", getpid());sleep(1);execl("./test", "test", NULL);printf("pid: %d, exec command begin\n", getpid());}pid_t rid = waitpid(id, NULL, 0);if(rid == id)printf("wait success, rid: %d\n", rid);return 0;
}

可以看到父进程继承了bash的环境变量,子进程继承了父进程的环境变量。

5.2 从父进程开始继承 

在程序中导入环境变量的接口为:putenv();

#include <stdlib.h>
int putenv(char *string);

在父进程中导入一个环境变量,看看子进程能否继承:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main()
{char *env_val = "MYVAL5=5555555555555555555555555";putenv(env_val);pid_t id = fork();if(id == 0){printf("pid: %d, exec command begin\n", getpid());sleep(1);execl("./test", "test", NULL);printf("pid: %d, exec command begin\n", getpid());}pid_t rid = waitpid(id, NULL, 0);if(rid == id)printf("wait success, rid: %d\n", rid);return 0;
}

 

5.3 环境变量默认继承

在进程地址空间这一章节我们了解到了进程地址空间中有环境变量和命令行参数,在父进程创建子进程的过程中,子进程以父进程为模版,子进程的地址空间也就以父进程的地址空间为模版创建了,进而父进程的环境变量和命令行参数也就都会被继承下来,所以通过地址空间可以让子进程继承父进程的环境变量和数据,所以说这是一种默认的行为。

那么继承下去的环境变量为什么不会收到程序替换的影响呢?环境变量也是数据呀!

在程序替换的过程中,只会替换程序的代码和数据,并不会对环境变量产生影响。

5.4 execle函数介绍 

将父进程的环境变量传递给子进程有两种方式:

1. 直接使用(默认集成,需要传)。

2. 使用execle函数的第三个参数直接传递。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>extern char ** environ; int main()
{pid_t id = fork();if(id == 0){printf("pid: %d, exec command begin\n", getpid());sleep(1);execle("./test", "test",NULL, environ);  // 直接传printf("pid: %d, exec command begin\n", getpid());}pid_t rid = waitpid(id, NULL, 0);if(rid == id)printf("wait success, rid: %d\n", rid);return 0;
}

如果要传递我们自己的环境变量,可以先定义,再传递;同样我们也可以把命令行参数传递给子进程,然后通过程序替换来展现出来:

源test.cc

#include <iostream>
#include <unistd.h>int main(int argc, char *argv[], char *env[])
{for(int i = 0; i < argc ; i++){std::cout << i << "->" <<  argv[i] << std::endl;}std::cout << "################################" << std::endl;for(int i = 0; environ[i]; i++){std::cout << i << " : " << env[i] << std::endl;}return 0;
}

源process.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main()
{char *const myenv[] = {"MYVAL1=11111111111111","MYVAL2=22222222222222","MYVAL3=33333333333333","MYVAL4=44444444444444",NULL};pid_t id = fork();if (id == 0){printf("pid: %d, exec command begin\n", getpid());sleep(1);execle("./test", "test", "-a", "-b", NULL, myenv); // 直接传printf("pid: %d, exec command begin\n", getpid());}pid_t rid = waitpid(id, NULL, 0);if (rid == id)printf("wait success, rid: %d\n", rid);return 0;
}

可以看到传递的环境变量不是新增,而是覆盖是传递!

程序替换可以将命令行参数、环境变量通过自己的参数,传递给被替换程序的mian函数中!

5.5 execve系统调用 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!      

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

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

相关文章

蓝牙人员定位精准吗?是否会对人体有伤害?

不知道大家现在使用的蓝牙人员定位系统都是什么样的呢&#xff1f;其实就出行而言&#xff0c;使用GPS定位也就是足够了的&#xff0c;而且目前的定位相对也比较精准了&#xff0c;效果还是很不错的。但是如果说是室内定位&#xff0c;很显然常规的定位系统是无法满足使用需求的…

【背就有效】软考中级信息系统监理师核心知识点汇总!

宝子们&#xff01;上半年软考已经告一段落了&#xff0c;准备下半年考信息系统监理师的小伙伴可以开始准备了。这里给大家整理了信息系统监理师核心知识点汇总&#xff0c;涵盖全书90%的重点&#xff0c;先把这个存下&#xff01;再慢慢看书&#xff0c;边看书边背这个&#x…

【机器学习】主成分分析(PCA):数据降维的艺术

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 主成分分析&#xff08;PCA&#xff09;&#xff1a;数据降维的艺术引言PCA的基…

Plaxis Python API的连接与配置、外部Python编译器的使用、Python命令流自动建模过程,典型岩土工程案例

目录 第一部分 Plaxis软件简介及 Plaxis Python API环境搭建 第二部分 Plaxis自动化建模-基础案例 第三部分 进阶案例—Python全自动实现 第四部分 高级案例—Python全自动实现 有限单元法在岩土工程问题中应用非常广泛&#xff0c;很多软件都采用有限单元解法。在使用各大…

平凉崆峒区特产美食亮相兰洽会文旅康养展馆

在第三十届兰洽会的文旅康养展馆中&#xff0c;平凉的特产无疑是一道亮丽的风景线。作为促进区域经济合作与交流的重要平台&#xff0c;每年都吸引着众多企业和特色产品的亮相。 走进这个充满活力与特色的展馆&#xff0c;首先映入眼帘的是琳琅满目的特色产品。平凉红牛的…

【ai_agent】从零写一个agent框架(三)实现几个示例中的service:llm,tool等

前言 上一篇文章里我们实现了一个基本的运行时&#xff0c;能够将service按照plan执行起来&#xff0c;本文我们尝试实现一些基本节点&#xff0c;最终运行一个最简单的agent。 代码仓库 1. Openai-llm 我们这里还是接openai的llm能力。 先定义一下模型的基本配置&#xf…

el-from中校验,如果某一项需要另一项填写才能校验

使用validateField <el-form:model"params":rules"rules":scroll-to-error"true"ref"refrom"v-else><el-form-item label"用户姓名" prop"name"><el-input placeholder"请输入用户姓名"…

5、闪存简述

闪存基本原理 如下图所示浮栅晶体管&#xff0c;当判断是0或1的时候&#xff0c;是在源极加电&#xff0c;当源极和漏极能导通则为1&#xff0c;否则为0&#xff1b;源极和漏极能导通的前提是源极和漏极之间充满电子。 假如在控制极加电(电子具有向电性)&#xff0c;衬底的电…

[计网初识1] TCP/UDP

学习内容 1.TCP建立链接的3次握手&#xff0c;断开连接的4次挥手 2.TCP报文段组成 内容 1.TCP 建立连接的3次握手? 假设主动方是客户端&#xff0c;被动方是服务端。 第一次 客户端给服务端发送 “hello,我是客户端” (TCP段中 SYN1) 第二次 服务端给客户端发送"我接…

一站式短视频矩阵开发,高效托管!

短视频矩阵系统源码SaaS解决方案提供全面的开发服务&#xff0c;包括可视化视频编辑、矩阵式内容分发托管以及集成的多功能开发支持。 短视频矩阵&#xff1a;引爆您的数字营销革命 短视频矩阵系统是一套多功能集成解决方案&#xff0c;专为提升在短视频平台上的内容创作、管理…

C语言笔记31 •单链表经典算法OJ题-3.反转链表•

反转链表 1.问题 给你单链表的头节点 head&#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 2.代码实现&#xff1a; //3.反转链表 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <assert.h>typedef int …

技术成神之路:设计模式(四)工厂方法模式

1.定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的接口&#xff0c;而不是通过具体类来实例化对象。工厂方法模式的主要作用是让子类决定实例化哪一个类&#xff0c;从而实现对象创建的延迟到具体子类…

4.2 存储管理

大纲 页式存储必考&#xff0c;段式存储看运气 页式存储 概念

迅为RK3588开发板RKNPU2项目实战2SORT目标追踪

1.介绍和视频演示2.在模拟器实现图片的目标追踪3.连板推理和嵌入式部署4.视频目标追踪的实现(python)5.C实现目标追踪性能强--iTOP-3588开发板采用瑞芯微RK3588处理器&#xff0c;是全新一代ALoT高端应用芯片&#xff0c;采用8nm LP制程&#xff0c;搭载八核64位CPU&#xff0c…

飞睿智能6公里WiFi图传接收模块,低延迟、抗干扰、高速稳定传输数据,无人机、农田远距离WiFi模块

在科技日新月异的今天&#xff0c;无线通信技术正以前所未有的速度发展&#xff0c;不仅改变了我们的生活方式&#xff0c;还为企业带来了前所未有的商业机遇。今天&#xff0c;我要向大家介绍一款飞睿智能的产品——6公里WiFi图传接收模块&#xff0c;它以其高性能、稳定的传输…

ES13的4个改革性新特性

1、类字段声明 在 ES13 之前,类字段只能在构造函数中声明, ES13 消除了这个限制 // 之前 class Car {constructor() {this.color = blue;this.age = 2

SpringBoot实现简单AI问答(百度千帆)

第一步&#xff1a;注册并登录百度智能云&#xff0c;创建应用并获取自己的APIKey与SecretKey&#xff0c;参考网址&#xff1a; 点击去百度智能云 第二步&#xff1a;引入千帆的pom依赖 <dependency><groupId>com.baidubce</groupId><artifactId>q…

python库(9):prettytable库快速实现ASCII表格

下面介绍一个快速制作ASCII表格库——prettytable&#xff0c;可以方便地制作简单表格。 1 安装prettytable pip install -i https://pypi.tuna.tsinghua.edu.cn/simple prettytable 结果如下&#xff1a; 2 代码实例 from prettytable import PrettyTable table PrettyTa…

CloudCanal监控告警配置问题

前言&#xff1a; 近日有几个库需要同步到另外一台机做备份&#xff0c;想起Cloudcanal 比较方便&#xff0c;那就尝试用下 Cloudcanal 做同步&#xff0c;但是同步任务建立好之后&#xff0c;需要做监控&#xff0c;不然那天停止同步了都不知道&#xff0c;真所谓有应用必上监…