嵌入式Linux裸機(jī)開發(fā)之shell實(shí)現(xiàn)

姓名:袁永輝? ? ? 學(xué)號(hào):17101223423

轉(zhuǎn)載自:http://mp.weixin.qq.com/s/85bVdghPnzJVIhhRIecK3w

【嵌牛導(dǎo)讀】:Linux系統(tǒng)提供給用戶的最重要的系統(tǒng)程序是Shell命令語言解釋程序,shell不屬于內(nèi)核部分,在核心之外,以用戶態(tài)方式運(yùn)行,基本功能是解釋并執(zhí) 行用戶打入的各種命令,實(shí)現(xiàn)用戶與Linux核心的接口。系統(tǒng)初啟后,核心為每個(gè)終端用戶建立一個(gè)進(jìn)程去執(zhí)行Shell解釋程序。

【嵌牛鼻子】:Linux 操作系統(tǒng)? shell? 程序框架

【嵌牛提問】:什么是shell 它的功能與命令有哪些?

【嵌牛正文】:

一、shell簡介

Shell是用戶與操作系統(tǒng)之間的接口,為用戶提供了使用操作系統(tǒng)的接口。

1、圖形界面shell

? ? 圖形界面shell(Graphical User Interface shell 即 GUI shell)

? ? 應(yīng)用最為廣泛圖形界面shell是Windows Explorer(微軟的windows系列操作系統(tǒng))和Linux shell,其中l(wèi)inux shell 包括 X window manager (BlackBox和FluxBox),以及功能更強(qiáng)大的CDE、GNOME、KDE、 XFCE。

2、命令行式shell

? ? 命令行式shell(Command Line Interface shell ,即CLI shell)

? ? 常見的命令行式shell有MS-DOS系統(tǒng)、Windows PowerShell、Bourne shell、Korn shell 、Bourne Again shell 、POSIX shell 、C shell(包括 csh and tcsh)。

二、Linux Shell工作原理

Linux系統(tǒng)提供給用戶的最重要的系統(tǒng)程序是Shell命令語言解釋程序,shell不屬于內(nèi)核部分,在核心之外,以用戶態(tài)方式運(yùn)行,基本功能是解釋并執(zhí) 行用戶打入的各種命令,實(shí)現(xiàn)用戶與Linux核心的接口。系統(tǒng)初啟后,核心為每個(gè)終端用戶建立一個(gè)進(jìn)程去執(zhí)行Shell解釋程序。shell的執(zhí)行過程如下:

1、讀取用戶由鍵盤輸入的命令行。

2、解析命令,以命令名作為文件名,并將其它參數(shù)改造為系統(tǒng)調(diào)用execve函數(shù)內(nèi)部處理所要求的形式。

3、終端進(jìn)程調(diào)用fork函數(shù)建立一個(gè)子進(jìn)程。

4、終端進(jìn)程本身用系統(tǒng)調(diào)用wait4( )來等待子進(jìn)程完成(如果是后臺(tái)命令,則不等待)。


當(dāng)子進(jìn)程運(yùn)行時(shí)調(diào)用execve,子進(jìn)程根據(jù)文件名(即命令名)到目錄中查找有關(guān)文件,將它調(diào)入內(nèi)存,執(zhí)行這個(gè)程序(解釋這條命令)。


5、如果命令末尾有&號(hào)(后臺(tái)命令符號(hào)),則終端進(jìn)程不用系統(tǒng)調(diào)用wait4( )等待,立即發(fā)提示符,讓用戶輸入下一個(gè)命令,轉(zhuǎn)到第1步。如果命令末尾沒有&號(hào),則終端進(jìn)程要一直等待,當(dāng)子進(jìn)程(即運(yùn)行命令的進(jìn)程)完成處理后終止,向父進(jìn)程(終端進(jìn)程)報(bào)告,此時(shí)終端進(jìn)程醒來,在做必要的判別等工作后,終端進(jìn)程發(fā)提示符,讓用戶輸入新的命令,重復(fù)上述處理過程。

