【Linux】进程控制3——进程程序替换

一,前言

        创建子进程的目的之一就是为了代劳父进程执行父进程的部分代码,也就是说本质上来说父子进程都是执行的同一个代码段的数据,在子进程修改数据的时候进行写时拷贝修改数据段的部分数据。

        但是还有一个目的——将子进程在运行时指向一个全新的程序代码,也就是我们的进程程序替换。

        一般情况下,对应的语言写的程序只能调用对应的语言的接口,而不能调用其他语言的接口,如C++不能调用Java或者Python或者Shell等语言的接口,那么如果我们想要调用别人写的程序应该怎么办呢?

        那么进程程序替换就能够很好地帮助我们解决这个问题,这在很大程度上减少了我们编程的成本,学会进程程序替换,在很多时候,如果有现成的程序,那么我们不需要自己再去写一个,是不是方便很多了?

二、什么是进程程序替换?

        所谓进程程序替换,顾名思义,就是使用一个新的程序替换原有的程序,进程将执行新程序的代码,而不再执行原有程序的代码,前面我们已经学习了如何创建一个进程,一般情况下,进程程序替换都不会使用父进程直接进行进程程序替换,而是让父进程调用fork()函数创建一个子进程,让子进程去执行一个新的程序即可

        进程程序替换是指在运行过程中将一个进程的地址空间中的代码、数据和堆栈等内容完全替换为另一个程序的代码、数据和堆栈的过程。

三,进程程序替换的原理 

  •  进程替换前的效果图

当一个进程成功创建一个子进程之后,父子进程的情况如下图所示:

        这个时候,我们这里先针对代码和数据进行分析,其他内容暂不做考虑,此时父子进程都没有修改代码和数据,因此,父子进程的代码和数据都是指向同一块内容的,也就是代码和数据共享的,如果其中一方对数据进行修改,则这一方就会进行写时拷贝,如果想要执行不同的代码,则此时就要进行进程程序替换

进程程序替换的原理:
        假如刚开始父子进程都是执行程序a.exe,后面,想要让子进程执行b.exe了,那么此时就要进行进程程序替换,替换的过程就是首先将b.exe从磁盘加载进内存,然后重新建立子进程的页表,更新子进程的页表中的映射关系(注意,这里修改的是页表中的物理地址而不是虚拟地址,此时父子进程代码块中虚拟地址是一样的,但是通过页表映射出来的物理地址是不一样的),从而实现父子进程的代码彻底分离,此时父子进程的代码是互不干扰的,很好地满足了进程的独立性

  •  进程替换之后的效果图

 3.1.进程替换注意事项

  • 1.进程替换不会创建新进程,因为进程替换只是将该进程的数据替换为指定的可执行程序。而进程PCB没有改变,所以不是新的进程,进程替换后不会发生进程pid改变
  • 2.进程替换后,如果替换成功后则替换函数下的代码不会执行,因为进程替换是覆盖式的替换,替换成功后进程原来的代码就消失了。同理在进程替换失败后会执行替换函数后的代码
  • 3.进程替换函数在进程替换成功后不返回,函数的返回值表示替换失败
  • 4.进程替换成功后,退出码为替换后的进程的退出码

四,为什么要进行进程程序替换

        在学习进程程序替换之前,我们知道当一个父进程创建一个子进程之后,父子进程的代码是共享的,子进程只能执行父进程的代码块

        但是现在我们的需求增加了,我们不仅要让子进程能够执行父进程的代码块,也要能够让子进程能够做一些父进程不能做的事情,也就是能够执行一个全新的代码(程序),这样就能实现父子进程做的事情有所差异,大大提高了办事效率,同时也使父子进程的代码彻底分离,维护进程的独立性

五,怎么实现进程程序替换

        进程程序替换是指在运行过程中将一个进程的地址空间中的代码、数据和堆栈等内容完全替换为另一个程序的代码、数据和堆栈的过程。

这个过程通常是由操作系统提供的 exec 系列函数来实现的:

  1. 地址空间替换:进程的地址空间是指进程可以访问的内存范围。通过地址空间替换,进程可以在运行时动态地加载并执行不同的程序,从而实现灵活的程序执行和管理。
  2. exec 函数族:exec 函数族是一组系统调用,用于执行程序替换操作。这些函数包括 execl, execv, execle, execve 等,它们允许以不同的方式传递参数给新程序,并执行地址空间替换。(我们要改变内存,那肯定是要调用系统调用接口的,这些函数会封装相应的接口)
  3. 程序入口点:新程序的入口点是程序中的起始执行位置,通常是 main 函数或其他指定的入口函数。替换完成后,控制权将转移到程序入口点,开始执行新程序的代码。

