目录
前言:
1.替换原理
编辑 2.替换函数
2.1函数 execl
2.2函数 execv
2.3函数 execlp
2.4函数 execvp
2.5函数 execle
2.6函数 execve
2.7函数 execvpe
前言:
前面我们介绍了进程控制中的创建,退出等待,本章节我们将继续介绍一下进程替换,以上就是关于 Linux进程控制(创建、终止、等待) 的相关知识了,我们学习了 子进程
是如何被创建的,创建后又是如何终止的,以及 子进程
终止 父进程
需要做些什么,有了这些知识后,在对 进程
进行操作时能更加灵活和全面。
1.替换原理
2.替换函数
其实有六种以exec开头的函数,统称exec函数
#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 *path, char *const argv[], char *const envp[]);
函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值。
命名理解:
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
2.1函数 execl
首先是最简单的替换函数 execl
#include <unistd.h>
int execl(const char* path, const char* arg, ...);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序的路径,如
/usr/bin/ls
- 参数2:待替换程序的名称,如
ls
- 参数3~N:待替换程序的选项,如
-a -l
等,最后一个参数为NULL
,表示选项传递结束 ...
表示可变参数列表,可以传递多个参数
注意: 参数选项传递结束或不传递参数,都要在最后加上 NULL
,类似于字符串的 '\0'
#include <stdio.h>
#include <unistd.h>
int main()
{
//execl 函数
printf("程序替换前,you can see me\n");
int ret = execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
//程序替换多发生于子进程,也可以通过子进程的退出码来判断是否替换成功
if(ret == -1)
printf("程序替换失败!\n");
printf("程序替换后,you can see me again?\n");
return 0;
}
可以看出,函数 execl
中的 命令+选项+NULL
是以 链式
的方式进行传递的,方便理解。
2.2函数 execv
替换函数 execv
是以顺序表 vector
的方式传递 参数2~N
的
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序的路径,如
/usr/bin/ls
- 参数2:待替换程序名及其命名构成的
指针数组
,相当于一张表
注意: 虽然 execv
只需传递两个参数,但在创建 argv
表时,最后一个元素仍然要为 NULL
#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
//execv 函数
pid_t id = fork();
if(id == 0)
{
printf("子进程创建成功 PID:%d PPID:%d\n", getpid(), getppid());
char* const argv[] =
{
"ls",
"-a",
"-l",
NULL
}; //argv 表,实际为指针数组
execv("/usr/bin/ls", argv);
printf("程序替换失败\n");
exit(-1); //如果子进程有此退出码,说明替换失败
}
int status = 0;
waitpid(id, &status, 0); //父进程阻塞等待
if(WEXITSTATUS(status) != 255)
{
printf("子进程替换成功,程序正常运行 exit_code:%d\n", WEXITSTATUS(status));
}
else
{
printf("子进程替换失败,异常终止 exit_code:%d\n", WEXITSTATUS(status));
}
return 0;
}
运行结果:
与 execl
函数不同,execv
是以表的形式进行参数传递的
2.3函数 execlp
可能有的人觉得写 path
路径很麻烦,还有可能会写错,那么能否换成 自动挡
替换呢?
答案是可以的,execlp
函数在进行程序替换时,可以不用写 path
路径
#include <unistd.h>
int execlp(const char* file, const char* arg, ...);
execlp
就像是 execl
的升级版,可以自动到 PATH
变量中查找程序
注意: 只能在环境变量表中的 PATH
变量中搜索,如果待程序路径没有在 PATH
变量中,是无法进行替换的
#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
//execlp 函数
pid_t id = fork();
if(id == 0)
{
printf("you can see me\n");
execlp("ls", "ls", "-a", "-l", NULL); //程序替换
printf("you can see me again?");
exit(-1);
}
int status = 0;
waitpid(id, &status, 0); //等待阻塞
if(WEXITSTATUS(status) != 255)
printf("子进程替换成功 exit_code:%d\n", WEXITSTATUS(status));
else
printf("子进程替换失败 exit_code:%d\n", WEXITSTATUS(status));
return 0;
}
使用 execlp
替换程序更加方便,只要待替换程序路径位于 PATH
中,就不会替换失败
2.4函数 execvp
execv
加个 p
也能实现自动查询替换,即 execvp
#include <unistd.h>
int execvp(const char* file, char* const argv[]);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序名,需要位于
PATH
中 - 参数2:待替换程序名及其命名构成的
指针数组
#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
//execvp 函数
pid_t id = fork();
if(id == 0)
{
printf("子进程创建成功 PID:%d PPID:%d\n", getpid(), getppid());
char* const argv[] =
{
"ls",
"-a",
"-l",
NULL
};
execvp("ls", argv);
printf("程序替换失败\n");
exit(-1); //如果子进程有此退出码,说明替换失败
}
int status = 0;
waitpid(id, &status, 0); //父进程阻塞等待
if(WEXITSTATUS(status) != 255)
{
printf("子进程替换成功,程序正常运行 exit_code:%d\n", WEXITSTATUS(status));
}
else
{
printf("子进程替换失败,异常终止 exit_code:%d\n", WEXITSTATUS(status));
}
return 0;
}
2.5函数 execle
e
表示 env
环境变量表,可以将自定义或当前程序中的环境变量表传给待替换程序
#include <unistd.h>
int execl(const char* path, const char* arg, ..., char* const envp[]);
函数解读
- 最后一个参数:替换成功后,待替换程序的环境变量表,可以自定义
char* const myenv[] = {"myval=100", NULL}; //自定义环境变量表
execle("./other/CPP", NULL, myenv); //程序替换
替换为自己写的程序 CPP
//当前源文件为 test.cc 即 C++源文件
// .xx 后缀也可以表示 C++源文件
#include <iostream>
using namespace std;
extern char** environ; //声明环境变量表
int main()
{
int pos = 0;
//只打印5条
while(environ[pos] && pos < 5)
{
cout << environ[pos++] << endl;
}
return 0;
}
可以看到,程序 CPP
中的环境变量表变成了自定义环境变量,即只有一个环境变量 myval=100
改变 execle
最后一个参数,传入默认环境变量表
extern char** environ;
execle("./other/CPP", NULL, environ); //继承环境变量表
结论: 如果主动传入环境变量后,待替换程序中的原环境变量表将被覆盖
现在可以理解为什么在 bash 中创建程序并运行,程序能继承 bash 中的环境变量表了
在 bash 下执行程序,等价于在 bash 下替换子进程为指定程序,并将 bash 中的环境变量表 environ 传递给指定程序使用
其他没有带 e 的替换函数,默认传递当前程序中的环境变量表
2.6函数 execve
execve 是系统真正提供的程序替换函数,其他替换函数都是在调用 execve
比如
execl 相当于将链式信息转化为 argv 表,供 execve 参数2使用
execlp 相当于在 PATH 中找到目标路径信息后,传给 execve 参数1使用
execle 的 envp 最终也是传给 execve 中的参数3
#include <unistd.h>
int execve(const char* filename, char* const argv[], char* const envp[]);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序的路径
- 参数2:待替换程序名及其参数组成的
argv
表 - 参数3:传递给待替换程序的环境变量表
替换 ls -a -l
程序
extern char** environ;
execve("/usr/bin/ls", argv, environ);
替换为自定义程序 CPP
extern char** environ;
execve("./other/CPP", argv, environ);
替换函数除了能替换为 C++
编写的程序外,还能替换为其他语言编写的程序,如 Java
、Python
、PHP
等等,虽然它们在语法上各不相同,但在 OS 看来都属于 可执行程序
,数据位于 代码段
和 数据段
,直接替换即可
系统级接口是不分语言的,因为不论什么语言最终都需要调用系统级接口,比如文件流操作中的
open
、close
、write
等函数,无论什么语言的文件流操作函数都需要调用它们
2.7函数 execvpe
对 execvp
的再一层封装,使用方法与 execvp
一致,不过最后一个参数可以传递环境变量表
#include <unistd.h>
int execvpe(const char* file, char* const argv[], char* const envp[]);
函数解读
- 返回值:替换失败返回
-1
- 参数1:待替换程序名,需要位于
PATH
中 - 参数2:待替换程序名及其命名构成的
指针数组
- 参数3:传递给待替换程序的环境变量表
extern char** environ;
execvpe("ls", argv, environ);