Implement a Shell by yourself -- MIT xv6 shell

来源:互联网 发布:淘宝金牌卖家靠谱吗 编辑:程序博客网 时间:2024/05/29 03:14


Implement a Shell by yourself -- MIT xv6 shell



这个其实是作为6.828的一个小课堂作业 ...

着重分析构建思想和过程,具体代码实现去github可以找到.


https://github.com/jasonleaster/MIT_6_828_assignments_2012/blob/homework1/sh.c


----------------------------------- 大家好,我是分割线 -------------------------------------------------------------------


这里主要实现了基础的三类命令

  • 可执行的程序命令
  • 重定向命令
  • 管道命令

实现的"基类" (原谅我用了这个词)就是struct cmd这个结构体就一个成员,用于记录命令的类型.

三类, ' ' 表示可执行程序 '|' 表示管道命令,  '<'  和'>' 表示重定向类型.

每一个类型分别继承基类,派生出对应的三类结构体 

struct execcmd

struct redircmd

struct pipecmd


对于可执行命令,主要记录可执行程序的程序名字还有各种选项参数.所以会有 char* argv[MAXARGS]

对于重定向命令,主要记录 cmd 即触发这个重定向的程序比方说 ./a.out > tmp.txt

                         那么cmd就是记录的./a.out 重定向到那个文件的文件名 char *file指针指向这个文件名.

对于管道,           则主要记录管道左右两侧的命令


void runcmd(struct cmd * cmd);

这个函数是真正驱动调用实现shell的核心.负责调用系统接口函数 execv(), open(), close(), dup(), pipe()等等一系列函数,来完成我们既定的目标.

作业也就是补全这个函数.

这是个递归的函数!很有意思.

你会发现,shell的命令实现居然是递归的哈哈


下面是我画的一个简单的流程图

你会发现,是先处理可执行程序,然后检查输入中是否有管道,如果有,那么递归的调用parsepipe去构建这些可执行程序间的输入输出的关系.直到所有管道被检查完,那么返回parsecmd(),进入到runcmd()开始执行命令.



这里是主函数:



调用getcmd()在标准输入读取sizeof(buf)大小的字符,然后,写入到buf中.

那个 if(buf[0] .... )是判断你是不是输入了 cd命令 如果是把buf尾部赋值为0,这样buf看起来就是储存的一个字符串,

然后调用chdir() 更换当buf+3开始的字符串指定的路径.

接着continue继续读取命令啦...

如果你不更换路径了

我们就fork1()出一个子进程,parent process就一直等待子进程挂掉...等啊等..等啊等..


这个时候,子进程就开始调用parsecmd()去分析你输入的命令字符串咯...




es指针指向字符串的末端,确切的说是空字符处

然后去调用 parseline(&s, es)

parseline() 看起来太弱了,就是一层简单的封装.实际核心函数还是parsepipe




这里要说一说里面关键的几个子函数



经常会看到下面这个while循环,作用是啥呢?

s 指向我们输入的命令字符串,strchr通过检查 *s是否是 whilespace里的任意一个字符,如果是其中某个字符,那么我们就要跳过这个字符,知道我们把s移动到指向一个非空格类的字符


再看另外一个一旦遇到空格字符,或者symbols中的任意一个字符,我们就不再移动指针s



下面是实现pipe的一部分.

分析:

第一次调用fork1() 产生 child process 1 该进程用于运行 pcmd->left 指向的进程

第二次调用fork1() 产生 child process 2 该进程用于运行 pcmd->right 指向的进程

child process 1 由于先 close(1)那么文件描述符1就被空余出来了, 调用dup(p[1])把 child process 1的标准输出(文件描述符默认的是1)和管道的输出关联起来

child process 2的伎俩差不多,只是把进程的标准输入关闭了,把从管道的输入作为进程的标准输入来用.



我刚差点被自己蠢哭了,睡了三个小时哈哈哈

这里有两个 wait(), 为什么呢?奇怪..

一点都不奇怪..必须两个wait(),因为这里有两个子进程,parent process必须等这两个进程都挂了之后再结束.


execcmd()返回一个struct cmd()结构体.

同样的*cmd()函数都会返回一个对应的 *结构体

值得特别强调好玩的事情是,你会发现这里 execcmd()返回的是一个 struct cmd* 指针

但是execcmd()函数确实申请的是一个struct execcmd()结构体.那么问题就来了..怎么会这样.

回过头去观察四种结构体的之间的关系你就会发现,这里巧妙之处就在于,他们的第一个成员都是相同的!

返回了一个"基类"指针.




最后的运行效果.



能在本地的文件 ./tmp.txt里找到输出.




update: 2015.04.19 原本的程序是不能调用/bin/目录下的程序的,那多无聊哇...我该了部分代码.然后还改了那天杀的两个对齐...









                               "骚年别闹~"



2 0
原创粉丝点击