本身動手實現一個Unix Shell

這個實驗經過實現一個支持做業控制的Unix Shell,讓咱們對進程控制和信號控制更加熟悉。課程Lab已經幫助咱們搭建起了Shell的總體框架,並實現了與本次實驗不太相關的代碼,核心部分須要咱們本身完成。git

總體框架

Shell從標準輸入(stdin)讀取用戶輸入的命令,而後解析命令,Shell支持兩種類型的命令:若是用戶輸入的是的內置命令(如quitjobs等),那麼直接執行該命令;若是用戶輸入的是某個可執行文件的路徑,那麼經過fork一個子進程,在子進程中加載並執行命令。Shell把每次用戶輸入的命令抽象爲一個job,一個job能夠包含多個進程(例如管道)。每一個job有兩種運行方式,若是用戶輸入的命令以'&'結尾,那麼job將會在後臺(background)運行,不然,job運行在前臺(foreground)。在任意時刻,只容許存在01個前臺job,可是能夠有0或多個後臺job運行。最後,爲了支持用戶可以向Shell發送信號,咱們還須要實現3個信號處理程序,分別處理信號SIGCHLDSIGINTSIGTSTPgithub

須要注意的地方

  • 默認的,一個子進程和它的父進程同屬於一個進程組,而Unix系統提供的大量向進程發送信號的機制,都是基於進程組這個概念的。當咱們輸入Ctrl + C,內核會發送一個SIGINT信號到前臺進程組的每一個進程,相似的,輸入Ctrl + Z會致使內核發送一個SIGTSTP信號給前臺進程組中的每一個進程。這兒的「前臺進程組」指的是Shell進程所屬的進程組。實驗中,咱們並不指望信號直接做用於Shell進程自己(不然Shell收到SIGINT信號就終止了),而是須要讓Shell將信號轉發給Shell前臺做業中的子進程及其所屬進程組中的全部進程。因此,咱們不能讓子進程和Shell進程同屬一個進程組。具體作法是經過使用setpgid函數來改變子進程的進程組,當調用setpgid(0, 0)時,內核會建立一個新的進程組,其進程組ID是調用者進程的PID,而且會把調用者進程加入到這個進程組中。
  • Shell收到信號時,具體的工做須要信號處理函數來完成。例如收到SIGINT信號,那麼信號處理函數會把該信號發往前臺job中的進程及其所屬進程組中的全部進程。實驗中,咱們是經過kill(pid_t pid, int sig)來發送信號,注意到咱們並不單單是向PID = pid的進程發送信號,kill函數幫助咱們實現了這一點:若是pid小於0kill發送信號sig給進程組|pid|pid的絕對值)中的每一個進程。咱們能夠意識到,上一點須要注意的地方正是爲這一點作鋪墊的。
  • 父進程(Shellfork了一個子進程後,父進程須要將這個進程做爲一個job添加到job隊列中去(addjob),當子進程終止時,內核會發送一個SIGCHLD信號給父進程,而後在相應的信號處理程序中,把終止的子進程對應的jobjob隊列中刪除(deletejob)。考慮一種狀況:當父進程fork了一個子進程以後,子進程先於父進程得到調度,而且在父進程執行addjob前,子進程就已經終止了,併發送了SIGCHLD信號給父進程。此時,在信號處理程序中deletejob不會作任何操做,由於此時父進程尚未把job加入到job隊列中。出現這個問題的根本緣由是在addjob以前調用了deletejob。解決這個問題的方法是:在父進程fork子進程以前,將SIGCHLD信號阻塞,當完成addjob以後,才解除對SIGCHLD信號的阻塞,這樣就能保證在子進程被添加到job隊列以後再回收該子進程。注意,子進程繼承了它們父進程的被阻塞信號集合,因此咱們必須在調用execve以前,解除子進程中阻塞的SIGCHLD信號。

代碼

Shell Lab的代碼在這裏併發

相關文章
相關標籤/搜索