5.1.原理

  • 当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换
  • 替换完成后,控制权将转移到新程序的入口点,开始执行新程序的代码。

5.2.使用execl()函数

        在学习使用进程程序替换的相关接口之前我们首先需要明确一点,这个在使用接口的时候需要做什么事情?

        很明显,最基本的我们首先得知道这个程序在哪里,其次,我们还需要知道怎么执行这个程序(比如在我们前面学习一些指令的时候,有些指令是可以携带选项的有些指令可以不用携带选项)。

        总结起来就是我们需要知道要执行的程序的路径和怎么执行新程序

        execl函数是Linux系统中用于执行新程序的函数之一,它属于exec函数族的一部分。

        这个函数的作用是在当前进程的上下文中启动一个新的程序,并替换当前进程的映像为新的程序映像。调用execl函数后,当前进程将停止执行,并由新的程序开始执行.

函数原型如下

参数说明:

  • path:要执行的程序的路径。
  • arg0:新程序的参数列表的开始,通常这会是新程序的名称(尽管这不是强制的,但它通常用于错误消息和程序内部)。
  • ...:一个可变参数列表(参数的数量不固定),新程序的参数列表,必须以NULL结尾。

        execl函数会根据提供的路径path找到并执行相应的程序,同时将arg0及其后面的参数作为新程序的命令行参数传递。注意,参数列表必须以NULL结尾,这是告诉execl参数列表结束的标志。 

  只看上面的解释,我相信小伙伴们对于这一块还是不太理解,那么上代码:

 我们来看个例子

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

int main()
{
	printf("I'm a process, pid: %d\n", getpid());
	printf("execl begin...\n");

	int a=execl("/usr/bin/ls", "ls", "-a", "-l", NULL);

	printf("execl end...\n");
	return 0;
}

        如果execl函数调用成功,那么它实际上不会返回,因为当前进程的映像已经被新程序替换。如果调用失败,它会返回-1,并设置全局变量errno以指示错误原因。常见的错误原因可能包括文件未找到、权限不足等。

        execl函数和其他exec函数一样,不会创建新的进程。它们只是在当前进程的上下文中启动另一个程序。因此,调用execl前后,进程的ID(PID)不会改变。

        同时,由于execl会替换整个进程映像,所以在调用execl之前,通常需要确保当前进程的所有打开的文件描述符、内存分配等都被适当地处理或释放,因为这些资源不会被新程序继承。

5.2.1.结论与细节

1,程序替换一旦成功,exec后面的代码不在执行。因为被替换掉了,这也是什么代码没有输出execl end的原因了

2,exec函数调用成功,那么它实际上不会有返回值;调用失败,它会返回-1

3,exec函数不会创建新的进程。它们只是在当前进程的上下文中启动另一个程序

4,创建一个进程。我们是先创建PCB、地址空间、页表等再先把程序加载到内存

                                如果先加载的话,页表都没办法映射的

5,程序替换的本质就是加载 (可以看成一个加载器),有替换就是替换,没有就是程序加载

        程序替换的本质是程序加载,因为在执行 exec 函数时,操作系统会加载新程序的可执行文件,并将其代码、数据和堆栈等部分加载到进程的地址空间中。这个过程涉及将新程序的内容从磁盘加载到内存中,为进程提供执行所需的资源。

        因此,虽然我们常说是“程序替换”,但实际上更准确地说是将新程序加载到内存中,替换掉原有的程序,以实现进程的功能切换和更新。

6,程序运行要加载到内存;为什么?

        冯诺依曼体系规定;

如何加载的呢?

        就是程序替换:程序替换是操作系统的接口,所谓的把磁盘里的数据加载到内存就是把磁盘设备的数据拷贝到内存里。把数据从一个硬件搬到另一个硬件,只有操作系统能做

5.3.多进程实现使用ls 

1,我们可以创建一个子进程,由子进程来进行程序替换,父进程来等待结果就可以。为什么? 父进程能得到子进程的执行结果

2,我们知道父进程与子进程映射到同一块代码,那么子进程进行程序替换后,不是会覆盖吗,替换为什么不影响父进程?

        进程具有独立性,在进行程序替换时要进行写时拷贝,写时拷贝的本质就是开辟新的空间

