【Linux修行路】进程控制——程序替换

devtools/2024/9/24 20:00:58/

目录

⛳️推荐

一、单进程版程序替换看现象

二、程序替换的基本原理

三、程序替换接口学习

3.1 替换自己写的可执行程序

3.2 第三个参数 envp 验证


⛳️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站

一、单进程版程序替换看现象

#include <stdio.h>
#include <unistd.h>int main()
{printf("befor: I am a process, pid:%d, ppid:%d\n", getpid(), getppid());//exec类函数的标准写法execl("/usr/bin/ls", "ls", "-a", "-l", NULL);printf("after: I am a process, pid:%d, ppid:%d\n", getpid(), getppid());return 0;
}

在调用了程序替换函数 execl 后,我们的进程去执行了 ls 指令,并且原进程的 after 信息没有打印。在前面的文章中讲过,指令本质上就是可执行程序。因此程序替换函数 execl 可以用其它进程来替换当前进程。

二、程序替换的基本原理

当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 pid 并未改变。

补充

  • 程序替换成功后,exec 系列函数后续的代码不会被执行,只有替换失败才有可能执行后续代码。exec 系列函数,只有失败返回,没有成功返回。

  • Linux 中形成的可执行程序本质上是 EFL 格式的文件,该文件有一个表头,里面保存了该可执行程序的入口地址。

三、程序替换接口学习

和程序替换有关的接口一共有七种,其中一个是系统调用,剩下六个都是库函数。

  • 系统调用:
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
  • 库函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

上面这六种函数的参数主要是为了解决以下两个问题:如何找到新替换进来的程序?如何执行新替换的程序。因此:

第一个参数就是解决第一问题的,有两种解决方案,第一种是函数名不带 p 的,此时第一个形参 path 表示的是可执行程序的全路径;第二种是函数名代 p 的,此时第一个形参 file 表示的是可执行程序的名字,函数会拿着这个名字去 PATH 环境变量下搜索这个可执行程序。

第二个参数就是解决第二个问题的,也有两种解决方案。如何执行新替换的可执行程序本质上就是要知道执行该程序的指令以及参数是什么。l 表示参数采用列表,以可变参数的形式将指令和选项传进去(命令行中输入什么,这里就传什么),最后要以 NULL 结尾。v 表示采用字符串指针数组的方式,把指令以及选项都存在一个字符串指针数组中(最后必须是 NULL),然后把这个数组作为实参传给程序替换函数。这个参数最终会作为命令行参数传递给新替换进来的可执行程序。

第三个参数 envp,如果使用这个参数,那么新替换进来的进程将采用覆盖的策略彻底替换掉父进程的环境变量,即使用该参数后,新替换进来的进程不再继承父进程的环境变量,而是完全以 envp 数组中的内容作为自己的环境变量。

小Tips:在将进程地址空间的时候画过一张关于进程地址空间的图,其中有一部分存储的就是命令行参数和环境变量,而子进程的进程地址空间是继承自父进程的,环境变量也是在这个时候继承下去的,因此一个进程的环境变量在该进程创建的时候就已经被该进程从父进程那里继承下来了,在程序替换的过程中,环境变量信息不会被替换

