APUE學習筆記:第九章 進程關係

9.1 引言shell

本章將更詳盡地說明進程組以及POSIX.1引入的會話的概念。還將介紹登錄shell(登陸時所調用的)和全部從登錄shell啓動的進程之間的關係。編程

9.1 終端登錄網絡

系統管理員建立一般名爲/etc/ttys的文件,其中每一個終端設備都有一行,每一行說明設備名傳遞給getty程序的參數。當系統自舉時,內核建立進程ID爲1的進程,依舊是init進程。init進程使系統進入多用戶狀態。init進程讀文件/etc/ttys,對每個容許登錄的終端設備,init調用一次fork,所生成的子進程則執行(exec)getty程序。(init以空環境執行getty程序)函數

getty爲終端設備調用open函數,以讀、寫方式將終端打開。若是設備是調制解調器,則open可能會在設備驅動程序中滯留,知道用戶撥號調制解調器,而且呼叫被應答。一旦設備被打開,則文件描述符0、一、2就被設置到該設備。而後getty輸出「login:」之類的信息,並等待用戶鍵入用戶名。若是終端支持多種速度,則getty能夠測試特殊字符以便適當地更改終端速度(波特率)測試

當用戶鍵入了用戶名後,getty的工做就完成了。而後調用login程序。init以一個空環境調用getty。getty以終端名和在gettytab中說明的環境字符串爲login建立一個環境。加密

login能執行多項工做。由於它獲得了用戶名,因此能調用getpwnam取得相應用戶的口令文件登錄項。而後調用getpass以顯示提示「password:」,接着讀用戶鍵入的口令。它調用crypt將用戶鍵入的口令加密,並與該用戶在陰影口令文件中登錄想的pw_passwd字段相比較。若是用戶幾回鍵入的口令都無效,則login以參數1調用exit表示登錄過程失敗。父進程瞭解到子進程終止狀況後,將在此調用fork,其後接着執行getty,對此終端重複上述過程。spa

若是用戶正確登錄,login就將執行以下工做:code

-將當前工做目錄更改成該用戶的起始目錄blog

-調用chown改變該終端的全部權,是登錄用戶成爲它的全部者繼承

-將對該終端設備的訪問權限改變成用戶讀和寫

-調用setgid和initgroups設置進程的組ID

-用login所獲得的全部信息初始化環境:起始目錄、shell、用戶名,以及一個系統默認路徑

-login進程改變爲登錄用戶的用戶ID並調用該用戶的登錄shell

到此爲止,登陸用戶的登錄shell開始運行。

 

 

9.3 網絡登錄

 

經過網絡登錄時,終端和計算機之間的鏈接不是點對點連接。在這種狀況下,login只是一種可用的服務,這與其餘網絡服務的性質相同。

在網絡登錄狀況下,全部登錄都經由內核的網絡接口驅動程序,實現並不知道將會有多少這樣的登錄。咱們不是使進程等待每一個可能的登錄,而是必須等待一個網絡鏈接請求的到達。

爲使同一個軟件既能處理終端login,又能處理網絡login,系統使用了一種稱爲僞終端的軟件驅動程序,它仿真串行終端的運行行爲,並將終端操做映射爲網絡操做

 

做爲系統啓動的一部分,init調用一個shell,使其執行shell腳本/etc/rc.由此shell腳本啓動一個守護進程inetd。一旦此shell腳本終止,inetd的父進程就變成init。inetd等待TCP/IP鏈接請求到達主機,而當一個鏈接請求到達時,它執行一次fork,而後生成的子進程執行適當的程序。

咱們假定到達了一個針對TELNET服務的TCP鏈接請求。TELNET是使用TCP協議的遠程登錄應用程序。在另外一臺主機(它經過某種形式的網絡與服務進程的主機相鏈接)上的用戶,或者同一臺主機上的用戶啓動TELNET客戶進程,由此啓動登錄過程:

telnet hostname

該客戶進程打開一個到hostname主機的TCP鏈接,在hostname主機上啓動的程序被稱爲TELNET服務進程。而後,客戶進程和服務進程之間使用TELNET應用協議經過TCP鏈接交換數據。所發生的是啓動客戶進程的用戶如今登陸到了服務進程所在的主機。

而後,telnetd進程打開一個僞終端設備,並用fork分紅兩個進程。父進程處理經過網絡鏈接的通訊,子進程則執行login程序。父子進程經過僞終端相鏈接。在調用exec以前,子進程使其文件描述符0,1,2與僞終端相連。若是登錄正確,login就執行:更改當前工做目錄爲起始目錄,設置登錄的組ID和用戶ID,以及登錄用戶的初始環境。而後login調用exec將自身替換爲登錄用戶的登錄shell

 

