會話、前臺進程組、後臺進程組、孤兒進程等相關概念

Linux進程組和會話

終端登錄:

當系統自舉時,內核創建ID爲1的進程,也就是init進程,init進程系統進入多用戶狀態。

init進程讀取/etc/inittab,對每一個允許登錄的終端設備,init調用一次fork,它所生成的子進程執行(exec)getty程序。

getty爲終端設備調用open函數,如果沒有請求則阻塞,如果有請求,則文件描述符0,1,2就設置到該設備,然後getty輸出”login“等的信息並等帶用戶輸入用戶名。

當用戶鍵入用戶名後,getty的工作就完成了。

然後以類似於這樣的方式調用login程序

execle(“/bin/login”,“login”,“-p”,username,(char*)0,envp);

init以空環境(即無envp參數)調用一個getty,gettty以終端名和gettytab中說明的環境字符串。-p標識通知login保留傳遞給它的環境,也可將其他環境字符串加到該環境中,但

是不要替換它。

 
 

一、進程

  傳統上,Unix操作系統下運行的應用程序、 服務器以及其他程序都被稱爲進程,而Linux也繼承了來自unix進程的概念。必須要理解下,程序是指的存儲在存儲設備上(如磁盤)包含了可執行機器指 令(二進制代碼)和數據的靜態實體;而進程可以認爲是已經被OS從磁盤加載到內存上的、動態的、可運行的指令與數據的集合,是在運行的動態實體。這裏指的 指令和數據的集合可以理解爲Linux上ELF文件格式中的.text .data數據段。


二、進程組

  每個進程除了有一個進程ID之外,還屬於一個進程組,那什麼是進程組呢?
  顧名思義,進程組就是一個或多個進程的集合。這些進程並不是孤立的,他們彼此之間或者存在父子、兄弟關係,或者在功能上有相近的聯繫。每個進程都有父進程,而所有的進程以init進程爲根,形成一個樹狀結構。

  那爲啥Linux裏要有進程組呢?其實,提供進程組就是爲了方便對進程進行管理。假設要完成一個任務,需要同時併發100個進程。當用戶處於某種原因要終止 這個任務時,要是沒有進程組,就需要手動的一個個去殺死這100個進程,並且必須要嚴格按照進程間父子兄弟關係順序,否則會擾亂進程樹。有了進程組,就可以將這100個進程設置爲一個進程組,它們共有1個組號(pgrp),並且有選取一個進程作爲組長(通常是“輩分”最高的那個,通常該進程的ID也就作爲進程組的ID)。現在就可以通過殺死整個進程組,來關閉這100個進程,並且是嚴格有序的。組長進程可以創建一個進程組,創建該組中的進程,然後終止。只要在某個進程組中一個進程存在,則該進程組就存在,這與其組長進程是否終止無關。

   進程必定屬於一個進程組,也只能屬於一個進程組。 一個進程組中可以包含多個進程。 進程組的生命週期從被創建開始,到其內所有進程終止或離開該組。

  內核中,sys_getpgrp()系統調用用來獲取當前進程所在進程組號;sys_setpgid(int pid, int pgid)調用用來設置置頂進程pid的進程組號爲pgid。


三、作業
       一個正在執行的進程稱爲一個作業,而且作業可以包含一個或多個進程,尤其是當使用了管道和重定向命令。例如“nroff -man ps.1|grep kill|more”這個作業就同時啓動了三個進程。
作業控制指的是控制正在運行的進程的行爲。比如,用戶可以掛起一個進程,等一會兒再繼續執行該進程。shell將記錄所有啓動的進程情況,在每個進程過程中,用戶可以任意地掛起進程或重新啓動進程。作業控制是許多shell(包括bash和tcsh)的一個特性,使用戶能在多個獨立作業間進行切換。
       一 般而言,進程與作業控制相關聯時,才被稱爲作業。

       在大多數情況下,用戶在同一時間只運行一個作業,即它們最後向shell鍵入的命令。但是使用作業控制,用戶可以同時運行多個作業,並在需要時在這些作業間進行切換。這會有什麼用途呢?例如,當用戶編輯一個文本文件,並需要中止編輯做其他事情時,利用作業控制,用戶可以讓編輯器暫時掛起,返回shell提示符開始做其他的事情。其他事情做完以後,用戶可以重新啓動掛起的編輯器,返回到剛纔中止的地方,就象用戶從來沒有離開編輯器一樣。這只是一個例子,作業控制還有許多其他實際的用途。