圖片發(fā)自簡書App

程序框架:

while(1) {/* repeat forever */

  type_prompt();/* display prompt on the screen */

  read_command(command,parameters);/* read input from terminal */

  if(fork()!=0) {             ? /* fork off child process */

    /* Parent code */

    waitpid(-1,&status,0);       ? /* wait for child to exit */

  } else {

    /* Child code */

    execve(command,parameters,0);/* execute command */

  }

}

剛接觸Linux時(shí),對shell總有種神秘感;在對shell的工作原理有所了解之后,便嘗試著動(dòng)手寫一個(gè)shell。下面是一個(gè)從最簡單的情況開始,一步步完成一個(gè)模擬的shell的過程。這個(gè)所謂的shell和主流的shell還是有不少區(qū)別的,最大的區(qū)別是它本身不能執(zhí)行shell腳本、也不能對一些復(fù)雜的命令行進(jìn)行分析——原因很簡單,我沒有寫相應(yīng)的解釋器。如果想自己實(shí)現(xiàn)一個(gè)簡化的shell腳本解釋器,如果有編譯原理的知識(shí)準(zhǔn)備,本身不是難事,但是工作量比較大,這里就不完成了,有興趣的可以進(jìn)行嘗試。

一.基本功能

1.1 程序框架

首先,shell的基本框架可以用下面的代碼概括,這部分代碼出自于《現(xiàn)代操作系統(tǒng)(英文第三版)》(Modern Operating Systems)原書P54圖1-19,這在上一篇博文《現(xiàn)代操作系統(tǒng)》精讀與思考筆記 第一章 引論中已經(jīng)提到過一次了:

#define TRUE 1

while(TRUE) {                  /* repeat forever */

  type_prompt();               /* display prompt on the screen */

  read_command(command,parameters);    /* read input from terminal */

  if(fork()!=0) {             ? /* fork off child process */

    /* Parent code */

    waitpid(-1,&status,0);       ? /* wait for child to exit */

  } else {

    /* Child code */

    execve(command,parameters,0);    /* execute command */

  }

}

不怕寫不出來代碼,就怕沒思路。想想這么搞確實(shí)能夠模擬出shell最基本的行為:接受用戶輸入<=>執(zhí)行相應(yīng)程序,甚至借助execv族函數(shù)可以直接給程序傳參數(shù)。有了這個(gè)框架就好辦了,把那幾個(gè)函數(shù)給實(shí)現(xiàn)不就成了唄?

1.2 type_prompt()的實(shí)現(xiàn)

來思考下type_prompt()該如何實(shí)現(xiàn)。顧名思義,這個(gè)要提供一個(gè)終端上的提示符,比如

圖片發(fā)自簡書App

再如

圖片發(fā)自簡書App

這里的實(shí)現(xiàn)需要注意的是,如果當(dāng)前路徑在用戶路徑下,那么用戶路徑就用~代替,否則會(huì)顯示完整路徑。分析這兩個(gè)例子,可以看到輸出是這樣的形式:“用戶名@主機(jī)名:路徑$”(root權(quán)限的#提示符馬上提到),對應(yīng)地:

· 用戶名使用getpwuid(getuid())獲得,同時(shí)可以獲得該用戶home目錄的路徑;

· 主機(jī)名使用gethostname()獲得;

· 路徑使用getcwd()獲得,如果這個(gè)路徑包含了該用戶home路徑,那么使用~把home路徑縮略。

· 對于提示符,模仿bash的風(fēng)格,對于普通用戶使用"$",root用戶使用"#",需要檢測執(zhí)行這個(gè)wshell的用戶權(quán)限,利用geteuid()是否為0來判斷。

這樣,就可以著手編寫type_prompt()了。為了以示和bash的區(qū)別,可以在提示符里加點(diǎn)自己的東西,比如下圖第二行那樣:

圖片發(fā)自簡書App

注:查看默認(rèn)shell版本的命令是echo $SHELL。

1.3 read_command()

在type_prompt()寫好之后,可以做一點(diǎn)簡單的測試,屏幕上會(huì)出現(xiàn)上一節(jié)最末的效果圖,乍一看還挺唬人的。不過此時(shí)還是徒有其表,尚且不能執(zhí)行任何程序,難道就讓它在這里孤芳自賞?接下來需要實(shí)現(xiàn)read_command(),它從用戶輸入中讀取命令和參數(shù),分別放入command[]和parameters[][]中,作為exec族函數(shù)執(zhí)行。

最初的版本只是通過fgets()把整行輸入讀入一個(gè)較大的緩沖區(qū)中,再對這行進(jìn)行分析,提取出命令以及參數(shù),分別放到相應(yīng)的位置。其實(shí)Linux本身接受的參數(shù)表總長度大小是有限的,這個(gè)限制由ARG_MAX給出。因此,這里的緩沖區(qū)也的大小用宏定義做一個(gè)硬性限制就行了。當(dāng)然,fgets()有個(gè)壞處:如果輸入時(shí)想要使用退格鍵修改前面的輸入,是不能完成的,這和真實(shí)的shell相差有點(diǎn)大。不過這里暫不考慮這個(gè)問題,留在后面補(bǔ)充。

輸入的分析,其實(shí)就是字符串的處理,把一個(gè)字符串拆成多個(gè)字符串(命令、參數(shù))并分別復(fù)制到由malloc()分配的空間中。最初版本的思路比較復(fù)雜,本文2.2提供了比較好的實(shí)現(xiàn)。

另外一點(diǎn)需要注意:實(shí)際上command保存的是路徑+命令,而命令本身按照慣例應(yīng)該存在parameters[0]中。這一點(diǎn)在最初時(shí)沒有注意,后面用ls命令測試時(shí)發(fā)現(xiàn)了這一點(diǎn)。

1.4 選擇execve()還是execvp()

既然示例中的execve()的環(huán)境變量參數(shù)env恒為0,沒有使用的必要了。況且execvp()能夠直接執(zhí)行l(wèi)s這樣的命令而不用加上路徑,更接近于shell,于是選擇后者。

1.5 簡單測試

動(dòng)手寫一個(gè)hello world的程序,然后用這個(gè)wshell運(yùn)行。下面的輸出包含了一些分析輸入的調(diào)試信息:

圖片發(fā)自簡書App

再試試最初未把command中命令放入parameters[0]時(shí)不能運(yùn)行的ls:

圖片發(fā)自簡書App

雖然和shell相比,沒有顏色區(qū)分,但已經(jīng)可以正常運(yùn)行了。這兩個(gè)測試表明,wshell已經(jīng)初具shell的基本功能。

二、改善用戶體驗(yàn):內(nèi)建命令、readline庫

2.1.內(nèi)建命令(built-in command)

當(dāng)完成基本功能、喜滋滋地在其中測試各種常用命令時(shí),top、vim等都乖乖就范,唯獨(dú)cd沒有任何效果。本來以為cd只能改變子進(jìn)程的工作目錄,而wshell是父進(jìn)程,導(dǎo)致無效。然而輸入whereis cd來查看cd所在目錄,沒有顯示它的路徑,頓生疑惑:cd是怎么實(shí)現(xiàn)的?看到stackoverflow上一個(gè)問答,解釋了這個(gè)疑惑:像cd這樣的命令實(shí)際并非可執(zhí)行程序,(如果想在自己編寫的shell里使用)需要自己來實(shí)現(xiàn)為內(nèi)建命令。那么,對于這種命令,肯定是不能exec()了,需要進(jìn)行分析和額外處理。而且可以看出,它的執(zhí)行并不需要建立子進(jìn)程。

這個(gè)分析和處理過程,實(shí)際上應(yīng)該是解釋器的一部分功能,當(dāng)然這里比較簡化,只是針對特定的命令進(jìn)行處理罷了。這個(gè)過程由buildin_command()完成,并且不創(chuàng)建子進(jìn)程。因此,主進(jìn)程相應(yīng)地添加

if(buildin_command(command,parameters))

? ? continue;

接下來實(shí)現(xiàn)幾個(gè)內(nèi)建命令。最簡單的是exit和quit,直接調(diào)用exit()結(jié)束wshell主進(jìn)程即可。

順便編寫一個(gè)about命令,這是我自己添加的,shell本身是沒有這個(gè)命令的,它會(huì)顯示一些關(guān)于wshell的簡短信息。

接下來是cd的實(shí)現(xiàn)了。對于以下幾種使用方法,使用chdir()就可以直接完成對應(yīng)的操作:

cd

cd PATHNAME

cd .

cd ..

但是對于cd ~以及cd ~/PATHNAME就不行了。對于這種情況,可以發(fā)現(xiàn)這個(gè)路徑的特點(diǎn)是以“~”開始,那么利用type_prompt()中的獲取工作目錄的方式,重新拼接出完整路徑再作為參數(shù)進(jìn)行傳遞即可。為了提高效率,把type_prompt()中獲取的信息做成是全局的,這樣實(shí)現(xiàn)cd時(shí)可以直接調(diào)用。

chdir()的出錯(cuò)處理也從簡了,直接把strerror的內(nèi)容顯示在屏幕上。如果想建立一個(gè)比較完善的錯(cuò)誤處理機(jī)制。

甚至可以發(fā)現(xiàn),shell本身似乎也是用對"~"路徑補(bǔ)全的方式來實(shí)現(xiàn)的,這可以通過cd一個(gè)不存在的目錄所表現(xiàn)的行為發(fā)現(xiàn):

圖片發(fā)自簡書App

這一天發(fā)現(xiàn)最初的版本沒有對分配的內(nèi)存進(jìn)行回收,可能導(dǎo)致內(nèi)存泄漏。打算重寫這一部分代碼,使其更接近于Linux內(nèi)部實(shí)現(xiàn)。

2.2 readline庫的使用以及read_command()的重寫

在1.3節(jié)提到,read_command()的行為和真實(shí)的shell命令輸入不一樣,后者是基于readline庫實(shí)現(xiàn)的。讓wshell也是用這個(gè)庫,就可以做出同樣的行為了。正好之前發(fā)現(xiàn)了原先的read_command()處理command和parameter兩個(gè)參數(shù)時(shí)沒有釋放,會(huì)導(dǎo)致內(nèi)存泄漏,這里重寫一下。為了便于理解,下圖是前后二者的區(qū)別:

圖片發(fā)自簡書App

使用后者,不必每次為command和parameter[][]分配空間,只需要一個(gè)足夠大(也就是ARG_MAX大?。┑腷uffer即可,不必操心內(nèi)存分配的問題了。同時(shí),由于后者中參數(shù)的定位全部是由指針完成,在添加更多的功能(后文的重定向、pipe)也會(huì)更加方便。這種實(shí)現(xiàn)我不確定是否為bash的實(shí)現(xiàn),但看上去更接近于“所有參數(shù)總長度限制為ARG_MAX”的設(shè)定。改寫之后,代碼也比之前精簡不少。

回到本節(jié)正題上來,看看readline庫是怎么使用的。

首先,這個(gè)庫是需要安裝的,我所使用的Ubuntu10.04上默認(rèn)并沒有安裝這個(gè)庫。執(zhí)行下面語句進(jìn)行安裝:

sudo apt-get install libreadline5-dev

同時(shí)為了便于以后調(diào)試的方便,同時(shí)提供了兩個(gè)版本的read_command(),使用READLINE_ON來控制編譯時(shí)是否選擇使用了readline庫的版本,并在對應(yīng)的makefile中加上-D READLINE_ON -I /usr/include -lreadline -ltermcap。

直接使用

buffer? = readline(NULL);

這時(shí),似乎已經(jīng)很接近shell的用戶體驗(yàn)了。但是使用退格鍵消除所有字符后,發(fā)現(xiàn)居然連著提示符也消失了??磥恚瑃ype_prompt()也需要重寫了:把完整的提示符字符串作為參數(shù)傳遞給readline()。

這樣之后,就能模仿shell的輸入體驗(yàn),甚至可以進(jìn)行命令補(bǔ)全和路徑補(bǔ)全。不過想實(shí)現(xiàn)歷史命令還是不行,可以參考使用readline庫實(shí)現(xiàn)應(yīng)用程序下的仿終端輸入模式等。

三、進(jìn)階功能:后臺(tái)執(zhí)行、輸入/輸出重定向、pipe

3.1 準(zhǔn)備工作

有了前面的經(jīng)驗(yàn),這些功能看上去無非也就是利用一些Linux的庫函數(shù)、系統(tǒng)調(diào)用等API完成的嘛。不過麻煩的地方在于,如何從用戶輸入中判斷使用哪一種或哪幾種功能?這似乎又繞不過句法分析這一步,因此繼續(xù)簡化設(shè)計(jì),首先把一個(gè)合法用戶輸入規(guī)定為下面的形式:

command1 [[parameter1_1] ... [parameter1_n]] [<</< file1] [>>/> file2] [| command2 [parameter2_1] ... [parameter2_n]] [&]

并作出規(guī)定:

1.一個(gè)正確輸入只能為上面的形式,一共可以有20個(gè)單元,長度為MAXLINE大小,非法輸入的執(zhí)行結(jié)果是未定義的;

2.當(dāng)輸出重定向>>/>和管道符|同時(shí)出現(xiàn)時(shí),command1的輸出只會(huì)重定向至file2,這樣之后才執(zhí)行command2;

3.無論是否出現(xiàn)command2,"&"只對command1有效,且必須與前一個(gè)可選項(xiàng)中有一個(gè)空格(bash可以直接使用"ls&"這樣的命令,但在這里只能寫成"ls &")

這樣,就可以專注于處理合法輸入的情況了。當(dāng)然,一個(gè)健壯的解釋器肯定是需要處理異常輸入的。

對于輸入的句法分析結(jié)果,使用一個(gè)結(jié)構(gòu)體來進(jìn)行保存,以便接下來的使用。這個(gè)結(jié)構(gòu)體如下:

struct parse_info

{

? ? int flag;       //表明使用了哪些功能的標(biāo)志位

? ? char* in_file;    //輸入重定向的文件名

? ? char* out_file;   //輸出重定向的文件名

? ? char* command2;   //命令2

? ? char** parameter2; //命令2的參數(shù)表

};

編寫句法分析函數(shù)parsing()來填充這個(gè)結(jié)構(gòu)體,以備后續(xù)使用。

以下各節(jié)的實(shí)現(xiàn)(API的選取)參考了《UNIX操作系統(tǒng)設(shè)計(jì)》7.8節(jié) shell部分,主干如下,在理解了1.1節(jié)介紹的《現(xiàn)代操作系統(tǒng)》上的shell框架之后,下面無非在這個(gè)框架里面加了點(diǎn)東西而已。不過這個(gè)框架似乎不適合我原先寫的代碼,需要進(jìn)行調(diào)整。

/*read command line until EOF*/while(read(stdin,buffer,numchars))

{

? ? /*parse command line*/

? ? if(/* command line contains & */)

? ? ? ? amper = 1;

? ? else

? ? ? ? amper = 0;

? ? /* for commands not part of the shell command language */

? ? if(fork() == 0)

? ? {

? ? ? ? /* redirection of IO?*/

? ? ? ? if(/* redirect output */)

? ? ? ? {

? ? ? ? ? ? fd = creat(newfile,fmask);

? ? ? ? ? ? close(stdout);

? ? ? ? ? ? dup(fd);

? ? ? ? ? ? close(fd);

? ? ? ? ? ? /* stdout is now redirected */

? ? ? ? }

? ? ? ? if(/* piping */)

? ? ? ? {

? ? ? ? ? ? pipe(fildes);

? ? ? ? ? ? if(fork() == 0)

? ? ? ? ? ? {

? ? ? ? ? ? /* first component of command line */

? ? ? ? ? ? ? ? close(stdout);

? ? ? ? ? ? ? ? dup(fildes[1]);

? ? ? ? ? ? ? ? close(fildes[1]);

? ? ? ? ? ? ? ? close(fildes[0]);

? ? ? ? ? ? ? ? /* stdout now goes to pipe */

? ? ? ? ? ? ? ? /* child process does command */

? ? ? ? ? ? ? ? execlp(command1,command1,0);

? ? ? ? ? ? }

? ? ? ? ? ? /* 2nd command component of command line*/

? ? ? ? ? ? close(stdin);

? ? ? ? ? ? dup(fildes[0]);

? ? ? ? ? ? close(fildes[0]);

? ? ? ? ? ? close(fildes[1]);

? ? ? ? ? ? /* standard input now comes from pipe */

? ? ? ? }

? ? ? ? execve(command2,command2,0);

? ? }

? ? /* parent continues over here ...

? ? /* waits for child to exit if required

? ? */

? ? if(amper == 0)

? ? ? ? retid = wait(&status);

}

3.2 后臺(tái)運(yùn)行

這個(gè)比較簡單,讓父進(jìn)程不等待子進(jìn)程退出而直接讀入用戶的下一步操作即可,不執(zhí)行wait()。為了進(jìn)一步模擬shell,可以把子進(jìn)程ID顯示出來。

(2014.4.14更新)

注意,對于后臺(tái)運(yùn)行的子進(jìn)程,如果父進(jìn)程提前退出了,自然會(huì)成為init進(jìn)程的孩子;而如果這些子進(jìn)程在父進(jìn)程退出前退出,又沒有對應(yīng)的waitpid()進(jìn)行回收,就會(huì)成為僵尸進(jìn)程。使用signal()處理SIGCHLD可以解決這個(gè)問題,并且由于Linux的信號(hào)是不排隊(duì)的,需要將所有的已結(jié)束的子進(jìn)程進(jìn)行回收。

但是,僅僅增加一個(gè)信號(hào)處理函數(shù),對于前臺(tái)運(yùn)行的進(jìn)程,waitpid()阻塞過程是否會(huì)失效?為了讓這兩種waitpid()不相互干擾,把后臺(tái)運(yùn)行進(jìn)程的pid放入一個(gè)專門的數(shù)組中,信號(hào)處理函數(shù)只對這一類進(jìn)程進(jìn)行處理。對于不是后臺(tái)運(yùn)行的子進(jìn)程,在信號(hào)處理函數(shù)什么也不做就返回后,使用指定了其pid的waitpid()處理。

3.3 輸入/輸出重定向

因?yàn)橹囟ㄏ蛑贿m用于用戶輸入中的command1,一旦判斷出有command2的存在,就應(yīng)該著手分離二者了,否則會(huì)導(dǎo)致二者的輸入/輸出都被重定向到同樣的文件上去。

不過要注意,雖然'>'和'>>'都是輸出重定向,前者會(huì)覆蓋原有文件內(nèi)容,而后者是在文件尾部增加,需要進(jìn)行簡單的分別對待。(12.8更新)

先利用flag的IS_PIPED標(biāo)志位判斷是否需要為command2創(chuàng)建新進(jìn)程,內(nèi)容暫略,先利用dup2()把重定向功能寫好,下面只寫出了輸入重定向,輸出重定向是類似的:?

? ? ? ? if(info.flag & IS_PIPED) //command2 is not null? ? ? ? ? ? {

? ? ? ? ? ? ? ? if(fork() == 0)//command2? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? //pipe

? ? ? ? ? ? ? ? ? ? //execvp(info.command2,info.parameters2);? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? int in_fd,out_fd;

? ? ? ? ? ? if(info.flag & IN_REDIRECT)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? in_fd = open(info.in_file, O_CREAT |O_RDONLY, 0666);

? ? ? ? ? ? ? ? close(fileno(stdin));

? ? ? ? ? ? ? ? dup2(in_fd, fileno(stdin));

? ? ? ? ? ? ? ? close(in_fd);

? ? ? ? ? ? }

? ? ? ? ? ? if(info.flag & OUT_REDIRECT)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? out_fd = open(info.out_file, O_CREAT|O_RDWR, 0666);

? ? ? ? ? ? ? ? close(fileno(stdout));

? ? ? ? ? ? ? ? dup2(out_fd, fileno(stdout));

? ? ? ? ? ? ? ? close(out_fd);

? ? ? ? ? ? }

? ? ? ? ? ? execvp(command,parameters);

3.4 管道

直接使用pipe()就可以了,管道的寫法沒什么特別,不過對于同時(shí)使用了輸出重定向和管道的command1,需要把它的管道關(guān)閉,這樣就會(huì)給command2發(fā)送一個(gè)EOF。

3.5 模仿,再模仿……

使用系統(tǒng)自帶的wc,并隨便編寫個(gè)1.txt,測試目前的版本吧。

圖片發(fā)自簡書App

看上去怎么就那么別扭呢?對比一下,下圖是真實(shí)的shell的行為:

圖片發(fā)自簡書App

這個(gè)問題的原因嘗試了很久才想明白:第二個(gè)wc是第一個(gè)wc的子進(jìn)程,而wshell最多只等待第一個(gè)wc,不等后一個(gè)進(jìn)程結(jié)束就顯示下一行提示符了!

