系統管理員創建一個通常名為/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的提示符,並能鍵入命令。
SVR4支持兩種方式的登錄:(a)getty方式,這與上面的一樣。(b)ttymon登錄,這是SVR4的一種新功能。通常getty用於控制台,ttymon則用於其他終端的登錄。
終端登錄時,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,其標准輸入、輸出和標准出錯連接到一個終端或者偽終端設備上。
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
如果調用此函數的進程不是一個進程組的組長,則此函數創建一個新對話期,結果為:
如果此調用進程已經是一個進程組的組長,則此函數返回出錯。為了保證不處於這種情況,通常先調用fork,然後使父進程終止,而子進程則繼續。因為子進程繼承了父進程的進程組ID,所以其不可能是進程組組長。
http://www.bkjia.com/Linuxjc/1195496.htmlwww.bkjia.comtrue