进程程序替换((>_<)子进程跑了),模拟编写一个入门shell

目录

进程程序替换

1.进程替换的原理

2.怎么来另起炉灶执行另一个程序

①execl

②execv

③execlp

④execvp​编辑

⑤execle

⑥execvpe 和 execve

3.执行一下自己的程序

4.丐版shell

 ①显示提示符

 ②获取用户输入​编辑

 ③.将字符串切分

 ④.创建子进程,执行 

 ⑤.程序替换

 ⑥内建命令


进程程序替换

        先前,我们可以用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,不用在乎,能成功运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值