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

打印 上一主题 下一主题

主题 844|帖子 844|积分 2532

个人主页:chian-ocean

文章专栏-Linux

媒介:

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

进程更换原理

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

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


  • 步伐开始

    • 父进程运行,打印自己的 PID 和 PPID(错误地显示 PPID 为自己的 PID)。

  • 创建子进程

    • fork 创建一个子进程。

  • 子进程实行 execl

    • 子进程更换为 /usr/bin/ls 步伐,并实行 ls -l -a 命令,列出当前目次中全部文件(包括隐蔽文件)的详细信息。
    • 如果 execl 成功,子进程的地址空间完全被 ls 步伐覆盖。
    • 如果 execl 失败,实行 exit(1),子进程退出,返回码为 1。

  • 父进程等待子进程

    • 父进程调用 waitpid,阻塞等待子进程终止。
    • 当子进程完成后,waitpid 返回子进程的 PID。

  • 父进程打印结果

    • 父进程输出自己的 PID 和已终止的子进程的 PID。




  • 子进程的PID没有变化,发成了进程更换。
exec系类函数

exec 系列函数是 UNIX/Linux 系统中用于进程更换的函数集合。通过 exec 系列函数,当进步程的用户态内容(如代码段、数据段、堆、栈等)会被新步伐更换,而进程的内核态资源(如 PID、打开的文件描述符等)被保留。
exec 系列函数不创建新进程,只是在当进步程中加载并运行一个新步伐。
exec 系列函数的成员

L:可以明白list
V:可以明白Vector
execl

  1. int execl(const char *path, const char *arg0, ..., NULL);
复制代码
参数说明


  • path

    • 新步伐的文件路径(可以是绝对路径或相对路径)。
    • 如 /bin/ls 或 ./myprogram。

  • arg0, ..., NULL

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

  • 示例:
  1. execl("/bin/ls", "ls", "-l", "-a", NULL);
复制代码
execlp

  1. int execlp(const char *file, const char *arg0, ..., NULL);
复制代码
参数说明


  • file

    • 新步伐的文件名。
    • 如果 file 不包含斜杠(/),execlp 会根据 PATH 情况变量搜刮可实行文件。
    • 如果 file 包含斜杠,则直接视为路径,无需搜刮 PATH。

  • arg0, ..., NULL

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

  • 示例
  1. execlp("ls", "ls", "-l", "-a", NULL);
复制代码
execle

  1. int execle(const char *path, const char *arg0, ..., NULL, char *const envp
  2. []);
复制代码
参数说明:

path


  • 新步伐的文件路径,可以是绝对路径或相对路径。
  • 如 /bin/ls 或 ./myprogram。
arg0, ..., NULL


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



  • 一个指向情况变量字符串数组的指针。
  • 每个情况变量字符串的格式为 key=value(比方,PATH=/usr/bin)。
  • 如果希望新步伐继承当进步程的情况变量,可以手动传递当进步程的 environ。
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/wait.h>
  5. #include<sys/types.h>
  6.   
  7. using namespace std;
  8.   
  9. int main()   
  10. {     
  11.       cout << "I'm a process: "<<"PID:"<<getpid()<< " PPID: "<< getpid()<< endl;   
  12.       pid_t id = fork()
  13. ;   
  14.       char *envp
  15. [] = {   
  16.         "MY_VAR=HelloWorld",   
  17.         "PATH=/bin:/usr/bin",   
  18.           NULL   
  19.       };     
  20.       if(id == 0)   
  21.       {   
  22.           cout <<"Child PID: "<< getpid() << endl;   
  23.           execle("/usr/bin/env","env",NULL,envp
  24. );                                 
  25.           exit(1);   
  26.       }   
  27.       int ret = waitpid(id,NULL,0);   
  28.       if(ret > 0)   
  29.       cout << "Father PID: "<<getpid()<< " " <<"Child PID: "<< ret << endl;   
  30.       
  31.       
  32.       return 0;   
  33. }
复制代码
execv

  1. int execv(const char *path, char *const argv
  2. []);
复制代码
参数说明


  • path: 指向可实行文件路径的字符串(以 \0 末端)。
  • argv
    : 一个字符串指针数组,用于传递给新步伐的参数列表。数组的第一个元素通常为步伐名称(argv
    [0]),最后一个元素必须为 NULL,以标记参数列表结束。
