【嵌牛导读】:Linux系统提供给用户的最重要的系统程序是Shell命令语言解释程序,shell不属于内核部分,在核心之外,以用户态方式运行,基本功能是解释并执 行用户打入的各种命令,实现用户与Linux核心的接口。系统初启后,核心为每个终端用户建立一个进程去执行Shell解释程序。
【嵌牛提问】:什么是shell 它的功能与命令有哪些?
Shell是用户与操作系统之间的接口,为用户提供了使用操作系统的接口。
Linux系统提供给用户的最重要的系统程序是Shell命令语言解释程序,shell不属于内核部分,在核心之外,以用户态方式运行,基本功能是解释并执 行用户打入的各种命令,实现用户与Linux核心的接口。系统初启后,核心为每个终端用户建立一个进程去执行Shell解释程序。shell的执行过程如下:
1、读取用户由键盘输入的命令行。
2、解析命令,以命令名作为文件名,并将其它参数改造为系统调用execve函数内部处理所要求的形式。
3、终端进程调用fork函数建立一个子进程。
4、终端进程本身用系统调用wait4( )来等待子进程完成(如果是后台命令,则不等待)。
当子进程运行时调用execve,子进程根据文件名(即命令名)到目录中查找有关文件,将它调入内存,执行这个程序(解释这条命令)。
5、如果命令末尾有&号(后台命令符号),则终端进程不用系统调用wait4( )等待,立即发提示符,让用户输入下一个命令,转到第1步。如果命令末尾没有&号,则终端进程要一直等待,当子进程(即运行命令的进程)完成处理后终止,向父进程(终端进程)报告,此时终端进程醒来,在做必要的判别等工作后,终端进程发提示符,让用户输入新的命令,重复上述处理过程。
刚接触Linux时,对shell总有种神秘感;在对shell的工作原理有所了解之后,便尝试着动手写一个shell。下面是一个从最简单的情况开始,一步步完成一个模拟的shell的过程。这个所谓的shell和主流的shell还是有不少区别的,最大的区别是它本身不能执行shell脚本、也不能对一些复杂的命令行进行分析——原因很简单,我没有写相应的解释器。如果想自己实现一个简化的shell脚本解释器,如果有编译原理的知识准备,本身不是难事,但是工作量比较大,这里就不完成了,有兴趣的可以进行尝试。
直接使用pipe()就可以了,管道的写法没什么特别,不过对于同时使用了输出重定向和管道的command1,需要把它的管道关闭,这样就会给command2发送一个EOF。
3.5 模仿,再模仿……
使用系统自带的wc,并随便编写个1.txt,测试目前的版本吧。
看上去怎么就那么别扭呢?对比一下,下图是真实的shell的行为:
这个问题的原因尝试了很久才想明白:第二个wc是第一个wc的子进程,而wshell最多只等待第一个wc,不等后一个进程结束就显示下一行提示符了!
首先想到两种解决办法:
因此,只好根据(1)的思路进行修改,为了能使用wait()/waitpid(),唯一的方法是让command1和command2都是wshell的子进程了。这样修改需要改变一部分已有逻辑关系,不过为了追求高仿,还是进行了。
嗯,还不错,这样修改后,甚至可以为command2也配置出"&"了。
4.1 和真实的shell相比,有什么不足
· 暗藏了不少bug是肯定的,毕竟调试次数还是很少;
· 内建指令不全,只实现了最常用的cd,作为示例,姑且算是足够了吧;
· 不能执行shell脚本、用户命令分析模式单一,这都是没有编写完整解释器的缘故。以前本科编译原理实验课的时候写过还算完整的一个小语言的词法分析和句法分析器,那时就写的有点吐血。当然,正则表达式这样高端的功能更是别想了。如果真写起来shell的解释器,代码量绝对比上文中的shell多。
· 异常处理机制不够健全,只有少数的异常处理,并且对不正确的用户命令也无法处理,部分还是因为解释器,另一部分是因为示例程序,我对它的健壮性就偷懒了不少;
· shell机制没模仿全,只有管道、重定向、后台执行——如果加其他功能,同样是需要扩充解释器的;
· 命令行没有历史命令,这是readline库的特性,我没有加上;
· 一些可能的性能优化没有进行,因为主要目的是展示原理,关于性能没有再做深入思考。