从理论到实践:Linux 进程替换与 exec 系列函数

个人主页:chian-ocean

文章专栏-Linux

前言:

在Linux中,进程替换(Process Substitution)是一个非常强大的特性,它允许将一个进程的输出直接当作一个文件来处理。这种技术通常用于Shell脚本和命令行操作中。

在这里插入图片描述

进程替换原理

进程替换(Process Replacement)是操作系统用来用一个新程序完全替换当前进程用户态内容的机制,其本质是清空当前进程的用户态内容并加载新程序,同时保留内核态资源(如 PID、文件描述符等)。它通过 exec 系列系统调用实现,以下是进程替换的详细原理。

进程替换的核心是:

  1. 清空当前进程的用户态地址空间,包括代码段、数据段、堆、栈等。
  2. 加载新程序到当前进程的地址空间,并切换到新程序的入口点执行。
  3. 保留进程的内核态资源,如 PID、打开的文件描述符、父子关系等。
  4. 如果 exec 调用成功,原进程的代码永远不会被执行。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>  using namespace std;int main() {// 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)cout << "I'm a process: " << "PID: " << getpid() << " PPID: " << getppid() << endl;// 调用 fork() 创建子进程pid_t id = fork();// 子进程逻辑if (id == 0) {cout <<"Child PID: "<< getpid() << endl;//打印子进程的PID// 使用 execl() 替换当前子进程为 /usr/bin/ls 程序// 第一个参数是程序路径,第二个参数是程序名称(通常为 argv[0]),后面是命令行参数execl("/usr/bin/ls", "ls", "-l", "-a", NULL);// 如果 execl() 执行失败(例如文件不存在),会执行以下代码perror("execl failed"); // 输出错误信息exit(1); // 子进程以退出码 1 结束}// 父进程逻辑// 使用 waitpid() 等待子进程结束int ret = waitpid(id, NULL, 0); // 第二个参数为 NULL,表示忽略子进程的退出状态if (ret > 0) {// 如果 waitpid() 成功返回,表示子进程已结束cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << endl;return 0; // 父进程正常退出
}

执行流程

  1. 程序开始
    • 父进程运行,打印自己的 PID 和 PPID(错误地显示 PPID 为自己的 PID)。
  2. 创建子进程
    • fork 创建一个子进程。
  3. 子进程执行 execl
    • 子进程替换为 /usr/bin/ls 程序,并执行 ls -l -a 命令,列出当前目录中所有文件(包括隐藏文件)的详细信息。
    • 如果 execl 成功,子进程的地址空间完全被 ls 程序覆盖。
    • 如果 execl 失败,执行 exit(1),子进程退出,返回码为 1
  4. 父进程等待子进程
    • 父进程调用 waitpid,阻塞等待子进程终止。
    • 当子进程完成后,waitpid 返回子进程的 PID。
  5. 父进程打印结果
    • 父进程输出自己的 PID 和已终止的子进程的 PID。

在这里插入图片描述

  • 子进程的PID没有变化,发成了进程替换。

exec系类函数

exec 系列函数是 UNIX/Linux 系统中用于进程替换的函数集合。通过 exec 系列函数,当前进程的用户态内容(如代码段、数据段、堆、栈等)会被新程序替换,而进程的内核态资源(如 PID、打开的文件描述符等)被保留。

exec 系列函数不创建新进程,只是在当前进程中加载并运行一个新程序。

exec 系列函数的成员

在这里插入图片描述

L:可以理解list

V:可以理解Vector

execl

int execl(const char *path, const char *arg0, ..., NULL);

参数说明

  1. path
    • 新程序的文件路径(可以是绝对路径或相对路径)。
    • /bin/ls./myprogram
  2. arg0, ..., NULL
    • 传递给新程序的参数列表,按照顺序传递给新程序的 argv 数组。
    • arg0 通常是程序名,相当于 argv[0]
    • 后续的参数是传递给新程序的命令行参数,相当于 argv[1], argv[2], ...
    • 参数列表必须以 NULL 结束。
  3. 示例:
execl("/bin/ls", "ls", "-l", "-a", NULL);

execlp

int execlp(const char *file, const char *arg0, ..., NULL);

参数说明

  1. file
    • 新程序的文件名。
    • 如果 file 不包含斜杠(/),execlp 会根据 PATH 环境变量搜索可执行文件。
    • 如果 file 包含斜杠,则直接视为路径,无需搜索 PATH
  2. arg0, ..., NULL
    • 传递给新程序的参数列表,必须以 NULL 结束。
    • arg0 通常是程序名,相当于 argv[0]
    • 后续参数为程序的命令行参数,相当于 argv[1]argv[2] 等。
  3. 示例
