[APUE]進程關係(上),apue進程關係
一、終端登入1. 4.3+BSD終端登入
系統管理員建立一個通常名為/etc/ttys的檔案,其中,每個終端裝置有一行,每一行說明裝置名稱和傳到getty程式的參數,這些參數說明了終端的傳輸速率。當系統bootstrap時核心建立進程ID 1,也就是init進程。init進程使系統進入多使用者狀態。init讀檔案/etc/ttys,對每一個允許登入的終端裝置,init調用一次fork,它所產生的子進程則執行程式getty。這種情況見:
圖中每個進程的實際使用者ID和有效使用者ID都是0(即都有root許可權)。init以空環境執行getty程式。
getty對終端裝置調用open函數,以讀、寫方式將終端開啟。如果裝置是數據機,則open可能會在裝置驅動程式中滯留,直到使用者撥號數據機,並且線路被接通。一旦裝置被開啟,則檔案描述符0、1、2就被設定到該裝置。然後getty輸出"login:"之類的資訊,並等待使用者鍵入使用者名稱。如果終端支援多種速度,則getty可以測試特殊字元以便適當地更改終端速度(傳輸速率)。
當使用者鍵入使用者名稱後,getty就完成了,然後它以類似於下列的方式調用login程式:
execle("/usr/bin/login", "login", "-p" username, (char *) 0, envp);
(在gettytab檔案中可能會有一些選擇使其調用其他程式,但系統預設是login程式)。init以一個空環境調用getty。getty以終端名(例如TERM=foo,其中終端foo的類型取自gettytab檔案)和在gettytab中的環境字串為login建立一個環境(envp參數)。-p標誌通知login保留傳給它的環境,也可以將其他環境字串加到該環境中,但是不要替換它。顯示了login剛被調用後這些進程的狀態。
因為最初的init進程具有root許可權,所以圖中所有進程都有root許可權。圖中底部三個進程的進程ID相同,因為進程ID不會因執行exec而改變。並且除了最初的init進程,所有的進程均有一個父進程ID。
login能處理多項工作。因為它得到了使用者名稱,所以能調用getpwnam取得相應使用者的口令檔案登陸項。然後調用getpass以顯示提示"Password:"接著讀使用者鍵入的口令。它調用crypt將使用者鍵入的口令加密,並與該使用者口令檔案中登陸項的pw_passwd欄位相比較。如果使用者幾次鍵入的口令都無效,則login以參數1調用exit表示登入過程失敗。父進程(init)瞭解到子進程的終止情況後,將再次調用fork,其後又跟著執行getty,對此終端重複上述過程。
如果使用者正確登入,login就將當前工作目錄更改為使用者的home目錄。它也調用chown改變該終端的所有權,使該使用者成為所有者和群組擁有者。將對該終端裝置的存取許可權改變成:使用者讀、寫和組寫。調用setgid及initgroup設定進程的組ID。然後調用login所得到的所有資訊初始化環境:起始目錄(HOME)、shell(SHELL)、使用者名稱(USER和LOGNAME),以及一個系統預設路徑(PATH)。最後login進程改變為登入使用者的使用者ID(setuid)並調用該使用者的登陸shell,其方式類似於:
execl("/bin/sh", "-sh", (char *) 0);
argv[0]的第一個字元是一個標誌,表示該shell被調用為登入shell。shell可以查看此字元,並相應地修改其啟動過程
login所做的比上面說的要多。
到此為止,登入使用者的登入shell開始運行。其父進程ID是init進程ID(進程ID 1),所以當此進程終止時,init進程會收到通知(接收到SIGGHLD訊號),它會對該終端重複全部上述過程。登陸shell的檔案描述符0,1和2設定為終端裝置。顯示了這種安排。
現在登入shell讀其開機檔案。這些開機檔案通常改變某些環境變數,加上一些環境變數。當執行完開機檔案後,使用者最後得到shell的提示符,並能鍵入命令。
2. SVR4終端登入
SVR4支援兩種方式的登入:(a)getty方式,這與上面的一樣。(b)ttymon登入,這是SVR4的一種新功能。通常getty用於控制台,ttymon則用於其他終端的登入。
二、網路登入1. 4.3+BSD網路登入
終端登入時,init知道哪些終端裝置可用來登入,並為每一個裝置產生一個getty進程。但是網路登入都經過核心的網路介面驅動程式,事先並不知道有多少個這樣的登入。不是使一個進程等待每一個可能的登入,而是必須等待一個網路連接請求的到達。在4.3+BSD中,有一個稱為inetd的進程,它等待大多數網路連接。
作為系統啟動的一部分,init調用一個shell,使其執行shell指令碼stc/rc。由此shell指令碼啟動一個精靈進程inetd。一旦此shell指令碼終止,inetd的父進程就變成init。inetd等待TCP/IP串連請求到達主機,而當一個串連請求到達時,它執行一次fork,然後該子進程執行適當的程式。
我們假定到達了一個對於TELNET伺服器的TCP串連請求。TELNET是使用TCP協議的遠程登入應用程式。在另一個主機上的使用者,或在同一個主機上的使用者啟動TELNET用戶端進程啟動登入過程:
telnet hostname
該用戶端進程開啟一個到名為hostname的主機的TCP串連,在hostname主機上啟動的程式被稱為TELNET伺服器。然後用戶端進程和伺服器處理序之間使用TELNET應用協議通過TCP串連交換資料。所發生的是啟動用戶端進程的使用者現在登入到了伺服器處理序所在的主機。顯示了在執行TELNET伺服器處理序(稱為telnetd)中所涉及的進程式列。
然後telnetd進程開啟一個偽終端裝置,並用fork產生一個子進程。父進程處理通過網路連接的通訊,子進程則執行login程式。父、子進程通過偽終端相串連。在調用exec之前,子進程使其檔案描述符0,1,2與偽終端相連。如果登入正確,login就執行:更改當前工作目錄為起始目錄,設定登入使用者的組ID和使用者ID,以及登入使用者的起始環境。然後login用exec將其自身替換為登入使用者的登入shell。顯示了到達這一點時的進程安排
當通過終端或網路登入時,我們得到一個登入shell,其標準輸入、輸出和標準出錯串連到一個終端或者偽終端裝置上。
2. SVR4網路登入
SVR4中網路登入的情況與4.3+BSD中的幾乎一樣。同樣使用了inetd伺服器處理序,但是在SVR4中inetd是作為一種服務存取控制器sac調用的,其父進程不是init。最後得到的結果與一樣。
三、進程組
每個進程除了有一進程ID之外,還屬於一個進程組,進程組是一個或多個進程的集合,每個進程組有一個唯一的進程組ID。進程組ID類似進程ID,它是一個正整數,並可存放在pid_t資料類型中。函數getpgrp返回調用進程的進程組ID
#include <sys/types.h>#include <unistd.h>pid_t getpgrp(void);傳回值: 調用進程的進程組ID
每個進程組都有一個組長進程。組長進程的標識是:其進程組ID等於其進程ID。
進程組組長可以建立一個進程組,建立該組中的進程,然後終止。只要在某個進程組中有一個進程存在,該進程組就存在,與進程組長是否終止無關。從進程組建立到其中最後一個進程終離開(該進程可以終止也可以加入另一個進程組)的時間區間稱為進程組的生命期。
進程調用setpgid可以參加一個現存的組或者建立一個新進程組
#include <sys/types.h>#include <unistd.h>int setpgid(pid_t pid, pid_t pgid);傳回值: 若成功則為0,出錯為-1
這將pid進程的進程組ID設定為pgid。如果這兩個參數相等,則由pid指定的進程變成進程組組長。
一個進程只能為它自己或它的子進程設定進程組ID。在它的子進程調用了exec後,它就不再改變該子進程的進程組ID
如果pid是0,則使用調用者的進程ID。如果pgid是0,則由pid指定的進程ID被用作為進程組ID。
如果系統不支援作業控制,那麼就不定義_POSIX_JOB_CONTROL,在這種情況下,該函數返回錯誤,errno設定為ENOSYS。
在大多數作業控制shell中,在fork之後調用此函數,使父進程設定其子進程的進程組ID,然後使子進程設定其自己的進程組ID。這些調用中有一個是冗餘的,但這樣做可以保證父、子進程在進一步操作之前,子進程都進入了該進程組。如果不這樣做的話,那麼就產生一個竟態條件,因為它依賴於哪一個進程先執行。
四、對話期
對話期(session)是一個或多個進程組的集合。例如,可以有中所示的安排。在一個對話期中有三個進程組。通常由shell的管道線將幾個進程編成一組的。例如中的安排可能是由下列形式的shell命令形成的:
proc1 | proc2 &proc3 | proc4 | proc5
進程調用setsid函數就可以建立一個新對話期。
#include <sys/types.h>#include <unistd.h>pid_t setsid(void);傳回值:若成功則為進程組ID,若出錯則為-1
如果調用此函數的進程不是一個進程組的組長,則此函數建立一個新對話期,結果為:
- 此進程變成該新對話期的對話期的首進程(session leader, 對話期首進程是建立該對話期的進程)。此進程是該新對話期中的唯一進程。
- 此進程成為一個新進程組的組長進程。新進程組ID是此調用進程的進程ID。
- 此進程沒有控制終端。如果在調用setsid之前此進程有一個控制終端,那麼這種聯絡也被解除。
如果此調用進程已經是一個進程組的組長,則此函數返回出錯。為了保證不處於這種情況,通常先調用fork,然後使父進程終止,而子進程則繼續。因為子進程繼承了父進程的進程組ID,所以其不可能是進程組組長。