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.rhkb.cn/news/488815.html

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

相关文章

网页爬虫技术全解析:从基础到实战

引言 在当今信息爆炸的时代&#xff0c;互联网上的数据量每天都在以惊人的速度增长。网页爬虫&#xff08;Web Scraping&#xff09;&#xff0c;作为数据采集的重要手段之一&#xff0c;已经成为数据科学家、研究人员和开发者不可或缺的工具。本文将全面解析网页爬虫技术&…

设计模式:24、访问者模式

目录 0、定义 1、访问者模式的五种角色 2、访问者模式的UML类图 3、示例代码 0、定义 表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下&#xff0c;定义作用于这些元素的新操作。 1、访问者模式的五种角色 抽象元素&#xff08;Element…

快速掌握Quartz.Net计划任务调度框架,轻松实现定时任务

前言 Quartz.Net是一个开源的作业调度框架&#xff0c;可以用于管理计划任务和定期执行。Quartz.Net提供了丰富的作业计划选项&#xff0c;例如精确或模糊时间表达式、日期和时间限制等。Quartz.Net采用分布式架构&#xff0c;允许在多个计算机上运行任务。 Quartz.Net架构设…

【C++】内存分布、new、delete、 operator new、operator delete

内存分布 在C语言和C中&#xff0c;程序内存被划分成六个部分&#xff1a; 内核空间、栈、内存映射段、堆、数据段、代码段 栈&#xff1a;又称堆栈&#xff0c;主要为非静态局部变量、函数参数、返回值等&#xff0c;栈的生长方向是向下生长的 内存映射段&#xff1a;高效的…

Quill富文本实现内容自定义格式format

在使用quill富文本编辑器时&#xff0c;我们输入文本会被作为类似DOM节点的数据对象存储在内部&#xff0c;渲染时生成相应的DOM节点。这是quill的文档模型Parchment,它提供了多种内容节点类型&#xff0c;如Inline \ Block \ Embed等。 quill 扩展了 Parchment 提供的的基础类…

Kael‘thas Sunstrider Ashes of Al‘ar

Kaelthas Sunstrider 凯尔萨斯逐日者 <血精灵之王> Kaelthas Sunstrider - NPC - 魔兽世界怀旧服TBC数据库_WOW2.43数据库_70级《燃烧的远征》数据库 Ashes of Alar 奥的灰烬 &#xff08;凤凰 310%速度&#xff09; Ashes of Alar - Item - 魔兽世界怀旧服TBC数据…

Rust之抽空学习系列(三)—— 编程通用概念(中)

Rust之抽空学习系列&#xff08;三&#xff09;—— 编程通用概念&#xff08;中&#xff09; 1、变量&可变性 在Rust中&#xff0c;变量默认是不可变的 fn main() {let x 5;println!("x is {}", x); }使用let来声明一个变量&#xff0c;此时变量默认是不可变…

C++ 运算符重载 (备查)

基础 运算符重载&#xff0c;就是对已有的运算符重新进行定义&#xff0c;赋予其另一种功能&#xff0c;以适应不同的数据类型。 运算符重载也可以发生函数重载。 语法&#xff1a; void operator(); //代表了被重载的运算符。函数的参数个数取决于两个因素。1)运算符是一元(一…

计算机网络之网络层超详细讲解

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络之网络层超详细讲解 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; …

嵌入式驱动开发详解6(RTC)

文章目录 前言RTC简介RTC驱动分析RTC驱动框架RTC驱动实现 RTC应用后续 前言 实时时钟是很常用的一个外设&#xff0c;通过实时时钟我们就可以知道年、月、日和时间等信息。 因此在需要记录时间的场合就需要实时时钟&#xff0c;可以使用专用的实时时钟芯片来完成此功能&#x…

什么是MAC地址?什么是IP地址?IP地址与MAC地址是什么关系?