示例:

  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/wait.h> 。
  5. #include<sys/types.h>
  6. int main()
  7. {
  8.     // 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)
  9.     std::cout << "I'm a process: "
  10.               << "PID:" << getpid()
  11.               << " PPID: " << getppid() << std::endl;
  12.     // 创建子进程
  13.     pid_t id = fork()
  14. ;
  15.     // 定义一个字符指针数组,用于存储传递给 `execv` 的参数
  16.     char *argv
  17. [] = {   
  18.         "ls",    // argv
  19. [0]: 通常是程序名称
  20.         "-l",    // argv
  21. [1]: 参数,表示以长格式列出文件
  22.         "-a",    // argv
  23. [2]: 参数,显示隐藏文件
  24.         NULL     // 终止符,必须为 NULL
  25.     };
  26.     if(id == 0) // 子进程执行的代码块
  27.     {   
  28.         // 子进程输出自己的 PID
  29.         std::cout << "Child PID: " << getpid() << std::endl;
  30.         // 用 execv 替换当前进程的执行映像
  31.         execv("/usr/bin/ls", argv
  32. );
  33.         // 如果 execv 返回,说明执行失败
  34.         exit(1); // 退出子进程,返回非零值表示错误
  35.     }
  36.     // 父进程等待子进程完成
  37.     int ret = waitpid(id, NULL, 0);
  38.     if(ret > 0) // 如果 `waitpid` 成功返回
  39.         std::cout << "Father PID: " << getpid()
  40.                   << " " << "Child PID: " << ret
  41.                   << std::endl;
  42.     return 0;
  43. }
复制代码
逐步功能分析


  • 主进程输出信息
    使用 getpid() 和 getppid() 分别获取当进步程 ID 和父进程 ID,并输出信息。
  • 创建子进程
    使用 fork()
    创建一个子进程:

    • 返回值 id == 0:表现当前是子进程。
    • 返回值 id > 0:表现当前是父进程,id 为子进程的 PID。

  • 子进程实行新步伐
    在子进程中调用 execv:

    • 更换当进步程映像为 /usr/bin/ls。
    • 参数数组 argv
      指定了步伐名称和选项。
    • 如果 execv 成功,后续代码不会实行;否则会继承实行并调用 exit(1) 终止子进程。

  • 父进程等待子进程
    父进程调用 waitpid:

    • 阻塞当进步程,直到子进程终止。
    • 返回值 ret 是子进程的 PID。

  • 父进程输出信息
    输出父进程和子进程的 PID 信息。

execvp

  1. int execvp(const char *file, char *const argv
  2. []);
复制代码
参数说明


  • file

    • 要实行的步伐名称或路径。
    • 如果提供的是步伐名称(非路径),execvp 会根据情况变量 PATH 中的目次列表查找该步伐。

  • argv


    • 一个字符串数组,表现传递给新步伐的参数。
    • argv
      [0] 通常是步伐名称,最后一个元素必须为 NULL。

execvp 与 execv 的区别



  • execv
    要求指定步伐的完备路径,且不会从情况变量 PATH 中查找。
  • execvp
    可以仅提供步伐名称,函数会自动从 PATH 中查找步伐。
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<sys/wait.h> 。
  5. #include<sys/types.h>
  6. int main()
  7. {
  8.     // 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)
  9.     std::cout << "I'm a process: "
  10.               << "PID:" << getpid()
  11.               << " PPID: " << getppid() << std::endl;
  12.     // 创建子进程
  13.     pid_t id = fork()
  14. ;
  15.     // 定义一个字符指针数组,用于存储传递给 `execv` 的参数
  16.     char *argv
  17. [] = {   
  18.         "ls",    // argv
  19. [0]: 通常是程序名称
  20.         "-l",    // argv
  21. [1]: 参数,表示以长格式列出文件
  22.         "-a",    // argv
  23. [2]: 参数,显示隐藏文件
  24.         NULL     // 终止符,必须为 NULL
  25.     };
  26.     if(id == 0) // 子进程执行的代码块
  27.     {   
  28.         // 子进程输出自己的 PID
  29.         std::cout << "Child PID: " << getpid() << std::endl;
  30.         // 用 execvp 替换当前进程的执行映像
  31.         execvp("ls", argv
  32. ); // 区别于execv
  33.         // 如果 execv 返回,说明执行失败
  34.         exit(1); // 退出子进程,返回非零值表示错误
  35.     }
  36.     // 父进程等待子进程完成
  37.     int ret = waitpid(id, NULL, 0);
  38.     if(ret > 0) // 如果 `waitpid` 成功返回
  39.         std::cout << "Father PID: " << getpid()
  40.                   << " " << "Child PID: " << ret
  41.                   << std::endl;
  42.     return 0;
  43. }