// 首先在 bash 中加入自定义的环境变量
export MY_VALUE=123456
// mycommand.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>int main(int argc, char* argv[], char* env[])
{printf("befor: pid: %d, ppid: %d, mycommand running...\n", getpid(), getppid());printf("mycommand get MY_VALUE: %s\n", getenv("MY_VALUE"));pid_t id = fork();if (id == 0){// childsleep(2);// exec类函数的标准写法char* const child_argv[] = {"otherexe", "-a", "-b", "-c", NULL};execv("./otherexe", child_argv);// 替换成otherexe程序printf("%s\n", strerror(errno));exit(errno);// 如果执行了这段代码说明程序替换失败}// fathersleep(4);pid_t ret = waitpid(id, NULL, 0);if(ret > 0){printf("wait success, my pid: %d, ret pid: %d\n", getpid(), ret);}return 0;
}
#include <iostream>
#include <unistd.h>
#include <stdlib.h>using namespace std;int main(int argc, char* argv[], char* env[])
{printf("befor: pid: %d, ppid: %d, otherexe running...\n", getpid(), getppid());printf("otherexe get MY_VALUE: %s\n", getenv("MY_VALUE"));return 0;
}

分析:首先在 bash 中添加环境变量 MY_VALUE,mycommand 程序作为 bash 的子进程一定会继承该环境变量,在 mycommand 先创建一个子进程,它也会继承 mycommand 的环境变量,然后在子进程中调用 execv 接口进程程序替换,将 otherexe 程序替换进来,在 otherexe 程序中我们调用 getenv 接口,仍然可以获得 MY_VALUE 这个环境变量的值,这证明了我们上面说的:在进行程序替换的过程中,环境变量信息不会被替换。补充一点,如果要在当前进程的地址空间中新增环境变量可以调用 putenv 接口。

小结:在 bash 中执行的所有程序其实都是子进程,并且都是通过 exec 系列函数将程序对应的代码和数据加载到内存中。因此 exev 系列函数起到加载器的效果,函数里面一定会涉及到内存申请、外设访问等动作。

3.1 替换自己写的可执行程序

// mycommand.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>int main()
{char* const argv[] = {"ls", "-a", "-l", NULL};printf("befor: I am a process, pid: %d, ppid: %d\n", getpid(), getppid());pid_t id = fork();if (id == 0){// childsleep(2);// exec类函数的标准写法execl("./otherexe", "otherexe", NULL);printf("%s\n", strerror(errno));exit(errno);// 如果执行了这段代码说明程序替换失败}// fathersleep(6);pid_t ret = waitpid(id, NULL, 0);if(ret > 0){printf("wait success, my pid: %d, ret pid: %d\n", getpid(), ret);}return 0;
}
// otherexe.cc
#include <iostream>using namespace std;int main()
{cout << "Hello Linux!" << endl;return 0;
}

代码解释:其中 otherexe 是我们自己写的一个 C++ 程序,在 mycommand 程序中先创建一个子进程,然后在该子进程中调用程序替换函数 execl,将我们自己写的 mycommand 程序替换进来。这里有一个细节就是 execl 的第二参数到底传 ./otherexe 还是 otherexe,因为上面说过在 bash 中如何输入这个参数就怎么传,在 bash 中执行 otherexe 程序输入的指令是:./otherexe,其中 ./ 主要是为了告诉 bash otherexe 这个程序就在当前目录下。而 execl 函数的第一个参数就已经将 otherexe 程序的详细路径传进去了,所以第二个参数可以直接传 otherexe。但经过验证传 othe

rexe 和 ./otherexe 都可以。

3.2 第三个参数 envp 验证

// mycommand.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>int main(int argc, char* argv[], char* env[])
{printf("befor: pid: %d, ppid: %d, mycommand running...\n", getpid(), getppid());pid_t id = fork();if (id == 0){// childsleep(2);// exec类函数的标准写法char* const child_argv[] = {"otherexe", "-a", "-b", "-c", NULL};char* const myenv[] = {"MY_PATH=./usr/wcy/linux-s","MY_VALUE=asdfg","NAME=wcy",NULL};// 自定义环境变量execle("./otherexe", "otherexe", "-a", "-w", "-z", NULL, myenv);// 替换成otherexe程序printf("%s\n", strerror(errno));exit(errno);// 如果执行了这段代码说明程序替换失败}// fathersleep(4);pid_t ret = waitpid(id, NULL, 0);if(ret > 0){printf("wait success, my pid: %d, ret pid: %d\n", getpid(), ret);}return 0;
}
// otherexe.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>using namespace std;int main(int argc, char* argv[], char* env[])
{printf("befor: pid: %d, ppid: %d, otherexe running...\n", getpid(), getppid());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;
}

🎁结语:

        今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是我前进的动力!


http://www.ppmy.cn/devtools/94549.html

相关文章

八股总结----计算机网络

0.OSI七层模型 自己的理解&#xff1a;应用层&#xff1a;生成HTTP请求报文-----表示层&#xff1a;将请求报文转换成适合网络传输的数据格式&#xff0c;加密压缩编码等-----会话层&#xff1a;管理两个应用程序之间的会话&#xff0c;包括连接中断等------传输层&#xff1a…

uni-app发布安卓app打包时必须在APP模块配置中选中需要的模块

最近尝试开发一个语音对话的app&#xff0c;在调试阶段没有选中“record录音”模块&#xff0c;安卓基座运行在雷电模拟器上是没有问题的&#xff0c;但是直接云打包在手机上运行就不行了&#xff0c;语音输入没有反应。 经过试验发现是manifest.json没有勾选APP模块配置中的“…

HarmonyOS.FA开发流程

开发环境配置 1、DevEco Studio的安装 2、DevEcoStudio模拟运行工程&#xff1a;运行Tools->Device Manager&#xff0c;使用已认证的HW开发者联盟帐号Login&#xff08;在DP平台申请测试者权限&#xff09;&#xff0c;点击"允许"授权&#xff0c;选择一个设备运…

【深度学习】【语音】TTS,MeloTTS代码讲解

文章目录 推理split_sentences_zh 函数短句子转为音素和bert特征get_text_for_tts_infer 函数text_normalize函数_g2p_v2函数def _g2p(segments)cleaned_text_to_sequencehps.data什么是滤波长度短时傅里叶变换(STFT)滤波长度(filter_length)影响分析get_bert_featureinfer…

java快速导出word文档

点关注不迷路&#xff0c;欢迎再访&#xff01; 精简博客内容&#xff0c;尽量已行业术语来分享。 努力做到对每一位认可自己的读者负责。 帮助别人的同时更是丰富自己的良机。 文章目录 前言一.添加 Apache POI 依赖二.填充文档内容三.导出文档效果测试 前言 在 Java 应用程序…

uniapp接口请求this.$request

代码示例&#xff1a; createPhoto(url) {this.$request({url: /emp/gallery-photo/create,method: post,header: {tenant-id: 1,},data: {galleryId: this.albumId,empUserId: this.empUserId,"url": url,}}).then((res) > {console.log(res,"返回值"…

PCB工艺

表面处理 提高焊接质量&#xff1a;提高焊接点的质量&#xff0c;确保电路板的可靠性和寿命。防止氧化&#xff1a;保护裸露的铜箔不受氧化&#xff0c;延长电路板的使用寿命。提高导电性&#xff1a;某些表面处理方法可以提高电路板的导电性&#xff0c;适用于高频和高速电路…

基于spring boot的校园商铺管理系统

TOC springboot188基于spring boot的校园商铺管理系统 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在这个新的时代&…