(當經過終端或網絡登錄時,咱們獲得一個登錄shell,其標準輸入、輸出和標準出錯鏈接到一個終端設備或者僞終端設備上。

 

9.4 進程組

函數getpgrp返回調用進程的進程組ID

#include<unistd.h>

pid_t getpgrp(void);

            //返回值:調用進程的進程組ID

 

進程能夠經過調用setpgid來加入一個現有的組或者建立一個新進程組

#include<unistd.h>

int setpgid(pid_t pid,pid_t pgid);

            //返回值:若成功則返回0,若出錯則返回-1

 

9.5 會話

會話是一個或多個進程組的集合。

一般是shell的管道線將幾個進程編程一組的。進程調用setsid函數創建一個新會話

#include<unistd.h>

pid_t setsid(void);

        //返回值:若成功則返回進程組ID,若出錯則返回-1

若是調用此函數的進程不是一個進程組的組長,則此函數就會建立一個新會話,結果將發生下面3件事:

(1)該進程編程新會話首進程(會話首進程是建立該會話的進程),此時該進程是新會話中的唯一進程

(2)該進程稱爲一個新進程組的組長進程。新進程組ID是該調用進程的進程ID

(3)該進程沒有控制終端。若是在調用setsid以前該進程有一個控制終端,那麼這種聯繫也會被中斷

 

若是該調用進程已是一個進程組的組長,則此函數返回出錯。爲了保證不發生這種狀況,一般先調用fork,而後使其父進程終止,而子進程繼續。由於子進程繼承了父進程的進程組ID,而其進程ID則是新分配的,二者不可能相等,因此這就保證子進程不會是一個進程組的組長。

 

9.6 控制終端

會話和進程組有一些其餘特性:

-一個會話能夠有一個控制終端。這一般是登陸到其上的終端設備(在終端登陸狀況下)或僞終端設備(在網絡登錄狀況下)

-創建與控制終端連接的會話首進程被稱爲控制進程

-一個會話中的幾個進程組可被分紅一個前臺進程組以及一個或幾個後臺進程組

-若是一個會話有一個控制終端,則它有一個前臺進程組,會話中的其餘進程組則爲後臺進程組

-不管什麼時候鍵入終端的中斷鍵,就會將中斷信號發送給前臺進程組的全部進程

-不管什麼時候鍵入終端的退出鍵,就會將退出信號發送給前臺進程組的全部進程

-若是終端接口檢測到調制解調器已經斷開鏈接,則將掛斷信號發送給控制進程

 

9.7 tcgetpgrp、tcsetpgrp和tcgetsid函數

須要有一種方法來通知內核哪個進程組是前臺進程組,這樣,終端設備驅動程序就能瞭解將終端輸入和終端產生的信號送到何處

#include<unistd.h>

pid_t tcgetpgrp(int filedes);
        //返回值:若成功則返回前臺進程組的進程ID,若出錯則返回-1

int tcsetpgrp(int filedes,pid_t pgrpid);

                //返回值:若成功則返回0,若出錯則返回-1

 

9.8 做業控制

做業控制要求下面三種形式的支持:

(1)支持做業控制的shell

(2)內核中的終端驅動程序必須支持做業控制

(3)內核必須提供對某些做業控制信號的支持

 

9.9 shell執行程序

管道線中最後一個進程是shell的子進程,該管道線中的第一個進程則是最後一個進程的子進程。當最後一個進程終止時,shell獲得通知

 

9.10 孤兒進程組

咱們曾說起,一個其父進程已經終止的進程稱爲孤兒進程,這種進程由init進程「收養」。如今咱們要說明整個進程組也可稱爲「孤兒」

實例: 9_1 建立一個孤兒進程組

 1 #include"apue.h"
 2 #include<errno.h>
 3 
 4 static void sig_hup(int signo)
 5 {
 6     printf("SIGHUP received,pid=%d\n",getpid());
 7 }
 8 static void pr_ids(char *name)
 9 {
10     printf("%s:pid=%d,ppid=%d,pgrp=%d,tpgrp=%d\n",name,getpid(),getppid(),
11         getpgrp(),tcgetpgrp(STDIN_FILENO));
12     fflush(stdout);
13 }
14 int main()
15 {
16     char c;
17     pid_t pid;
18     pr_ids("parent");
19     if((pid=fork())<0){
20     err_sys("fork error");
21     }else if(pid>0){
22     sleep(5);
23     exit(0);
24     }
25     else{
26     pr_ids("child");
27     signal(SIGHUP,sig_hup);
28     kill(getpid(),SIGTSTP);
29     pr_ids("child");
30     if(read(STDIN_FILENO,&c,1)!=1)
31     printf("read error from controlling TTY,errno=%d\n",errno);
32     exit(0);
33 }}
相關文章
相關標籤/搜索