execlp("ls", "ls", "-l", "-a", NULL);

execle

int execle(const char *path, const char *arg0, ..., NULL, char *const envp[]);

参数说明:

path

  • 新程序的文件路径,可以是绝对路径或相对路径。
  • /bin/ls./myprogram

arg0, ..., NULL

  • 传递给新程序的参数列表,必须以 NULL 结束。
  • arg0 通常是程序名,相当于 argv[0]
  • 后续参数为程序的命令行参数,相当于 argv[1], argv[2], ...

envp

  • 一个指向环境变量字符串数组的指针。
  • 每个环境变量字符串的格式为 key=value(例如,PATH=/usr/bin)。
  • 如果希望新程序继承当前进程的环境变量,可以手动传递当前进程的 environ
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>using namespace std;int main()    
{     cout << "I'm a process: "<<"PID:"<<getpid()<< " PPID: "<< getpid()<< endl;    pid_t id = fork();    char *envp[] = {    "MY_VAR=HelloWorld",    "PATH=/bin:/usr/bin",    NULL    };     if(id == 0)    {    cout <<"Child PID: "<< getpid() << endl;    execle("/usr/bin/env","env",NULL,envp);                                  exit(1);    }    int ret = waitpid(id,NULL,0);    if(ret > 0)    cout << "Father PID: "<<getpid()<< " " <<"Child PID: "<< ret << endl;    return 0;    
}

execv

int execv(const char *path, char *const argv[]);

参数说明

  1. path: 指向可执行文件路径的字符串(以 \0 结尾)。
  2. argv: 一个字符串指针数组,用于传递给新程序的参数列表。数组的第一个元素通常为程序名称(argv[0]),最后一个元素必须为 NULL,以标记参数列表结束。

示例:

#include<iostream> 
#include<unistd.h> 
#include<stdlib.h> 
#include<sys/wait.h> 
#include<sys/types.h> int main()
{// 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)std::cout << "I'm a process: "<< "PID:" << getpid() << " PPID: " << getppid() << std::endl;// 创建子进程pid_t id = fork();// 定义一个字符指针数组,用于存储传递给 `execv` 的参数char *argv[] = {    "ls",    // argv[0]: 通常是程序名称"-l",    // argv[1]: 参数,表示以长格式列出文件"-a",    // argv[2]: 参数,显示隐藏文件NULL     // 终止符,必须为 NULL};if(id == 0) // 子进程执行的代码块{    // 子进程输出自己的 PIDstd::cout << "Child PID: " << getpid() << std::endl; // 用 execv 替换当前进程的执行映像execv("/usr/bin/ls", argv);// 如果 execv 返回,说明执行失败exit(1); // 退出子进程,返回非零值表示错误}// 父进程等待子进程完成int ret = waitpid(id, NULL, 0);if(ret > 0) // 如果 `waitpid` 成功返回std::cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << std::endl;return 0;
}

逐步功能分析

  1. 主进程输出信息
    使用 getpid()getppid() 分别获取当前进程 ID 和父进程 ID,并输出信息。
  2. 创建子进程
    使用 fork() 创建一个子进程:
    • 返回值 id == 0:表示当前是子进程。
    • 返回值 id > 0:表示当前是父进程,id 为子进程的 PID。
  3. 子进程执行新程序
    在子进程中调用 execv
    • 替换当前进程映像为 /usr/bin/ls
    • 参数数组 argv 指定了程序名称和选项。
    • 如果 execv 成功,后续代码不会执行;否则会继续执行并调用 exit(1) 终止子进程。
  4. 父进程等待子进程
    父进程调用 waitpid
    • 阻塞当前进程,直到子进程终止。
    • 返回值 ret 是子进程的 PID。
  5. 父进程输出信息
    输出父进程和子进程的 PID 信息。

在这里插入图片描述

execvp

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

参数说明

  1. file
    • 要执行的程序名称或路径。
    • 如果提供的是程序名称(非路径),execvp 会根据环境变量 PATH 中的目录列表查找该程序。
  2. argv
    • 一个字符串数组,表示传递给新程序的参数。
    • argv[0] 通常是程序名称,最后一个元素必须为 NULL

execvpexecv 的区别

  • execv
    要求指定程序的完整路径,且不会从环境变量 PATH 中查找。
  • execvp
    可以仅提供程序名称,函数会自动从 PATH 中查找程序。
