当前位置: 首页 > news >正文

【Linux】进程控制-----进程替换

目录

一、为什么要进行进程替换:

二、进程替换的原理:

三、exec家族:

1、execl:

2、execlp:

3、execv:

4、execvp:

5、execle和execve

​编辑

putenv:


一、为什么要进行进程替换:

当父进程创建子进程之后,这两个进程后面的代码是共享的,但是更多的场景下是给子进程执行分配任务,让子进程执行它自己的任务,这个时候就需要进程替换了,让别的进程替换子进程,但是这个替换并没有创建PCB

程序替换的目的是让子进程帮我们执行特定任务

二、进程替换的原理:

单进程替换图解:

父进程创建子进程,然后子进程进行程序替换图解:

替换前:

如上,父进程创建子进程,然后子进程的代码和数据继承父进程的代码和数据的,然后即将发生替换的程序加载到内存中,页表中子进程的物理内存映射变大或变小为新进程的大小,
替换后:

替换后会发生写时拷贝,不仅仅是数据,代码也会,
当子进程需要执行不同的代码时,操作系统会将父进程的代码和数据拷贝一份,然后将新的程序加载到内存中,重新建立子进程的页表,并更新子进程页表中的映射关系。这一过程确保了父子进程的代码分离,使得它们互不干扰,写时拷贝技术保证了父子进程在没有对代码和数据进行修改时共享相同的物理内存,一旦发生写操作,就会为子进程分配新的物理空间,并拷贝数据,从而实现进程间的独立性,因此,当子进程进行程序替换时,父进程不受影响,因为替换过程中发生的写时拷贝确保了子进程的独立性

接下来理解两个问题

进程程序替换时,有没有创建新的进程?

当进程程序替换后,子进程的PCB,进程地址空间和页表都没有变,只是物理内存的数据和代码发生写时拷贝,但是这个子进程的pid并没有改变,也就是说,程序替换不创建新进程,只是进行进程的代码和数据的切换,

CPU是如何获取程序的入口地址的?

首先,在Linux中形成的可执行程序并不是杂乱无章的,是有格式的,Linux中这个格式成为ELF,这个可执行程序的最开始有可执行程序的表头,这个表里面有可执行程序的入口地址,我们之前了解到程序加载是惰性加载的,在程序加载到内存中可以先不把它的代码和数据加载到内存中,但一定要把表头加载到内存中,

在这个表里面有可执行程序的入口地址,这样CPU就能够从表中读取到程序的入口地址了

三、exec家族:

子进程替换父进程是用exec系列的函数来进行程序替换

如上,虽然有7个但是只有execve是系统调用接口,其他的都是调用这个接口进行了一次封装,

1、execl:

理解:

参数1:新替换程序的路径,(./当前路径,或者绝对路径如/usr/bin/ls)
参数2:新替换程序的名称,如ls,自己写的程序名
参数3:新替换程序的选项,如-a,-l,但是最后一个要是NULL表示结束
。。。:这是表示可变参数列表,可以传递不一样的参数个数
返回值:替换失败返回-1,替换成功的话子进程后面程序就不执行了,所以返回值也就没意义了

例如我要把子进程替换为ls进程:

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

在代码中的使用:

int main()
{pid_t id = fork();if(id < 0){printf("进程创建失败\n");exit(1);}else if(id == 0){//子进程printf("子进程替换进程前\n");int ret = execl("/usr/bin/ls","ls","-a","-l",NULL);if(ret == -1){printf("进程替换失败\n");}printf("子进程替换进程后\n");exit(1);}else{//父进程printf("父进程正常执行\n");pid_t ret = waitpid(id,NULL,0);if(ret == id){printf("等待成功\n");}}return 0;
}

如上为执行结果:

可以看到execl之后的代码没有被执行,因为这些代码是属于原来子进程的代码,在execl之后这些代码就被替换了,原本的代码也就肯定不会执行

为什么,父进程能够等待得到已经被替换的进程呢?
这是因为虽然进程被替换了,但是并没有创建新的进程,替换后这个进程依然是父进程的子进程,pid也没有变,那么就可以正常等待

2、execlp:

理解:


参数1:这个和execl不同的是这个就只需要传执行的文件名就行了,路径会自动在环境变量中找
参数2:新替换程序的名称,如ls,自己写的程序名
参数3:新替换程序的选项,如-a,-l,但是最后一个要是NULL表示结束
。。。:这是表示可变参数列表,可以传递不一样的参数个数
返回值:替换失败返回-1,替换成功的话子进程后面程序就不执行了,所以返回值也就没意义了

这个就可以看做execl的升级版,也就是不需要手动传路径,会自动在环境变量中找

例如我要把子进程替换为ls进程:

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

注意:
这虽然有两个ls,但是含义是不同的第一个参数是要执行程序的名字,第二个参数是可变参数列表

在代码中的使用:

int main()
{pid_t id = fork();if(id < 0){printf("进程创建失败\n");exit(1);}else if(id == 0){//子进程printf("子进程替换进程前\n");int ret = execlp("ls","ls","-a","-l",NULL);if(ret == -1){printf("进程替换失败\n");}printf("子进程替换进程后\n");exit(1);}else{//父进程printf("父进程正常执行\n");pid_t ret = waitpid(id,NULL,0);if(ret == id){printf("等待成功\n");}}return 0;
}

执行结果和execl是一样的

3、execv:

理解:

参数1:新替换程序的路径,(./当前路径,或者绝对路径如/usr/bin/ls)
参数2:这个是传一个 指针数组,里面成员就是类似于execl后面的参数

例图:(差不多就是下面这个意思)

int main()
{pid_t id = fork();if(id < 0){printf("进程创建失败\n");exit(1);}else if(id == 0){//子进程printf("子进程替换进程前\n");char* const myargv[] = { "ls" , "-a" , "-l" , NULL };int ret = execv("/usr/bin/ls",myargv);if(ret == -1){printf("进程替换失败\n");}printf("子进程替换进程后\n");exit(1);}else{//父进程printf("父进程正常执行\n");pid_t ret = waitpid(id,NULL,0);if(ret == id){printf("等待成功\n");}}return 0;
}

执行结果和execl是一样的

4、execvp:

char* const myargv[] = { "/usr/bin/ls" , "-a" , "-l" , NULL };int ret = execv("/usr/bin/ls",myargv);

这个和execl升级成execlp是一样的,就只是从自己在第一个参数中给路径变为只需传执行的文件名就行了,路径会自动在环境变量中找

5、execle和execve

理解:

参数1,参数2和返回值是一样的,就是多了一个环境参数,envp就是你自己设置的环境变量

putenv:

首先了解一下putenv函数:
这就是一个创建环境变量

putenv("环境变量=数值")

作用:在当前进程下创建一个环境变量并继承给子进程,并不会影响父进程

exec系列带e的函数中,就可以传环境变量:

int main()
{putenv("MYENV=6666666");pid_t id = fork();if(id < 0){printf("进程创建失败\n");exit(1);}else if(id == 0){//子进程printf("子进程替换进程前\n");execlp("./other","other",NULL);		printf("子进程替换进程后\n");exit(1);}else{printf("父进程正常执行\n");pid_t ret = waitpid(id,NULL,0);if(ret == id){printf("等待成功\n");}}return 0;
}

上面是bash的环境变量,下面是上述程序的运行结果,所以可以通过putenv来增加环境变量

也可以使用自己的环境变量,这个时候就需要使用execle或者execve了:

int main()
{pid_t id = fork();if(id < 0){printf("进程创建失败\n");exit(1);}else if(id == 0){//子进程printf("子进程替换进程前\n");char* const myenv[] = {"MYENV1=123","MYENV2=456","MYENV3=789",NULL};char* const myargv[] = {"other","-a","-b",NULL};execve("./other",myargv,myenv);		printf("子进程替换进程后\n");exit(1);}else{//父进程printf("父进程正常执行\n");pid_t ret = waitpid(id,NULL,0);if(ret == id){printf("等待成功\n");}}return 0;
}

如上,其中自己所写的other进程就是打印命令行参数和环境变量

int main(int argc,char* argv[],char* env[])
{cout<<"命令行参数"<<endl;for(int i = 0;argv[i];i++){cout<<i<<":"<<argv[i]<<endl;}cout<<"环境变量表"<<endl;for(int i = 0;env[i];i++){cout<<i<<":"<<env[i]<<endl;}return 0;
}

这样,在调用之后就可以打印出自己所传的命令行参数,和自己建立的环境变量表

想要理解好带e的函数,就需要先理解好exec在bash中的作用:

首先,在bash中,所有进程都是bash的子进程,那么这些子进程的命令行参数是从哪儿来的呢?就是通过exec系列的函数进行传参的,把第二个参数的选项作为命令行参数传给第一个参数指向的可执行性程序,所以在Linux中,当进程启动的时候就是父进程创建一个子进程,然后这个子进程的数据和代码替换为所执行的命令,exec系列的函数就是起到了类似于加载的作用

exec也可以加载脚本

比如下面写一个Python的脚本语言,

接着在子进程中调用脚本语言:

这样就能够运行脚本语言

所以,尽管Python和cpp是两种语言,但是竟然可以进行跨语言调用,这是为什么呢?

其实所有语言运行起来都是进程,只要是进程就可以被调用,所以,只要这个语言运行起来变成进程,那么就可以使用exec系列函数进行调用!!!

http://www.lryc.cn/news/495671.html

相关文章:

  • 安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本
  • 使用ECharts创建带百分比标注的环形图
  • 学习threejs,设置envMap环境贴图创建反光效果
  • go语言里的mkdir mkdirall有什么区别?
  • 使用Python OpenCV实现图像形状检测
  • 继上一篇,设置弹框次数以及自适应图片弹框,部分机型(vivo)老手机不显示的问题
  • 基于RISC-V 的代理内核实验(使用ub虚拟机安装基本环境)
  • 【MMKV】HarmonyOS中的优秀轻量化存储方式
  • docker安装hadoop环境
  • 开源多媒体处理工具ffmpeg是什么?如何安装?使用ffmpeg将M3U8格式转换为MP4
  • 算法刷题Day5: BM52 数组中只出现一次的两个数字
  • 55 基于单片机的方波频率可调
  • 23.useUnload
  • linux环境搭建
  • 《C++与生物医学的智能融合:医疗变革新引擎》
  • Matlab 绘制雷达图像完全案例和官方教程(亲测)
  • Lua的环境与热更
  • HTML CSS JS基础考试题与答案
  • 若依解析(一)登录认证流程
  • Redis设计与实现第17章 -- 集群 总结1(节点 槽指派)
  • 汽车控制软件下载移动管家手机控车一键启动app
  • 推荐几个可以免费下载网站模板的资源站
  • H3C OSPF实验
  • Vue框架开发一个简单的购物车(Vue.js)
  • Windows Terminal Solarized Dark 配色方案调整
  • PyTorch张量运算与自动微分
  • 【从零开始的LeetCode-算法】3264. K 次乘运算后的最终数组 I
  • 【Linux】gdb / cgdb 调试 + 进度条
  • Jenkins Nginx Vue项目自动化部署
  • 视频汇聚平台Liveweb国标GB28181视频平台监控中心设计