文章目录
- 什么是进程程序替换
- 为什么要进行进程程序替换
- 怎么进行进程程序替换
- execl
- execv
- execlp
- execvp
- execle
- execvpe
- 使用c的可执行程序调用一个python脚本
- 如何理解进程程序替换
- 进程程序替换接口的返回值
- 从进程独立性体会程序替换
什么是进程程序替换
在讲进程程序替换之前,首先我们先回顾一下我们先前学习的内容:
在前面的学习里面,我们学会了使用fork系统调用来进行子进程的创建。并且我们知道,子进程的大多数属性继承自父进程。 这就意味着,子进程会重复做和父进程一样的事情。即使使用if和else控制,但是这一部分仍然属于是父进程的部分! 显然,如果我们只是单纯地让子进程重复父进程的行为是没有任何实际应用价值的! 那么我们渴望一种方式,能够让子进程做和父进程不一样的事情!
而这样的一种方式就是进程程序替换
为什么要进行进程程序替换
除了让子进程的能够执行和父进程不同的事情以外。有的场景下,c++开发组需要使用python开发组提供的某种组件,但是由于语言之间的不能直接兼容,所以往往需要第三方工具做转换。而比较简单的方式就是使用进程程序替换的方式完成跨语种的调用! 所以在跨语种的调用的时候,进程程序替换也是一种较好的方式。
怎么进行进程程序替换
前面我们讲了一系列的有关进程程序替换的概念和好处。接下来我们就来见一见猪跑 来使用一下Linux系统提供的进程程序替换接口。
下面是Linux进程程序替换接口:
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 execvpe(const char *file, char *const argv[],
char *const envp[]);
接下来我们就来详细看一看这些接口如何使用
execl
首先我们先来看第一个接口:execl,对应的函数声明如下
int execl(const char *path, const char *arg, …);
在C语言里面,…代表的是可变参数列表。即这个参数可以带上不限数目的参数。下面我们就来对这个接口的两个参数进行解读。
path:路径:就是告诉被替换的进程要去执行那一个程序,要执行一个程序的前提是要找到这个程序!
args:执行程序的方式:选项参数
接下来我们使用代码执行ls命令
#include<stdio.h>
#include<unistd.h>
/*
* 进程程序替换--->替换成ls -l
* */
int main()
{
printf("我是一个进程我的pid是%d\n",getpid());
//最后必须一NULL结尾
execl("/usr/bin/ls","ls","-l",NULL);
//程序替换以后是否会执行这句代码呢?
printf("我是一个进程,我的pid是%d\n",getpid());
return 0;
}
执行结果如下:
从运行的结果来看,后续的打印代码并没有执行. 而对于这一现象我们会在后面详细解析。或许你会觉得这样使用这个接口太麻烦了,而实际上,官方还给我们提供了一系列更简便的方式来进行程序替换。接下来我们再来看下面的接口
execv
老规矩,先从官方文档里面来看函数的声明
int execv(const char *path, char *const argv[]);
参数解析:1.第一和先前一样,指向我们的要替换程序的所在路径,第二个参数则是一个字符指针数组,指向的就是我们所要执行的命令和命令的选项
#include<stdio.h>
#include<unistd.h>
/*
*进程程序替换 ,依旧是执行ls相关的代码
* execv
* */
int main()
{
char* args[]={
(char*) "ls",
(char*)"-a",
(char*)"-l",
NULL
};
execv("/usr/bin/ls",args);
return 0;
}
执行结果也是一样的
也就是说,execl和execv仅仅只在传递参数上存在一些不同,其余都是一致的!而对于l和v的理解可以如下理解
L list, 是一个列表,所以是一个列表
V: vector, 是一个向量,所以是一个数组
但是聪明的你又会开始抱怨,每次都要告诉我这个进程去哪里替换这个程序。我大多数情况下替换的都是系统的命令,难道就不能让这个进程去默认的系统路径下找对应的命令。这样我只要告诉进程怎么执行这个新的程序的命令就好了。别急,接下来我们就来看一看程序替换接口里面能够自动搜索系统路径的接口
execlp
先来看一看函数的声明:
int execlp(const char *file, const char *arg, …);
参数解读:也是列表方式传参,而这里的第一个参数是对应的可执行程序的名字,接口的p就代表的是path,这个接口会从系统默认的可执行路径下寻找程序
#include<stdio.h>
#include<unistd.h>
/*
*调用execlp调用,p是path的意思,带有p的进程程序替换接口
都会默认从系统的路径进行查找
* */
int main()
{
//这两个ls的意义不一样,前者说明我要执行那一个程序,后者是执行的方式
//最后都要一NULL结尾
execlp("ls","ls","-a","-l",NULL);
return 0;
}
execvp
这个接口和execlp类似,也会从默认的系统路径下搜索,仅仅是传参的方式不同罢了。
#include<stdio.h>
#include<unistd.h>
/*
*调用execvp调用,p是path的意思,带有p的进程程序替换接口
都会默认从系统的路径进行查找,用法和execlp基本类似
* */
int main()
{
char* agrc[]={
(char*)"ls",
(char*)"-a",
(char*)"-l"
};
execvp("ls",agrc);
return 0;
}
运行结果就不在演示了,和前面的execlp的效果是一样的。接下来,我们就来介绍最后的两个接口,带了一个e版本的进程替换接口
execle
末尾带e的进程程序替换接口允许我们自定义环境变量,下面我们就来简单用一用这个样例代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{ //使用C语言自带的环境变量,替换对应的c++程序
extern char** environ;
execle("./test_execle","./test_execle",NULL,environ);
return 0;
}
//--------------------------------------
/*
*仅仅是为了观察exelce环境变量的变化问题
* */
#include<iostream>
#include<cstdlib>
int main()
{
std::cout<<"替换成我以后,PATH是"<<getenv("PATH")<<"\n";
std::cout<<"替换成我以后,MYVALUE是"<<getenv("MYVALUE")<<"\n";
return 0;
}
我们可以看到这里我们的MYVALUE没有打印出来,原因是我们还没有把对应的环境变量导入,下面我们把对应的变量导入一下
export MYVALUE="测试环境变量"
导入完毕以后,对应的系统的打印结果如下。可以看到,这里我们的系统变量和path变量都打印出来了。
那么接下来,我们就尝试自己设置进程替换的环境变量
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<unistd.h>
int main()
{ /*
自行设置环境变量
*/
printf("我是一个C进程,我要替换一个C++程序\n");
char* const env[]={
(char*) "MYVALUE=测试环境变量",
NULL
};
int ret=execle("./test_execle","./test_execle",NULL,env);
printf("%d\n",ret);
return 0;
}
运行结果:
可以看到,我们导入的环境变量是正常打印了,但是系统的环境
变量却找不到了!也就是说,这个环境变量的添加是覆盖式地写入!
所以我们在使用带e系列的接口的使用要特别注意环境变量覆盖式写入的问题。
execvpe
这个接口使用的方式和execle的方式相似,不过就是因为它带了一个p,所以我们可以直接指定替换的程序名字即可。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
/*
* 使用execvpe接口,使用方式是execle类似,只不过不要自己带路径了
* */
int main()
{
printf("我是一个进程,我要使用进程程序替换接口了\n");
extern char** environ;
char* const arg[]={
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
char* const env[]={
*environ,
NULL
};
execvpe("ls",arg,env);
return 0;
}
使用c的可执行程序调用一个python脚本
那么进程程序替换除了可以让父子进程执行不同的任务以外,另外一个用途就是可以用来跨语言调用,即用可以用一个cpp程序去替换一个python代码,我们准备如下的py脚本
#!usr/bin/python3
print('hello python')
print('hello python')
print('hello python')
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
/*
*跨语言平台调用
*
* */
int main()
{
printf("我是一个C语言写的程序,我跑起来就是一个进程,我要去替换一个python代码\n");
//要执行的是/usr/bin/python3
char* const argv[]={
(char*)"python3",
(char*)"./test.py",
NULL,
};
execvp("python",argv);
return 0;
}
运行结果如下:
如何理解进程程序替换
接下来,我们来深入谈一谈进程在进行程序替换的时候发生了什么。
进程程序替换接口的返回值
首先,细心的读者一定会注意到,这些进程程序替换都有一个返回值。先前我们使用其他的系统接口,我们都会特别注意返回值的判断,那么进程程序替换的返回值我们需要做判断吗?
并不需要! 因为一旦进程程序替换成功,当前的进程就去执行别人的代码了,根本就不会在返回我们这个进程继续执行了!这种情况下根本不会拿到对应的返回值,而一旦进程程序替换失败。那么我们就能拿到这个返回值,此时只要我们拿到这个返回值,必然已经是替换失败了。所以不必多此一举来判断返回值。
从进程独立性体会程序替换
接下来我们从进程的独立性和进程地址空间的角度来看待程序替换:
简单的一个结论;进程程序替换:替换的程序载入内存,写时拷贝代码段,重新修改页表映射关系
这就是本文的主要内容,如有不足之处,还望指出。