【Linux】进程控制(四):进程程序替换 exec 函数簇
一、进程程序替换
1.1 概念
??用 fork 创建子进程后执行的是和父进程相同的程序,此时子进程就需要进程程序替换,进程程序替换就是将已经跑起来的进程替换成执行其它程序的进程,也就是说该进程的用户空间代码和数据完全被新程序替换,子进程从新程序的启动历程开始执行。
1.2 原理
??替换函数将正在执行的进程的进程虚拟地址空间里的代码段和数据段替换成新的程序,当前在执行的进程就会执行替换之后的程序代码,并且堆区和栈区都需要更新。
1.3 为什么要进程程序替换
??守护进程先启动,创建一个子进程,让子进程程序替换成为另一个程序,实现不同的一个功能,父子进程会进行通信,实现 “当子进程异常退出时,守护进程就会重新拉起子进程”。(但不能说守护进程一定能够解决子进程异常退出的问题,异常退出可能是子进程的程序代码存在一定的问题)
??我们常用的 shell 命令行解释器,就是采用的进程程序替换的原理。
二、进程替换接口
??exce 函数簇: 它不是一个函数,而是一堆函数。(execl 、execlp、execle、execv、execvp、execve)
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
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 *file, char *const argv[],char *const envp[]);
2.1 execl 函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
参数:
??path: 带路径的可执行程序,即替换的可执行程序的路径和名称
??arg: 给可执行程序传递的参数(传递的第一个参数必须是可执行程序的名称,后面依次传递,中间以 “逗号” 隔开,最后以 “NULL” 结束)
返回值:
??替换成功,没有返回值;替换失败,返回小于 0 的值,仍旧执行当前的程序代码。
验证:
??shift 程序替换 execl 程序
#include<unistd.h>
#include<stdio.h>
int main()
{
printf("---begin---\n");
printf("i am execl\n");
execl("./shift","shift",NULL);
printf("---end---\n");
return 0;
}
#include <stdio.h>
int main()
{
int count = 5;
printf("---repalce---\n");
while(count--)
{
printf("i am shift\n");
}
return 0;
}
??可以看到进程程序替换成功,execl 程序执行 i am execl 后,去运行 shift 程序了,和之前的程序就没有关系了,也不会打印 ---end--- 。
??如果将 shift 可执行程序移动到上级目录,和 execl 函数中 path 路径不一致。可以看到进程程序替换失败,打印 ---end--- 后 execl 程序从 return 返回。
??使用 which ls 查看到 ls 可执行程序的路径为 /usr/bin/ls 。我们使用 ls 程序来替换 execl 程序。
execl("/usr/bin/ls","ls","-a",NULL);
??可以看到 execl 程序执行 i am execl 后,去运行 ls 程序了。我们的可执行程序是 ls,传递的参数是 -a , 可以看到它和 ls -a 执行的效果是一样的,只是我们使用的进程程序替换后的结果没有高亮显示。
2.2 execlp 函数
#include <unistd.h>
int execlp(const char *file, const char *arg, ...);
参数:
??file: 替换的可执行程序的名称(替换的可执行程序必须是在 PATH 环境变量当中可以找到的,也就是说待替换的可执行程序需要能被操作系统找到。这里也可以传递带路径的可执行程序)
??arg: 给可执行程序传递的参数(传递的第一个参数必须是可执行程序的名称,后面依次传递,中间以 “逗号” 隔开,最后以 “NULL” 结束)
返回值:
??替换成功,没有返回值;替换失败,返回小于 0 的值。
验证: ??pwd 程序替换 execlp 程序
#include<unistd.h>
#include<stdio.h>
int main()
{
printf("---begin---\n");
execlp("pwd","pwd",NULL);
printf("---end---\n");
return 0;
}
??可以看到进程程序替换成功, execlp 程序执行 ---begin--- 后,在 PATH 环境变量中找 pwd 路径去运行 pwd 程序了。
??带 p 和不带 p : 如果 exec 函数当中带有 p,则表示会搜索环境变量,并且第一个参数传入可执行程序的名称即可。不带 p,则表示不会搜索环境变量,所以需要传递带路径的可执行程序。
2.3 execle 函数
#include <unistd.h>
int execle(const char *path, const char *arg,..., char * const envp[]);
参数:
??path: 带路径的可执行程序。
??arg: 给可执行程序传递的参数(传递的第一个参数必须是可执行程序的名称,后面依次传递,中间以 “逗号” 隔开,最后以 “NULL” 结束)
??envp: 程序员自己组织的环境变量,如果不传入,则认为当前替换之后的程序没有环境变量。
返回值:
??替换成功,没有返回值;替换失败,返回小于 0 的值。
??带 e 和不带 e : 如果 exec 函数当中带有 e,则需要自己组织环境变量。不带 e,则不需要程序员自己组织环境变量。
2.4 execv 函数
#include <unistd.h>
int execv(const char *path, char *const argv[]);
参数:
??path: 带路径的可执行程序。
??argv: 给可执行程序传递的参数(数组的第一个参数是可执行程序的名称,最后一个参数是 “NULL”)
返回值:
??替换成功,没有返回值;替换失败,返回小于 0 的值。
验证:
??ls 程序替换 execv 程序
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("---begin---\n");
char * argv[10];
argv[0] = "ls";
argv[1] = "-l";
argv[2] = "..";
argv[3] = NULL;
execv("/usr/bin/ls",argv);
printf("---end---\n");
return 0;
}
??可以看到进程程序替换成功, execv 程序执行 ---begin--- 后,去运行 ls 程序了。我们传递的参数是 -l 和 .. ,可以看到它和 ls -l .. 执行的效果是一样的,都是显示上级目录的文件详细信息。
??带 l 和不带 l: 如果 exec 函数当中带有 l,则表示参数是可变参数列表的形式。不带 l,则表示参数是字符指针数组的形式。
??带 v 和不带 v: 如果 exec 函数当中带有 v,则表示参数是以字符指针数组的形式传递给 exec 函数的,进而传递给待替换的可执行程序。不带 v,则表示参数是可变参数列表的形式。
2.5 execvp 函数
#include <unistd.h>
int execvp(const char *file, char *const argv[]);
参数:
??file: 替换的可执行程序的名称。
??argv: 给可执行程序传递的参数(数组的第一个参数是可执行程序的名称,最后一个参数是 “NULL”)
返回值:
??替换成功,没有返回值;替换失败,返回小于 0 的值。
2.6 execve 函数
#include <unistd.h>
int execve(const char *file, char *const argv[],char *const envp[]);
参数:
??file: 替换的可执行程序的名称。
??argv: 给可执行程序传递的参数(数组的第一个参数是可执行程序的名称,最后一个参数是 “NULL”)
??envp: 程序员自己组织的环境变量,如果不传入,则认为当前替换之后的程序没有环境变量。
返回值:
??替换成功,没有返回值;替换失败,返回小于 0 的值。
验证:
?? ls 程序替换 execve 程序
#include<stdio.h>
#include<unistd.h>
int main()
{
char * argv[10];
argv[0] = "ls";
argv[1] = "-l";
argv[2] = "..";
argv[3] = NULL;
char * envp[10];
envp[0] = "PATH=/usr/bin";
envp[1] = NULL;
execve("/usr/bin/ls",argv,envp);
return 0;
}
??可以看到进程程序替换成功,可以看到它和 ls -l .. 执行的效果是一样的,都是显示上级目录的文件详细信息。
三、总结
int execl(const char *path, const char *arg, ...);
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[]);
??(1)带 l 和不带 l:
??如果 exec 函数当中带有 l,则表示参数是可变参数列表的形式。不带 l,则表示参数是字符指针数组的形式。
??(2)带 v 和不带 v:
??如果 exec 函数当中带有 v,则表示参数是以字符指针数组的形式传递给 exec 函数的,进而传递给待替换的可执行程序。不带 v,则表示参数是可变参数列表的形式。
??(3)带 p 和不带 p :
??如果 exec 函数当中带有 p,则表示会搜索环境变量,并且第一个参数传入可执行程序的名称即可。不带 p,则表示不会搜索环境变量,所以需要传递带路径的可执行程序。
??(4)带 e 和不带 e :
??如果 exec 函数当中带有 e,则需要自己组织环境变量。不带 e,则不需要程序员自己组织环境变量。
??规律:
????l (list) 表示参数采用可变参数列表
????v (vector) 表示参数采用字符指针数组
????p (path) 表示自动搜索环境变量 PATH
????e (env) 表示自己维护环境变量
|