Linux之进程替换

创建子进程的目的

创建子进程的第一个目的是让子进程执行父进程对应的磁盘代码中的一部分, 第二个目的是让子进程想办法加载磁盘上指定的程序,让子进程执行新的代码和程序 

一是让子进程执行父进程代码的一部分, 比如:

  1 #include<stdio.h>  2 #include<unistd.h>  3 #include<sys/types.h>  4 #include<sys/wait.h>  5 int main()  6 {                                                                                                                                   7     pid_t id=fork();                  8     if(id==0)                          9     {                                  10         //执行子进程的内容             11     }                                  12     else                               13     {                                  14         //执行父进程的内容                                                   15         wait(NULL);                                                          16     }                                                                        17     return 0;                          18 }  

另外一个就是让子进程执行一个全新的程序, 比如说我们在其他路径下写的一些可执行程序.

第二种方法可称之为进程替换, 进程替换是父进程创建子进程之后让子进程执行其他程序的代码而不是执行父进程代码的一部分.

注意和进程切换不同, 进程切换是不同的进程都有一个属于自己的时间片, cpu每次只能执行一个进程的数据和代码, 所以为了保证多个进程能够正常的运行, cpu每次执行一个进程都只会执行时间片长度的时间, 时间到了就硬件上下文把信息保存在PCB, 然后换下一个进程到cpu里执行这叫进程切换.

如何实现进程替换 

如何来实现进程替换呢?

实现进程替换得用到函数execl:

平时执行程序的时候需要告诉bash可执行程序在哪里, 以及采用什么样的方式调用执行这个命令, 这里也是同样的道理, 在子进程中使用execl函数执行其他可执行程序时也需要给函数传相应的参数如可执行程序位置, 以及以什么样的方式来执行这个程序(cmd 选项1 选项2…)

参数path就是可执行程序所在的位置, 后面的可变参数列表是可执行程序所用的方法, 后面的...是可变参数列表, 该函数的参数个数由传参的个数决定(命令行里怎么写的,就怎么传参),最后必须以NULL结尾,表示参数传递完毕..


单进程版的程序替换代码:

系统指令本质上是可执行程序所以我们也可以让子进程执行系统中的指令: 

  1 #include<stdio.h>2 #include <unistd.h>3 int main()4 {5     printf("pid: %d, exec command begin\n",getpid());6     execl("/usr/bin/ls","ls","-l","-a",NULL);                                                      7     printf("pid: %d, exec command end\n",getpid());8     return 0;9 }

平时使用ls指令时可以添加一些选项比如说:-a -l等, 那调用函数时就得把指令和选项分开当成一个个字符串传递给这个函数并且参数的最后必须以NULL进行结尾:

 命令行中ls是ls --color=auto的别名, 所以会显示颜色: 

 

在代码中加上"--color=auto":

  

也可以执行自己写的可执行程序:

 myprocess.cc:

  1 #include <iostream>2 3 int main()4 {5     std::cout << "hello C++" << std::endl;6     std::cout << "hello C++" << std::endl;7     std::cout << "hello C++" << std::endl;8     std::cout << "hello C++" << std::endl;                                                         9     return 0;10 }

myprocess.cc生成的可执行文件命名为myprocess, 在 test.c中进行程序替换

test.c

  1 #include<stdio.h>2 #include <unistd.h>3 int main()4 {5     printf("pid: %d, exec command begin\n",getpid());6     //execl("/usr/bin/ls","ls","-l","-a","--color=auto",NULL);7     execl("./myprocess","./myprocess",NULL);                                                         8     printf("pid: %d, exec command end\n",getpid());9     return 0;10 }

myprocess这个程序没有选项所以这个程序执行的方法就是./myprocess, 这里的方法也可以直接写成process不需要添加上面的相对路径 

makefile: 

结果: 

这就说明execl函数既可以让程序执行系统自带的指令, 也可以让程序执行我们自己写的可执行程序

但是这个运行结果执行结果好像"不太对", 在execl函数后面我们还使用了一个printf函数, 但是程序执行完execl函数之后并没有执行printf函数, 那就要了解一下进程替换的原理.


多进程版程序替换代码

进程替换的原理

关于进程替换的返回值,execl只在发生错误的时候有返回值, 如果替换失败了就会返回-1: 

