1. 进程程序替换是什么
我们直到一个进程被创建出来,OS会给它分配进程PCB,mm_struct,页表等信息,同时会将程序的代码和数据加载到物理内存是吧;
而进程程序替换就是:正在执行的进程本身的pcb,mm_struct,页表等信息不会发生改变,仅仅把一个新的程序代码和数据替换了原来进程的代码和数据;这就是进程程序的替换;
如下图:我们有个进程,在执行中,如果发生进程程序替换,只不过事把该进程的代码和数据从磁盘加载到了物理内存罢了,其他信息不会变化;
进程程序替换并不会创建新的进程,它只会加载程序的代码和数据,去替换原来的进程!!!
2. 进程替换函数–exec族
#include <unistd.h>` //exec函数族的头文件
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[]);
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回;
- 如果调用出错则返回-1;
- 所以exec函数只有出错的返回值而没有成功的返回值;
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表;
v(vector) : 参数用数组;
p(path) : 有p自动搜索环境变量PATH;
e(env) : 表示自己维护环境变量;
最简单的程序替换程序:
int main()
{
execl("/usr/bin/ls","ls","-a","-l",NULL); //程序替换函数
printf("这段代码是在execl执行成功是不会执行的\n");
return 0;
}
而我们程序替换的本质:
而我们的程序替换函数,通常不父进程去执行,而是交给子进程执行,因为这也子进程可以做它的事,父进程也可以做自己的事,由于进程之间的独立性,即使子进程去执行execl函数时候,替换的也是子进程的代码和数据,而父进程的代码和数据是不会被影响的;
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0){
//child process
execl("/usr/bin/ls","ls","-a","-l",NULL);
//退出子进程
exit(0);
}
//父进程执行的代码
wait(NULL);
printf("i am father[%d] ,do my thing ,不会被子进程的[%d]execl函数影响\n",getpid(),pid);
return 0;
}
请问:发生了子进程的程序替换,此时:父子进程的代码还是共享的码?
当然不会共享了,此时发生了写时拷贝,也就是会拷贝出一份代码和数据出来,然后子进程再被它的execl函数进行函数替换;
3. execl函数的使用
4. execv函数的使用
execv和execl也是没啥区别,只不过execv叭execl的以列表形式的传参,变成了以数组形式的传参;
int main()
{
if(fork() == 0){
// execl("/usr/bin/ls","ls","-a","-l",NULL);
//等价下面的execv("/usr/bin/ls",argv);
char*const argv[] = {
"ls",
"-a",
"-l",
NULL
};
execv("/usr/bin/ls",argv);
exit(1);
}
int waitRet = waitpid(-1,NULL,0); //阻塞等待所有子进程结束
if(waitRet< 0){
perror("wait error\n");
}
printf("parent wait child success\n");
return 0;
}
5. execlp函数的使用
execlp和execl的区别在于,execlp在第一个参数时候,不需要全路径,只需要写上执行命令的文件名即可,表示你需要执行谁,往后的参数也就是和execl的传参一样;
#include<stdio.h>
#include<unistd.h>//使用fork,exec函数
#include<sys/wait.h>//使用waitpid
#include<stdlib.h> //使用exit的头文件
int main()
{
if(fork() == 0){
// execl("/usr/bin/ls","ls","-a","-l",NULL);
execlp("ls","ls","-a","-l",NULL); //等价上面的execl()
//虽然这里的第一个参数和第二个参数都一样,但是含义不一样;
//第一个参数表示iexeclp函数要执行命令的路径文件名,
//第二个参数表示execlp在命令行上如何执行该命令
exit(1);
}
int waitRet = waitpid(-1,NULL,0); //阻塞等待所有子进程结束
if(waitRet< 0){
perror("wait error\n");
}
printf("parent wait child success\n");
return 0;
}
6. execvp函数的使用
这个函数execvp和execv的区别在于。execvp第一个传参时候,不需要给出要执行命令的绝对路径,只需要给出要执行命令的文件名即可;
#include<stdio.h>
#include<unistd.h>//使用fork,exec函数
#include<sys/wait.h>//使用waitpid
#include<stdlib.h> //使用exit的头文件
int main()
{
if(fork() == 0){
// execl("/usr/bin/ls","ls","-a","-l",NULL);
//execlp("ls","ls","-a","-l",NULL); //等价上面的execl()
char* const argv[] = {
"ls",
"-a",
"-l",
NULL
};
//execv("/usr/bin/ls",argv);
execvp("ls",argv);//等价上面的execv()
exit(1);
}
int waitRet = waitpid(-1,NULL,0); //阻塞等待所有子进程结束
if(waitRet< 0){
perror("wait error\n");
}
printf("parent wait child success\n");
return 0;
}
7. 验证exec函数族执行自己写的程序
- makefile 文件,编译链接生成myload和myexe两个文件;
- 这两个文件的作用是:myload加载myexe的程序,验证execv函数执行自己写的程序是否可以;
.PHONY:all
all:myload myexe # 该目标文件all不会被生成,是因为没有依赖方法
myload:myload.c
gcc $^ -o $@
myexe:myexe.c
gcc $^ -o $@
.PHONY:clean
clean:
rm -rf myload myexe
myexe.c文件的内容:
#include<stdio.h>
int main(){
printf("i am myexe\n");
return 0;
}
myload.c文件
#include<stdio.h>
#include<unistd.h>//使用fork,exec函数
#include<sys/wait.h>//使用waitpid
#include<stdlib.h> //使用exit的头文件
int main()
{
if(fork() == 0){
execl("./myexe","./myexe",NULL);
exit(1);
}
int waitRet = waitpid(-1,NULL,0); //阻塞等待所有子进程结束
if(waitRet< 0){
perror("wait error\n");
}
printf("parent wait child success\n");
return 0;
}
测试结果:执行myoad,成功加载了myexe程序;
8. execle 和 execve函数的使用
这个函数只是比execl多了一个e,表示最后一个参数需要给execle传入自定义的环境变量数组;
它的使用情况是:假如你要给一个你需要执行新的程序,加载一些自定义的环境变量给新的程序时候,你可以使用该函数;
测试:
myexe.c文件:打印环境变量的值,这个文件假如自己执行自己的话,那么就打印默认的环境变量;
假如其他文件使用execle传参给myexe.c环境变量,myexe.c就会执行该execle传过来的环境变量;
#include<stdio.h>
int main(){
extern char** environ; //系统提供的环境变量指针,指向环境变量的指针数组
int i = 0;
for(;environ[i];i++){
printf("%s\n",environ[i]);
}
return 0;
}
myload.c文件:使用execle函数,给该函数传递环境变量;使得myexe可执行程序可以获得该myload.c传递过去的环境变量;文章来源:https://www.toymoban.com/news/detail-437037.html
#include<stdio.h>
#include<unistd.h>//使用fork,exec函数
#include<sys/wait.h>//使用waitpid
#include<stdlib.h> //使用exit的头文件
int main()
{
if(fork() == 0){
char* const env[] = {
"MYENV0=123",
"MYENV1=456",
"MYENV2=789",
NULL
};
//char* const argv[] = {
// "./myexe",
// NULL
//};
execle("./myexe","./myexe",NULL,env);
//execlv("./myexe",argv,,env); //等价上execle的方式
exit(1);
}
int waitRet = waitpid(-1,NULL,0); //阻塞等待所有子进程结束
if(waitRet< 0){
perror("wait error\n");
}
printf("parent wait child success\n");
return 0;
}
测试结果:
文章来源地址https://www.toymoban.com/news/detail-437037.html
9. exec函数族使用的总结
- 有了exec函数族我们就可以用它来调用任何程序,也就是说,你还可以在C/C++,可以调用其他任何语言,比如python,Java等程序;
- 这些exec函数的接口函数本质是没有任何差别的,只是参数选项不同罢了;
- 为什么有那么多种接口,本质满足不同引用场景而已;
- 这些函数之间的调用关系,本质都会调用系统调用函数execve;
到了这里,关于【Linux】进程控制(exec函数族)的理解和使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!