這個實驗經過實現一個支持做業控制的Unix Shell
,讓咱們對進程控制和信號控制更加熟悉。課程Lab
已經幫助咱們搭建起了Shell
的總體框架,並實現了與本次實驗不太相關的代碼,核心部分須要咱們本身完成。git
Shell
從標準輸入(stdin
)讀取用戶輸入的命令,而後解析命令,Shell
支持兩種類型的命令:若是用戶輸入的是的內置命令(如quit
、jobs
等),那麼直接執行該命令;若是用戶輸入的是某個可執行文件的路徑,那麼經過fork
一個子進程,在子進程中加載並執行命令。Shell
把每次用戶輸入的命令抽象爲一個job
,一個job
能夠包含多個進程(例如管道)。每一個job
有兩種運行方式,若是用戶輸入的命令以'&
'結尾,那麼job
將會在後臺(background
)運行,不然,job
運行在前臺(foreground
)。在任意時刻,只容許存在0
或1
個前臺job
,可是能夠有0
或多個後臺job
運行。最後,爲了支持用戶可以向Shell
發送信號,咱們還須要實現3
個信號處理程序,分別處理信號SIGCHLD
、SIGINT
和SIGTSTP
。github
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
小於0
,kill
發送信號sig
給進程組|pid|
(pid
的絕對值)中的每一個進程。咱們能夠意識到,上一點須要注意的地方正是爲這一點作鋪墊的。Shell
)fork
了一個子進程後,父進程須要將這個進程做爲一個job
添加到job
隊列中去(addjob
),當子進程終止時,內核會發送一個SIGCHLD
信號給父進程,而後在相應的信號處理程序中,把終止的子進程對應的job
從job
隊列中刪除(deletejob
)。考慮一種狀況:當父進程fork
了一個子進程以後,子進程先於父進程得到調度,而且在父進程執行addjob
前,子進程就已經終止了,併發送了SIGCHLD
信號給父進程。此時,在信號處理程序中deletejob
不會作任何操做,由於此時父進程尚未把job
加入到job
隊列中。出現這個問題的根本緣由是在addjob
以前調用了deletejob
。解決這個問題的方法是:在父進程fork
子進程以前,將SIGCHLD
信號阻塞,當完成addjob
以後,才解除對SIGCHLD
信號的阻塞,這樣就能保證在子進程被添加到job
隊列以後再回收該子進程。注意,子進程繼承了它們父進程的被阻塞信號集合,因此咱們必須在調用execve
以前,解除子進程中阻塞的SIGCHLD
信號。Shell Lab
的代碼在這裏。併發