比如说下面的代码, 我们给execl函数传递一个不存在的路径, 就可以发现execl函数的返回值是-1 :

如果正常执行呢?

这里好像没有打印出来execl函数的返回值, 并且连printf函数都没有正常执行.

原因分析:

首先在没有执行fork函数之前操作系统中就只有一个父进程, 这个父进程有对应的数据区和代码区:

 

当执行fork函数之后操作系统中就会多出来一个子进程, 并且操作系统会以父进程的部分数据结构为这个子进程创建页表,虚拟地址空间和PCB, 所以子进程的页表也指向物理空间上的那块区域, 也就是说此时的子进程和父进程是共用内存上同一块数据区和代码区 

当子进程调用execl函数时会将磁盘上的程序B加载进内存并替换原来的子进程所指向的数据区和代码区, 如果此时子进程的这块数据区和代码区只有它自己使用, 则B程序的数据区的代码区会直接覆盖原来的, 但是进程之间是有独立性的, 当前内存中的程序A的数据区和代码区不仅仅子进程在使用父进程也再使用, 所以此时execl函数再发生替换时会发生写时拷贝, 操作系统会在内存上再开辟一个空间用来存放程序B的数据和代码再将子进程的页表指向新的空间, 这样子程序就能执行程序B的代码:

这个新空间和原来父进程的数据代码就没有任何关系了, 他是一个全新的内容所以执行完execl函数之后就不会再执行原来程序中execl函数后面的内容, 所以execl函数替换成功之后的返回值是什么也就不重要了.


有关进程替换的函数

实现程序替换不止execl函数还有execlp,execv,execvp,execle,execve函数

execl

调用接口:int execl(const char *path, const char argv, ...);

头文件:unistd.h

参数:path表示新程序在磁盘的地址, argv是一个可变参数列表, 需要将我们执行这个程序时向控制台输入的东西以空格为分割一段一段字符串传参, 最后以NULL结尾.

功能:通过程序位置和程序运行名加选项进行进程替换.

execlp

调用接口:int execlp(const char *file, const char *argv, ...);

头文件:unistd.h

参数:file表示新程序的文件名,argv与上面一样(注意传空指针),此时这个函数会在环境变量的位置中寻找相应可执行程序

功能:通过程序名和程序名加选项进行进程替换

 这个函数相较于execl多了一个p, 这个p表示的意思就是path也就是环境变量中的PATH.

 环境变量中的PATH记录着各种操作系统指令所在的地址, 所以使用这个函数替换程序时不需要传地址, 直接传程序名就行, 因为这个函数会自动在PATH所记录的路径查找这个程序, 这个函数的第一个参数就对应着程序名, 比如:

  1 #include <stdio.h>2 #include <unistd.h>5 int main()6 {7     printf("exec command begin:\n");8     execlp("ls","ls","-l","-a",NULL);                                                              9     return 0;10 }

第一个ls表示的是程序名, 第二个ls表示的是执行程序的方法, 也就是可变参数列表中的第一个参数


execv

调用接口:int execv(const char *path, char *const argv[]);

头文件:unistd.h

参数:path表示新程序在磁盘的地址. 这里的argv就不太一样了, 是一个指针数组, 每一个指针指向一个字符串, 最后的位置也必须是NULL, const修饰的是这个字符串数组, 数组内的元素不能改变.

功能:通过程序位置、程序名加选项组成的数组进行进程替换

 这个函数的v表示的是vector, 也就是说将程序的所有指令全部放入到数组中比如说下面的代码: 

    1 #include <stdio.h>2 #include <unistd.h>5 int main()6 {7     char *const arr[]={"ls","-a","-l",NULL};8     printf("exec command begin:\n");9     execv("/usr/bin/ls",arr);10     return 0;11 }

 


execvp

调用接口:int execvp(const char *file, char *const argv[]);  

头文件:unistd.h

参数:file表示新程序的文件名, argv与上面的execv一样。

功能:通过程序名和程序名加选项进行进程替换和环境变量表进行进程替换

    1  #include<stdio.h>2  #include<unistd.h>3  int main()4  {      5      char *const arr[]={"ls","-a","-l",NULL}; 6      printf("exec command begin:\n");                                                                                    7      execvp("ls",arr);8      return 0;9  }


几个函数之间非常相似, 可以通过字母辅助记忆:

字母p表示该函数取文件名file作为参数, 并且用环境变量寻找可执行文件, 不需要传递它在磁盘的位置
字母l表示该函数取一个list
字母v表示该函数取一个数组,可以理解为vector
字母e表示该函数取环境变量envp[]数组


 execle

这个函数的参数如下 

这个函数就多一个参数第三个参数表示的意思是环境变量,如果子进程要用到自定义环境变量或者系统的环境变量的话就可以用到第三个参数,比如说在当前路径下再创建一个文件: 

 先来看这样一段程序替换的代码:

//mytest.c 1 #include <stdio.h>2 #include <unistd.h>3 #include <sys/types.h>4 #include <sys/wait.h>5 #include<stdio.h>6 #include<unistd.h>7 8 9 int main()10 {11     pid_t id = fork();12     if(id == 0)13     {14         //child15         printf("pid :%d,exec command begin:\n",getpid());                                            16         int ret = execl("./myprocess","myprocess",NULL);17         if(ret == -1)18             printf("process replace fail\n");19         printf("pid : %d,exec command end,return value is %d.\n",getpid(),ret);20     }21     else22     {23         //father24         pid_t rid = waitpid(-1,NULL,0);25         if(rid > 0)26             printf("wait success, rid : %d\n",rid);27     }28     return 0;29 }
//myprocess.cc  1 #include <iostream>2 
W>  3 int main(int argc, char* argv[],char* env[])4 {5     for(int i = 0; env[i];i++)6     {7         std::cout << i << ":" << env[i] << std::endl;                                              8     }9     return 0;10 }

问题1: 执行后进程替换如期打印出了环境变量表, 那我们程序替换时, 子进程(myprocess)的环境变量从哪里来的? 是从父进程来的,它是父进程调用的, 也只能由父进程传递. 

父进程的环境变量从哪里来? 是从bash来的, mytest创建进程首先要成为bash的子进程, bash传递环境变量给它, 然后它才作为父进程去创建自己的子进程.

所以bash, mytest, myprocess 用的是一套环境变量:

可以看到孙子进程确实打印出来了自己在bash导入的环境变量.

现在用putenv在父进程中导入一个环境变量: 

 可以看到myprocess继承了mytest的环境变量, 但bash并没有更新MYVAL, 因为mytest中的MYVAL是在bash传入环境变量之后才更改的, bash不会有影响:

问题2: 环境变量被子进程继承下去是一种默认的行为, 不受程序替换的影响, 为什么?

父进程在创建子进程时, 子进程继承父进程的部分PCB,进程地址空间和页表, 进程地址空间上的虚拟地址, 通过页表映射到物理内存, 所以其实环境变量被子进程继承下去是一件很正常的事, 所以是通过地址空间让子进程继承父进程环境变量数据.

环境变量和命令行参数也是进程的数据, exec*程序不是会替换进程的代码和数据吗? 程序替换只替换新程序的代码和数据, 环境变量不会被替换.

所以其实不用所谓的execle之类的函数传递环境变量环境变量也是默认传递下去的.

问题3:子进程执行的时候获得的环境变量.

a. 将父进程的环境变量原封不动的传递给子进程.

1.直接用

environ变量也是可以直接打印出进程所对应的环境变量的, 不用env参数也能访问环境变量. 

2.直接传参

environ是char**类型的, execle的第三个参数是char* []类型, 是匹配的.

b.我们想传递自己的环境变量 , 我们可以直接构造自己的环境变量表, 给子进程传递,这个传递不是新增而是覆盖式地传递.

 

 c.新增传递

新增之前已经实现过, 想新增哪些环境变量在父进程中putenv添加即可.

结论: 程序替换可以将命令行参数和环境变量通过自己的参数, 传递给被替换的程序的main函数中.


execvpe

这个函数的使用就和之前完全类似了,e表示环境变量, p表示直接传环境变量名即可, v表示这里需要方法组成的数组, 那这个函数的参数就如下:


execve

上面讲了那么多的函数,但是这些函数都有一个共同的特点就是这些函数都是c语言函数提供的,系统提供了一个函数调用接口execve,这个函数的参数如下:

 上面介绍的六个函数最后都会被转化成execve:

 最终底层调的是一个接口, 那为什么还有这么多不一样的呢? 虽然接口形式是不一样的, 但是本质还是一样的, 主要还是为了满足各种调用的场景, 不一定自己在进程程序替换时, 程序名这个字符串就替代好了, 环境变量也不一定都是以表的形式呈现好了, (比如execl传的是一个个的选项,底层execve调用时就封装成了一个数组),所以直接选择合适的就可以.

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据分析基础之《matplotlib(2)—折线图》

