Linux下进程替换exec系列接口

文章目录

  • Linux下进程替换
    • 1. c库exec函数族
      • 一、exec函数族简介
      • 二、exec函数族函数原型及参数说明
      • 三、exec函数族的工作机制
      • 四、注意事项
      • 五、示例代码
    • 2. 系统调用execve接口
      • 一、execve接口与C库exec函数族的关系
      • 二、函数原型
      • 三、参数说明
      • 四、工作原理
      • 五、返回值
      • 六、注意事项
      • 七、 图示
      • 八、示例代码
    • 3. 基于进程相关接口实现的自定义简单的shell

Linux下进程替换

在Linux系统中,exec程序替换接口是一组函数,允许当前进程被一个新的程序映像替换。这些函数不会创建新的进程,而是会替换当前进程的地址空间、代码、数据、堆栈等,使新的程序成为当前进程的继续。以下是Linux下exec程序替换接口的详解:
在这里插入图片描述

1. c库exec函数族

一、exec函数族简介

exec函数族包括多个函数,如execl、execlp、execle、execv、execve、execvp等。这些函数在功能上是相似的,但在参数传递方式和环境变量处理上有所不同。


exec



二、exec函数族函数原型及参数说明

  1. execl
int execl(const char *path, const char *arg, ...);
  • path:要执行的程序文件的路径。
  • arg:传递给新程序的参数列表,必须以NULL结尾。
  1. execlp
int execlp(const char *file, const char *arg, ...);
  • file:要执行的程序文件的名称(不带路径),函数会在PATH环境变量中查找该文件。
  • arg:与execl相同。
  1. execle
int execle(const char *path, const char *arg, ..., char *const envp[]);
  • path:与execl相同。
  • arg:与execl相同。
  • envp:传递给新程序的环境变量数组,以NULL结尾。
  1. execv
int execv(const char *path, char *const argv[]);
  • path:与execl相同。
  • argv:传递给新程序的参数数组,数组的第一个元素通常是程序名,数组以NULL结尾。
  1. execve
int execve(const char *filename, char *const argv[], char *const envp[]);
  • filename:要执行的程序文件的路径。
  • argv:与execv相同。
  • envp:与execle相同。
  1. execvp
int execvp(const char *file, char *const argv[]);
  • file:与execlp相同。
  • argv:与execv相同。

三、exec函数族的工作机制

  1. 查找程序:根据提供的路径或文件名(对于execlp和execvp),找到要执行的程序文件。
  2. 加载程序:将程序文件加载到当前进程的地址空间中,替换掉原有的代码、数据等。
  3. 初始化新程序:为新程序设置参数、环境变量等。
  4. 开始执行:从新程序的入口点开始执行,通常是main函数。

四、注意事项

  1. exec函数不返回:如果exec函数调用成功,它不会返回给调用者。如果调用失败,它会返回-1,并设置errno以指示错误类型。
  2. 进程ID不变:尽管进程的内容被替换了,但进程ID(PID)保持不变。
  3. 环境变量:对于execl和execv等不直接处理环境变量的函数,新的程序将继承调用exec函数之前的进程的环境变量。对于execle和execve等可以指定环境变量的函数,新的程序将使用提供的环境变量。

五、示例代码

以下是一个使用execl函数的示例代码:

#include <stdio.h>
#include <unistd.h>int main() {printf("Before exec: PID = %d\n", getpid());execl("/bin/ls", "ls", "-l", NULL);// 如果execl调用成功,下面的代码将不会被执行。printf("After exec: This line will not be printed.\n");return 0;
}

在这个例子中,当程序执行到execl函数时,它会替换当前进程的映像为/bin/ls程序,并传递-l参数。因此,输出将是当前目录下的文件和目录列表,而不是"After exec"这条消息。

在这里插入图片描述

2. 系统调用execve接口

execve接口与C库中的exec函数族有着密切的关系。以下是对它们之间关系的详细解释:

一、execve接口与C库exec函数族的关系

  1. 基础execve是Linux内核提供的一个底层系统调用,它允许一个进程加载并执行一个新的程序。而C库中的exec函数族则是对这个系统调用的封装和扩展,提供了更易于使用的接口。
  2. 功能:C库中的exec函数族(如execl、execv、execle、execlp、execvp等)都基于execve系统调用实现。它们之间的主要区别在于参数传递的方式和是否使用环境变量等方面。例如,execl和execv直接接受参数列表和环境变量(或继承当前环境变量),但参数传递方式不同execlp和execvp则接受程序名而不是路径,并使用PATH环境变量来查找程序;execle则类似于execve,但允许指定文件描述符的关闭和重定向操作。
  3. 调用过程当C库中的exec函数被调用时,它们会构建适当的参数和环境变量数组,然后调用execve系统调用来执行新的程序。如果execve调用成功,新的程序将替换当前进程的映像,并且不会返回到调用exec函数的代码。如果execve调用失败,则exec函数会返回一个错误码,并设置errno以指示错误类型。

二、函数原型

int execve(const char *filename, char *const argv[], char *const envp[]);

三、参数说明

  1. filename:指定要执行的程序的路径。这个路径可以是绝对路径,也可以是相对于当前工作目录的相对路径。
  2. argv:传递给新程序的命令行参数数组。数组的第一个元素通常是程序名(即filename中的basename部分,但也可以不遵循这个约定),后续元素是传递给程序的参数,数组以NULL结尾。
  3. envp:传递给新程序的环境变量数组。每个元素都是一个形如"name=valu"的字符串,数组以NULL结尾。如果不需要传递特定的环境变量,可以传递NULL,此时新程序将继承调用execve之前的进程的环境变量。

四、工作原理

  1. 查找可执行文件:内核根据提供的filename参数查找可执行文件。这个过程涉及到路径搜索、文件访问权限检查等。
  2. 加载可执行文件:找到可执行文件后,内核会将其内容映射到进程的虚拟地址空间。这个过程包括读取可执行文件头部信息、分配内存、设置内存保护等。
  3. 设置参数和环境变量内核将argv和envp参数传递给新进程,并在新进程的内存中复制这些信息。
  4. 设置程序入口点:内核设置新程序的入口点,并跳转到新程序的执行地址。此时,新进程开始执行,原来进程的程序代码和数据都被替换掉。

五、返回值

  • 如果execve调用成功,它不会返回给调用者。新程序将作为当前进程的继续开始执行。
  • 如果execve调用失败,它会返回-1,并设置errno以指示错误类型。常见的错误包括文件不存在、没有执行权限、内存不足等。

六、注意事项

  1. execve函数不返回:由于execve调用成功后会替换当前进程的映像,因此它不会返回给调用者。如果需要在execve之后执行代码,应该将其放在另一个进程中,或者使用其他机制(如信号处理)来确保代码的执行。
  2. 进程ID不变:尽管进程的内容被替换了,但进程ID(PID)保持不变。这意味着新的程序将继承原来进程的PID和其他相关属性。
  3. 安全性:execve系统调用涉及到程序加载和执行,因此其安全性至关重要。内核需要确保只有授权的程序才能被执行,以防止恶意代码的攻击。

七、 图示

execve

八、示例代码

以下是一个使用execve函数的示例代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {char *args[] = {"/bin/ls", "-l", NULL};char *envp[] = {NULL}; // 可以传递NULL以继承当前环境变量,也可以传递自定义的环境变量数组printf("Before execve: PID = %d\n", getpid());if (execve("/bin/ls", args, envp) == -1) {perror("execve failed");exit(EXIT_FAILURE);}// 如果execve调用成功,下面的代码将不会被执行printf("After execve: This line will not be printed.\n");return 0;
}

在这个例子中,当程序执行到execve函数时,它会替换当前进程的映像为/bin/ls程序,并传递-l参数。因此,输出将是当前目录下的文件和目录列表的详细信息,而不是"After execve"这条消息。如果execve调用失败,则会打印错误信息并退出程序。
在这里插入图片描述


