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

进程的程序替换——exec系列函数的使用

目录

前言

一、替换函数

二、程序替换的本质

一些细节:

三、程序替换与环境变量间的关系

1.介绍其他参数的意义并总结

2.自定义环境变量

1)通过execcle传参全局环境变量

2)通过execcle传参自定义环境变量

3)将自定义环境变量通过putenv导入全局环境变量后

3.execve系统调用

最后有个小问题,当我们通过子进程实现进程替换是时,是先调用的main函数,还是先调用的exec*函数呢?

总结



前言

我们创建子进程的目的无外乎两种:

①是想让子进程执行父进程代码的一部分,比如同时运行父进程的if -else if 条件语句:

想让子进程执行一个磁盘中的全新的程序。

进程程序替换谈的就是如何让子进程执行一个全新的程序。


一、替换函数

为了让子进程执行新的程序,Linux系统为我们提供了一些相关函数:exec系列函数,如下图所示。提供这么多函数的目的是满足在不同的使用场景下的使用需求,但他们的核心逻辑是一样的。

让我们先以execl函数为例,解释这些参数都有哪些含义。

首先要明白的是,运行一个新的程序,得满足两个步骤:先得在磁盘中找到指定程序加载到磁盘中,其次是如何执行,会带哪些选项。

const char * pathpath就记录着新程序在磁盘中的位置,即路径。

const char *arg:arg则记录着用户想要执行得选项有哪些,所以后面跟有“……”,因为选项有时很多,如ls -a -l指令。但值得注意的是参数列表需以NULL结尾。

execl运行失败则返回-1,成功则无返回值。

上述看着有些有些抽象,笔者结合fork、waitpid等编写一段代码以实现ls指令先供笔者参考,好在脑中有个概念。

在子程序实现中运行ls -a -l指令:

注意在命令行怎么写的指令,在execl中就怎么传。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>//exit
#include<sys/types.h>//waitpid
#include<sys/wait.h>//waitpid
int main()
{pid_t id=fork();if(id<0){perror("error fork:\n");exit(1);//判断子进程是否创建成功}if(id==0){//子进程execl("/usr/bin/ls","ls","-a","-l",NULL);exit(1);//如果运行到这里说明execl运行出错}int status=0;waitpid(id,&status,0);if(WIFEXITED(status)){printf("child exit code:%d\n",WEXITSTATUS(status));}else{printf("error waitpid\n");}return 0;
}

执行结果:

二、程序替换的本质

我们知道,进程在内存中有着task_struct(PCB)结构体,用于记录进程代码与数据存在于物理地址的何处。而程序替换的本质,就是将指定程序的代码和数据加载到子进程的物理地址,将原代码和数据覆盖。

换句话说,进程替换只替换内存中的代码和数据,而与task_struct页表等内核数据结构无关。所以进程替换并没有创建新进程,而是在原进程基础上的替换操作。

一些细节:

1)值得注意的是在execl函数之后的代码是不会执行的,如上述代码中execl之后的exit(1),因为原代码和数据已经被替换了,除非execl执行失败。这也是为什么进程替换一般通过子进程去做的原因。

2)现在也便能理解为什么exec系列函数执行成功后无返回值,因为没有意义——源代码已经被替换了。

3)进程替换并不会影响父进程,由于写实拷贝保证的进程独立性,当执行execl时会拷贝立即发生写实拷贝。

三、程序替换与环境变量间的关系

1.介绍其他参数的意义并总结

exec系列的函数,他们的核心逻辑是一样的,但各参数不同原因是为满足在不同的使用场景下的使用需求。

如execvp函数

file参数:如果该程序在环境变量PATH中有记录,那么只用传程序名即可。

对环境变量有疑的可参考:进程优先级介绍,详解环境变量,详解进程地址空间-CSDN博客

argv【】数组:将所有执行参数,放入数组中统一传递。

2.自定义环境变量