3,shell是如何运行起来一个指令的?

        首先创建子进程,shell会waitpid()等待进程结果,子进程会继承shell的代码,但是不影响。子进程进行程序替换,替换为我们输入的指令

 上面的实验是没有子进程的,是一个纯单进程的实验,下面将演示一个多进程的例子执行ls指令

上面的实验思路就是父进程创建一个子进程,然后本来子进程是要执行父进程的代码块和父进程进行代码共享的,但是我们在子进程中调用execl函数接口,因此,在子进程中会进行程序替换

6.exec系列函数

        由我们库封装的exec函数常用的有上面的几种,这些函数都有下面的特性,当该函数成功执行,那么进程替换成功,代码不再返回,如果函数调用失败,例如不正确的地址,不正确的文件等等,函数会返回一个-1,并且exec函数之后在函数调用失败时才有返回值,成功没有返回值。

        只有失败时有返回值其实很好理解,因为函数调用成功那就表示程序替换成功,原来的代码都被替换了,我返回之后给谁?没了,之后的代码就是新代码了

1.execl:

该函数允许通过提供可变数量的参数来执行指定的可执行文件。它的原型如下:

int execl(const char *path, const char *arg0, ... /*, (char *)0 */);

path 是要执行的可执行文件的路径,arg0 是第一个参数,后续参数都是传递给可执行文件的命令行参数,以 NULL 结尾。

观察上图发现进程替换成功后,替换函数下的打印没有执行,原因与注意事项的第二条相同

如果替换失败:

2.execlp:

该函数与 execl 类似,但是它会在系统的环境变量 PATH 指定的目录中查找可执行文件。它的原型如下:

int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);

file 是要执行的可执行文件的文件名,arg0 是第一个参数,后续参数都是传递给可执行文件的命令行参数,以 NULL 结尾。

相比于execl函数,execlp函数的第一个参数能直接写文件名,系统会PATH环境变量里去查找

多的字母p的意思:PATH环境变量

int main()
{
	pid_t id = fork();
	if (id == 0)
	{
		printf("I'm a process, pid: %d\n", getpid());
		printf("execl begin...\n");

		execl("ls", "ls", "-a", "-l", NULL);

		printf("execl end...\n");
		exit(1);
	}

	pid_t rid = waitpid(id, NULL, 0);
	if (rid > 0)
	{
		printf("wait successfully\n");
	}
	return 0;
}

3.execv:

类似于 execl,但是允许传递一个参数数组给被执行的程序。它的原型如下:

int execv(const char *path, char *const argv[]);

path 是要执行的可执行文件的路径,argv 是一个以 NULL 结尾的参数数组,其中每个元素都是一个字符串,表示命令行参数。

相比于exec多个字母v:代表vector

int main()
{
	pid_t id = fork();
	if (id == 0)
	{
		printf("I'm a process, pid: %d\n", getpid());
		printf("execl begin...\n");
		char* argv[] = { "ls","-a","-l",NULL};

		execv("/usr/bin/ls",argv);

		printf("execl end...\n");
		exit(1);
	}

	pid_t rid = waitpid(id, NULL, 0);
	if (rid > 0)
	{
		printf("wait successfully\n");
	}
	return 0;
}

4.execvp:

类似于 execv,但是它会在系统的环境变量 PATH 指定的目录中查找可执行文件。它的原型如下:

int execvp(const char *file, char *const argv[]);

file 是要执行的可执行文件的文件名,argv 是一个以 NULL 结尾的参数数组,其中每个元素都是一个字符串,表示命令行参数。

既有字母p 又有v,结合上面那两种就行

5.execle:

函数与 execl 函数类似,但允许在启动新程序时传递额外的环境变量。它的原型如下:

int execle(const char *path, const char *arg, ..., char *const envp[]);

path 是要执行的可执行文件的路径,arg 是要传递给新程序的命令行参数,后面的参数是额外的环境变量,以 NULL 结尾。

进程程序替换不会替换环境变量的

想要子进程继承全部的环境变量,不用管,直接就能拿到

单纯新增环境变量,在父进程里使用putenv()函数,会影响子进程

putenv 是 C 语言中的一个库函数,它定义在 <stdlib.h> 头文件中。这个函数用于将字符串添加到环境变量中,或者修改已经存在的环境变量的值。

int putenv(const char *string);

使用全新的环境变量,就使用execle()函数,那么替换后的代码切换后的环境变量就只是我们传入的表里的内容

 因为此时我们没有环境变量MYSTR所以第一行打印为空

这里在myProc子进程中用execle函数来导入环境变量MYSTR

