个人主页:chian-ocean
文章专栏-Linux
媒介:
在Linux中,进程更换(Process Substitution)是一个非常强大的特性,它允许将一个进程的输出直接当作一个文件来处理。这种技术通常用于Shell脚本和命令行操纵中。
进程更换原理
进程更换(Process Replacement)是操纵系统用来用一个新步伐完全更换当进步程用户态内容的机制,其本质是清空当进步程的用户态内容并加载新步伐,同时保留内核态资源(如 PID、文件描述符等)。它通过 exec 系列系统调用实现,以下是进程更换的详细原理。
进程更换的焦点是:
- 清空当进步程的用户态地址空间,包括代码段、数据段、堆、栈等。
- 加载新步伐到当进步程的地址空间,并切换到新步伐的入口点实行。
- 保留进程的内核态资源,如 PID、打开的文件描述符、父子关系等。
- 如果 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; // 父进程正常退出
- }
复制代码 实行流程
- 步伐开始:
- 父进程运行,打印自己的 PID 和 PPID(错误地显示 PPID 为自己的 PID)。
- 创建子进程:
- 子进程实行 execl:
- 子进程更换为 /usr/bin/ls 步伐,并实行 ls -l -a 命令,列出当前目次中全部文件(包括隐蔽文件)的详细信息。
- 如果 execl 成功,子进程的地址空间完全被 ls 步伐覆盖。
- 如果 execl 失败,实行 exit(1),子进程退出,返回码为 1。
- 父进程等待子进程:
- 父进程调用 waitpid,阻塞等待子进程终止。
- 当子进程完成后,waitpid 返回子进程的 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);
复制代码 参数说明
- path:
- 新步伐的文件路径(可以是绝对路径或相对路径)。
- 如 /bin/ls 或 ./myprogram。
- arg0, ..., NULL:
- 传递给新步伐的参数列表,按照顺序传递给新步伐的 argv
数组。
- arg0 通常是步伐名,相当于 argv
[0]。
- 后续的参数是传递给新步伐的命令行参数,相当于 argv
[1], argv
[2], ...。
- 参数列表必须以 NULL 结束。
- 示例:
- execl("/bin/ls", "ls", "-l", "-a", NULL);
复制代码 execlp
- 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] 等。
- 示例
- 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
- []);
复制代码 参数说明
- path: 指向可实行文件路径的字符串(以 \0 末端)。
- 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;
- }
复制代码 逐步功能分析
- 主进程输出信息
使用 getpid() 和 getppid() 分别获取当进步程 ID 和父进程 ID,并输出信息。
- 创建子进程
使用 fork()
创建一个子进程:
- 返回值 id == 0:表现当前是子进程。
- 返回值 id > 0:表现当前是父进程,id 为子进程的 PID。
- 子进程实行新步伐
在子进程中调用 execv:
- 更换当进步程映像为 /usr/bin/ls。
- 参数数组 argv
指定了步伐名称和选项。
- 如果 execv 成功,后续代码不会实行;否则会继承实行并调用 exit(1) 终止子进程。
- 父进程等待子进程
父进程调用 waitpid:
- 阻塞当进步程,直到子进程终止。
- 返回值 ret 是子进程的 PID。
- 父进程输出信息
输出父进程和子进程的 PID 信息。
execvp
- int execvp(const char *file, char *const argv
- []);
复制代码 参数说明
- file
- 要实行的步伐名称或路径。
- 如果提供的是步伐名称(非路径),execvp 会根据情况变量 PATH 中的目次列表查找该步伐。
- argv
- 一个字符串数组,表现传递给新步伐的参数。
- argv
[0] 通常是步伐名称,最后一个元素必须为 NULL。
execvp 与 execv 的区别
- 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
- []);
复制代码 参数说明
- file
- 要实行的步伐名称或路径。
- 如果提供的是步伐名称,execvpe
会根据情况变量 PATH 自动查找该步伐。
- argv
- 一个字符串数组,用于传递给新步伐的参数。
- argv
[0] 通常是步伐的名称,最后一个元素必须是 NULL。
- envp
- 一个字符串数组,用于指定新步伐的情况变量。
- 每个字符串的格式为 KEY=VALUE,比方 "
ATH=/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
复制代码 功能分析
- 父进程输出信息
- 使用 getpid() 获取当进步程的 ID。
- 使用 getpid() 显示父进程的 PPID(此处写错,正确用法应是 getppid())。
- 创建子进程
- 调用
创建子进程:
- 返回值 id == 0:表现当前是子进程。
- 返回值 id > 0:表现当前是父进程,id 为子进程的 PID。
- 定义情况变量和参数
- 是自定义的情况变量数组:
- 包括 MY_VAR=HelloWorld 和 PATH=/bin:/usr/bin。
- 是传递给
的参数列表:
- 子进程实行新步伐
- 子进程调用
- execvpe
- ("ls", argv
- , envp
- )
复制代码
- 更换当前子进程的映像为 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企服之家,中国第一个企服评测及商务社交产业平台。 |