MAC地址是指Media Access Control Address&#xff0c;媒体访问控制地址。MAC地址被烧录在网络设备的ROM之内&#xff0c; IP地址类似于门牌号码&#xff0c;有了门牌号码&#xff0c;邮差才知道把邮件投送到哪里。 有人新建房屋了&#xff0c;就会分配新的门牌号码&#xff08…

go语言的成神之路-标准库篇-os标准库

一、权限 在操作系统&#xff08;OS&#xff09;中&#xff0c;标准库的权限管理是非常重要的&#xff0c;它确保了不同用户和进程能够安全地访问系统资源。以下是一些常见的权限概念和说明&#xff1a; 1.用户权限 用户ID&#xff08;UID&#xff09;&#xff1a;每个用户在…

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

ASP.NET&#xff5c;日常开发中连接Sqlite数据库详解 前言一、安装和引用相关库1.1 安装 SQLite 驱动1.2 引用命名空间 二、配置连接字符串2.1 连接字符串的基本格式 三、建立数据库连接3.1 创建连接对象并打开连接 四、执行数据库操作4.1 创建表&#xff08;以简单的用户表为例…

机器学习:监督学习、无监督学习

1. 引言 机器学习是一种人工智能领域的技术&#xff0c;它旨在让计算机通过学习数据和模式&#xff0c;而不是明确地进行编程来完成任务。 机器学习分为监督学习、无监督学习、半监督学习、强化学习 四种。 ​ 2. 监督学习 2.1 什么是监督学习 定义&#xff1a;根据已有的数…

IEEE T-RO 软体机器人手指状态估计实现两栖触觉传感

摘要&#xff1a;南方科技大学戴建生院士、林间院士、万芳老师、宋超阳老师团队近期在IEEE T-RO上发表了关于软体机器人手指在两栖环境中本体感知方法的论文。 近日&#xff0c;南方科技大学戴建生院士、林间院士、万芳老师、宋超阳老师团队在机器人顶刊IEEE T-RO上以《Propri…

MySQL-DML之数据表操作

文章目录 一. 插入表记录1. 向表中插入部分字段2. 向表中插入所有字段,字段的顺序为创建表时的顺序3. 一次添加多条数据信息 二. 更新表记录1. 更新所有记录的指定字段2. 更新符号条件记录的指定字段 三. 删除表记录1. 按条件删除记录2. 清空记录 四. SQL约束1. 主键约束① 添加…

Exp 智能协同管理系统前端首页框架开发

一、 需求分析 本案例的主要目标是开发一个智能学习辅助系统的前端界面&#xff0c;涵盖以下功能模块&#xff1a; 首页&#xff1a;显示系统的总体概览和关键功能介绍。 班级学员管理&#xff1a;实现班级管理和学员管理。 系统信息管理&#xff1a;管理部门和员工信息。 …

5G中的ATG Band

Air to Ground Networks for NR是R18 NR引入的。ATG很多部分和NTN类似中的内容类似。比较明显不同的是&#xff0c;NTN的RF内容有TS 38.101-5单独去讲&#xff0c;而ATG则会和地面网络共用某些band&#xff0c;这部分在38.101-1中有描述。 所以会存在ATG与地面网络之间的相邻信…

MongoDB与阿里云庆祝合作五周年,展望AI赋能新未来

12月3日&#xff0c;在印尼举行的阿里云合作伙伴大会2024上&#xff0c;MongoDB荣膺阿里云“2024技术创新成就奖”&#xff0c;该奖项旨在表彰与阿里云保持长期稳定合作&#xff0c;通过深度技术融合&#xff0c;在产品技术创新、行业区域深耕等领域取得卓越成就的伙伴。自2019…

未来已来:人工智能如何重塑我们的生活与工作

引言 未来的生活和工作场景正从想象走向现实。想象一下&#xff0c;一个清晨&#xff0c;语音助手已经为你安排好一天的任务&#xff0c;自动驾驶汽车准时送你上班&#xff0c;智能冰箱提醒你需要补充的食材。曾经只存在于科幻小说中的场景&#xff0c;如今正在我们的身边实现。…