✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】
目录
1、进程程序替换
1.1、替换原理
1.2、替换函数
1.3、函数解释
1.4、命名理解
1.5、代码演示
1.5.1、execl调用举例
1.5.2、execv 和 execvp 调用举例
1.5.3、execvpe调用举例
1、进程程序替换
1.1、替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
1.2、替换函数
其实有六种以exec开头的函数,统称exec函数:
#include <unistd.h>
// C语言封装的库函数
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[]);
// 系统调用
int execve(const char *filename, char *const argv[],char *const envp[]);
1.3、函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1。
- 所以exec函数只有出错的返回值而没有成功的返回值。
1.4、命名理解
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
- l(list) : 表示参数采用列表。
- v(vector) : 参数用数组。
- p(path) : 有p自动搜索环境变量PATH。
- e(env) : 表示自己维护环境变量。
1.5、代码演示
1.5.1、execl调用举例
int execl(const char *path, const char *arg, ...);
代码演示一
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("testexec ... begin!\n");
execl("/usr/bin/ls","ls","-a","-l","--color",NULL);
printf("testexec ... end!\n");
return 0;
}
运行结果
代码演示二
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("testexec ... begin!\n");
execl("/usr/bin/lsss","ls","-a","-l","--color",NULL);
printf("testexec ... end!\n");
return 0;
}
运行结果
多进程版本
fork创建子进程,让子进程自己去替换。
创建子进程,让子进程完成任务:
- 1、让子进程执行父进程代码的一部分
- 2、让子进程执行一个全新的程序(替换)
代码演示一(替换系统命令)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
// 多进程版本
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
// child
printf("I am child process,pid:%d\n",getpid());
sleep(2);
execl("/usr/bin/ls","ls","-a","-l",NULL);
}
// father
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0)
{
printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
}
else
{
printf("father wait failed!\n");
}
printf("testexec ... end!\n");
return 0;
}
运行结果
上面的程序替换,替换的都是系统命令,能否替换自己写的程序呢?
答案是可以的。
补充:
C++文件后缀名的三种方式:.cpp / .cc / .cxx
代码演示二(替换自己程序)
testexec.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
// 多进程版本
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
// child
printf("I am child process,pid:%d\n",getpid());
sleep(2);
execl("./mypragma","mypragma",NULL);
}
// father
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0)
{
printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
}
else
{
printf("father wait failed!\n");
}
printf("testexec ... end!\n");
return 0;
}
mypragma.cc
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
return 0;
}
makefile
此处是自己写的程序,也需要先编译生成可执行程序,下面代码可以使用make一次编译多(两)个文件。
.PHONY:all
all:testexec mypragma
testexec:testexec.c
gcc -o $@ $^ -std=c99
mypragma:mypragma.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf testexec mypragma
运行结果
上面的程序替换的是C++,能否替换其他语言呢?
答案是可以的,我们替换的是可执行程序,只要是程序即可。
在替换之前,我们先简单使用其他语言写一个程序!!!
python
#!/usr/bin/python3
print("hello python")
print("hello python")
print("hello python")
print("hello python")
运行结果
能否像以前一样使用./可执行程序运行程序呢?
答案是可以的,但是需要加执行的权限。
shell
#!/usr/bin/bash
cnt=0
while [ $cnt -le 10 ]
do
echo "hello shell, cnt: ${cnt}"
let cnt++
done
运行结果
代码演示
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
// 多进程版本
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
// child
printf("I am child process,pid:%d\n",getpid());
sleep(2);
//execl("./mypragma","mypragma",NULL);
execl("/usr/bin/python3","phthon3","testpy",NULL);
}
// father
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0)
{
printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
}
else
{
printf("father wait failed!\n");
}
printf("testexec ... end!\n");
return 0;
}
运行结果
1.5.2、execv 和 execvp 调用举例
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
代码演示
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
// 多进程版本
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
// child
printf("I am child process,pid:%d\n",getpid());
sleep(2);
char* const argv[]=
{
(char*)"ls",
(char*)"-a",
(char*)"-l",
(char*)"--color",
NULL
};
// execv("/usr/bin/ls",argv);
execvp("ls",argv);
}
// father
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0)
{
printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
}
else
{
printf("father wait failed!\n");
}
printf("testexec ... end!\n");
return 0;
}
运行结果
1.5.3、execvpe调用举例
int execvpe(const char *file, char *const argv[],char *const envp[]);
代码演示:
mypragma.cc
#include<iostream>
#include<unistd.h>
using namespace std;
int main(int argc,char* argv[],char* env[])
{
int i=0;
for(;argv[i];i++)
{
printf("argv[%d] : %s\n",i,argv[i]);
}
printf("-------------------------------\n");
for(i=0;env[i];i++)
{
printf("env[%d] : %s\n",i,env[i]);
}
printf("-------------------------------\n");
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
cout<<"hello C++,I am a C++ pragma!:"<<getpid()<<endl;
return 0;
}
testexec.c(手写环境变量)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
// 多进程版本
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
// child
char* const argv[]=
{
(char*)"mypragma",
(char*)"-a",
(char*)"-b",
NULL
};
char* const envp[]=
{
(char*)"HAHA=111111",
(char*)"HEHE=222222",
NULL
};
printf("I am child process,pid:%d\n",getpid());
sleep(2);
execvpe("./mypragma",argv,envp);
}
// father
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0)
{
printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
}
else
{
printf("father wait failed!\n");
}
printf("testexec ... end!\n");
return 0;
}
运行结果
testexec.c(系统环境变量+手写)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
// 多进程版本
int main()
{
printf("testexec ... begin!\n");
pid_t id = fork();
if(id == 0)
{
// child
// 我的父进程本身就有一批环境变量,从bash来,自己想添加可以使用putenv
putenv("HHHH=333333333");// 头文件 stdlib.h
char* const argv[]=
{
(char*)"mypragma",
(char*)"-a",
(char*)"-b",
NULL
};
printf("I am child process,pid:%d\n",getpid());
sleep(2);
extern char** environ;
execvpe("./mypragma",argv,environ);
}
// father
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0)
{
printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
}
else
{
printf("father wait failed!\n");
}
printf("testexec ... end!\n");
return 0;
}
运行结果
事实上,只有execve是真正的系统调用,其它六个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
下图exec函数族 一个完整的例子: