首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 25-02-16 03:20
  • 3015
  • 11696
blog.csdn.net

个人主页:chian-ocean

文章专栏-Linux

前言:

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

在这里插入图片描述

进程替换原理

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

进程替换的核心是:

  1. 清空当前进程的用户态地址空间,包括代码段、数据段、堆、栈等。
  2. 加载新程序到当前进程的地址空间,并切换到新程序的入口点执行。
  3. 保留进程的内核态资源,如 PID、打开的文件描述符、父子关系等。
  4. 如果 exec 调用成功,原进程的代码永远不会被执行。
#include 
#include 
#include 
#include   

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

执行流程

  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

参数说明

  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);
  • 1

execlp

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

参数说明

  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);
  • 1

execle

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

参数说明:

path:

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

arg0, ..., NULL:

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

envp:

  • 一个指向环境变量字符串数组的指针。
  • 每个环境变量字符串的格式为 key=value(例如,PATH=/usr/bin)。
  • 如果希望新程序继承当前进程的环境变量,可以手动传递当前进程的 environ。
#include
#include
#include
#include
#include
  
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;    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

execv

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

参数说明

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

示例:

#include 
#include 
#include 
#include 。
#include 

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

逐步功能分析

  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

参数说明

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

execvp 与 execv 的区别

  • execv
    要求指定程序的完整路径,且不会从环境变量 PATH 中查找。
  • execvp
    可以仅提供程序名称,函数会自动从 PATH 中查找程序。
#include 
#include 
#include 
#include 。
#include 

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

ecexvpe

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

参数说明

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

示例

#include  
#include   
#include   
#include 
#include

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

功能分析

  1. 父进程输出信息

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

    • 调用

      fork()
      
      • 1

      创建子进程:

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

    • envp
      
      • 1

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

      • 包括 MY_VAR=HelloWorld 和 PATH=/bin:/usr/bin。
    • argv
      
      • 1

      是传递给

      execvpe
      
      • 1

      的参数列表:

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

    • 子进程调用

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

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

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

在这里插入图片描述

exec 系列函数总结

函数名称程序路径参数传递环境变量特点
execl完整路径列表传参继承父进程环境手动传递每个参数;易用但不适合动态参数数量。
execlp搜索 PATH列表传参继承父进程环境在 PATH 中查找程序;适合提供命令名称的情况。
execle完整路径列表传参自定义环境与 execl 类似,但支持自定义环境变量。
execv完整路径数组传参继承父进程环境参数通过数组传递,适合动态生成参数的情况。
execvp搜索 PATH数组传参继承父进程环境在 PATH 中查找程序,适合命令名称和动态参数。
execve完整路径数组传参自定义环境底层实现函数;用户可完全控制路径、参数和环境变量。
execvpe搜索 PATH数组传参自定义环境GNU 扩展,结合 execvp 和 execve 的优点。
注:本文转载自blog.csdn.net的chian-ocean的文章"https://blog.csdn.net/Cayyyy/article/details/145412565"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

122
操作系统
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top