首页 > 系统相关 >从0开始linux(18)——进程(9)进程程序替换

从0开始linux(18)——进程(9)进程程序替换

时间:2024-10-23 19:19:39浏览次数:3  
标签:execl const 函数 18 char ls linux 进程

欢迎来到博主的专栏——从0开始linux
博主ID:代码小豪

文章目录


进程程序替换的主要的函数为execl系列,注意这个execl可不是windows的办公软件,而是c标准库中的函数,由于其运行原理与命令行参数和环境变量相关,因此读者在观看这篇博客时,请先补充一下命令行参数与环境变量的知识。当然博主在之前也写了一篇相关的内容。

从0开始linux(12)——命令行参数与环境变量

进程程序替换

进程替换的需要用到以下的函数

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[]);

excel函数

excel函数原型如下:

int execl(const char *path, const char *arg, ...);

其中const char *arg, ...是可变参数列表,如果你对C标准库中的函数原型比较了解,那么这个这边参数列表你一定见过,那就是printf函数的参数,也有一个可变参数列表。

int printf(const char *format, ...);

可变参数列表的意思就是,在这个函数当中,你可以传递非定数的参数,比如可以传递一个,也可以传递多个。

博主先不将execl函数的参数到底有什么用,而是先写出使用execl的代码,再讲解它的原理。

execl函数是定义在标准库中的函数,在linux系统中,其定义在<unistd.h>头文件中,而在windows系统,其定义在<process.h>头文件中。

#include<unistd.h>

int main()
{
    execl("/usr/bin/ls","ls","-a","-l",nullptr);
    return 0;
}

我们编译并运行该程序,查看结果。
在这里插入图片描述
嘶,这个进程的运行结果怎么有点眼熟啊,这不是和我们在命令行中输入ls -a -l的结果一样吗?
在这里插入图片描述
其实这就是为什么将execl函数叫做进程程序替换,因为ls本身就是一个程序,因此运行ls -a -l时,它也就成为了一个进程,执行execl("/usr/bin/ls","ls","-a","-l",nullptr);就相当于将ls进程,替换了test_exec进程。

那么它的参数该如何理解呢?其中

  • path代表执行的程序所在的路径,而ls的路径为/usr/bin/ls,因此传递的参数为"/usr/bin/ls"
  • arg是一个可变参数列表,实际上该参数传递的是给替换的新进程的命令行参数

因此我们想要将ls -a -l指令切换到test_exec进程当中,首先我们要告诉系统我们要切换的进程的路径,即/usr/bin/ls,接着告诉进程,我们运行你时,你的命令行参数应该为“ls”,“-a”,“-l”,“nullptr”.

在这里插入图片描述
现在我们已经清楚execl函数该如何调用了,接下来我们要搞清楚execl函数的原理,首先当一个二进制文件变成进程,第一步是被系统加载到内存中,接着是创建该进程对应的pcb,最后是将进程放入cpu中处理。

而pcb与内存之间还存在一个页表,以划分进程的地址空间。
在这里插入图片描述

也就是说,cpu处理进程时,会根据页表当中的内容,来运行进程,比如该执行什么代码,什么数据要进行计算,以及数据处理的结果都会根据页表的内容来进行。因此execl函数想要达到替换进程的效果,只需要将页表中的数据内容,代码内容替换成目标进程的内容即可。
在这里插入图片描述
好了,现在问题有两个,一个就是excel函数的返回值代表什么,另一个则是替换出来的进程是不是新进程。

为了验证第一个问题,我们先来写如下的代码。

#include<unistd.h>
#include<iostream>

int main()
{
    int n=execl("/usr/bin/ls","ls","-a","-l",nullptr);
    std::cout<<"execl 函数的返回值为:"<<n<<std::endl;//当execl函数结束后,输出它的返回值
    return 0;
}

接着我们运行该程序。
在这里插入图片描述
我们惊奇的发现,当excel函数成功将ls进程替换掉test_exec进程后,它竟然不打印后续的内容了?我们明明在execl函数后面写了别的代码啊,为什么它不执行呢?

这是因为,execl函数的原理,是将原进程的代码,数据,替换成目标进程的代码,既然原进程的代码都被替换了,cpu在页表中都找不到对应的代码了,那么肯定是无法执行excel函数的后续代码的。

所以我们得出结论,execl函数如果成功执行,那么它的返回值没有任何的意义,因为保存返回值的数据,和后续的代码,都被目标进程替换了,即使它给出了返回值,我们也没有任何办法可以看到了啊。

但是执行失败的话,excel函数的返回值为-1,由于目标进程替换原进程失败了,所以页表中的内容还是原进程的内容,cpu会继续执行后续代码。要让excel函数出错也很简单,将路径写错就行了。
在这里插入图片描述
接着运行该代码。
在这里插入图片描述

这说明,excel函数的返回值其实不重要,因为想要对excel函数的返回值进行判断,首先我们需要给excel函数写上后续的判断代码,但是excel只有失败了才能执行后续的代码,因此我们也就不需要判断了,只需要在excel函数的后续写上输出信息,或者补救代码。因为只要执行了后续的函数,那么excel函数必然是失败的。

现在我们来看第二个问题,那就是替换的进程,是不是新进程?

首先,我们先写一个自己的进程,这里就不用系统的进程了。取名为exec

#include<iostream>
#include<unistd.h>

int main()
{
    std::cout<<"我是替换的进程,我的pid为:"<<getpid()<<std::endl;
    return 0;
}

接着我们将使用execl让exec进程替换掉原进程。

#include<unistd.h>
#include<iostream>

int main()
{
    std::cout<<"我是被替换的进程,我的pid:"<<getpid()<<std::endl;
    execl("./exec","exec",nullptr);
    std::cout<<"execl函数执行失败"<<n<<std::endl;
    return 0;
}

接着我们运行该进程,结果如下:
在这里插入图片描述
可以发现,被替换的进程,与替换的目标进程的pid一致,这是因为。其实所谓的替换进程,其实不仅仅只替换页表,其中也包括pcb,即将原进程的pcb也一起拿来用了,因此实际上替换的情况并非如上图所示,而是这样更加贴切。
在这里插入图片描述
因此替换进程的本质不是创建新进程,而是将原来的进程的东西,换成exec的东西,使用的pcb其实是同一个。

其他的替换函数

exec系列的函数很多,但是原理都和execl一样,只是参数不同,因此博主在此只展示用法,不再讲解原理了。

execv

int execv(const char *path, char *const argv[]);

execv和execl切换进程的原理一致,实际上exec系列函数切换进程的原理都一致,只是参数传递的方法不同罢了。

execl当中的l其实就是list(链表),即将命令行参数像链表一样,一个一个的传递给execl,而execv当中的v是vector(顺序表),即传递命令行参数的方式,是将命令行参数写成数组传递给execv函数。

#include<unistd.h>
#include<iostream>


int main()
{
    std::cout<<"我是被替换的进程,我的pid:"<<getpid()<<std::endl;
    char* const argv[]={//将命令行参数写成一个数组
        "ls",
		"-a",
		"-l",
		nullptr
    };
    execv("/usr/bin/ls",argv);
    return 0;
}

execvp

int execvp(const char *file, char *const argv[]);

execvp与execv相比,其多了一个p,这个p其实是PATH的意思,PATH是环境变量之一,如果我们执行的程序在PATH保存的路径当中,那么我们可以省略掉路径。
在这里插入图片描述

int main()//execvp
{
    std::cout<<"我是被替换的进程,我的pid:"<<getpid()<<std::endl;
    char* const argv[]={//将命令行参数写成一个数组
        "ls",
		"-a",
		"-l",
		nullptr
    };
    //execvp("ls",argv);//ok
    //execvp(argv[0],argv);//ok,argv[0]就是"ls"
    //execvp("exec",argv);//error,exec不在PATH指定的路径下
    //execvp("./exec",argv);//ok,将路径写全也能找到对应文件
    return 0;
}

execvpe

int execvpe(const char *file, char *const argv[],char *const envp[]);

execvpe的e代表环境变量,即该程序允许我们给切换的进程传递函数变量,这里我们在同一路径下写上一个新的源文件other.cc。我们需要让其打印出它具有的所有环境变量。代码如下:

#include<unistd.h>
#include<iostream>
extern char** environ;

int main()
{
    for(int i=0;environ[i]!=nullptr;i++)
    {
     	std::cout<<"environ["<<i<<"]:"<<environ[i]<<std::endl;
    }
    return 0;
}

other进程的运行结果如下:
在这里插入图片描述

接着我们使用execvpe切换成other程序,且为其传入新的环境变量。

#include<unistd.h>
#include<iostream>

char* env[]={//环境变量
    "A=helloworld",
    "B=hellobit",
    "C=114514",
    "D=hahahahaha",
    nullptr
};

int main()//execvpe
{
    std::cout<<"我是被替换的进程,我的pid:"<<getpid()<<std::endl;
    char* const argv[]={//将命令行参数写成一个数组
        "other",
        nullptr
    };
    execvpe("./other",argv,env);
    printf("切换失败\n");
    return 0;

}

运行该程序,可以发现other的环境变量被切换了。