一、折线图绘制与保存图片 1、matplotlib.pyplot模块 matplotlib.pyplot包含了一系列类似于matlab的画图函数。它的函数作用于当前图形&#xff08;figure&#xff09;的当前坐标系&#xff08;axes&#xff09; import matplotlib.pyplot as plt 2、折线图绘制与显示 展示城…

【实用】mysql配置 及将线上数据导入本地 问题解决及记录

[ERR] 1292 - Incorrect datetime value: ‘0000-00-0000:00:00‘ for column ‘BIRTH_DATE‘ at row 1 此问题是mysql当前配置不支持日期为空&#xff0c;或者为‘0000-00-0000:00:00‘得情况 1、直接在数据库执行 # 修改全局 set global.sql_mode ONLY_FULL_GROUP_BY,STR…

“老人护工-预约上门” 技术支持网址

我们是一家为新加坡华人提供老人护工上门服务的软件。您可以选择满意的护工&#xff0c;然后提交联系人的信息&#xff0c;我们会在规定的时间安排护工人员上门&#xff0c;然后您线下支付所需的费用 如果您在使用过程中有什么问题&#xff0c;您可以随时联系我们。 邮箱地址&…

Docker部署Vue+Springboot项目

一、部署Springboot项目 1.1先将本地的java项目打成jar包。 再右上角进行maven操作。 1.2将jar包上传到服务器当中。 先再目录/home 下创建一个文件夹&#xff08;classRoom&#xff09;用于存放后端打镜像时需要的文件。 如果是服务器的话可以直接将文件拖拽到想要转移的地方…

基准电压源的工作原理和作用是什么(高精度电压源)

基准电压源是一种能够提供固定、稳定的直流电压输出的电源设备。它广泛应用于精密仪器、测试设备、通信设备等领域&#xff0c;是实现精确电压测量和校准的重要工具。本文将为您介绍基准电压源的工作原理和作用。 一、基准电压源的工作原理 基准电压源采用了高精度的电路设计和…

Sam Altman回归OpenAI,新董事会成员曝光!

11月22日下午&#xff0c;OpenAI在社交平台宣布&#xff0c;在原则上已达成协议&#xff0c;让 Sam Altman重返 OpenAI担任首席执行官&#xff0c;并重组董事会。稍后会公布更详细的内容。 初始董事会成员包括前Salesforce联合首席执行官Bret Taylor&#xff08;担任主席&…

鸿蒙开发-ArkTS 语言

鸿蒙开发-ArkTS 语言 1. 初识 ArkTS 语言 ArkTS 是 HarmonyOS 优选主力开发语言。ArkTS 是基于 TS(TypeScript)扩展的一门语言&#xff0c;继承了 TS 的所以特性&#xff0c;是TS的超集。 主要是扩展了以下几个方面&#xff1a; 声明式UI描述和自定义组件&#xff1a; ArkTS允…

labview 安捷伦 34970A 采集温度等

本文详细描述了怎么用安捷伦34970A采集温度&#xff0c;并列出了labview的下载链接&#xff0c;具有一定的参考价值。 1.必要条件&#xff1a; RS-232电缆一根 IO Libraries Suite 软件 BenchLink Data Logger 软件 软件可以在http://www.keysight.com.cn下载 检查RS-232…

SQLite3 数据库学习(五):Qt 数据库高级操作

参考引用 SQLite 权威指南&#xff08;第二版&#xff09;SQLite3 入门 1. Qt 数据库密码加密 MD5 加密在线工具 1.1 加密流程 加密后的密码都是不可逆的 1.2 代码实现 loginsqlite.h #ifndef LOGINSQLITE_H #define LOGINSQLITE_H#include <QWidget> #include <Q…

通过Whisper模型将YouTube播放列表中的视频转换成高质量文字稿的项目

项目简介 一个通过Whisper模型将YouTube播放列表中的视频转换成高质量文字稿的项目。 这个基于 Python 的工具旨在将 YouTube 视频和播放列表转录为文本。它集成了多种技术&#xff0c;例如用于转录的 Fast-Whisper、用于自然语言处理的 SpaCy 以及用于 GPU 加速的 CUDA&…

