目录
自定义程序替换
Makefile
mypragma.cc☞mypragma
testexec.c☞testexec
test.py&test.sh
execle&execvpe
1.自定义
testexec.c☞testexec
mypragma.cc☞mypragma
2.系统
3.系统修改putenv
execve
替换函数总结
自定义程序替换
前面我们举例进程程序替换用的都是系统命令,可不可以替换我们自己写的程序呢❓
- 当然可以
- 我们以execl为例子,请看下面代码C语言替换C++语言。(C++源文件的后缀.cpp/.cc/.cxx均可)
当然无论是C/C++,Java,shell脚本语言都是可以进程程序替换的- shell脚本语言,有自己的解释器bash
- python语言,自己的解释器python3;Java也是一个半解释语言也有编译器。
- 所有的脚本语言都是需要一个解释器,解释器本身也是由C++语言所写的二进制文件。(可以形成一个可执行程序形成一个进程)
- 脚本语言也可以加上可执行权限,在命令行中带路径去执行,本质上也是交给解释器去解释的。
- 所以解释的过程就相当于进程程序替换的过程。
- 综上所述,所有语言在Linux底下执行,直接间接变成进程,就是被进程程序替换。所以可以用C语言调用其他语言,因为其他语言写成二进制文件(可执行程序),传参到替换函数中(解释器中),形成了进程被调用执行。
Makefile
- Makefile在形成可执行程序时,默认从上到下匹配。
- Makefile只会默认形成遇到的第一个二进制文件的可执行程序。
如果想要一次性形成多个可执行程序❓一次编译形成多个可执行程序❓
- 定义一个伪目标放到第一个匹配列包含形成的可执行程序
- Makefile为了完成第一个匹配,就会依次完成下面的匹配
- 伪目标有依赖关系,无依赖方法
- Makefile从上往下扫描。先all,all无依赖方法,有依赖关系(需要推导)。
- 所以Makefile会推导完所有的依赖关系(形成必要的可执行程序去完成依赖关系)
- all没有依赖方法不执行
Makefile
1 .PHONY:all
2 all:testexec mypragma
3
4 testexec:testexec.c
5 gcc -o $@ $^
6 mypragma:mypragma.cc
7 g++ -o $@ $^ -std=c++11
8 .PHONY:clean
9 clean:
10 rm -f testexec mypragma
mypragma.cc☞mypragma
mypragma.cc
1 #include<iostream>
2 using namespace std;
3 int main()
4 {
5 cout << "hello C++,I am a C++ pragama !" << endl;
6 cout << "hello C++,I am a C++ pragama !" << endl;
7 cout << "hello C++,I am a C++ pragama !" << endl;
8 cout << "hello C++,I am a C++ pragama !" << endl;
9 cout << "hello C++,I am a C++ pragama !" << endl;
10 cout << "hello C++,I am a C++ pragama !" << endl;
11 return 0;
12 }
testexec.c☞testexec
testexec.c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6
7 int main()
8 {
9 printf("testexec.... begin!\n");
10 pid_t id = fork();
11 if(id == 0)
12 {
13 //child
14 sleep(2);
15 execl("./mypragma","mypragma",NULL); //❗
16 exit(1);
17 }
18 //father
19 int status = 0;
20 pid_t rid = waitpid(id,&status,0);
21 if(rid > 0)
22 {
23 printf("father wait success!,child exit code: %d\n",WEXITSTATUS(status));
24 }
25 printf("testexec... end!\n");
26 return 0;
27 }
【证明并没有创建新的进程】
test.py&test.sh
execle&execvpe
- e:environment环境变量
- int execle(const char *path, const char *arg, ...,char *const envp[ ]);
- int execvpe(const char *file, char *const argv[ ],char *const envp[ ]);
- file程序的路径
- argv怎么执行(命令行参数)
- envp允许你传递的环境变量
- envp:
- 自定义环境变量(会整体替换系统的环境变量)
- bash系统的环境变量(extern char**environ)
- 系统的环境变量稍微修改,给子进程(增删)(putenv)
- putenv
- man putenv
- 调用函数。谁调用putenv。这个函数就给这个进程导出一个全新的环境变量
- 想要在子进程中使用修改系统的环境变量,可以在父进程/子进程任何一个都可以调用putenv函数
- 环境变量字符串指针直接传入putenv即可(KV形式)
- 相当于添加到了当前的环境变量中。
bash有环境变量,也可以获取命令行参数- bash创建父进程
- fork创建子进程
- 通过exec*等函数将环境变量表和命令行参数表交给可执行程序
- 子进程运行起来,并使用两张表
- 程序替换的写法很多,用标准写法即可。
- 平时使用的指令以及指令选项都是父进程给的,命令行参数和环境变量都是父进程给的
- 父进程内部有两张表,也可以通过execvpe等替换函数将2张表传给子进程
- 父可以自定义环境变量和命令行参数表通过exec函数传给子进程
- 父进程本身就有一批环境变量!!从bash来!!
1.自定义
testexec.c☞testexec
testexec.c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6
7 int main()
8 {
9 printf("testexec.... begin!\n");
10 pid_t id = fork();
11 if(id == 0)
12 {
13 char *const argv[]=
14 {
15 (char*)"mypragma",
16 NULL
17 };
18 char *const envp[]=
19 {
20 (char*)"HAHA=11111111111",
21 (char*)"HEHE=22222222222",
22 NULL
23 };
24 //child
25 printf("child id:%d\n",getpid());
26 sleep(2);
27 execvpe("./mypragma",argv,envp);
28 exit(1);
29 }
30 //father
31 int status = 0;
32 pid_t rid = waitpid(id,&status,0);
33 if(rid > 0)
34 {
35 printf("father wait success!,child exit code: %d\n",WEXITSTATUS(status));
36 }
37 printf("testexec... end!\n");
38 return 0;
39 }
mypragma.cc☞mypragma
mypragma.cc
1 #include<iostream>
2 #include<unistd.h>
3 using namespace std;
W> 4 int main(int argc,char* argv[],char *env[])
5 {
6 int i=0;
7 cout << "hello C++,I am a C++ pragama !" << getpid() << endl;
8 cout << "hello C++,I am a C++ pragama !" << getpid() << endl;
9 cout << "hello C++,I am a C++ pragama !" << getpid() << endl;
10 cout << "hello C++,I am a C++ pragama !" << getpid() << endl;
11 cout << "hello C++,I am a C++ pragama !" << getpid() << endl;
12 cout << "hello C++,I am a C++ pragama !" << getpid() << endl;
13 printf("-------------------------------------------------\n");
14 for(;argv[i];i++)
15 {
16 printf("argv[%d] : %s\n",i,argv[i]);
17 }
18 printf("-------------------------------------------------\n");
19 for(i = 0;env[i];i++)
20 {
21 printf("env[%d] : %s\n",i,env[i]);
22 }
23 printf("-------------------------------------------------\n");
24 return 0;
25 }
2.系统
25 extern char**environ;
26 printf("child id:%d\n",getpid());
27 sleep(2);
28 execvpe("./mypragma",argv,environ);
29 exit(1);
3.系统修改putenv
if(id == 0)
12 {
W> 13 putenv("tangsiqi=777777777");
14 char *const argv[]=
15 {
16 (char*)"mypragma",
17 NULL
18 };
W> 19 char *const envp[]=
20 {
21 (char*)"HAHA=11111111111",
22 (char*)"HEHE=22222222222",
23 NULL
24 };
25 //child
26 extern char**environ;
27 printf("child id:%d\n",getpid());
28 sleep(2);
29 execvpe("./mypragma",argv,environ);
30 exit(1);
31 }
execve
- Linux支持的C语言标准GNU封装的库函数
- man 2 execve
- execve是唯一的替换函数中的系统调用接口
- 形式单一,传参数简单
- 所有的程序替换库函数底层全部都是由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 execvpe(const char *file, char *const argv[],char *const envp[]);
//系统调用函数
int execve(const char *path, char *const argv[], char *const envp[]);
函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值。
函数解释:
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
- 磁盘中的可执行程序变成子进程☞父进程是bash☞程序被bash加载内存变成进程这个过程本来也就是进程的程序替换
exec调用举例如下:
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}
🙂感谢大家的阅读,若有错误和不足,欢迎指正。下篇进入基础IO篇。进程基础和进程控制总结和练习题以及具体的自编写shell我们会在本周末实现。