那么到此,我们关于exec系列的函数就介绍完了,但是有人可能会说,exec系列的函数不是还有execlp和execlpe没讲吗?实际上exec系列的函数,其实都是exec,v,l,p,e的排列组合罢了,其中v表示以将命令行参数以数组的方式传递,而l表示将命令行参数一个一个的传递,p表示将可以不带路径,前提是文件要在环境变量PATH记录的路径下,e则表示我们可以传递环境变量。只要搞清楚了这些,exec系列函数我们也就掌握了。

但是要注意,有一个函数是特殊的,即execvpe,为什么这么说呢,因为execl,execlp,execlpe,execv,execvp其实都是c标准库中的函数,而execvp却是实打实的系统调用,实际上,exec系列的函数,其底层都是execvp。

标签:execl,const,函数,18,char,ls,linux,进程
From: https://blog.csdn.net/2301_77239666/article/details/143027042

相关文章

  • 操作指南|远程连接linux或windows系统的服务器跑深度学习项目
    目录远程连接linux系统服务器软件清单list使用winscp传输文件操作指南使用pycharmpro连接远程服务器运行项目1、下载并打开pycharmpro2、配置环境3、配置环境完成后,选择python解释器4、运行文件5、查看GPU使用情况远程连接windows系统服务器使用winscp传输文件远程连接服务器远......
  • P11218 【MX-S4-T2】「yyOI R2」youyou 不喜欢夏天
    算法博弈类型的题这个题属于最优解法的问题最初可以看出\(\rm{yy}\)交换的列一定是一黑一白的,不然无意义考虑\(\rm{youyou}\)怎么选对于两个都是黑的情况,显然是都要选的,这种贡献yy影响不了对于两个都是白的情况,显然是只选一个,最大化贡献对于一白一黑的情况......
  • 深入理解Linux内核网络(五):TCP连接的建立过程
    本文将深入探讨TCP协议中的listen和connect系统调用及其相关机制,并对TCP连接建立的完整过程进行详细分析,同时讨论异常情况及其处理方法。部分内容来源于《深入理解Linux网络》、《Linux内核源码分析TCP实现》listen原理系统调用概述listen用于将一个主动套接字(主......
  • 【Linux】进程间通信(匿名管道)
     ......
  • 内存优化的秘密:深入理解 Linux 中的 madvise
    madvise是一个在Linux和其他类Unix操作系统中使用的系统调用,用于向内核提供关于内存映射区域的建议。它可以帮助操作系统优化内存使用,以提高性能。使用场景madvise函数通常用于以下几种情况:预取数据:如果应用程序知道将来会使用某些数据,可以建议操作系统提前加载这些数据到内......
  • Linux安装Redis(保姆教程)
    1,安装GCC依赖#sudo表示以管理员身份运行,如果使用的是管理员用户就不需要sudosudoyuminstall-ygcc2,添加EPEL仓库yuminstallepel-release#更新yum源yumupdate3,安装redisyuminstallredis4,查看redis安装的路径,默认安装路径为:/var/lib/redisfindI-nameredis5,修改......
  • 2024.6.18
    2024.6.18T1题面给定若干个自然数\(a_{1\simn}\)。你需要选出其中一些数,然后将你选出的数划分为若干个集合。你需要最大化每个集合mex的异或和,输出这个值。\(1\lea_i\len\le10^6\)解法找出所有的\(0\to1\to2\to\cdots\tox\)链,每一个链对应集合\(\{0,1,\cdots,......
  • Linux运行时动态库搜索路径优先级
    Windows运行时动态库搜索路径优先级:在Windows运行时,动态库(通常指DLL文件)的搜索路径遵循一定的优先级顺序,以确保程序能够正确地加载所需的动态库。以下是对Windows运行时动态库搜索路径优先级的总结:应用程序所在的目录:当一个应用程序(如exe文件)尝试加载一个DLL时,它首先会在自......
  • Linux常用命令(自用记录)
    CentOS添加用户useradd-d/home/testuser-mtestuserpasswdtestuserroot修改/etc/sudoers文件AllowroottorunanycommandsanywhereusernameALL=(ALL)ALL切换ROOT权限suexit退出root文件目录相关操作cd/切换到根目录cd/home切换到根目录下的......
  • 工程车辆集团数字化转型SAP解决方案| 186页PPT
    PPT文档是关于工程车辆集团数字化转型的SAP解决方案的详细介绍。文档涵盖了行业与市场分析、经营管理核心赋能、组织架构、产品结构和工艺路线、业务调研需求总结、供应链管理、生产组织与物流管理、财务成本管理、信息化系统建设规划、项目实施计划、财务核算及控制业务需求等......