pytest测试框架介绍(1)

又来每天进步一点点啦~~~ 一、Pytest介绍&#xff1a; pytest 是一个非常成熟的全功能的Python测试框架&#xff1b; pytest 简单、灵活、易上手&#xff1b; 支持参数化 能够支持简单的单元测试和复杂的功能测试&#xff0c;可以做接口自动化测试&#xff08;pytestrequests&…

根据商品链接获取拼多多商品详情数据接口|拼多多商品详情价格数据接口|拼多多API接口

拼多多&#xff0c;作为中国最大的社交电商之一&#xff0c;为卖家提供了丰富的商品详情接口。这些接口可以帮助卖家快速获取商品信息&#xff0c;提高销售效率。本文将详细介绍如何使用拼多多商品详情接口&#xff0c;以及它的优势和注意事项。 一、拼多多商品详情接口概述 …

大宗商品贸易集团数据治理实践,夯实数字基座 | 数字化标杆

某大型央企是首批全国供应链创新与应用示范企业&#xff0c;在“十四五”规划期内以聚焦供应链管理核心主业作为主要战略发展方向。供应链运营管理以大宗商品贸易为主&#xff0c;其交易往往具有交易量巨大、交易环节复杂、风险交易难识别、风险客商难管控等痛点。 随着集团数…

五分钟搭建开源ERP:Odoo,并实现公网远程访问

文章目录 前言1. 下载安装Odoo&#xff1a;2. 实现公网访问Odoo本地系统&#xff1a;3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件&#xff0c;是一个一站式全功能ERP及电商平台。 开源性质&#xff1a;Odoo是一个开源的ERP软件&#xff0c;这意味着企…

小猪优版的前世今生:从籍籍无名到行业瞩目,再到骤变的风暴中心

1. 前世&#xff1a;籍籍无名到行业新星的崛起 小猪优版在初创时期&#xff0c;并不被大众所知。然而&#xff0c;它凭借对短视频行业的深度洞察&#xff0c;以及独特的商业模式&#xff0c;开始在这个领域崭露头角。它提供了一个平台&#xff0c;不仅助力内容创作者更好地展现…

使用whisper实现语音转文本

项目地址&#xff1a;GitHub - openai/whisper: Robust Speech Recognition via Large-Scale Weak Supervision 1、需要py3.8环境 conda activate p38 2、安装 pip install -U openai-whisper 3、下载项目 pip install githttps://github.com/openai/whisper.git 4、安装…

如何避免Steam搬砖项目中账号被盗

购买steam余额有风险吗&#xff1f;及N种被红锁的情况 相信最近很多人都已经听说过steam游戏搬砖这个项目&#xff0c;也叫CSGO游戏搬砖项目&#xff0c;还有人叫它&#xff1a;国外steam游戏汇率差项目&#xff0c;无论怎么称呼&#xff0c;都是同一个项目。 那么什么是stea…

泼天的富贵来啦,快带着你的PMP证书一起迎接

考过PMP认证的威宝们&#xff0c;这波泼天的富贵大家一定要接住呀&#xff01; 很多威宝们在学习PMP之前都在担心&#xff0c;这个证书含金量高吗&#xff1f;转岗跳槽用得上吗&#xff1f;有必要考吗&#xff1f;今天&#xff0c;喜番大声地告诉大家&#xff1a;含金量高&…

Class文件转Java文件

目录 1、下载一个反编译工具2、在文件夹下打开命令窗口3、在此目录下随意建一个文件夹4、在打开的命令窗口输入命令5、返回解压目录下 1、下载一个反编译工具 下载链接&#xff1a;https://varaneckas.com/jad/ 下载的是第一个 下载后放至任意目录下解压即可 2、在文件夹下打…

JetLinks设备接入的认识与理解【woodwhales.cn】

为了更好的阅读体验&#xff0c;建议移步至笔者的博客阅读&#xff1a;JetLinks设备接入的认识与理解 1、认识 JetLinks 1.1、官网文档 官网&#xff1a;https://www.jetlinks.cn/ JetLinks 有两个产品&#xff1a;JetLinks-lot和JetLinks-view 官方文档&#xff1a; JetLi…