首先想到兩種解決辦法:

(1)wait()/waitpid()。但發(fā)現(xiàn)Linux本身的wait()/waitpid()函數(shù)不能處理子進(jìn)程的子進(jìn)程,同時(shí)command1在執(zhí)行時(shí)就被替換成了wc,不能讓它執(zhí)行wait()/waitpid();

(2)改變command2的父進(jìn)程ppid使其為wshell的pid。但沒有查到可以完成的API。

因此,只好根據(jù)(1)的思路進(jìn)行修改,為了能使用wait()/waitpid(),唯一的方法是讓command1和command2都是wshell的子進(jìn)程了。這樣修改需要改變一部分已有邏輯關(guān)系,不過為了追求高仿,還是進(jìn)行了。

修改完再試試:  

圖片發(fā)自簡書App

嗯,還不錯(cuò),這樣修改后,甚至可以為command2也配置出"&"了。

四、總結(jié)

4.1 和真實(shí)的shell相比,有什么不足

· 暗藏了不少bug是肯定的,畢竟調(diào)試次數(shù)還是很少;

· 內(nèi)建指令不全,只實(shí)現(xiàn)了最常用的cd,作為示例,姑且算是足夠了吧;

· 不能執(zhí)行shell腳本、用戶命令分析模式單一,這都是沒有編寫完整解釋器的緣故。以前本科編譯原理實(shí)驗(yàn)課的時(shí)候?qū)戇^還算完整的一個(gè)小語言的詞法分析和句法分析器,那時(shí)就寫的有點(diǎn)吐血。當(dāng)然,正則表達(dá)式這樣高端的功能更是別想了。如果真寫起來shell的解釋器,代碼量絕對比上文中的shell多。

這個(gè)shell只用parsing()來替代了這部分功能;

· 異常處理機(jī)制不夠健全,只有少數(shù)的異常處理,并且對不正確的用戶命令也無法處理,部分還是因?yàn)榻忉屍?,另一部分是因?yàn)槭纠绦?,我對它的健壯性就偷懶了不少?/p>

· shell機(jī)制沒模仿全,只有管道、重定向、后臺(tái)執(zhí)行——如果加其他功能,同樣是需要擴(kuò)充解釋器的;

· 命令行沒有歷史命令,這是readline庫的特性,我沒有加上;

· 一些可能的性能優(yōu)化沒有進(jìn)行,因?yàn)橹饕康氖钦故驹?,關(guān)于性能沒有再做深入思考。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容