上节中已经实现了对普通命令的解析,包括输入重定向,输出重定向,管道,后台作业,这次就来执行已经解析好的命令,对应的函数为:execute_command(),首先对带有管道的命令进行执行:
比如:"ls | grep init | wc -w"这条命令,有两条管道,其中最后一条命令是不需要管道的:
![](https://i-blog.csdnimg.cn/blog_migrate/c24195d4cb589cd5ae04b36bb72782b1.png)
【说明】:对于管道的创建,可以参考博文:http://www.cnblogs.com/webor2006/p/3768752.html
另外我们知道,对于创建的管道fds,其中fds[0]表示读端,fds[1]表示写端,会有如下关系:
![](https://i-blog.csdnimg.cn/blog_migrate/92514e948f95ac27a1d99094c7e338f1.png)
所以:
![](https://i-blog.csdnimg.cn/blog_migrate/58ab942dd45e3de18059caf2541af9ab.png)
所以可以联想到,默认命令的输入是标准输入,输出是标准输出,所以可以在init中对所有命令进行初始化:
![](https://i-blog.csdnimg.cn/blog_migrate/26d525b12d8cb00c064c7309ba177dd5.png)
接下来,执行命令,这个最初已经实现了,就是fork出一个进程来用execvp函数来进行进程替换既可,这里将命令的执行封装成一个函数:
![](https://i-blog.csdnimg.cn/blog_migrate/ff4c9cea1441d4f2ae6d445f5a01a417.png)
所以需要在头部声明且实现:
![](https://i-blog.csdnimg.cn/blog_migrate/84886fe96a13a1b76fa10b54e38c695c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ec616fe74306022cf2b01dfbe1e0201e.png)
好了,先来编译一下所编写的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/9222152012cd5a91ec546d093f5fc305.png)
很遗憾,木能一次到位,啥问题呢?
![](https://i-blog.csdnimg.cn/blog_migrate/a2eadca93d15802db75ac6834997eada.png)
所以修改如下:
![](https://i-blog.csdnimg.cn/blog_migrate/ff53b5bd569c11a3901bdc943055bbf4.png)
再次编译:
![](https://i-blog.csdnimg.cn/blog_migrate/46009cb16387f804a8a0166a545d22a3.png)
这次就成功了,另外在运行之前,还需加一个,就是需将3以上的文件描述符全给关掉,因为描述符0、1已经被使用了,之后由于会有重定向一个文件,所以留一个文件描述符2,具体代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a1a3d11d6f0b900e0f3e78873767f283.png)
编译运行:
![](https://i-blog.csdnimg.cn/blog_migrate/9b20ef49188616206dd7fb8d3ed069bf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dc122a1089c6d033bc5a0dbbe804e4e7.png)
再次编译:
另外,在execute_command命令中,需要关闭命令的描述符:
![](https://i-blog.csdnimg.cn/blog_migrate/66262f36d5fb563b46c0bfe0d4e3366b.png)
接下来看下运行效果:
![](https://i-blog.csdnimg.cn/blog_migrate/17e108de4e961bde1f61ed3d5903a992.png)
这是因为父进程已经退出了,子进程运行在父进程之后了,所以要解决此问题,则父进程需要等待子进程的执行,修改如下:
定义一个变量来记录最后运行的父进程:
![](https://i-blog.csdnimg.cn/blog_migrate/223875fb05c7c3e43336d693198def08.png)
在extends.h中进行声明:
![](https://i-blog.csdnimg.cn/blog_migrate/0767554f7688cdcf72e1184f0ff39a13.png)
并对变量进行初始化:
![](https://i-blog.csdnimg.cn/blog_migrate/3e140a19d19db597e9fc43eee7b57bdd.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0ff151a68a674c7f6057cb150bbf547d.png)
当fork一个进程时,则对lastpid进行赋值:
![](https://i-blog.csdnimg.cn/blog_migrate/e9c68f8669cbc1f51d7837a728649b4c.png)
这时,再来看效果:
![](https://i-blog.csdnimg.cn/blog_migrate/4d5306b893ed8d3cc40a71c4409768ae.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ca96de7badb2fab436a51f89593826f0.png)
下面开始解析带输入输出重定向的命令:
![](https://i-blog.csdnimg.cn/blog_migrate/586b6668226088c9e6e3b5e52b53503d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7d208fc5aad9fbfb20a0065d84e6c33d.png)
另外需要处理一下后台作业的情况:
![](https://i-blog.csdnimg.cn/blog_migrate/27a7330f2608f4cbe8dd6c0f3efd391e.png)
但是如果是后台作业的话,则会引起僵尸进程(关于什么是僵尸进程,可以参考博文:http://www.cnblogs.com/webor2006/p/3512781.html),所以说需要解决一下:
![](https://i-blog.csdnimg.cn/blog_migrate/8d2e14f61a0a6eb5745c9862ce63a122.png)
下面来make一下:
![](https://i-blog.csdnimg.cn/blog_migrate/8ef8a2a517053fc99759f1e392180e54.png)
查看man帮助:
![](https://i-blog.csdnimg.cn/blog_migrate/7521eb012d21c412f224492f44272842.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d3e6d15332e9cbedb2e85eede45a98ff.png)
再次编译:
![](https://i-blog.csdnimg.cn/blog_migrate/9cb27a5ccff14ff8d55bb99077845dbe.png)
下面来看下是否支持输入和输出重定向:
![](https://i-blog.csdnimg.cn/blog_migrate/49c7fd2c69725c3b63936b5746590b12.png)
下面采用输入重定向来输出同样的效果:
![](https://i-blog.csdnimg.cn/blog_migrate/f08bf66c6a0beb523f94ce78f381a55a.png)
下面来看下输出重定向:
![](https://i-blog.csdnimg.cn/blog_migrate/c1e98f51e49f68cc2154c255c8c9e19d.png)
可见,现在已经支持输入输出重定向了,下面还需看一种异常情况:
![](https://i-blog.csdnimg.cn/blog_migrate/47b1bfd0ad782e921fff461c8824aaa1.gif)
当输入不带参数的cat命令时,表是从键盘获取输入,当我们按下ctrl+c时,居然打印出了两个[myshell]$,这个有异常了,我们知道,ctrl+c是向当前进程发送sigint信号,由于我们在setup()已经注册了sigint信号,而且行为为打印[myshell]$
![](https://i-blog.csdnimg.cn/blog_migrate/ce5257a88c764fa87c0ec45b5e3cd5ba.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7dc0ff3f364883c3c075cf28f78807ab.png)
而由于父进程和子进程都能收到sigint信号,因为sigint是向进程组发送的,所以组里面的所有子进程都能收到,所以要解决此问题,需要做如下操作:
![](https://i-blog.csdnimg.cn/blog_migrate/b7ece15d9507ac80069699a382881b43.png)
因为在后台作业时,已经忽略SIGINT信号了,所以如果是前台作业,则需要恢复,编译再来看效果:
![](https://i-blog.csdnimg.cn/blog_migrate/639b06d6c13dcde398f9c7d0fdf73d41.png)
这时为啥呢?这个可能是进程组的关系(关于进程组的概念,可以参考博文:http://www.cnblogs.com/webor2006/p/3514552.html),可以查看一下关系:
![](https://i-blog.csdnimg.cn/blog_migrate/385db848028b8783e0ed7e665b046e34.png)
但是进入我们自己的shell来查看一下进程组关系:
![](https://i-blog.csdnimg.cn/blog_migrate/6c5773a75f7f7d7ff8a2f055a85d7547.png)
所以,SIGINT发送给进程组26945时,也就发送了该进程的父进程,因为当前该父进程为进程组,而子进程26946同样也会收到SIGINT信号,所以就打印了两次。
这就涉及到会话期的概念,其中创建新的会话期可以通以以下函数:
![](https://i-blog.csdnimg.cn/blog_migrate/8d492e174cce1048b826b752316b93fc.png)
所以,在第一个命令fork时,则将这个进程做为进程组组长既可,做法如下:
![](https://i-blog.csdnimg.cn/blog_migrate/730376f583f55b475c82a39ef458f773.png)
![](https://i-blog.csdnimg.cn/blog_migrate/108f9502917d046340e9a70de949b01e.png)
所以,函数的参数应该发生变化:
![](https://i-blog.csdnimg.cn/blog_migrate/7baf0865fd3894d0e1785ebf9c8f7c58.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f5426012971b342408ae444b5e1bc2f7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0e8b9f46180252a837a6bbc1365e9d39.png)
然后在子进程中做一个判断,创建新的会话期:
![](https://i-blog.csdnimg.cn/blog_migrate/d86e4a720927040f10eb757c9761e12f.png)
这次再来编译下:
![](https://i-blog.csdnimg.cn/blog_migrate/b0ff5fc438d5df780ddc6a7d42f43af2.png)
这时再来看一下刚才的问题是否还存在?
![](https://i-blog.csdnimg.cn/blog_migrate/2b24cd45db80736d75c4d4512bc217ec.gif)
这时按下ctrl+c就没有出现两个[myshell]$了,这是为什么呢?
因为ctrl+c是将SIGINT信号发送给当前进程组,也就是对应于上面的27278,那么该进程组下面的所有进程都会收到该信号,由于在前台进程时,将SIGINT信号还原成了默认值:
![](https://i-blog.csdnimg.cn/blog_migrate/f7cd2a7d741d4f2b9b2ddac13f7c3367.png)
所以,这时ppid父进程是不会收到该信号的,因为该信号只会发送给当前进程组成里面的所有进程,所以这次就只会打印一次了。
接下来,我们来看一下后台作业:
![](https://i-blog.csdnimg.cn/blog_migrate/2ab06f1077ef6cc7ae49c3c42663ad39.gif)
这里来看,后台作业有问题,由于这里并
没有实现作业控制(bg,fg),所以先屏蔽后台作业,等之后有时间再来研究,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/d282c1bcc06d6e87d09ff5317e23fb5f.png)
这时再来看下之前的bug是否还存在?
![](https://i-blog.csdnimg.cn/blog_migrate/0284ae3e32703e52fb1f127e1ca49f83.gif)
看样子还是有问题,还没有屏蔽成功,在屏蔽之前,先来解决一个很明显的bug:
![](https://i-blog.csdnimg.cn/blog_migrate/c1ffcb7c1d1957b995ecca4dc8e9f194.gif)
也就是当cmd_count=0时,则没反应了,这时应该做一个容错处理,当为0时不应该执行命令:
![](https://i-blog.csdnimg.cn/blog_migrate/5d82d472f10bee99e69aa0d78029d704.png)
这时再来看效果:
![](https://i-blog.csdnimg.cn/blog_migrate/3cadee00453db488330850ff683b90f2.gif)
下面再来解决屏蔽后台作业的bug,该bug就是如果先敲了一个后台作业命令,之后再执行一个简单命令就会卡住,这是为什么呢,原因其实比较简单:
![](https://i-blog.csdnimg.cn/blog_migrate/dc2c9c04c08533a5b4f081314a1063f0.png)
这时再来看效果:
![](https://i-blog.csdnimg.cn/blog_migrate/781eb85e0763feed93f3dac47c61966b.gif)
这样这个bug就成功被解决,另外我们来看下真实的后台作业的输出是怎么样的:
![](https://i-blog.csdnimg.cn/blog_migrate/4bef2c075f4a5dbb13d71136bd4cf3d3.png)
所以,我们也可以给打印一下当前的pid,虽说后台作业的功能没有完全实现:
![](https://i-blog.csdnimg.cn/blog_migrate/9099924e5e1d8cce3c19094d8340d0f0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/da2d4e4b1a84fcc8a5f169c97d626ed6.png)
好了,先学到这,下节见~