??进程的程序替换就是让子进程执行新程序, 执行全新的代码和数据,不再和父进程有瓜葛。
替换原理
??用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
??以下函数的参数末尾都一定需要带上一个==“NULL”==。
#include <unistd.h>
// path是要替换的程序文件路径+文件名,main函数的argv参数中是什么,这里就怎么写。
int execl(const char *path, const char *arg, ...);
// 会自动到环境变量PATH中根据file寻找新程序的文件,所以file可以直接写成文件名即可。
int execlp(const char *file, const char *arg, ...);
// 自己可以维护环境变量
int execle(const char *path, const char *arg, ...,char *const envp[]);
// 将命令装入数组中,下面的三个函数其余的跟上面的三个函数都是一样的。
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
??事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve, man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
下图exec函数族 一个完整的例子:
int execl(const char *path, const char *arg, ...);
// test.cpp:
#include <iostream>
#include <unistd.h>
int main()
{
pid_t fd = fork();
if (fd == 0)
{
// child
execl("/root/Y23_12_28/t", "t", "-start", NULL);
}
return 0;
}
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("%s %s\n", argv[0], argv[1]);
printf("我是t.cpp进程\n");
return 0;
}
int execlp(const char *file, const char *arg, ...);
此时我们打印环境变量PATH:
#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main(int argc, char *argv[])
{
printf("%s", getenv("PATH"));
return 0;
}
打印当前的文件路径cwd:
??对比会发现PATH中没有cwd,若是直接使用execlp的话一定会报错。我们来试一下:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t fd = fork();
if (fd == 0)
{
// child
int ret = execlp("t", "t", "-start", NULL);
if (ret < 0)
{
printf("execpl错误\n");
}
}
return 0;
}
??果然报错了,原因是PATH中没有cwd,无法自动找到文件位置。所以我们需要添加环境变量,使用putenv()函数,使用这个函数需要注意的是,会将原来的PATH内容全部清除,然后再添加新的PATH。
//test.cpp:
#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main()
{
pid_t fd = fork();
if (fd == 0)
{
char *old_PATH = getenv("PATH");
char new_PATH[] = ":/root/Y23_12_28";
char new1[1000];
sprintf(new1, "PATH=%s%s", old_PATH, new_PATH);
putenv(new1);
// child
int ret = execlp("t", "t", "-start", NULL);
if (ret < 0)
{
printf("execpl错误\n");
}
}
return 0;
}
//t.cpp:
#include <stdio.h>
#include <cstdlib>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
printf("%s %s\n", argv[0], argv[1]);
printf("我是t.cpp进程\n");
return 0;
}
??这里有一个需要注意的地方,就是合并新老PATH时,一定不要忘记PATH=%s%s
的PATH=
int execle(const char *path, const char *arg, ...,char *const envp[]);
??多出来的这个e表明了可以自己设定环境变量:
// test.cpp
#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main()
{
pid_t fd = fork();
if (fd == 0)
{
char *const envp[] = {"PATH=/root/Y23_12_28", "NULL"};
// child
int ret = execle("t", "t", "-start", NULL);
if (ret < 0)
{
printf("execpl错误\n");
}
}
return 0;
}
int execv(const char *path, char *const argv[]);
??v表示使用数组来装载命令, 而不是vector;
#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main()
{
pid_t fd = fork();
if (fd == 0)
{
// child
char *arg[3] = {"t", "-start", "NULL"};
int ret = execv("/root/Y23_12_28/t", arg);
if (ret < 0)
{
printf("execpl错误\n");
}
}
return 0;
}
??至于execvp和execve在这里就不过多赘述了。
????😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