作業控制(jobcontrol)

        是shell的另一個特性,它允許用戶同時運行多個作業而產生,並且根據需求可將前後臺的作業進行切換。當啓動某個作業時,它通常是運行在前臺,因此該作業是與終端相連接的。利用作業控制這一功能,可將正處於前臺工作的作業切換到後臺去,在後臺該作業可繼續運行,並且在前臺可以監視另一個作業。如果想關注一下某個正在後臺運行的作業,那麼可將其切換到前臺工作,以使其又一次與終端相連接。作業控制的這一概念起源於BSDUNIX,後來又出現在CShell中。


四、會話

  再看下會話。由於Linux是多用戶多任務的分時系統,所以必須要支持多個用戶同時使用一個操作系統。當一個用戶登錄一次系統就形成一次會話 。一個會話可包含多個進程組,但只能有一個前臺進程組。每個會話都有一個會話首領(leader),即創建會話的進程。 sys_setsid()調用能創建一個會話。必須注意的是,只有當前進程不是進程組的組長時,才能創建一個新的會話。調用setsid 之後,該進程成爲新會話的leader。

  一個會話可以有一個控制終端。這通常是登陸到其上的終端設備(在終端登陸情況下)或僞終端設備(在網絡登陸情況下)。建立與控制終端連接的會話首進程被稱爲控制進程。一個會話中的幾個進程組可被分爲一個前臺進程組以及一個或多個後臺進程組。所以一個會話中,應該包括控制進程(會話首進程),一個前臺進程組和任意後臺進程組。 

  一次登錄形成一個會話

  一個會話可包含多個進程組,但只能有一個前臺進程組


五、控制終端

  會話的領頭進程打開一個終端之後, 該終端就成爲該會話的控制終端 (SVR4/Linux)  
  與控制終端建立連接的會話領頭進程稱爲控制進程 (session leader) 
  一個會話只能有一個控制終端 
  產生在控制終端上的輸入和信號將發送給會話的前臺進程組中的所有進程 
  終端上的連接斷開時 (比如網絡斷開或 Modem 斷開), 掛起信號將發送到控制進程(session leader)

  進程屬於一個進程組,進程組屬於一個會話,會話可能有也可能沒有控制終端。一般而言,當用戶在某個終端上登錄時,一個新的會話就開始了。進程組由組中的領頭進程標識,領頭進程的進程標識符就是進程組的組標識符。類似地,每個會話也對應有一個領頭進程。
  同一會話中的進程通過該會話的領頭進程和一個終端相連,該終端作爲這個會話的控制終端。一個會話只能有一個控制終端,而一個控制終端只能控制一個會話。用戶通過控制終端,可以向該控制終端所控制的會話中的進程發送鍵盤信號。

      shell可以同時運行一個前臺作業和任意多個後臺作業,一個前臺作業可以由多個進程組成,一個後臺作業也可以由多個進程組成,這稱爲作業控制。可以通過fg和bg命令控制作業的前後臺運行。由於一個session只能有一個前臺進程組,如果某個進程組需要在前臺運行,shell所在的進程組就自動變爲後臺進程組。

孤兒進程組

POSIX定義爲:該組中的每個成員要麼是該組的一個成員,要麼不是該組所屬會話的成員(一個進程組中的所有進程的父進程要麼是該進程組的一個進程,要麼不是該進程組所在的會話中的進程。一個進程組不是孤兒進程組的條件是,該組中有一個進程其父進程在屬於同一個會話的另一個組中。)

簡單來說:有bash產生的新進程組中,至少有一個進程的PPID指向該bash,否則該進程組成爲孤兒進程組,無法將進程狀態改變通知bash

POSIX要求系統向孤兒進程中處於停止狀態的每一個進程發送掛斷信號(SIGHUP),接着又向其發送繼續信號(SIGCONT)對掛斷信號的默認動作是終止該進程(注意終止

進程和停止進程是有區別的,停止指暫停,終止指退出)


爲什麼有孤兒進程組

       當一個終端控制進程(即會話首進程)終止後,那麼這個終端可以用來建立一個新的會話。這可能會產生一個問題,原來舊的會話(一個或者多個進程組的集合)中的任一進程可再次訪問這個的終端。爲了防止這類問題的產生,於是就有了孤兒進程組的概念。當一個進程組成爲孤兒進程組時,posix.1要求向孤兒進程組中處於停止狀態的進程發送SIGHUP(掛起)信號,系統對於這種信號的默認處理是終止進程,然而如果無視這個信號或者另行處理的話那麼這個掛起進程仍可以繼續執行。    

       有孤兒進程,對應的也有孤兒進程組的概念。爲何引入這個概念以及這個概念的引入需要OS的實現者作些什麼呢?先看兩個前提,首先,posix用一個session的概念來描述一次用戶的登錄以及該用戶在此次登錄後的操作,然後用作業的概念描述不同操作的內容,最後才用進程的概念描述不同操作中某一個具體的工作;其次,unix最初將所有的進程組織成了樹的形式,這樣就便於追蹤每個進程也便於管理,有了上述兩個前提事情就很明白了,一切都是爲了便於管理,一切都是爲了登錄用戶的安全,即此次登錄用戶的作業是不能被下個登錄用戶所控制的,即使它們的用戶名一致也是不行的,因此所謂的孤兒進程組簡單點說就是脫離了創造它的session控制的,離開其session眼線的進程組,unix中怎樣控制進程,怎樣證明是否在自己的眼線內,那就是樹形結構了,只要處於以自己爲根的子樹的進程就是自己眼線內的進程,這個進程就是受到保護的,有權操作的,而在別的樹枝上的進程原則上是觸動不得的,unix中建立進程使用fork,自然地這麼一“叉子”就形成了自己的一個樹枝,當然在自己眼線了,一般對於登錄用戶而言一個會話起始於一次login之後的shell,只要該用戶不logout,在該終端的shell上執行的所有的非守護進程都是該shell的後代進程,因此它們組成一個會話,全部在shell的眼線中,一個會話終止於會話首長的death。現在考慮一下終端上的shell退出後的情景,按照規定,該終端上所有的進程都過繼給了別的進程,大多數情況是init進程,然後緊接着另外一個用戶登錄了這個終端或者知道前一個登錄用戶密鑰的另一個有不好念頭的人登錄了該終端,當然爲其啓動的shell創建了一個新的session,由於之前登錄的用戶已退出,現在登錄的用戶由於之前用戶的進程組都成了孤兒進程組,所以它再有惡意也不能控制它們了,那些孤兒進程組中的成員要麼繼續安全的運行,要麼被shell退出時發出的SIGHUP信號殺死。
 
需要注意的
      一般一個登錄shell就是一個會話首進程,會話首進程獲得一個控制終端給前臺進程組用,會話首進程也只能通過控制終端來控制別的進程,所謂的控制就是發送信號(如ctl+c等)
 
一些函數
獲得進程組id
#include<unistd.h>
pid_t getpgrp(void); 返回值:調用進程的進程組id
 
#include<unistd.h>
int setpgid(pid_t pid,pid_t pgid); 返回值:成功返回0,出錯返回-1


Setpgid函數將pid進程的進程組id設置爲pgid,
(1)如果兩個參數相等,則由pid指定的進程變成進程組組長。
(2)如果pid爲0,則使用調用者的進程id
(3)如果pgid爲0,則由pid指定的進程id用作進程組id
 
#include<unistd.h>
pid_t setsid(void); 返回值:成功返回進程組id,若出錯返回-1

如果調用此函數的進程不是一個進程組的組長,則此函數就會創建一個新會話,結果會發生下面三件事:
(1)該進程變爲會話首進程。此時,該進程是新會話中的唯一進程
(2)該進程成爲一個新進程組的組長進程。新進程的組id是調用進程的進程id。
(3)該進程沒有控制終端。如果在調用setsid之前該進程有一個控制終端,那麼也會被中斷
如果調用進程調用進程是一個進程組的組長,則此函數返回出錯。爲了保證不會出錯,通常先調用fork,然後使其父進程終止,而子進程則繼續。因爲子進程繼承了父進程的進程組id,而其進程id則是重新分配的,兩個不肯能相等,所以這就保證子進程不會是一個進程組的組長。
 
#include<unistd.h>
pid_t tcgetpgrp(int filedes);返回值:成功則返回前臺進程組id,出錯返回-1
int tcsetpgrp(int fileds ,pid_t pgrpid);返回值:成功返回0,出錯返回-1


tcgetpgrp返回前臺進程組的進程組id,該前臺進程組與在fileds上打開的終端相關聯
tcsetpgrp將前臺進程組id設置爲pgrpid,pgrpid應是在同一會話中的一個進程組的id。fileds必須引用該會話的控制終端。
 
#include<termios.h>
pid_t tcgetsid(int filedes); 返回值:成功返回會話首進程的進程組id,出錯返回-1.
需要管理控制終端的應用程序可以調用tcgetsid函數識別出控制終端的會話首進程的會話id
 
觀察進程組id與進程id需要用到的命令
ps  -o pid,ppid,pgrp,session,tpgid,comm
其中pgrp表示進程組id,tpgid表示前臺進程組id,comm表示啓動該進程的命令。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章