文章目录
- 前言
- 一、替换原理
- 二、替换函数
- 三、函数解释
- 四、命名理解
- 总结
前言
加油加油!!!
一、替换原理
用 fork 创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支),若想让子进程执行另一个程序,往往需要调用一种 exec 函数。
当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,并从新程序的启动例程开始执行。
当进行进程程序替换时,有没有创建新的进程?
进程程序替换之后,该进程对应的PCB、进程地址空间以及页表等数据结构都没有发生改变,只是进程在物理内存当中的数据和代码发生了改变,所以并没有创建新的进程,而且进程程序替换前后该进程的pid并没有改变。
子进程进行进程程序替换后,会影响父进程的代码和数据吗?
子进程刚被创建时,与父进程共享代码和数据,但当子进程需要进行进程程序替换时,也就意味着子进程需要对其数据和代码进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据也就分离了,因此子进程进行程序替换后不会影响父进程的代码和数据。
二、替换函数
#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 execve(const char *path, char *const argv[], char *const envp[]);
替换函数有六种以 exec 开头的函数,它们统称为 exec 函数:
一、int execl(const char *path, const char *arg, …);
第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。
例如,要执行的是ls程序
#include <stdio.h>
#include <unistd.h>int main()
{// execl 函数printf("程序替换前,you can see me\n");int ret = execl("/usr/bin/ls", "ls", "-a", "-l", NULL);// 程序替换多发生于子进程,也可以通过子进程的退出码来判断是否替换成功if(ret == -1)printf("程序替换失败!\n");printf("程序替换后,you can see me again?\n");return 0;
}
可以看出,函数 execl 中的 命令+选项+NULL 是以 链式 的方式进行传递的
execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);
二、int execlp(const char *file, const char *arg, …);
第一个参数是要执行程序的名字,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾
例如,要执行的是ls程序
#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{//execv 函数pid_t id = fork();if(id == 0){printf("子进程创建成功 PID:%d PPID:%d\n", getpid(), getppid());char* const argv[] = {"ls","-a","-l",NULL}; //argv 表,实际为指针数组execv("/usr/bin/ls", argv);printf("程序替换失败\n");exit(-1); //如果子进程有此退出码,说明替换失败}int status = 0;waitpid(id, &status, 0); //父进程阻塞等待if(WEXITSTATUS(status) != 255){printf("子进程替换成功,程序正常运行 exit_code:%d\n", WEXITSTATUS(status));}else{printf("子进程替换失败,异常终止 exit_code:%d\n", WEXITSTATUS(status));}return 0;
}
execlp("ls", "ls", "-a", "-i", "-l", NULL);
三、int execle(const char *path, const char *arg, …, char *const envp[]);
第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾,第三个参数是你自己设置的环境变量。
例如,你设置了 MYVAL 环境变量,在 mycmd 程序内部就可以使用该环境变量。
我们可以思考一个问题:为什么在 bash 中创建程序并运行,程序能继承 bash 中的环境变量表了?
- 在 bash 下执行程序,等价于在 bash 下替换子进程为指定程序,并将 bash 中的环境变量表 environ 传递给指定程序使用
- 其他没有带 e 的替换函数,默认传递当前程序中的环境变量表
char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);
四、int execv(const char *path, char *const argv[]);
第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。
例如,要执行的是ls程序
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);
五、int execvp(const char *file, char *const argv[]);
第一个参数是要执行程序的名字,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。
例如,要执行的是ls程序
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);
六、int execve(const char *path, char *const argv[], char *const envp[]);
第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。
例如,你设置了 MYVAL 环境变量,在 mycmd 程序内部就可以使用该环境变量。
char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=2021", NULL };
execve("./mycmd", myargv, myenvp);
替换函数除了能替换为 C++ 编写的程序外,还能替换为其他语言编写的程序,如 Java、Python、PHP等等,虽然它们在语法上各不相同,但在 OS 看来都属于 可执行程序,数据位于 代码段 和 数据段,直接替换即可
系统级接口是不分语言的,因为不论什么语言最终都需要调用系统级接口,比如文件流操作中的 open、close、write 等函数,无论什么语言的文件流操作函数都需要调用它们
三、函数解释
- 这些函数如果调用成功,则加载指定的程序并从启动代码开始执行,不再返回。
- 如果调用出错,则返回-1
也就是说,exec系列函数只要返回了,就意味着调用失败。
四、命名理解
这六个 exec 系列函数的函数名都以 exec 开头,其后缀的含义如下:
- l(list):表示参数采用列表的形式,一一列出。
- v(vector):表示参数采用数组的形式。
- p(path):表示能自动搜索环境变量PATH,进行程序查找。
- e(env):表示可以传入自己设置的环境变量。
函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 否 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 否 | 否,需自己组装环境变量 |
execv | 数组 | 否 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 否 | 否,需自己组装环境变量 |
事实上,只有 execve 才是真正的系统调用,其它五个函数最终都是调用的 execve ,所以 execve 在 man 手册的第2节,而其它五个函数在 man 手册的第3节,也就是说其他五个函数实际上是对系统调用 execve 进行了封装,以满足不同用户的不同调用场景的。
下图为exec系列函数族之间的关系:
- 这就表明程序替换并不是进程替换
- 因为是同一个进程,所以对父进程没有任何影响,体现了进程间的独立性
总结
下一篇给大家带来综合的训练 —— 实现一个自己的 Shell