Implement a Shell by yourself -- MIT xv6 shell


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/目錄下的程序的,那多無聊哇...我該了部分代碼.然後還改了那天殺的兩個對齊...









                               "騷年別鬧~"



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章