当前位置: 首页 > news >正文

【Linux系统编程】程序替换:execve(execl、execlp、execle、execv、execvp、execvpe)

文章目录

  • 前言
    • exec系列函数关系图
    • 程序替换的原理
  • 函数的介绍及使用
    • execl( )
    • execlp()
    • execle()
    • execv()
    • execvp()
    • execvpe()
    • execve(系统调用)
  • 拓展
    • putenv
    • setenv / unsetenv
      • setenv
      • unsetenv

前言

在现代操作系统中,进程是执行程序的基本单位,每个进程拥有独立的地址空间、代码和数据。当一个程序正在运行时,有时我们希望它能够“脱胎换骨”,从自身变成另一个程序——这就是程序替换(Process Replacement)的概念。在 Unix/Linux 系统中,这种能力被 exec 系列函数实现,它允许一个进程在原地加载并执行新的可执行文件,而无需创建新进程,从而节省资源并保持进程的身份标识(PID)不变。


exec系列函数关系图

                  +--> execl()  ——  参数用列表|execve()  <------+--> execv()  ——  参数用数组|+--> execlp() —— 查 PATH|+--> execvp() —— 查 PATH,参数用数组|+--> execle() —— 自定义环境|+--> execvpe() —— 查 PATH + 自定义环境
  • 返回值:成功则不返回(当前程序被覆盖);失败返回-1
  • exec*系列函数都是对系统调用接口execve()的封装,封装函数的形参中未实现的则使用系统默认的值;
    • 例如: execve接口的参数有pathnameargvenvpexecl的形参只有两个,pathargv;那么一个进程的程序数据使用execl替换后,在替换的程序中访问argv,那就是我显示传入的形参argv的值,
      但是如果访问envp,那么就是系统默认传入的envp值(environ指向的环境变量);
  • 函数的命名规律:exec[l|v][p][e]
    • l:list(可变参数列表)
    • v:vector(数组)
    • p:path(PATH查找)
    • e:environ(自定义环境)

程序替换的原理

  1. 可执行文件=代码+数据
  2. 在一个进程在执行exec*替换函数时,会将当前进程的代码段、数据段、堆、栈都被清空,并初始化新的堆、栈(argv,envp)、数据和代码内容;
  3. 程序替换会将当前进程的代码和数据替换为其他文件的代码数据,所以我们一般会创建一个子进程,将其他文件的代码和数据替换到子进程中,进而不影响父进程的正常运行;

函数的介绍及使用

在这里插入图片描述

execl( )

函数原型: int execl(const char *path, const char *arg, ... /* (char *) NULL */);
参数说明:

  • path:文件的绝对路径
  • arg:可变参数列表(最后以NULL结尾,表示参数传入完毕)

代码示例

#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){execl("/usr/bin/ls","ls","-l","-a",NULL);exit(1);//如果返回并继续向下执行,说明替换失败,直接退出;}waitpid(-1,NULL,0);//进程等待printf("当前程序停止运行……\n");
}

执行结果:


execlp()

再execl基础上不仅可以通过文件名方式(环境变量PATH)查找还可以通过绝对路径的方式查找

原型int execlp(const char *file, const char *arg, .../* (char *) NULL */);
参数说明:

  • file:文件名或文件绝对路径;
  • arg:可变参数列表(最后以NULL结尾,表示参数传入完毕)

代码示例:(与execl基本相同)

execlp("ls","ls","-l","-a",NULL);

execle()

指定文件绝对路径,自定义环境变量

原型:int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);

参数说明:

  • path:文件绝对路径
  • arg:可变参数列表
  • envp:自定义的环境变量(注意:必须写在NULL的后面

代码示例:

//test.c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){printf("hello test,child pid:%d\n",getpid());char* env[]={	//自定义环境变量"PATH=/learn/linux/leason15",NULL};execle("proc","./proc",NULL,env);}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}
//proc.c
#include <stdio.h>
#include <unistd.h>int main(int argc,char* argv[],char* env[])
{printf("hello proc,child pid:%d\n",getpid());for(int i=0;env[i];i++)printf("env:%s\n",env[i]);return 0;
}

运行结果:

  • 这里可以看到test.c的子进程将proc文件进行替换;并在替换前和替换后输出PID,可以证明程序替换后确实没有创建新的进程;

execv()

指定绝对路径,自定义命令行参数

原型:int execv(const char *path, char *const argv[]);
参数说明:

  • path:文件绝对路径
  • argv: 自定义命令行参数
    • (指针数组,数组与系统命令行参数格式相同,最后一个参数为NULL;可看作可变参数列表)

代码示例:

#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"ls","-l",NULL};execv("/usr/bin/ls",argv);	}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}

运行结果:


execvp()

与execv基本相同,唯一不同的是 可以通过文件名(PATH环境变量)的方式查找文件;

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

参数说明:

  • file:要执行文件名/绝对路径
  • argv: 自定义命令行参数

代码示例:

#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"ls","-l",NULL};execvp("ls",argv);	//通过PATH查询}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}

execvpe()

相当于execvpexecle的结合版;

原型:int execvpe(const char *file, char *const argv[],char *const envp[]);
参数说明:

  • file:要执行文件名/绝对路径
  • argv: 自定义命令行参数
  • envp:自定义环境变量

代码示例:

//test.c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"-a","-b","-c",NULL};char* envp[]={"PATH=/1/2/3","NAME=YANG",NULL};execvpe("./proc",argv,envp);	}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");
}
//proc.c
#include <stdio.h>
#include <unistd.h>int main(int argc,char* argv[],char* env[])
{printf("hello proc,child pid:%d\n",getpid());for(int i=0;env[i];i++)printf("env:%s\n",env[i]);printf("argv:");for(int i=0;argv[i];i++)printf("%s ",argv[i]);printf("\n");return 0;
}

运行结果:
在这里插入图片描述


execve(系统调用)

前面的六个程序替换函数底层都是调用的execve接口;这里execve函数的参数和前面替换函数的用法相同,这里就简单说明每个参数的含义即可,了解完前面的函数自然就会使用这个函数接口

在这里插入图片描述

函数原型:int execve(const char *pathname, char *const argv[], char *const envp[]);

参数说明:

  • pathname:文件的绝对路径
  • argv[]:命令行参数数组(最后以 NULL 结束)
  • envp[]:环境变量数组(同样以 NULL 结束)

拓展

putenv

作用: 把指定的字符串指针直接放入进程的环境变量表(char** environ)中
返回值:

  • 成功返回 0
  • 失败返回 非 0(通常是内存不足)

参数:
string:字符串 (是一个形如 "NAME=VALUE" 的字符串)

特点: 直接将你传入的指针地址放到当前进程的环境变量表中;(也就意味着后续你如果修改这段字符串的内容,环境变量中的这个值也会随之改变)


在使用程序替换函数中envp参数时,都是我们自定义的;但是这里会有一个小问题:

  • 在我们还没对一个进程中的代码数据进行替换时,它的环境变量等参数都是从父进程继承下来的,也就是系统原本的环境变量,当我们传入自定义的envp参数之后,系统的环境变量表中的值也会被全部替换,在实际情况中,系统的环境变量中的参数我们也需要用到,自定义的环境变量参数也要用到;所以这里就需要用putenv函数来将我们自定义的环境变量参数插入到系统的环境变量表中,这样就不会覆盖掉原本的环境变量了;
  • char** environ参数: 全局变量,指向当前进程的环境变量表;
  • 自定义的环境变量传给envp时,我们就可以通过putenv将自定义环境变量中的值插入到environ的后面,就不会改变当前进程原有的环境变量了;

代码示例:

//test.c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"-a","-b","-c",NULL};char* envp[]={"TEST_PATH=/1/2/3",//避免与进程环境变量中的PATH重名,导致PATH的value值"NAME=YANG",NULL};extern char** environ; for(int i=0;envp[i];i++)putenv(envp[i]);	//将自定义环境变量中的值插入到当前进程的环境变量中execvpe("./proc",argv,environ);	//最后envp参数直接传入当前进程的环境变量environ}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}
//proc.c
#include <stdio.h>
#include <unistd.h>int main(int argc,char* argv[],char* env[])
{for(int i=0;env[i];i++)printf("env:%s\n",env[i]);printf("argv:");for(int i=0;argv[i];i++)printf("%s ",argv[i]);printf("\n");return 0;
}

运行结果:
在这里插入图片描述


setenv / unsetenv

setenvputenv的作用基本相同;
在底层setenv会在堆上重新分配environ的空间并拷贝字符串,将参数设置(插入)到拷贝后的空间中;之后我们如果修改原来的字符串并不会影响到环境变量中的参数,相对putenv更加的安全,只是不断的拷贝性能会有所下降;(在实际编码时,更推荐使用setenv

setenv

原型:int setenv(const char *name, const char *value, int overwrite);
参数说明:

  • name:环境变量名,不能包含 =
  • value:环境变量值
  • overwrite
    • 若为 0,且环境变量已存在,则不修改;
    • 若为 非 0,则强制更新;

返回值

  • 成功返回 0
  • 失败返回 -1

unsetenv

作用: 删除指定名称的环境变量;
原型:int unsetenv(const char* name)
返回值:

  • 成功返回 0
  • 失败返回 -1

注意: 如果name在环境变量中不存在,unsetenv什么也不做;如果name为空字符串或者字符串中包含=,就会报错;


本篇文章就到此结束了,如果本文对你有帮助,麻烦你 👍点赞 ⭐收藏 ❤️关注 吧~

http://www.dtcms.com/a/520749.html

相关文章:

  • 西安市城乡房地产建设管理局网站wordpress国外主题修改
  • 巨鹿网站建设网络公司云南住房和建设厅网站
  • 前端八股文 | HTTP - 实时通信方式/前后端通信方式
  • 谈一谈ViewDragHelper的工作原理?
  • Flutter框架机制详解
  • 火山引擎推出Data Agent评测体系,并发布《2025数据智能体实践指南》
  • SpringBoot-Web开发之异常处理
  • wap网站和app的区别php网站后台建设
  • 舞阳网站建设如何引流被动加好友
  • js wordpress 菜单管理如何给网站做seo优化
  • Nginx server_name 配置详解
  • 做宣传网站网页制作素材去哪找
  • 百度地图网站开发wordpress会员权限
  • 微硕WSF2040 N沟MOSFET:汽车电动尾门“防夹升降核”
  • 网站建设投标书报价表建设电子商务网站的好处
  • 网站建设与开发教学大纲全网商机app招标
  • Less-4 GET-Error based-Double Quotes-String
  • 互斥锁、读写锁、ref()函数使用
  • 2.6、Web漏洞挖掘实战(下):XSS、文件上传与逻辑漏洞深度解析
  • 邢台做网站的公司有那个营销型网站建设营销型
  • 青岛住房和城乡建设厅网站首页iis7.0 asp网站配置
  • vue3表格显示隐藏列全屏拖动功能
  • Git Commit Message 规范:写出清晰、可维护的提交记录
  • Orleans + Kubernetes + Istio 服务网格集成深度解析
  • 51网站怎么打开注册城乡规划师有什么用
  • 相向指针|盛最多水的容器|接雨水|验证回文串
  • Web3j 中使用 Transaction 类进行以太坊交互的核心方法
  • 承德微网站开发怎么弄一个自己的网站
  • web及h5录音wav下载
  • Kotlin 协程中常见的异步返回与控制方式(速览)