Linux进程替换(下)
💦 函数解释及使用
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回 -1。
- 所以 exec 函数只有出错的返回值而没有成功的返回值。
✔ 测试用例一:
单进程,父进程亲自干活。
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("my process begin!\n");
execl("/usr/bin/ls", "ls", "-a", "-l", "-i", NULL);
printf("my process end!\n");
return 0;
}
多进程,父进程创建子进程干活。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
execl("/usr/bin/ls", "ls", "-a", "-l", "-i", NULL);
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("child status -> sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
}
else
{
printf("wait error!\n");
}
return 0;
}
💨运行结果:
为什么图一没有输出 my process end ! && 图二的退出码是 0 ❓
因为在这之前 execl 已经程序替换了,所以 execl 后面的代码已经不是当前进程的代码了,所以图二获取到的退出码 0 是 ls 的退出码。换言之,一旦程序替换,你到底执行正确与否是取决于 ls 程序。
所以 exec 系列的函数不用考虑返回值,只要返回了,一定是这个函数调用或程序替换失败了。
注意编程规范是父进程创建子进程干活。
加载器 ❓
一个完整的集成开发环境的组件肯定包括编辑器、编译器、调试器、加载器等。一个软件被加载到内存里,肯定是运行起来,形成进程,进程再调用 exec 系列的函数就可以完成加载的过程。所以 exec 可以理解成一种特殊的加载器。
✔ 测试用例二:
execv 与 execl 较为类似,它们的唯一差别是,如果需要传多个参数,那么:execl 是以可变参数的形式进行列表传参;execv 是以指针数组的形式传参。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//const char* const my_argv[] = {"ls", "-l", "-a", "-i", NULL};//err,注意要与函数原型的参数类型匹配
char* const my_argv[] = {"ls", "-l", "-a", "-i", NULL};
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
execv("/usr/bin/ls", my_argv);
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("child status -> sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
}
else
{
printf("wait error!\n");
}
return 0;
}
💨运行结果:
✔ 测试用例三:
execlp 相比 execl 在命名上多了 1 个 p,且参数只有第 1 个不同:不同点在于 execlp 不需要带路径,execlp 在执行时,它会拿着你要执行的程序自动的在系统 PATH 环境变量中查找你要执行的目标程序。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
execlp("ls", "ls", "-a", "-i", "-l", NULL);
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("child status -> sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
}
else
{
printf("wait error!\n");
}
return 0;
}
💨运行结果:
带 p 的含义就是不用带路径,系统会自动搜索你要执行的程序,不带 p 则相反。所以你要执行哪个程序,背后的含义是 a) 你在哪 b) 你是谁。可见 execlp 就是 b,execl 就是 ab。当然这里的搜索默认只有系统的命令才能找到,如果需要执行自己的命令,需要提前把自己的命令与 PATH 关联。
✔ 测试用例四:
所以 execvp 无非就是不带路径,使用指针数组传参。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
char* const my_argv[] = {"ls", "-l", "-a", "-i", NULL};
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
execvp("ls", my_argv);
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("child status -> sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
}
else
{
printf("wait error!\n");
}
return 0;
}
💨运行结果:
✔ 测试用例五:
e 表示传入默认的或者自定义的环境变量给目标可执行程序。
子进程跑自己的程序 mycmd.c。
makefile 里需要 make 时一次生成 2 份可执行程序。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc, char* argv[], char* env[])
{
pid_t id = fork();
if(id == 0)
{
char* const my_env[] = {"MYENV=hellobit!", NULL};
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
//测试1
//execle("./mycmd", "mycmd", NULL, my_env);
//测试2
//char* const my_argv[] = {"mycmd", NULL};
//execve("./mycmd", my_argv, my_env);
//测试3
//execvpe("mycmd", my_argv, my_env);
//测试4
execle("./mycmd", "mycmd", NULL, env);
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("child status -> sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF);
}
else
{
printf("wait error!\n");
}
return 0;
}
💨运行结果:
-
测试 1 && 测试 2
-
测试 3
execvpe 并没有替换成功,因为它带了 p,所以它会在环境变量里查找目标程序,但是此时目标程序就在当前路径。所以当前场景是不适合使用带 p 的函数的。如果想让 execvpe 查找到目标程序,就只能将当前路径添加到环境变量中,或将目标程序添加到任意环境变量的路径下。
-
测试 4
main 函数可以获得环境变量,环境变量再传给子进程。所以现在我们就能理解环境变量是怎么被子进程继承的,本质是通过 exec 函数将环境变量传入的。
💦 命名理解
这些函数原型看起来很容易混淆,但只要掌握了规律就很好记。
- l(list),表示参数采用列表。
- v(vector),表示参数使用数组。
- p(path),自动搜索环境变量 PATH。
- e(env),表示自己维护环境变量。
- 点赞
- 收藏
- 关注作者
评论(0)