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.hqwc.cn/news/209587.html

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

相关文章

笔记本只使用Linux是什么体验?

笔记本只使用Linux是什么体验&#xff1f; 之后安了Windows双系统之后也不怎么想再进Windows了。 开发环境就不用说了&#xff0c;Linux下配各种开发环境都方便的多&#xff0c;当然你要用 vs 那还是乖乖回 Windows 吧。 最近很多小伙伴找我&#xff0c;说想要一些Linux的资…

德迅云安全-德迅卫士:保障您的主机安全

主机安全是指保证主机在数据存储和处理的保密性、完整性、可用性&#xff0c;包括硬件、固件、系统软件的自身安全&#xff0c;以及一系列附加的安全技术和安全管理措施。 为什么要主机安全&#xff1f; 服务器一旦被黑客入侵&#xff0c;个人和企业面临以下安全风险&#xff…

shell 脚本 批量 修改 文件名

shell 脚本 批量 修改 文件名 原始文件 你可以使用以下的shell脚本来批量修改文件名&#xff1a; #!/bin/bashinput_dir$1if [ -z "$input_dir" ]; thenecho "Usage: ./rename.sh input_directory"exit 1 ficd "$input_dir" || exitfor file in…

python命令行 引导用户填写ssh登录信息

字多不看&#xff0c;直接体验&#xff1a; 待补充 演示代码 # -*- coding:UTF-8 -*- """ author: dyy contact: douyaoyuan126.com time: 2023/11/23 9:20 file: 引导用户填写ssh接口信息.py desc: xxxxxx """# region 引入必要的依赖 impor…

《YOLOv8创新改进》专栏指导书册 手把手创新教程

&#x1f680;&#x1f680;&#x1f680;YOLOv8改进专栏&#xff1a;http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8&#xff0c;从入门到创新&#xff0c;轻轻松松搞定科研&#xff1b; 本专栏为订阅者提供答疑服务&#xff0c;每一篇提供源代码和详细的每一个步骤改进地方。…

TikTok青年领袖:短视频如何塑造新一代

在数字时代的潮流中&#xff0c;短视频平台TikTok崭露头角&#xff0c;成为年轻一代最喜爱的社交媒体之一。这个平台不仅改变了用户的娱乐方式&#xff0c;更在其中催生了一批富有创造力和影响力的青年领袖。 本文将深入探讨TikTok如何通过短视频内容塑造新一代的青年领袖&…

可编程交流回馈式负载箱在电源设备中的应用

可编程交流回馈式负载箱可以用于测试电源设备的输出能力&#xff0c;通过在负载箱中设置不同的负载条件&#xff0c;可以模拟不同的工作负载情况&#xff0c;从而测试电源设备在不同负载下的输出能力和稳定性。这对于电源设备的设计和生产非常重要&#xff0c;可以帮助制造商评…

购买Zoho CRM系统要花多少钱?

相信对CRM系统有过了解的人都知道&#xff0c;CRM系统的价格取决于功能需求&#xff0c;用户数量&#xff0c;部署方式&#xff0c;定制开发等因素。Zoho CRM是全球领先的云CRM系统&#xff0c;自2005年推出以来&#xff0c;累计服务25万企业&#xff0c;那么Zoho CRM系统的价格…

【Java工具篇】Java反编译工具Bytecode Viewer

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

3.7寸墨水屏蓝牙卡证

超薄机身&#xff0c;厚度不足一厘米&#xff0c;轻松佩戴无负重感。 无需基站&#xff0c;服务器&#xff0c;手机APP直接更新~ 独创快速扫描技术&#xff0c;智能感应标签 超长待机&#xff0c;超低功耗&#xff0c;Type C接口充电&#xff0c;一次充电可续航一年&#xf…

python-opencv划痕检测

python-opencv划痕检测 这次实验&#xff0c;我们将对如下图片进行划痕检测&#xff0c;其实这个比较有难度&#xff0c;因为清晰度太差了。 我们做法如下&#xff1a; &#xff08;1&#xff09;读取图像为灰度图像&#xff0c;进行自适应直方图均衡化处理&#xff0c;增强图…

redis运维(十三) hash哈希

一 哈希 ① 定义 hash&#xff1a; 散列说明&#xff1a;key对应是值是键值对[python中的字典],其中键在redis中叫field.形如&#xff1a;value[{field1,value1},...{fieldN,valueN}],值本身又是一种键值对结构 ② 优点和缺点 wzj_height 180wzj_age 18等价 -->…