目录
进程程序替换
先前,我们可以用fork()创建一个子进程,子进程代码继承父进程代码,执行跟父进程相同的代码,我们能不能让子进程离经叛道,另起炉灶干一番事业,脱离父进程的束缚,当然欧克啦。
我们可以让它执行c/c++、Java、Php、shell、Python呀什么的...可神奇了。
1.进程替换的原理
在没有让子进程另起炉灶之前,没有写时拷贝时,他们是这样存在的。
突然,子进程撂挑子了。来了个b.exe就把他拐跑了
原理就是:
①.将磁盘的程序,加载到内存结构中。
②.页表重新建立映射,指向另一块加载了程序的内存空间。(让父子进程彻底分离,再也没有关系,让子进程执行一个新的程序)((>_<)变心了明明)
2.怎么来另起炉灶执行另一个程序
当然还是要借助他人的援手,上述替换的过程都是由操作系统来完成,所以我们要借助os之手,分离他们,我们来学习第一个函数接口。这些函数都是系统调用。
①execl
程序替换:
1.调用函数去替换,第一步我们已经做好了。
2.接下来就是要找这个程序在哪,这便是第一个参数所要干的事情。
3.我们找到了程序之后,需要程序怎么做,这是第二个参数要干的。第二参数是可变参数,我们可以不拘泥于传一个参数。比如我们想执行ls这个进程(ls为什么是进程,因为在命令行上启动的进程都是BASH进程的子进程),我们可以传 ls,也可以传ls -a,可以传ls -a -l,还可以传ls -a -l -i。命令行上怎么打,就怎么传,当然要有标识程序执行完毕的NULL放在最后。
我们来试试:
结果如下图:
哟,到这大家有没有发现一个问题,最后一个printf没有打印这是为什么呢?
因为execl一旦替换成功,execl以下的代码要全部被替换,printf被替换走了,当然执行不了。
这个execl函数,有返回值,那我们要不要关注返回值呢?
根本就不用,函数成功之后都会直接执行要执行的进程,就不会有返回值。失败了,execl后面的代码就不会被替换,自然会执行后面的代码。一旦执行后面的代码,只有一种结果,程序替换失败了。然后通过返回值判断什么原因导致失败。
先前这些都是自己的进程去替换执行一个进程,execl后面的代码不会执行,接下来咋们要通过子进程,来执行一个进程,让子进程被拐走,不影响父进程的代码。
代码如下:
结果如图:
成功证明子进程跑了(>_<)、
大家有没有注意为什么退出码是零,明明我设置了退出码是12呀。
因为上文说过一旦执行另一个进程,下面的代码都会被替换,这时exit(12)已经没有了,这时接收的返回值是execl执行完 ls -l -a 这个进程的退出码。注意,我是让子进程去替换进程(执行一个新的进程),对父进程没有任何影响,这全然是因为写实拷贝的作用。上文有讲。
②execv
它和execl没有本质的区别
execl: “l” 是list 链表
execv:“v” 是vector 数组
path:需要执行的程序的位置
argv[]:刚刚是可变参数(arg ...),现在是数组。
意味着我们可以这样:将数组传给第二个参数
我们传入数组结果是相同的:
③execlp
我觉得挺好用的一个同类型的函数。
它命名中的“p”,指的是path 环境变量,意味着我们不用去明确的输入目标进程的地址,我们只用输入想执行的进程,前提是这个进程在你所在的环境变量目录中,这样os才能找到,目标的进程。
代码如下:
结果如下:
千万要注意,一定要写NULL,来指明进程执行完毕。
不然会成这样:
这里如愿的拿到了12这个退出码,说明execlp这个函数寄了,进程没执行成功。
④execvp
大同小异,这就不细讲了
⑤execle
用这个函数,我们可以将环境变量传给将要执行的进程。
代码如下:
这是父进程:
这是要被执行的进程:
结果如下:
要注意我们的PATH会将原本进程的PATH给覆盖掉。
我们如果用execl的话,不传MYPATH,它自己的PATH会打印出来。
我们用excele
将自己的MYPATH传过去,PATH将会被覆盖
后面的MYPATH也打不出了。
我们要想PATH被打出,自己的MYPATH也被打出,可以这样:
然后在父进程中这样写: 这样就成功咯,大家可以私下试试
⑥execvpe 和 execve
这俩和上面的都大同小异。
exec +l/v(链表or数组) +p(可以不输入地址直接输入进程名称) +e(可以自己传环境变量给子进程)
3.执行一下自己的程序
其实上面有两个实例我已经用子进程去调用自己的程序了,下面正式说一下。
平常用vs不能直接运行两个exe,今天我们用一个进程去调动另一个进程。
其中的一个circulate 程序:
我们要用test的子进程去调用circulate
黄天不负有心人,我们这里的WEXITSTATUS(status)终于能获得我们设置的退出码了,关于退出码,上一篇文章有讲。
结果:
成功了,当然也可以去调动Java、Python呀什么的程序。大家可以去尝试下。
4.丐版shell
虽然是精简版,但是它可以创建 终止 等待 程序替换。
这些是头文件和宏定义:
①显示提示符
②获取用户输入
哎?为什么会换两次行,我明明printf中只有一个\n。
因为,我们再输入命令的时候,最后都要敲一个回车,来表示执行DOS命令。
这个回车(不可见字符)自然也被存到了command_line字符数组中。
我们可以做的操作就是将这个回车符赋值为‘\0’。
这样就不会换行了。
③.将字符串切分
因为我们平常输入的命令之间都是带有空格的。比如:ls -a -l。
strtok函数可以让字符串以指定的分隔符分开。
while的循环条件很怪,为什么要这样写呢?
strtok函数截取成功,返回字符串起始地址。
截取失败返回NULL,正好赋给command_args。
那为什么在循环条件中的strtok中第一个参数要传NULL?
strtok的函数原型为char *strtok(char *s, char *delim),功能为“Parse S into tokens separated by characters in DELIM.If S is NULL, the saved pointer in SAVE_PTR is used as the next starting point. ” 翻译:作用于字符串s,以包含在delim中的字符为分界符,将s切分成一个个子串;如果,s为空值NULL,则函数保存的指针SAVE_PTR在下一次调用中将作为起始位置。 (摘自百度百科)
意味着,我第一次用传要分隔的字符串,第二次及以后如果传入NULL,那么函数的上一次保存的地址将在下一次调用作为起始地址。
我们将分割好的字符串打印一下:
结果是:
④.创建子进程,执行
⑤.程序替换
将上面我们所学的内容汇聚在一起,以及上一篇文章的内容。
这一切尽在代码中,我们实测一下效果。
⑥内建命令
在我们的shell里,通过cd .. 去回到上一级目录的方法是不可行的。
我们通过exec*去执行cd命令,只是让子进程的路径发生变化,之后子进程运行结束,接下来通过“pwd”这个进程来查看当前路径,当前路径没有发生任何变化。因为这时的路径是通过父进程继承下来的,执行cd命令的进程只是改变了自己的进程,然而对父进程的路径发生不了改变。要想改变成功,只能通过在父进程中执行内建命令。一些命令只能通过父进程执行,不能让子进程执行,这就是内建命令。
我们修改下代码:在分隔开字符串之后,去判断是否要求子进程进行cd
之后使用chdir函数
传入想转换到的路径,执行成功后,会使调用者的当前路径变成传入的路径。
我们看下效果:
同样:
export也是一个内建命令。它的作用是在系统环境变量中,导入自己的环境变量。
在环境变量中就能看到,我们导入的环境变量。
看看在我们的shell中行不行,不用实验肯定不行,环境变量具有全局属性,它可以被子进程继承。在我们的shell中,用一个子进程去导入环境变量,只会将环境导入子进程的所处的环境变量之中。根本不会修改父进程的环境变量。
我们在父进程修改下,让我们自己的shell也能添加环境变量。
putenv是一个将传入参数加入环境变量的函数。
为什么还是不行?
因为,系统的环境变量在char**数组中,是全局的,被系统所维护。而我们的环境变量在我们的command_args中,但是我们shell是循环执行,我们是将所要的环境变量导出了,但是到下一次我们又去执行新的子进程时,comman_args又被清空了,当然环境变量也没了。如何改变,其实可以很简单,我们可以在循环外面定义一个数组将我们的环境变量存起来,无论怎么循环,都影响不到全局变量。
成功了!
我们的简易shell到此就完善的差不多了,希望以后有机会,还能再次完善他,感谢观看,我们下次再见。
下面是simpleshell的所有代码:至于编译时会warning,不用在乎,能成功运行。