#include<iostream> 
#include<unistd.h> 
#include<stdlib.h> 
#include<sys/wait.h> 
#include<sys/types.h> int main()
{// 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)std::cout << "I'm a process: "<< "PID:" << getpid() << " PPID: " << getppid() << std::endl;// 创建子进程pid_t id = fork();// 定义一个字符指针数组,用于存储传递给 `execv` 的参数char *argv[] = {    "ls",    // argv[0]: 通常是程序名称"-l",    // argv[1]: 参数,表示以长格式列出文件"-a",    // argv[2]: 参数,显示隐藏文件NULL     // 终止符,必须为 NULL};if(id == 0) // 子进程执行的代码块{    // 子进程输出自己的 PIDstd::cout << "Child PID: " << getpid() << std::endl; // 用 execvp 替换当前进程的执行映像execvp("ls", argv); // 区别于execv// 如果 execv 返回,说明执行失败exit(1); // 退出子进程,返回非零值表示错误}// 父进程等待子进程完成int ret = waitpid(id, NULL, 0);if(ret > 0) // 如果 `waitpid` 成功返回std::cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << std::endl;return 0;
}

ecexvpe

int execvpe(const char *file, char *const argv[], char *const envp[]);

参数说明

  1. file
    • 要执行的程序名称或路径。
    • 如果提供的是程序名称,execvpe 会根据环境变量 PATH 自动查找该程序。
  2. argv
    • 一个字符串数组,用于传递给新程序的参数。
    • argv[0] 通常是程序的名称,最后一个元素必须是 NULL
  3. envp
    • 一个字符串数组,用于指定新程序的环境变量。
    • 每个字符串的格式为 KEY=VALUE,例如 "PATH=/usr/bin"
    • 最后一个元素必须为 NULL

示例

