欢迎各位大佬光临本文章!!!
还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。
本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。
我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog
我的gitee:冰冰棒 (bingbingsupercool) - Gitee.comhttps://gitee.com/bingbingsurercool
系列文章推荐
目录
前言
我们之前使用fork函数创建子进程来执行父进程中不同的代码逻辑,父子进程之间代码共享,数据写时拷贝。但是如果我们想让子进程不执行父进程的代码,去执行一个全新的程序那应该如何做到呢?这就需要使用进程程序替换函数来做到。
1.替换原理
什么是进程的程序替换呢?简单来说就是父进程创建的子进程想要拥有独立的代码和数据,之前父子进程之间是代码共享,数据各自拥有,现在子进程需要使用特定的接口重新加载磁盘上新的代码和数据,但是并不会重新创建进程,内核数据结构还是原来子进程拥有的,只是页表映射关系发生了改变!
2.替换函数
进程程序替换的函数具有多个,每个函数之间使用方式大体相同,但是有些细微的差距。
int excel (const char* path, const char * arg, ...);
返回值:整形,函数调用成功并不会返回,调用失败返回-1
参数:第一个参数表示进程的路径,第二个参数表示进程的名称,后面的为可变参数列表,
表示传入选项,最后一个参数必须是NULL。
简单来说,我们调用进程替换函数传参时,我们在命令行上怎么执行的,这里就怎么填写。
但是,通过上面的程序替换结果,我们会发现,为什么原来程序的打印语句少了一个?“当前进程的结束代码”并没有被打印。
原因在于excel是程序替换,调用函数成功,会将当前进程的所有代码和数据都会替换,包含已经执行过的或者未执行的代码, 原程序后面的代码不会在执行。调用失败原程序不会被替换,将继续执行。
这也就说明了为什么execl函数没有调用成功的返回值,因为根本不会有人去接收。所以我们应该在调用后面直接用进程退出函数退出即可,一旦调用错误,直接退出。
所以我们一般都会创建子进程去进行程序替换,父进程来接受子进程的状态,就算子进程调用失败也不会影响父进程,父进程可以继续工作。
int execlp(const char* file, const char *arg, ...);
该函数与execl不同的地方在于第一个参数不用指定路径,只需要指定进程名称,它会自己从环境变量PATH中寻找。
进程替换程序不仅可以调用系统命令程序,也可以调用我们自己写的程序,例如调用我们自己写的python程序,test.py。
所以exec类型的函数实际上就是加载器的底层接口!
int execv(const char *path, char *const argv[ ]);
该函数的第一个参数也是需要指定路径,但是第二个参数需要传递一个指针数组,数组里面存放是调用程序的名字和命令。
int execvp(const char* file,char* const argv[ ]);
与上面的函数的区别是不需要传递路径,会自己去环境变量中寻找
int execle(const char* path, const char *arg, ... , char* const envp[ ]);
int execvpe(const char* file,char* const argv[ ],char* const envp[ ]);
这两个函数可以向目标进程传递环境变量
例如我们调用我们自己写的子进程,里面打印出父进程自定义的环境变量就可以使用这个函数。
除了以上几个函数,操作系统还提供了一个函数execve函数,上面的函数都是经过封装后,底层调用的这个函数进行任务执行。
3.MiniShell的实现
既然学习了进程替换的函数,现在我们可以使用该函数进行一个minishell的建立,让其能够简单的实现shell的部分功能,并且能简单的运行起来。
我们发现,shell在执行命令的时候,shell本身不会受到影响,这是因为shell是通过创建子进程来实现命令的执行。在子进程中利用替换函数,将对应的命令执行程序替换到当前子进程中,从而完成命令的执行。
因此我们也可以使用这种方式来实现自己的shell。
首先,shell一旦运行起来就不会退出,因此我们需要父进程一直执行,所以我们通过fork函数来创建子进程来执行对应的命令。在执行之前,我们首先需要接受输入的参数并且将其解析出来。
由于shell输入的参数是直接在命令提示行之后的,所以我们不能直接使用换行符刷新提示符。输入的参数中我们是要敲一下回车才能输入,但是命令执行时的字符串是不能有回车符的,所以也要去除。
最终执行结果如下所示:
接下来就是对命令字符串的分解了,我们使用库函数strtok利用输入的空格进行打断,并且将打断的结果字符串存放到指针数组g_argv中。
strtok函数的使用要注意,第一次使用时传入的是原始字符串,再次调用时需要传递空指针。
字符串解析完毕后,剩下的就是创建子进程并执行程序替换,将我们解析的字符串对应的命令一一执行。最后使用父进程接受状态。
这里要注意一点,执行cd命令并不能创建子进程进行执行,因为目录的跳转是父进程之间的跳转,所以我们需要使用父进程自己进行命令的执行,需要调用系统接口函数进行实现。
当我们想设置环境变量时,就需要特殊的设置,我们需要先将解析的环境变量的字符串保存起来,避免地址被清空覆盖,然后再将对应的环境变量使用putenv函数进行设置,但是设置之前需要先将环境变量environ引进来。
这样我们就能实现基本的shell模拟实现了。