3. 基于进程相关接口实现的自定义简单的shell

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>#define LEFT        "["     // 左边界符号
#define RIGHT       "]"     // 右边界符号
#define DELIM       " \t"   // 分隔符:空格和制表符
#define LINE_SIZE   1024    // 命令行输入的最大长度
#define ARGC_SIZE   32      // 命令行参数的最大数量char commandline[LINE_SIZE]; // 存储命令行输入
char pwd[LINE_SIZE];         // 存储当前工作目录
char* argv[ARGC_SIZE];       // 存储命令行参数
int lastcode = 0;            // 上一个命令的退出码
int quit = 0;                // 是否退出Shell的标志
char myenv[LINE_SIZE];       // 存储export命令设置的环境变量enum EXIT {GETHOSTNAME_FAILED = 1,FORK_FAILED = 2,EXIT_CODE = 3,
};// 获取当前工作目录
void GetPwd() {getcwd(pwd, sizeof(pwd));
}// 获取当前用户名
const char* GetUserName() {return getenv("USER");
}// 获取主机名
const char* GetHostName() {static int initialized = 0;if (!initialized) {// 设置HOSTNAME为"remote"if (putenv("HOSTNAME=remote") != 0) {perror("HOSTNAME获取失败");exit(GETHOSTNAME_FAILED);}initialized = 1;}return getenv("HOSTNAME");
}// 交互函数,获取用户输入的命令行
void interact(char* cline, int size) {GetPwd();// 打印Shell提示符:[用户名@主机名当前工作目录]printf(LEFT "%s@%s%s" RIGHT "\n", GetUserName(), GetHostName(), pwd);// 从标准输入读取用户输入的命令行char* s = fgets(cline, size, stdin);if (s == NULL) {perror("fgets失败");exit(EXIT_CODE);}// 去掉末尾的换行符size_t len = strlen(cline);if (cline[len - 1] == '\n') {cline[len - 1] = '\0';}
}// 字符串分割函数,将命令行字符串分割为参数数组
int SplitString(char* cline, char* _argv[]) {int i = 0;_argv[i++] = strtok(cline, DELIM); // 使用strtok第一次分割// 使用strtok继续分割,直到达到最大参数数量或分割结束while (i < ARGC_SIZE && (_argv[i++] = strtok(NULL, DELIM)));return i - 1;
}// 内建命令处理函数
int BuildCommand(int _argc, char* _argv[]) {if (!strcmp(_argv[0], "quit")) {quit = 1; // 设置退出标志} else if (_argc == 2 && strcmp(_argv[0], "cd") == 0) {chdir(_argv[1]); // 改变工作目录GetPwd();setenv("PWD", pwd, 1); // 更新PWD环境变量return 1;} else if (_argc == 2 && strcmp(_argv[0], "export") == 0) {//重要的是,putenv只是修改环境变量的指针,并不复制字符串内容。//这意味着如果原始字符串(这里是_argv[1]指向的内容)被修改或释放//环境变量的值也会受到影响。strcpy(myenv, _argv[1]); // 复制设置的环境变量putenv(myenv); // 设置环境变量return 1;} else if (_argc == 2 && strcmp(_argv[0], "echo") == 0) {if (strcmp(_argv[1], "$?") == 0) {printf("%d\n", lastcode); // 输出上一个命令的退出码lastcode = 0;} else if (*_argv[1] == '$') {char* val = getenv(_argv[1] + 1);if (val) printf("%s\n", val); // 输出环境变量的值} else {printf("%s\n", _argv[1]); // 输出指定的字符串}return 1;}// 特殊处理下的ls命令if (strcmp(_argv[0], "ls") == 0) {_argv[_argc++] = "--color"; // 增加参数--color_argv[_argc] = NULL;}return 0; // 返回0表示不是内建命令
}// 外部命令执行函数
void NormalExecute(char* _argv[]) {pid_t pid = fork();if (pid < 0) {perror("fork失败");exit(FORK_FAILED);} else if (pid == 0) {execvp(_argv[0], _argv); // 在子进程中执行命令exit(EXIT_CODE);} else {int status = 0;pid_t rtpid = waitpid(pid, &status, 0); // 等待子进程退出if (rtpid == pid) {lastcode = WEXITSTATUS(status); // 获取子进程的退出状态}}
}int main() {while (!quit) {interact(commandline, sizeof(commandline)); // 获取用户输入的命令行if (strlen(commandline) == 0) continue; // 如果是空行则继续下一轮循环int argc = SplitString(commandline, argv); // 分割命令行字符串为参数数组if (argc == 0) continue; // 如果没有参数则继续下一轮循环int is_builtin = BuildCommand(argc, argv); // 执行内建命令if (!is_builtin) NormalExecute(argv); // 执行外部命令}return 0;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【一本通】质因数分解

【一本通】质因数分解 C语言实现C 语言实现Java语言实现Python语言实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 已知正整数n 是两个不同的质数的乘积&#xff0c;试求出较大的那个质数。 输入 输入只有一行&#xff0c;包含一个正…

xtu oj 1618 素数个数

文章目录 前言代码思路 前言 有点儿难&#xff0c;至少对我来说。去年考试我没写出来。 代码 #include<stdio.h> #include<stdbool.h> #include<stdlib.h>//加 math 那个头文件好像要加这个头文件&#xff0c;我之前编译错误过&#xff0c;血泪教训 #incl…

2024149读书笔记|Hans的阿狸五部曲——成长的路上分离在所难免

2024149读书笔记|Hans的阿狸五部曲——成长的路上分离在所难免 1. 《阿狸和小小云》2. 《阿狸和小玉》3. 《阿狸呓语》4. [202480读书笔记|《阿狸和弯月亮》——生的再普通&#xff0c;也是限量版](https://blog.csdn.net/qq_40985985/article/details/139731131)5. 《阿狸永远…

SQL项目实战与综合应用——项目设计与需求分析

项目设计与需求分析是软件开发过程中的核心环节&#xff0c;尤其在涉及数据库的应用时&#xff0c;良好的设计将直接影响到项目的可扩展性、性能和维护性。本文将深入探讨数据库设计的最佳实践&#xff0c;结合 C 与 SQL 的实际应用场景&#xff0c;涵盖项目需求收集、数据库设…

D3实现站点路线图demo分享

分享一下通过D3实现的站点路线分布图&#xff0c;这是一个demo。效果图如下&#xff1a; 源码如下&#xff1a; <template><div class"map-test" ref"d3Chart"><div class"tooltip" id"popup-element"><span>…

算法日记 42 day 图论

今天来看看广度优先搜索&#xff0c;并且写几个题。刷到这里我才想起来&#xff0c;当时第一次面试的时候问的就是这个题&#xff0c;当时大概知道一点思路&#xff0c;但不清楚是图论方面的&#xff0c;更别说写出来了。 广度优先搜索&#xff08;BFS&#xff09; 不同于深度…

【NLP 13、实践 ② 判断文本中是否有特定字符出现】

人活着就是为了救赎自己&#xff1b;为了经历世间的美好&#xff1b;为了在挫折中成长变得更坚强 —— 24.12.10 一、定义模型 1.嵌入层 nn.Embedding&#xff1a;将离散值转化为向量 # embedding层&#xff0c;vocab&#xff1a;词表&#xff0c;要多少个数据&#xff08;向…

GD32中断

1.什么是中断&#xff1a;打断现在正在做的事&#xff0c;去执行其他事。 2.ARM异常中断结构 3.中断向量编号。中断向量是 进行了映射的&#xff0c;直接映射到 flash中的地址。 4.中断执行结构。向量里面保存的是执行函数的地址。&#xff08;具体可在编译完后的map文件中查看…

三菱FX3U模拟量产品的介绍

FX3u可编程控制器模拟量产品包括&#xff1a;特殊适配器、特殊功能模块的连接 1、连接在FX3U可编程控制器的左侧。 2、连接特殊适配器时&#xff0c;需要功能扩展板。 3、最多可以连接4台模拟量特殊适配器。 4、使用高速输入输出特殊适配器时&#xff0c;请将模拟量特殊适配器连…

【PlantUML系列】流程图(四)

目录 目录 一、基础用法 1.1 开始和结束 1.2 操作步骤 1.3 条件判断 1.4 并行处理 1.5 循环 1.6 分区 1.7 泳道 一、基础用法 1.1 开始和结束 开始一般使用start关键字&#xff1b;结束一般使用stop/end关键字。基础用法包括&#xff1a; start ... stopstart ...…

idea压缩js,css

这是需要的jar包(文章顶部也可以下载) 地址:https://download.csdn.net/download/yuzheh521/90109966?spm1001.2101.3001.9500 压缩js arguments: -jar E:\swj\jar_packages\css_js_compress\yuicompressor-2.4.8.jar --type js --charset utf-8 $FilePath$ -o $FileNameWith…

ASP.NET |日常开发中连接Oracle数据库详解

ASP.NET &#xff5c;日常开发中连接Oracle数据库详解 前言一、安装和配置 Oracle 数据访问组件1.1 安装ODP.NET&#xff08;Oracle Data Provider for.NET&#xff09;&#xff1a;1.2 引用相关程序集&#xff1a; 二、配置连接字符串2.1 连接字符串的基本组成部分&#xff1a…

【linux系统】基础开发工具(yum、Vim)

1. 软件包管理器 1.1 什么是软件包 在Linux下安装软件, ⼀个通常的办法是下载到程序的源代码, 并进⾏编译, 得到可执⾏程序. 但是这样太麻烦了, 于是有些⼈把⼀些常⽤的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在⼀个服务器上, 通过包管理器可以很⽅便的…

C语言:define定义常量和定义宏(详解)

本篇博客给大家带来的是#define定义常量和#define定义宏的方法 &#x1f41f;&#x1f41f;文章专栏&#xff1a;C语言 &#x1f680;&#x1f680;若有问题评论区下讨论&#xff0c;我会及时回答 ❤❤欢迎大家点赞、收藏、分享 你们的支持就是我创造的动力 今日思想&#xff1…

Let up bring up a linux.part2 [十一]

之前的篇幅中我们已经将 Linux 内核 bringup 起来了&#xff0c;不知道大家有没有去尝试将根文件系统运行起来&#xff0c;今天我就带领大家完成这个事情&#xff0c;可以跟着下面的步骤一步步来完成&#xff1a; 在这里我们使用 busybox 构建 rootfs&#xff1a; 下载 busyb…

使用GO--Swagger生成文档

概述 在前后端分离的项目中&#xff0c;后端配置swagger可以很好的帮助前端人员了解后端接口参数和数据传输。go-swagger 是一个功能全面且高性能的Go语言实现工具包&#xff0c;用于处理Swagger 2.0&#xff08;即OpenAPI 2.0&#xff09;规范。它提供了丰富的工具集&#x…

pushgateway HA高可用方案

未经本人同意不得转载&#xff0c;若引用请附上原文链接。 项目使用flink来处理kafka中的无界流数据&#xff0c;采用的是flink on yarn的模式部署flink任务。最近做flink任务的监控过程中&#xff0c;踩了一些坑。下面是过程&#xff0c;只想看最终方案的直接拉到最后。 先说…

01-Chromedriver下载与配置(mac)

下载地址&#xff1a; 这里我用的最后一个&#xff0c;根据自己chrome浏览器选择相应的版本号即可 ChromeDriver官网下载地址&#xff1a;https://sites.google.com/chromium.org/driver/downloads ChromeDriver官网最新版下载地址&#xff1a;https://googlechromelabs.git…

使用docker-compose安装Milvus向量数据库及Attu可视化连接工具

首先确保系统已经安装上了docker 然后去https://github.com/docker/compose/releases/下载安装docker-compose 跟随自己下系统和服务器情况下载 上传到服务器 mv docker-compose-linux-aarch64 docker-compose chmod x docker-compose2.dockr-compose命令 docker-compose …

Conda + JuiceFS :增强 AI 开发环境共享能力

Conda 是当前 AI 应用开发领域中非常流行的环境和包管理系统&#xff0c;因其能够简单便捷地创建与系统资源相隔离的虚拟环境广受欢迎。 Conda 支持在不同的操作系统上重建相同的工作环境&#xff0c;但在环境共享复用方面仍存在一些挑战。比如&#xff0c;在不同机器上复用相…