#include<iostream>  
#include<unistd.h>   
#include<stdlib.h>   
#include<sys/wait.h> 
#include<sys/types.h>using namespace std;int main()                                                                    
{                     // 输出当前进程的 PID 和父进程 ID(PPID)cout << "I'm a process: "<< "PID:" << getpid() << " PPID: " << getpid() << endl;// 创建子进程pid_t id = fork();      // 自定义环境变量数组char *envp[] = {"MY_VAR=HelloWorld", // 自定义变量 MY_VAR,值为 "HelloWorld""PATH=/bin:/usr/bin", // 自定义 PATH,确保能找到可执行文件NULL                 // 终止标志};       // 命令参数数组,传递给 `ls` 命令char *argv[] = {"ls",   // argv[0] 通常为程序名称"-l",   // 参数:长格式输出"-a",   // 参数:显示隐藏文件NULL    // 终止标志};                                          if(id == 0) // 子进程{cout <<"Child PID: " << getpid() << endl;// 使用 execvpe 执行 ls 命令,传递自定义环境变量execvpe("ls", argv, envp);// 如果 execvpe 执行失败exit(1); // 退出子进程,返回非零值表示错误}                                                                    // 父进程等待子进程完成int ret = waitpid(id, NULL, 0);if(ret > 0) // 如果子进程正常退出cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << endl;return 0

功能分析

  1. 父进程输出信息

    • 使用 getpid() 获取当前进程的 ID。
    • 使用 getpid() 显示父进程的 PPID(此处写错,正确用法应是 getppid())。
  2. 创建子进程

    • 调用

      fork()
      

      创建子进程:

      • 返回值 id == 0:表示当前是子进程。
      • 返回值 id > 0:表示当前是父进程,id 为子进程的 PID。
  3. 定义环境变量和参数

    • envp
      

      是自定义的环境变量数组:

      • 包括 MY_VAR=HelloWorldPATH=/bin:/usr/bin
    • argv
      

      是传递给

      execvpe
      

      的参数列表:

      • 包括 ls 命令及其参数 -l-a
  4. 子进程执行新程序

    • 子进程调用

      execvpe("ls", argv, envp)
      
      • 替换当前子进程的映像为 ls 命令。
      • 使用自定义的环境变量。
    • 如果 execvpe 失败,子进程调用 exit(1) 退出。

  5. 父进程等待子进程完成

    • 调用 waitpid 等待子进程完成。
    • 输出父进程和子进程的 PID 信息。

在这里插入图片描述

exec 系列函数总结

函数名称程序路径参数传递环境变量特点
execl完整路径列表传参继承父进程环境手动传递每个参数;易用但不适合动态参数数量。
execlp搜索 PATH列表传参继承父进程环境PATH 中查找程序;适合提供命令名称的情况。
execle完整路径列表传参自定义环境execl 类似,但支持自定义环境变量。
execv完整路径数组传参继承父进程环境参数通过数组传递,适合动态生成参数的情况。
execvp搜索 PATH数组传参继承父进程环境PATH 中查找程序,适合命令名称和动态参数。
execve完整路径数组传参自定义环境底层实现函数;用户可完全控制路径、参数和环境变量。
execvpe搜索 PATH数组传参自定义环境GNU 扩展,结合 execvpexecve 的优点。

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

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

相关文章

【数据结构】初识链表

顺序表的优缺点 缺点&#xff1a; 中间/头部的插入删除&#xff0c;时间复杂度效率较低&#xff0c;为O(N) 空间不够的时候需要扩容。 如果是异地扩容&#xff0c;增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间&#xff0c;会有不小的消耗。 扩容可能会存在…

增删改查(CRUD)操作

文章目录 MySQL系列&#xff1a;1.CRUD简介2.Create(创建)2.1单行数据全列插入2.2 单行数据指定插入2.3 多⾏数据指定列插⼊ 3.Retrieve(读取)3.1 Select查询3.1.1 全列查询3.1.2 指定列查询3.1.3 查询字段为表达式&#xff08;都是临时表不会对原有表数据产生影响&#xff09;…

使用Pygame制作“贪吃蛇”游戏

贪吃蛇 是一款经典的休闲小游戏&#xff1a;玩家通过操控一条会不断变长的“蛇”在屏幕中移动&#xff0c;去吃随机出现的食物&#xff0c;同时要避免撞到墙壁或自己身体的其他部分。由于其逻辑相对简单&#xff0c;但可玩性和扩展性都不错&#xff0c;非常适合作为新手练习游戏…

JavaScript闭包深入剖析:性能剖析与优化技巧

一、引言 在 JavaScript 的奇妙世界里&#xff0c;闭包无疑是一个既强大又迷人的特性。它就像是一把万能钥匙&#xff0c;为开发者打开了实现各种高级功能的大门。从数据封装与保护&#xff0c;到函数的记忆化&#xff0c;再到模块化开发&#xff0c;闭包都发挥着举足轻重的作…

蓝桥杯嵌入式赛道备考1 —— 基础GPIO实战

1. 点亮一个LED 蓝桥杯的板子资料的URL&#xff0c;笔者是从GitHub - JoyRiderJie/LanQiaoBei-QianRuShi拉去下来的。这个是Github仓库地址。 从应用层去玩一个开发板子&#xff0c;首先需要的是去尝试是点亮一个LED。让我们切换到手册《CT117E——产品手册》的第11页&#x…

浅析DNS污染及防范

DNS污染&#xff08;DNS Cache Poisoning&#xff09;是一种网络攻击手段&#xff0c;通过篡改DNS服务器的缓存数据&#xff0c;将域名解析结果指向错误的IP地址&#xff0c;从而误导用户访问恶意网站或无法访问目标网站。这种攻击利用了DNS协议的特性&#xff0c;例如“只认第…

AI编程:如何编写提示词

这是小卷对AI编程工具学习的第2篇文章&#xff0c;今天讲讲如何编写AI编程的提示词&#xff0c;并结合实际功能需求案例来进行开发 1.编写提示词的技巧 好的提示词应该是&#xff1a;目标清晰明确&#xff0c;具有针对性&#xff0c;能引导模型理解问题 下面是两条提示词的对…

linux asio网络编程理论及实现

最近在B站看了恋恋风辰大佬的asio网络编程&#xff0c;质量非常高。在本章中将对ASIO异步网络编程的整体及一些实现细节进行完整的梳理&#xff0c;用于复习与分享。大佬的博客&#xff1a;恋恋风辰官方博客 Preactor/Reactor模式 在网络编程中&#xff0c;通常根据事件处理的触…

【思维导图】并发编程

学习计划&#xff1a;将目前已经学的知识点串成一个思维导图。在往后的学习过程中&#xff0c;不断往思维导图里补充&#xff0c;形成自己整个知识体系。对于思维导图里的每个技术知识&#xff0c;自己用简洁的话概括出来&#xff0c; 训练自己的表达能力。 并发和并行的区别 并…

【B站保姆级视频教程:Jetson配置YOLOv11环境(四)cuda cudnn tensorrt配置】

Jetson配置YOLOv11环境&#xff08;4&#xff09;cuda cudnn tensorrt配置 文章目录 0. 简介1. cuda配置&#xff1a;添加cuda环境变量2. cudnn配置3. TensorRT Python环境配置3.1 系统自带Python环境中的TensorRT配置3.2 Conda 虚拟Python环境中的TensorRT配置 0. 简介 官方镜…

【深度分析】DeepSeek 遭暴力破解,攻击 IP 均来自美国,造成影响有多大?有哪些好的防御措施?

技术铁幕下的暗战&#xff1a;当算力博弈演变为代码战争 一场针对中国AI独角兽的全球首例国家级密码爆破&#xff0c;揭开了数字时代技术博弈的残酷真相。DeepSeek服务器日志中持续跳动的美国IP地址&#xff0c;不仅是网络攻击的地理坐标&#xff0c;更是技术霸权对新兴挑战者的…

如何在数据湖中有效治理和管理“数据沼泽”问题,提高数据的可发现性和利用率?

在数据湖中有效治理和管理“数据沼泽”问题&#xff0c;提高数据的可发现性和利用率&#xff0c;需要从多个方面入手&#xff0c;包括数据治理、元数据管理、数据质量控制、安全性保障以及生命周期管理等。以下是具体的策略和方法&#xff1a; 1. 构建强大的数据治理框架 数据…

【4Day创客实践入门教程】Day3 实战演练——桌面迷你番茄钟

Day3 实战演练——桌面迷你番茄钟 目录 Day3 实战演练——桌面迷你番茄钟1. 选择、准备元件、收集资料2. 硬件搭建3.编写代码 Day0 创想启程——课程与项目预览Day1 工具箱构建——开发环境的构建Day2 探秘微控制器——单片机与MicroPython初步Day3 实战演练——桌面迷你番茄钟…

Oracle Primavera P6自动进行进度计算

前言 在P6 Professional 有一个自动计划计算的选项&#xff0c;很多人不了解该设置如何使用&#xff0c;以及什么时候该启动这项配置。 详情 P6 Professional 默认为非自动进度计算。启用自动选项后&#xff0c;可以快速查看调度更改的效果。 ​ ​ 如图所示&#xff0c;当你…

DeepSeek-R1 论文解读 —— 强化学习大语言模型新时代来临?

近年来&#xff0c;人工智能&#xff08;AI&#xff09;领域发展迅猛&#xff0c;大语言模型&#xff08;LLMs&#xff09;为通用人工智能&#xff08;AGI&#xff09;的发展开辟了道路。OpenAI 的 o1 模型表现非凡&#xff0c;它引入的创新性推理时缩放技术显著提升了推理能力…

大模型GUI系列论文阅读 DAY4续:《Large Language Model Agent for Fake News Detection》

摘要 在当前的数字时代&#xff0c;在线平台上虚假信息的迅速传播对社会福祉、公众信任和民主进程构成了重大挑战&#xff0c;并影响着关键决策和公众舆论。为应对这些挑战&#xff0c;自动化假新闻检测机制的需求日益增长。 预训练的大型语言模型&#xff08;LLMs&#xff0…

LevelDB 源码阅读:写入键值的工程实现和优化细节

读、写键值是 KV 数据库中最重要的两个操作&#xff0c;LevelDB 中提供了一个 Put 接口&#xff0c;用于写入键值对。使用方法很简单&#xff1a; leveldb::Status status leveldb::DB::Open(options, "./db", &db); status db->Put(leveldb::WriteOptions…

【Proteus仿真】【51单片机】多功能计算器系统设计

目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 联系作者 一、主要功能 1、LCD1602液晶显示 2、矩阵按键​ 3、加减乘除&#xff0c;开方运算 4、带符号运算 5、最大 999*999 二、使用步骤 基于51单片机多功能计算器 包含&#xff1a;程序&…

origin调整图像的坐标轴,修改坐标轴起始点,增量

接上一篇帖子&#xff0c;如果再修改数据之后或者当前的数据之间差距较小&#xff0c;怎么通过调整坐标轴来使数据之间的差距更明显&#xff0c;举个例子&#xff0c; 像下面这个图的entropy指标&#xff0c;都是介于6到9之间&#xff0c;如果y轴坐标都从0开始&#xff0c;使用…

Redis_Redission的入门案例、多主案例搭建、分布式锁进行加锁、解锁底层源码解析

目录 ①. Redis为什么选择单线程&#xff1f; ②. 既然单线程这么好,为什么逐渐又加入了多线程特性&#xff1f; ③. redis6的多线程和IO多路复用入门篇 ④. Redis6.0默认是否开启了多线程&#xff1f; ⑤. REDIS多线程引入总结 ①. Redis为什么选择单线程&#xff1f; ①…