一、終端的概念
在UNIX系統中,使用者通過終端登入系統後得到一個Shell進程,這個終端成為Shell進程的控制終端(Controlling Terminal),控制終端是儲存在PCB中的資訊,而我們知道fork會複製PCB中的資訊,因此由Shell進程啟動的其它進程的控制終端也是這個終端。預設情況下(沒有重新導向),每個進程的標準輸入、標準輸出和標準錯誤輸出都指向控制終端,進程從標準輸入讀也就是讀使用者的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上。在控制終端輸入一些特殊的修飾鍵可以給前台進程發訊號,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。
每個進程都可以通過一個特殊的裝置檔案/dev/tty訪問它的控制終端。事實上每個終端裝置都對應一個不同的裝置檔案,/dev/tty提供了一個通用的介面,一個進程要訪問它的控制終端既可以通過/dev/tty也可以通過該終端裝置所對應的裝置檔案來訪問。ttyname函數可以由檔案描述符查出對應的檔案名稱,該檔案描述符必須指向一個終端裝置而不能是任意檔案。在linux上的命令tty 也可以查看到當前的終端。
比如我們在圖形介面下開啟一個終端可能是/dev/pts/0, 第二個可能是/dev/pts/1 ...(網路終端)
而切換到字元介面下可能是/dev/tty1 ...(虛擬終端)
二、作業控制
事實上,Shell分前後台來控制的不是進程而是作業(Job)或者進程組(Process Group)。一個前台作業可以由多個進程組成,一個後台作業也可以由多個進程組成,Shell可以同時運行一個前台作業和任意多個後台作業,這稱為作業控制(JobControl)。例如用以下命令啟動5個進程(這個例子出自APUE):
$ proc1 | proc2 &
$ proc3 | proc4 | proc5
其中proc1和proc2屬於同一個後台進程組,proc3、proc4、proc5屬於同一個前台進程組,Shell進程本身屬於一個單獨的進程組。這些進程組的控制終端相同,它們屬於同一個Session,一個Session與一個控制終端相關。當使用者在控制終端輸入特殊的修飾鍵(例如Ctrl-C)時,核心會發送相應的訊號(例如SIGINT)給前台進程組的所有進程。各進程、進程組、Session的關係如下圖所示。
在上面的例子中,proc3、proc4、proc5被Shell放到同一個前台進程組,其中有一個進程是該進程組的Leader,Shell調用wait等待它們運行結束。一旦它們全部運行結束,Shell就調用tcsetpgrp函數將自己提到前台繼續接受命令。但是注意,如果proc3、proc4、proc5中的某個進程又fork出子進程,子進程也屬於同一進程組,但是Shell並不知道子進程的存在,也不會調用wait等待它結束。換句話說,proc3 | proc4 | proc5是Shell的作業,而這個子進程不是,這是作業和進程組在概念上的區別。一旦作業運行結束,Shell就把自己提到前台,如果原來的前台進程組還存在(如果這個子進程還沒終止),則它自動變成後台進程,被init進程接管。