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. 《阿狸永远…

Atcoder ABC383

C BFS。放入所有的H点&#xff0c;bfs在D步内能访问到的点。 #include <bits/stdc.h> using namespace std;typedef long long ll; typedef pair<int, int> pii; typedef pair<ll, ll> pll; typedef vector<int> vi;int h, w, d; string s[1100]; in…

Xcode模拟器运行报错:The request was denied by service delegate

Xcode模拟器运行报错&#xff1a;The request was denied by service delegate 造成的原因: &#xff08;1&#xff09;新的苹果M系列芯片的Mac电脑 &#xff08;2&#xff09;此电脑首次安装启动Xcode的应用程序 &#xff08;3&#xff09;此电脑未安装Rosetta 2 解决方法: …

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

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

Python+OpenCV系列:图像的位运算

文章目录 引言 1. 位运算简介2. OpenCV 中的位运算2.1 按位与运算&#xff1a;cv2.bitwise_and()2.2 按位或运算&#xff1a;cv2.bitwise_or()2.3 按位异或运算&#xff1a;cv2.bitwise_xor()2.4 按位取反运算&#xff1a;cv2.bitwise_not() 3. 位运算在图像处理中的应用3.1 图…

高效的 Python Web 开发与数据库操作:基于 FastAPI 的实战与优化

高效的 Python Web 开发与数据库操作&#xff1a;基于 FastAPI 的实战与优化 目录 &#x1f40d; 1. 数据库连接池的使用与管理&#x1f504; 2. 数据库事务的处理与控制&#x1f4a1; 3. FastAPI 中的 ORM 集成与优化 &#x1f40d; 1. 数据库连接池的使用与管理 数据库连接…

D3实现站点路线图demo分享

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

CTF-WEB: 配置一个Ubuntu 多版本php服务器

今天复现题windows的php /tmp缓存一直是空的,直接配一个新虚拟机 开始 准备一个全新的虚拟机,可以在这里下载镜像 Verifying - USTC Mirrors 走完常规安装流程继续 设置中文(可选) sudo apt install language-pack-zh-hans language-pack-gnome-zh-hans然后在设置->语…

python爬虫--小白篇【爬虫实践】

一、前言 1.1、王者荣耀皮肤爬虫 根据王者荣耀链接&#xff0c;将王者荣耀的全部英雄的全部皮肤图片爬取保存到本地。经过分析得到任务的三个步骤&#xff1a; 根据首页全部英雄列表连接获取全部英雄的名称hero_name以及对应的hero_id&#xff1b;根据单个英雄的hero_name和h…

算法日记 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;向…

软件注册机 | QT给自己的桌面软件实现软件注册码功能

之前做的一个项目&#xff0c;想要给软件做一个注册码功能。当软件发布之后&#xff0c;不想给所有人用&#xff0c;这时就可以通过注册机给软件生成授权码来软件加密。整个过程实现分为两大步骤&#xff0c;一是在自己的软件打开时&#xff0c;增加一段判断逻辑&#xff1b;二…

GD32中断

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

三菱FX3U模拟量产品的介绍

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

WHAT - webpack、vite(rollup)、rsbuild 对比

目录 一、分析二、其他阅读 一、分析 以下是 Webpack、Vite 和 rsbuild 在多个维度上的比较分析表格&#xff1a; 维度WebpackVitersbuild核心语言/技术使用 JavaScript 和 Node.js基于 JavaScript/TypeScript&#xff0c;依赖原生 ESM 和浏览器支持使用 Rust 编写&#xff0…

软件测试丨Appium 源码分析与定制

在本文中&#xff0c;我们将深入Appium的源码&#xff0c;探索它的底层架构、定制化使用方法和给软件测试带来的优势。我们将详细介绍这些技术如何解决实际问题&#xff0c;并与大家分享一些实用的案例&#xff0c;以帮助读者更好地理解和应用这一技术。 Appium简介 什么是App…

【PlantUML系列】流程图(四)

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

Linux 串口编程

目录 前言一、tty体系二、串口硬件基础知识三、Linux下的串口编程3.1 打开串口3.2 从串口读写数据,问题1、2的诞生3.3 关闭串口3.4 串口配置3.4.1 获取/设置串口的参数3.4.2 设置波特率3.4.3 设置控制模式标志3.4.4 设置本地模式标志3.4.5 设置输入模式标志3.4.6 设置输出模式标…