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         //child
 15         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     else
 22     {
 23         //father
 24         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调用时就封装成了一个数组),所以直接选择合适的就可以.

 

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

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

相关文章

【中间件】服务化中间件理论intro

中间件middleware 内容管理 intro服务化middleware架构注册中心intro服务治理系统intro 本文主要intro服务化中间件的探讨 去年cfeng写了一篇博客走马观花般阐述了Spring Cloud下面的各种中间件&#xff0c;连深入使用都谈不上&#xff0c;只能说intro&#xff0c;在实际work中…

时态图根据时间轴动态播放热力图

效果图如下&#xff1a; <!DOCTYPE html> <html><head><title>时态图</title><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><!-- 引入样式 --&g…

练习九-利用状态机实现比较复杂的接口设计

练习九-利用状态机实现比较复杂的接口设计 1&#xff0c;任务目的&#xff1a;2&#xff0c;RTL代码3&#xff0c;RTL原理框图4&#xff0c;测试代码5&#xff0c;波形输出 1&#xff0c;任务目的&#xff1a; &#xff08;1&#xff09;学习运用状态机控制的逻辑开关&#xff…

101. 对称二叉树

101. 对称二叉树 题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例&#xff1a; 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输…

【云原生-Kurbernetes篇】 玩转K8S不得不会的HELM

Helm 一、Helm1.1 使用背景1.2 Helm简介1.3 Helm的几个概念1.4 helm2 和 helm3 的区别1.5 chart包的关键组成 二、Helm相关命令2.1 应用管理操作2.2 Helm repository仓库管理命令2.2 Helm chart包管理命令2.3 Helm release(实例) 管理命令2.4 Helm私有仓库管理命令 三、部署He…

react大文件上传

目录 大文件上传优点&#xff1a; 大文件上传缺点: 大文件上传原理&#xff1a; 为什么要用md5 实现流程&#xff1a; 部分代码1&#xff1a; 部分代码2&#xff1a;​ 大文件上传优点&#xff1a; 文件太大分片上传能加快上传速度,提高用户体验能断点续传 如果上次上传失败…

C++:继承

一、继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象 程序设计的层次结构&#xff0c…

网络异常检测

随着社交网络、视频流、点对点技术、云计算和 SaaS 的出现&#xff0c;可以肯定地说&#xff0c;现代企业的好坏取决于他们的网络&#xff0c;尤其是在它们提供的带宽和安全性方面。无论是银行保护其数据免遭盗窃&#xff0c;还是商业组织保护其网络免受安全威胁和攻击&#xf…

Linux 中 .tar 和 tar.gz 的区别

1、前言 有时候你会发现&#xff0c;即便是有些拥有 3 年左右工作经验的运维或开发工程师对 .tar 和 .tar.gz 的区别并不是很清楚。.tar 和 .tar.gz 是在 Linux 系统中用于打包和压缩文件的两种常见格式。它们之间的主要区别在于压缩算法和文件扩展名。 2、区别 .tar .tar 是…

python练习题(markdown中的60道题)

1.Demo01 摄氏温度转化为华氏温度 celsius float(input(输入摄氏温度&#xff1a;)) fahrenheit (9/5)*celsius 32 print(%0.1f 摄氏温度转为华氏温度为 %0.1f % (celsius, fahrenheit))结果&#xff1a; 2.Demo02 计算圆柱体的体积 h, r map(float, input().split())# …

581. 最短无序连续子数组

581. 最短无序连续子数组 题目&#xff1a; 给你一个整数数组 nums &#xff0c;你需要找出一个 连续子数组 &#xff0c;如果对这个子数组进行升序排序&#xff0c;那么整个数组都会变为升序排序。 请你找出符合题意的 最短 子数组&#xff0c;并输出它的长度。 示例&…

【Android】声浪 UI 效果并附上详细代码

声浪效果是基于第三方实现的。 https://github.com/xfans/VoiceWaveView 将三方的 Kotlin 代码转 java 使用&#xff08;按照他的readme 进行依赖&#xff0c;好像少了点东西&#xff0c;至少本项目跑不起来&#xff09; 声浪效果在android 8 以上都是比较好的&#xff0c;不会…

【JavaEE】操作系统与进程

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

Oracle的控制文件多路复用,控制文件备份,控制文件手工恢复

一.配置控制文件多路复用 1.查询Oracle的控制文件所在位置 SQL> select name from v$controlfile;NAME -------------------------------------------------------------------------------- /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/fast_recovery_a…

规划类3d全景线上云展馆帮助企业轻松拓展海外市场

科技3D线上云展馆作为一种基于VR虚拟现实和互联网技术的新一代展览平台。可以在线上虚拟空间中模拟真实的展馆&#xff0c;让观众无需亲自到场&#xff0c;即可获得沉浸式的参观体验。通过这个展馆&#xff0c;您可以充分、全面、立体展示您的产品、服务以及各种创意作品&#…

实用篇 | T-SNE可视化工具详情及代码示例

本文主要是为了快速的了解t-sne和如何快速使用&#xff01; 简要了解TSNE TSNE&#xff0c;降维方法之一。降维在机器学习中非常重要。这是因为如果使用高维数据创建模型&#xff0c;则很容易欠拟合。换句话说&#xff0c;有太多无用的数据需要学习。可以通过从各种数据中仅…

Golang版本处理Skywalking Trace上报数据

Tips: 中间记录了解决问题的过程&#xff0c;如不感兴趣可直接跳至结尾 首先去es里查询skywalking trace的元数据 可以拿到一串base64加密后的data_binary(直接解密不能用&#xff0c;会有乱码&#xff0c;可参考https://github.com/apache/skywalking/issues/7423) 对data_b…

第四代智能井盖传感器:智能井盖位移怎么进行监测

井盖是城市基础设施的一个重要组成部分&#xff0c;若井盖出现移位等现象&#xff0c;可能会对路过的车辆和行人造成潜在危险。特别是那些含有甲烷气体的井盖&#xff0c;一旦气体超过阈值且被意外踩踏&#xff0c;可能会导致气体的释放&#xff0c;这便会引发一系列安全事故&a…

Linux wait函数用法

wait 函数是用于等待子进程结束并获取子进程的终止状态的系统调用。它在父进程中使用&#xff0c;用于等待其子进程终止并获得子进程的退出状态。 函数原型&#xff1a; pid_t wait(int *status);status 是一个指向整型的指针&#xff0c;用于存储子进程终止时的退出状态&…

clang+llvm多进程gdb调试

clangllvm多进程gdb调试 前言1. 命令行gdb2. 父进程调试3. 子进程调试4. 返回父进程 前言 在学习新增llvm的优化pass时&#xff0c;需要跟踪clang及llvm的调用栈。然而llvm通过posix_spawn()创建了新进程&#xff0c;这使得gdb调试必须有一定的技巧了。 1. 命令行gdb 以下命…