注意:

  • 1.导环境变量的数组最后以NULL结尾
  • 2.导入环境变量后原系统环境变量的值被清空,这种导入环境变量的方式为覆盖式导入 

 6.使用方法总结

有人就说了上面那些函数,我怎么记得住他们的用法啊?

 使用这些函数其实简单,先将函数名的exec提取出来看后面的几个字母。

l:表示用列表方式传递。

    其中/bin/ls表示需要执行的文件是谁,ls表示执行方式,而-a和-l表示这个执行的参数列表。

v:表示使用数组的方式传递。 

 可以看到我们用过指针数组的方式将我们的执行和参数列表存到了一起,然后将这个指针数组作为参数传递给我们的execv函数就行。

p:表示自己只需要传递需要执行的文件是谁,操作系统会从默认环境变量当中去查找。

 e:表示可以传递自己的环境变量。 

 注意:当我们传递自己的环境变量时会替换默认环境变量,所以如果想要添加一个环境变量,而不是替换那就需要下方的操作。

通过系统提供的存环境变量的environ变量,在用putenv函数添加自己的环境变量,以达到添加环境变量的操作。

        上面的几个字母通过不同的组合可以达到不同的操作方式。

7.也可以调用其他语言的程序

code.c里:

int main()
{
	char* const env[] = {
		(char*)"first",
		(char*)"second",
		NULL };

	pid_t id = fork();
	if (id == 0)
	{
		printf("I'm a process, pid: %d\n", getpid());
		printf("execl begin...\n");

		execle("./mytest", "mytest", NULL, env)

		printf("execl end...\n");
		exit(1);
	}

	pid_t rid = waitpid(id, NULL, 0);
	if (rid > 0)
	{
		printf("wait successfully\n");
	}
	return 0;
}

 test.cpp里:

#include <iostream>
#include <unistd.h>

using namespace std;

int main()
{
	for (int i = 0; environ[i]; i++)
	{
		printf("env[%d]: %s\n", i, environ[i]);
	}

	cout << "This is C++" << endl;
	return 0;
}

当然我们也能传系统环境变量,但是没必要,这样的话直接默认就行

execle("./mytest", "mytest", NULL, environ)//传入这个全局变量

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

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

相关文章

自动控制原理【期末复习】(二)

无人机上桨之后可以在调试架上先调试&#xff1a; 1.根轨迹的绘制 /// 前面针对的是时域分析&#xff0c;下面针对频域分析&#xff1a; 2.波特图 3.奈维斯特图绘制 1.奈氏稳定判据 2.对数稳定判据 3.相位裕度和幅值裕度

JavaScript的数组排序

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Sora和快手可灵背后的核心技术 | 3DVAE:通过小批量特征交换实现身体和面部的三维形状变分自动编码器

【摘要】学习3D脸部和身体生成模型中一个解开的、可解释的和结构化的潜在表示仍然是一个开放的问题。当需要控制身份特征时,这个问题尤其突出。在本文中,论文提出了一种直观而有效的自监督方法来训练一个3D形状变分自动编码器(VAE),以鼓励身份特征的解开潜在表示。通过交换不同…

自学网络安全的三个必经阶段(含路线图)

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入…

Python:基础爬虫

Python爬虫学习&#xff08;网络爬虫&#xff08;又称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追逐者&#xff09;&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字…

上海晋名室外危废品暂存柜助力储能电站行业危废品安全储存

近日又有一台SAVEST室外危废暂存柜项目成功验收交付使用&#xff0c;此次项目主要用于储能电站行业废油、废锂电池等危废品的安全储存。 用户单位在日常工作运营中涉及到废油、废锂电池等危废品的室外安全储存问题。4月中旬用户技术总工在寻找解决方案的过程中搜索到上海晋名的…

uniapp地图自定义文字和图标

这是我的结构&#xff1a; <map classmap id"map" :latitude"latitude" :longitude"longitude" markertap"handleMarkerClick" :show-location"true" :markers"covers" /> 记住别忘了在data中定义变量…

46.Python-web框架-Django - 多语言配置

目录 1.Django 多语言基础知识 1.1什么是Django国际化和本地化&#xff1f; 1.2Django LANGUAGE_CODE 1.3关于languages 1.4RequestContext对象针对翻译的变量 2.windows系统下的依赖 3.django多语言配置 3.1settings.py配置 引用gettext_lazy 配置多语言中间件&#x…

(代数:解一元二次方程)可以使用下面的公式求一元二次方程 ax2+bx+c0 的两个根:

(代数:解一元二次方程)可以使用下面的公式求一元二次方程 ax2bxc0 的两个根: b2-4ac 称作一元二次方程的判别式。如果它是正值,那么一元二次方程就有两个实数根。 如果它为 0&#xff0c;方程式就只有一个根。如果它是负值&#xff0c;方程式无实根。 编写程序&#xff0c;提示…

Linux结业测试题,旨在检测ip网络配置,文件权限等基础

Linux期末结业考试 一、评分方式&#xff08;总分100分&#xff0c;理论40分在职教云考试&#xff09; 主要涉及的知识和技能点*分值权重*Linux的最小安装10%激活网络&#xff0c;并正确设置ip地址10%克隆1台机器&#xff0c;并正确设置ip地址10%SSH免密互信服务10%文件和目录…

CNAS认证是什么?怎么做?

在全球化日益深入的今天&#xff0c;产品质量和安全已经成为企业生存和发展的重要基石。而在这个过程中&#xff0c;CNAS认证作为一种权威性的认可机制&#xff0c;发挥着不可替代的作用。那么&#xff0c;CNAS认证究竟是什么&#xff1f;我们又该如何进行这一认证过程呢&#…

LabVIEW水箱液位控制系统

介绍了如何使用LabVIEW软件和硬件工具开发水箱液位控制系统。系统集成了数据采集、实时控制和模拟仿真技术&#xff0c;展示了高精度和高可靠性的特点&#xff0c;适用于需要精细水位调节的工业应用。 项目背景 在制造和化工行业&#xff0c;液位控制是保证生产安全与效率的关…

vue-loader

Vue Loader 是一个 webpack 的 loader&#xff0c;它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件 起步 安装 npm install vue --save npm install webpack webpack-cli style-loader css-loader html-webpack-plugin vue-loader vue-template-compiler webpack…

TcpClient 服务器、客户端连接

TcpClient 服务器 TcpListener 搭建tcp服务器的类&#xff0c;基于socket套接字通信的 1 创建服务器对象 TcpListener server new TcpListener(IPAddress.Parse("127.0.0.1"), 3000); 2 开启服务器 设置最大连接数 server.Start(1000); 3 接收客户端的链接,只能…

ARM-V9 RME(Realm Management Extension)系统架构之调试

安全之安全(security)博客目录导读 本节中&#xff0c;“RMSD外部调试”一词用于描述任何系统或PE的外部调试功能&#xff0c;这些功能能够实现以下目的&#xff1a; 监控或修改RMSD行为。对Realm PAS或Realm安全状态的外部访问。 本节中&#xff0c;“Root外部调试”一词用于…

vue2动态路由实现

实现一个简单的动态路由&#xff1a; 1、先定义菜单页面组件的结构&#xff0c;使用的是elementUI的NavMenu 导航菜单 <template><div><el-menu default-active"1" router><el-submenu :index"item.path" v-for"item in menu_…

想设计完美Banner?这7个步骤教你快速上手!

一个合格的网页横幅设计体现在吸引用户点击&#xff0c;促进用户的购物欲望上。网页横幅设计可能是一个漫长而复杂的过程&#xff0c;涉及到每个职位。团队工作时&#xff0c;横幅设计的沟通过程越长&#xff0c;越容易忘记某些步骤&#xff0c;或者因为时间限制而忽略某些部分…

visio添加表格

插入Excel表格&#xff1a; 打开Microsoft Visio&#xff0c;新建一个空白画布。点击菜单栏中的“插入”。在插入中点击“图表”。在弹出的插入对象设置页面中选择“Microsoft Excel工作表”。点击确定按钮&#xff0c;然后在表格中输入内容。将鼠标点击到画布的空白处&#x…

翻译《The Old New Thing》- The case of the exception that a catch (…) didn’t catch

The case of the exception that a catch (...) didnt catch - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20240405-00/?p109621 Raymond Chen 2024年04月05日 一位客户认为他们修复了一个bug&#xff0c;但他们仍然因为这个bug而崩溃。…

基于SpringBoot3+Vue3宠物小程序宠物医院小程序的设计与实现

大家好&#xff0c;我是程序员小孟。 最近开发了一个宠物的小程序&#xff0c;含有详细的文档、源码、项目非常的不错&#xff01; 一&#xff0c;系统的技术栈 二&#xff0c;项目的部署教程 前端部署包&#xff1a;npm i 启动程序&#xff1a;npm run dev 注意事项&…