复制代码
ecexvpe

  1. int execvpe
  2. (const char *file, char *const argv
  3. [], char *const envp
  4. []);
复制代码
参数说明


  • file

    • 要实行的步伐名称或路径。
    • 如果提供的是步伐名称,execvpe
      会根据情况变量 PATH 自动查找该步伐。

  • argv


    • 一个字符串数组,用于传递给新步伐的参数。
    • argv
      [0] 通常是步伐的名称,最后一个元素必须是 NULL。

  • envp


    • 一个字符串数组,用于指定新步伐的情况变量。
    • 每个字符串的格式为 KEY=VALUE,比方 "ATH=/usr/bin"。
    • 最后一个元素必须为 NULL。

示例

  1. #include<iostream>  
  2. #include<unistd.h>   
  3. #include<stdlib.h>   
  4. #include<sys/wait.h>
  5. #include<sys/types.h>
  6. using namespace std;
  7. int main()                                                                    
  8. {                     
  9.     // 输出当前进程的 PID 和父进程 ID(PPID)
  10.     cout << "I'm a process: "
  11.          << "PID:" << getpid()
  12.          << " PPID: " << getpid() << endl;
  13.     // 创建子进程
  14.     pid_t id = fork()
  15. ;      
  16.     // 自定义环境变量数组
  17.     char *envp
  18. [] = {
  19.         "MY_VAR=HelloWorld", // 自定义变量 MY_VAR,值为 "HelloWorld"
  20.         "PATH=/bin:/usr/bin", // 自定义 PATH,确保能找到可执行文件
  21.         NULL                 // 终止标志
  22.     };      
  23.     // 命令参数数组,传递给 `ls` 命令
  24.     char *argv
  25. [] = {
  26.         "ls",   // argv
  27. [0] 通常为程序名称
  28.         "-l",   // 参数:长格式输出
  29.         "-a",   // 参数:显示隐藏文件
  30.         NULL    // 终止标志
  31.     };                                          
  32.     if(id == 0) // 子进程
  33.     {
  34.         cout <<"Child PID: " << getpid() << endl;
  35.         // 使用 execvpe
  36. 执行 ls 命令,传递自定义环境变量
  37.         execvpe
  38. ("ls", argv
  39. , envp
  40. );
  41.         // 如果 execvpe
  42. 执行失败
  43.         exit(1); // 退出子进程,返回非零值表示错误
  44.     }                                                                    
  45.     // 父进程等待子进程完成
  46.     int ret = waitpid(id, NULL, 0);
  47.     if(ret > 0) // 如果子进程正常退出
  48.         cout << "Father PID: " << getpid()
  49.              << " " << "Child PID: " << ret << endl;
  50.     return 0
复制代码
功能分析


  • 父进程输出信息

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

  • 创建子进程

    • 调用
      1. fork()
      复制代码
      创建子进程:

      • 返回值 id == 0:表现当前是子进程。
      • 返回值 id > 0:表现当前是父进程,id 为子进程的 PID。


  • 定义情况变量和参数

      1. envp
      复制代码
      是自定义的情况变量数组:

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

      1. argv
      复制代码
      是传递给
      1. execvpe
      复制代码
      的参数列表:

      • 包括 ls 命令及其参数 -l 和 -a。


  • 子进程实行新步伐

    • 子进程调用
      1. execvpe
      2. ("ls", argv
      3. , envp
      4. )
      复制代码

      • 更换当前子进程的映像为 ls 命令。
      • 使用自定义的情况变量。

    • 如果 execvpe
      失败,子进程调用 exit(1) 退出。

  • 父进程等待子进程完成

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


exec 系列函数总结

函数名称步伐路径参数传递情况变量特点execl完备路径列表传参继承父进程情况手动传递每个参数;易用但不得当动态参数数量。execlp搜刮 PATH列表传参继承父进程情况在 PATH 中查找步伐;得当提供命令名称的情况。execle完备路径列表传参自定义情况与 execl 类似,但支持自定义情况变量。execv完备路径数组传参继承父进程情况参数通过数组传递,得当动态天生参数的情况。execvp搜刮 PATH数组传参继承父进程情况在 PATH 中查找步伐,得当命令名称和动态参数。execve完备路径数组传参自定义情况底层实现函数;用户可完全控制路径、参数和情况变量。execvpe
搜刮 PATH数组传参自定义情况GNU 扩展,联合 execvp 和 execve 的长处。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

八卦阵

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表