hello~ 很高兴见到大家! 这次带来的是Linux系统中关于进程控制这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?
个 人 主 页 : 默|笙

文章目录
- 一、理解进程程序替换
- 二、程序替换函数
-
- [2.1 笼统介绍](#2.1 笼统介绍)
- [2.2 execl](#2.2 execl)
- [2.3 execv](#2.3 execv)
- [2.4 execlp和execvp](#2.4 execlp和execvp)
- [2.5 execve、execle和execvpe](#2.5 execve、execle和execvpe)
- [2.6 命名总结](#2.6 命名总结)
接上次的博客->[进程等待]。
一、理解进程程序替换
如果我们想要在一个进程里面执行另一个程序,那么就需要进行程序替换,也就是父进程创建一个子进程,然后通过程序替换的方式将想要执行的程序交给这个子进程去去执行。

- 用fok创建一个进程的子进程之后,如果要用这个子进程去执行一个新的程序,那么就要用到exec系列接口进行程序替换。
- 创建一个子进程,这个子进程会拷贝父进程的从而拥有自己的task_struct(新建结构体并大部分拷贝父进程)、虚拟地址空间和页表,代码和数据与父进程共享。而一旦通过特殊接口进行程序替换,这个子进程的task_struct还是那个task_struct,但是虚拟地址空间会按照新的程序(经由编译器分配好的虚拟地址)来且所有的页表映射将重置。之后执行程序的时候按需将对应程序的代码和数据加载到物理内存里面去,然后建立新的映射关系。
- 所以调用exec系列函数前后pid是不会变的。
- 为什么一定是父进程创建子进程 + 子进程程序替换的方式?因为程序替换会直接重置页表,虚拟地址用的又是新的程序的。如果直接由父进程进行程序替换的话,父进程它本身原来要执行的程序就相当于直接销毁了。
二、程序替换函数
2.1 笼统介绍
cpp
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[]);
- 这是一系列/一套函数,这些函数的作用都是进行程序替换的,它们的前缀都是exec。
- 如果程序替换成功,那么会直接执行替换后的程序的代码,不会有返回值,如果执行失败了会返回-1。所以exec系列函数只有出错的返回值,没有成功的返回值。
- 它们的命名都很有特点,接下来会分别介绍这些exec系列函数,然后进行总结。
2.2 execl

- 使用的时候要包含unistd.h头文件。
- 这个函数具有两个参数,其中第一个要传递的参数是目标程序的完整路径,第二个参数是程序参数列表,是一个可变参数,其中传递的参数的个数是不确定的。
- const char* path:需要传递一个完整的路径。相对路径和绝对路径都是可以的,但必须完整。
- const char* arg, ...:这个参数列表要求第一个参数为该程序的名字,而最后一个参数必须是NULL,作为可变参数列表的结束标志。中间的为功能参数。我们在传递这一部分参数的时候,命令行上面是怎么写的,这里就怎么写。比如,调用ls时命令行:ls -a -l,那么这里就写"ls", "-a", "-l", NULL。
- 为什么取名叫execl?l对应list。这是因为在传递第二个参数的时候,传递的参数就跟list一样是逐个列出的。---参数以列表形式逐个传入。
- 示例:
cpp
1 #include<unistd.h>
2 #include<stdio.h>
3 #include<sys/types.h>
4 #include<wait.h>
5
6 int main()
7 {
8 pid_t id = fork();
9 if (id == 0)
10 {
11 printf("我是一个子进程,接下来我要进行程序切换了!\n");
12 execl("/usr/bin/ls", "ls", "-a", "-b", NULL);
13 }
14 printf("我是一个父进程,我回收了子进程\n");
15 pid_t sid = waitpid(id, NULL, 0);
16 return 0;
17 }

- 成功进行了程序的切换,在这个程序里面执行了ls这个二进制程序。
2.3 execv

- 使用的时候包含头文件unistd.h。
- 这个函数同样具有两个参数,第一个参数就不做解释了,第二个参数是一个参数数组,也就是需要传递一个参数数组,这个数组里面的元素是char*类型的。
- char* const argv[]:要使用这个函数,就必须先创建一个参数数组,同样,这个参数数组的第一个元素必须是目标程序的程序名,而最后一个元素必须是NULL。之后把这个参数数组传递给这个函数就好。
- 为什么取名叫execv?v对应vector。因为execv在传递第二个参数的时候,与execl不同,它传递的是一个参数数组。---参数以数组形式传入。
- 示例:
cpp
7 int main()
8 {
9 pid_t id = fork();
10 if (id == 0)
11 {
12 printf("我是一个子进程,pid:%d,我要进行程序切换了\n", getpid());
13 char* argv[] = {(char*)"ls",
14 (char*)"-a",
15 (char*)"-l",
16 NULL};
17 execv("/usr/bin/ls", argv);
18 exit(0);
19 }
W> 20 pid_t sid = waitpid(id, NULL, 0);
21 printf("我是一个父进程,我回收了子进程\n");
22 return 0;
23 }

- 不过execl和execv还有一点不同,参数类型execl要求的是const char*,直接传入字符串常量就行,而execv要求的是char*,在数组里面还要给字符串常量强转成char*。
2.4 execlp和execvp


-
execlp相比于execl多了个p,execvp相对于execv也是同样,这个p就是PATH环境变量,这两个函数的第一个参数是file,也就是要我们传递所要执行文件的名字,而不是一个路径了。
-
传递所要执行文件的名字之后,操作系统会根据这个名字去指定PATH环境变量下面存储的路径挨个去找。
-
示例:
cpp
7 int main()
8 {
9 pid_t id = fork();
10 if (id == 0)
11 {
12 printf("我是一个子进程,pid:%d,我要进行程序切换了\n", getpid());
13 char* argv[] = {(char*)"ls",
14 (char*)"-a",
15 (char*)"-l",
16 NULL};
17 execvp("ls", argv);
18 exit(0);
19 }
W> 20 pid_t sid = waitpid(id, NULL, 0);
21 printf("我是一个父进程,我回收了子进程\n");
22 return 0;
23 }

2.5 execve、execle和execvpe


- 这个e就是environment,代表的是环境变量表,带e的exec系列函数支持手动传递环境变量表即第三个参数。
- 如果你给它传递一个新的环境变量表,它就会用这个新的环境变量表;如果你传的是environ,那么它就还是用原来的这个环境变量表;如果你不想让它用新的环境变量表,而只是想在environ的基础之上添加一个环境变量给替换后的程序使用,那就先对environ进行预处理,可以用putenv函数添加环境变量。

- 示例,execvpe执行自定义文件 myfile:
test.c
cpp
7 int main()
8 {
9 extern char** environ;
10 pid_t id = fork();
11 if (id == 0)
12 {
13 printf("我是一个子进程,pid:%d,我要进行程序切换了\n", getpid());
14 char* argv[] = {(char*)"myfile",
15 NULL};
16 putenv((char*)"haha=helloworld");
17 putenv((char*)"PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/mosheng/.local/bin:/home/mosheng/bin:/home/mosheng /my_file");
18 execvpe("myfile", argv, environ);
19 exit(0);
20 }
W> 21 pid_t sid = waitpid(id, NULL, 0);
22 printf("我是一个父进程,我回收了子进程\n");
23 return 0;
24 }
myfile.c
cpp
3 int main()
4 {
5 printf("我是file文件,程序切换成功啦!\n");
6 printf("%s\n", getenv("haha"));
7 return 0;
8 }

- 我在test.c文件里面用putenv给PATH环境变量添加了一个新的路径用来找到目标文件myfile,然后又用putenv给环境变量表添加了一个全新的环境变量haha=helloworld。然后把预处理好的环境变量表传递过去,execvpe最终执行成功。
2.6 命名总结
- v和l只能有一个(彼此互斥),它们代表了参数的传入方式。
- l就是list,参数以列表形式逐个传入。
- v就是vector,参数以数组形式传入。
- p就是PATH环境变量,第一个参数不再是绝对路径,而是文件名,操作系统会根据这个文件名去PATH环境变量里面存储的路径挨个去找。
- 有p就不用传递路径,前提是环境变量PATH有这个路径,没p就一定要传递路径,相对路径或者是绝对路径。
- 程序替换,替换为一个二进制可执行文件,无论是用什么语言(c++、java、python、脚本语言)编写的都是可以切换的,毕竟这些语言编译链接了之后都是二进制可执行文件---跨语言。
- e就是environment,是环境变量表。
- execve是系统提供的函数,而其他的六个exec系列函数都是库函数,都是在execve的基础之上进行实现的。
今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~
让我们共同努力, 一起走下去!