经过上述总结,其他的参数读者大概已经有数,那说半天程序替换和环境变量在哪有直接联系呢?

如上图execle中的参数,前俩个参数已经介绍过了,第三个参数envp[ ]的作用是:导入环境变量

这个环境变量可以是:

①系统的全局环境变量

②是父进程自定义的环境变量

③也可以通过putenv将自定义环境变量导入到全局环境变量中实现一起使用

下面分别介绍上述三种情况。

1)通过execcle传参全局环境变量

这里笔者先创建了一个myproc程序:

然后将上面execl的示例程序中的execl改成execle,用execle替换上述写的myproc程序。

然后执行结构是:

可以看到PATH全局环境变量打印出了,自定义环境变量没打印

2)通过execcle传参自定义环境变量

这里在函数内部创建了一个MYENV的数组,并传给了execle函数

执行结果:

可以看到PATH全局环境变量没打印,自定义环境变量打印了

3)将自定义环境变量通过putenv导入全局环境变量后

执行结果:

可以看到全局环境变量与自定义环境变量都打印出来了

总结:

到这里exec*函数系列的所有参数都讲解完毕,使用时具体用哪个,完全看使用环境选择合适参数

3.execve系统调用

上面的exec*系列的函数其实都是基于execve系统调用做的封装,由于系统自带的程序替换execve功能单一,于是C库通封装给出了六个在不同场景下使用的exec*系列函数,使用户有更多的选择性。

最后有个小问题,当我们通过子进程实现进程替换是时,是先调用的main函数,还是先调用的exec*函数呢?

答案是先调用的exec*函数!

让我们回忆一下程序执行的过程,首先是将磁盘中的可执行文件的代码调入内存,创建PCB等内核数据结构对吧?那问题是系统是怎么调的呢,其实系统正是通过execve系统调用加载的,execve本质就是个加载器,只有先将代码调入内存,才谈得上执行,故exec*系类函数先于main函数执行。

所有main函数的3个参数,其实是exec*系列的函数传给它的!

关于main函数的三个参数,笔者之前的文章有提过,感兴趣的读者可以点击下方链接查看:

进程优先级介绍,详解环境变量,详解进程地址空间-CSDN博客


总结

本文主要通过介绍exec系列函数的使用方法解释了什么是进程的程序替换,最后还解释main函数的三个参数都是由exec系列函数传入的概念。

看完点赞,手留余香~

相关文章:

  • 深入浅出数据库规范化的三大范式
  • Redis能保证数据不丢失吗之AOF
  • Red Hat6.4环境下搭建NFS服务器
  • 30天通过软考高项-第十一天
  • 算法中的数学:质数(素数)
  • chili3d笔记11 连接yolo python http.server 跨域请求 flask
  • CRS 16 slot 设备硬件架构
  • [学成在线]23-面试题总结
  • window 系统 使用ollama + docker + deepseek R1+ Dify 搭建本地个人助手
  • GZIPOutputStream 类详解
  • GPIO引脚的上拉下拉以及转换速度到底怎么选
  • macbook install chromedriver
  • linux 中inotify与inode的关系是什么?
  • Linux系统编程--基础指令(!!详细讲解+知识拓展)
  • [人机交互]交互设计
  • 物联网mqtt和互联网http协议区别
  • 经典的 Masked + Self-supervised learning 的模型方法
  • “Copy-On-Write” (COW)
  • 基于思考过程评价的心理问题咨询对话记性评估
  • 端口(Port)
  • “五一”假期第四天,全社会跨区域人员流动量预计超2.7亿人次
  • 德国巴斯夫:关税政策加剧全球市场不确定性,间接影响已显现
  • 三亚回应“买水果9斤变6斤”:反映属实,拟对流动摊贩罚款5万元
  • 阿根廷发生5.6级地震,震源深度30公里
  • 《大风杀》上海首映,白客说拍这戏是从影以来的最大挑战
  • 5月起,这些新规将施行