从理论到实践: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) // 子进程执行的代码块
    {    
        // 子进程输出自己的 PID
        std::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) // 子进程执行的代码块
    {    
        // 子进程输出自己的 PID
        std::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 的优点。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ZZNUOJ(C/C++)基础练习1041——1050(详解版)

1041 : 数列求和2 题目描述 输入一个整数n&#xff0c;输出数列1-1/31/5-……前n项的和。 输入 输入只有一个整数n。 输出 结果保留2为小数,单独占一行。 样例输入 3 样例输出 0.87注意sum 1相当于sumsum1 注意sum * 1相当于sumsum*1 C语言版 #include<stdio.h> // 包含…

2021 年 6 月大学英语四级考试真题(第 2 套)——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;目前中南大学MBA在读&#xff0c;也考取过HCIE Cloud Computing、CCIE Security、PMP、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &…

【Block总结】CPCA,通道优先卷积注意力|即插即用

论文信息 标题: Channel Prior Convolutional Attention for Medical Image Segmentation 论文链接: arxiv.org 代码链接: GitHub 创新点 本文提出了一种新的通道优先卷积注意力&#xff08;CPCA&#xff09;机制&#xff0c;旨在解决医学图像分割中存在的低对比度和显著…

grpc 和 http 的区别---二进制vsJSON编码

gRPC 和 HTTP 是两种广泛使用的通信协议&#xff0c;各自适用于不同的场景。以下是它们的详细对比与优势分析&#xff1a; 一、核心特性对比 特性gRPCHTTP协议基础基于 HTTP/2基于 HTTP/1.1 或 HTTP/2数据格式默认使用 Protobuf&#xff08;二进制&#xff09;通常使用 JSON/…

Qt常用控件 输入类控件

文章目录 1.QLineEdit1.1 常用属性1.2 常用信号1.3 例子1&#xff0c;录入用户信息1.4 例子2&#xff0c;正则验证手机号1.5 例子3&#xff0c;验证输入的密码1.6 例子4&#xff0c;显示密码 2. QTextEdit2.1 常用属性2.2 常用信号2.3 例子1&#xff0c;获取输入框的内容2.4 例…

[b01lers2020]Life on Mars1

打开题目页面如下 看了旁边的链接&#xff0c;也没有什么注入点&#xff0c;是正常的科普 利用burp suite抓包&#xff0c;发现传参 访问一下 http://5edaec92-dd87-4fec-b0e3-501ff24d3650.node5.buuoj.cn:81/query?searchtharsis_rise 接下来进行sql注入 方法一&#xf…

前端自动化测试(一):揭秘自动化测试秘诀

目录 [TOC](目录)前言自动化测试 VS 手动测试测试分类何为单元测试单元测试的优缺点优点缺点 测试案例测试代码 测试函数的封装实现 expect 方法实现 test 函数结语 正文开始 &#xff0c; 如果觉得文章对您有帮助&#xff0c;请帮我三连订阅&#xff0c;谢谢&#x1f496;&…

FFmpeg工具使用基础

一、FFmpeg工具介绍 FFmpeg命令行工具主要包括以下几个部分: ‌ffmpeg‌:编解码工具‌ffprobe‌:多媒体分析器‌ffplay‌:简单的音视频播放器这些工具共同构成了FFmpeg的核心功能,支持各种音视频格式的处理和转换‌ 二、在Ubuntu18.04上安装FFmpeg工具 1、sudo apt-upda…

upload labs靶场

upload labs靶场 注意:本人关卡后面似乎相比正常的关卡少了一关&#xff0c;所以每次关卡名字都是1才可以和正常关卡在同一关 一.个人信息 个人名称&#xff1a;张嘉玮 二.解题情况 三.解题过程 题目&#xff1a;up load labs靶场 pass 1前后端 思路及解题&#xff1a;…

解锁豆瓣高清海报(二) 使用 OpenCV 拼接和压缩

解锁豆瓣高清海报(二): 使用 OpenCV 拼接和压缩 脚本地址: 项目地址: Gazer PixelWeaver.py pixel_squeezer_cv2.py 前瞻 继上一篇“解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路”成功爬取豆瓣电影海报之后&#xff0c;本文将介绍如何使用 OpenCV 对这些海报进行智…

C++:虚函数与多态性习题2

题目内容&#xff1a; 编写程序&#xff0c;声明抽象基类Shape&#xff0c;由它派生出3个派生类&#xff1a;Circle、Rectangle、Triangle&#xff0c;用虚函数分别计算图形面积&#xff0c;并求它们的和。要求用基类指针数组&#xff0c;使它每一个元素指向一个派生类对象。 …

JVM运行时数据区域-附面试题

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直存在&#xff0c;有些区域则是 依赖用户线程的启动和结束而建立和销毁。 1. 程序计…

电脑优化大师-解决电脑卡顿问题

我们常常会遇到电脑运行缓慢、网速卡顿的情况&#xff0c;但又不知道是哪个程序在占用过多资源。这时候&#xff0c;一款能够实时监控网络和系统状态的工具就显得尤为重要了。今天&#xff0c;就来给大家介绍一款小巧实用的监控工具「TrafficMonitor」。 「TrafficMonitor 」是…

跨组织环境下 MQTT 桥接架构的评估

论文标题 中文标题&#xff1a; 跨组织环境下 MQTT 桥接架构的评估 英文标题&#xff1a; Evaluation of MQTT Bridge Architectures in a Cross-Organizational Context 作者信息 Keila Lima, Tosin Daniel Oyetoyan, Rogardt Heldal, Wilhelm Hasselbring Western Norway …

Baklib揭示内容中台实施最佳实践的策略与实战经验

内容概要 在当前数字化转型的浪潮中&#xff0c;内容中台的概念日益受到关注。它不再仅仅是一个内容管理系统&#xff0c;而是企业提升运营效率与灵活应对市场变化的重要支撑平台。内容中台的实施离不开最佳实践的指导&#xff0c;这些实践为企业在建设高效内容中台时提供了宝…

牛客周赛round78 B,C

B.一起做很甜的梦 题意&#xff1a;就是输出n个数&#xff08;1-n&#xff09;&#xff0c;使输出的序列中任意选连续的小序列&#xff08;小序列长度>2&&<n-1&#xff09;不符合排列&#xff08;例如如果所选长度为2&#xff0c;在所有长度为2 的小序列里不能出…

微机原理与接口技术期末大作业——4位抢答器仿真

在微机原理与接口技术的学习旅程中&#xff0c;期末大作业成为了检验知识掌握程度与实践能力的关键环节。本次我选择设计并仿真一个 4 位抢答器系统&#xff0c;通过这个项目&#xff0c;深入探索 8086CPU 及其接口技术的实际应用。附完整压缩包下载。 一、系统设计思路 &…

Android记事本App设计开发项目实战教程2025最新版Android Studio

平时上课录了个视频&#xff0c;从新建工程到打包Apk&#xff0c;从头做到尾&#xff0c;没有遗漏任何实现细节&#xff0c;欢迎学过Android基础的同学参加&#xff0c;如果你做过其他终端软件开发&#xff0c;也可以学习&#xff0c;快速上手Android基础开发。 Android记事本课…

设计模式Python版 组合模式

文章目录 前言一、组合模式二、组合模式实现方式三、组合模式示例四、组合模式在Django中的应用 前言 GOF设计模式分三大类&#xff1a; 创建型模式&#xff1a;关注对象的创建过程&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式…

4-图像梯度计算

文章目录 4.图像梯度计算(1)Sobel算子(2)梯度计算方法(3)Scharr与Laplacian算子4.图像梯度计算 (1)Sobel算子 图像梯度-Sobel算子 Sobel算子是一种经典的图像边缘检测算子,广泛应用于图像处理和计算机视觉领域。以下是关于Sobel算子的详细介绍: 基本原理 Sobel算子…