進程組與會話 Linux Process Groups and Sessions


在類Unix系統中,用戶通常會跟各種相關的進程打交道。雖然在登錄的時候只有一個終端進程(用戶對應的登錄shell ,通過這個shell啟動各種程序和服務),但通常不久以后就會產生許多相關的進程,例如進行如下動作:

  • 在后台運行無交互的程序(例如bash命令中末位的"&")
  • 通過shell的 job control在各種交互進程之間切換
  • 通過管道啟動一組程序
  • 在圖形環境下(例如X window system)啟用多個終端窗口

為了管理這些進程,內核便對這些進程進行了分組,稱其為進程組,幾個進程組又構成一個會話。例如下圖所示,lsless在一個進程組里,而grepwc在一個進程組里。這兩個進程組又同屬於一個會話。

下面具體講一講進程組和會話。


1.1 進程組

每一個進程都屬於一個“進程組”,當一個進程被創建的時候,它默認是其父進程所在組的成員。傳統上,一個進程的組ID(pgid)等於這個組的第一個成員(也稱為進程組領導)。

可以使用ps j這個命令獲取進程的PPID (父進程ID), PID (本進程 ID), PGID (進程組 ID) and SID (會話 ID)。

frank@under:~$ ps j
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
24120 24125 24125 24125 pts/5    24125 Ss+   1000   0:00 bash
24120 30633 30633 30633 pts/6    31468 Ss    1000   0:00 bash
30633 31468 31468 30633 pts/6    31468 R+    1000   0:00 ps j

當使用不具有工作管理(job control)的shell時(例如ash ),每一個shell創建的子進程都會和shell在同一個進程組和會話里。當使用具有工作管理的shell時(例如bash ),如果使用管道(參考:Pipes: A Brief Introduction), 那么這些管道連接起來的進程單獨組成一個進程組,和shell在一個會話中。例如:

% cat paper | ideal | pic | tbl | eqn | ditroff > out

這幾個程序運行后的進程都在一個進程組里面。

前台進程組

每一個會話最多有一個進程組是“前台進程組”,控制終端(下面的第二部分會講)會將輸入和信號 傳給該進程組的成員(例如你在終端按下Ctrl+C就會向前台進程組發送SIGINT信號)。進程可以通過系統調用 tcgetpgrp(fd)獲取所在會話的前台進程組ID,其中fd對應會話的控制終端的文件描述符;也可以通過tcsetpgrp(fd,pgrp)設置所在會話的前台進程組,其中fd對應會話的控制終端的文件描述符, pgrp是這個會話中的一個進程組。

那么如何得到fd呢? ctermid() 調用會返回控制終端的名字,在符合 POSIX標准的系統上,它會返回 /dev/tty 這個文件,然后我們就可以用系統調用open()打開這個文件從而得到文件描述符了。

后台進程組

在一個會話中,除前台進程組外的進程組都稱為“后台進程組”,這些后台進程組的進程不參與終端的輸入輸出,如果它們嘗試從終端讀取數據,會收到SIGTTIN信號(其默認操作是Stop),同時終端會通知用戶:

SIGTTIN   21,21,26    Stop    Terminal input for background process

但是如果后台進程忽略或者blockSIGTTIN信號了,或者它所在的進程組是一個“孤兒進程組”(下面有講),那么讀取終端就會得到一個EIO(error in operation)錯誤而非收到SIGTTIN信號。當后台進程嘗試向終端寫操作時,它可能會受到SIGTTOU信號:

SIGTTOU   22,22,27    Stop    Terminal output for background process

同樣地,如果后台進程忽略或者blockSIGTTOU信號了,或者它所在的進程組是一個“孤兒進程組”,那么讀取終端就會得到一個EIO(error in operation)錯誤而非收到SIGTTOU信號。

為了讓后台進程加入前台進程組,可以使用fg命令。

相應操作

可以通過系統調用 setpgid()將進程加入到另一個進程組中:

int setpgid(pid_t pid, pid_t pgid);

其中pid是要操作的進程,0代表本進程;pgid是要加入的進程組,0代表要加入的進程組的pgid是這個進程的pid(也就是說,這個組的組領導就是這個進程)。

使用 setpgid() 要注意以下幾點:

  1. 一個進程可能將pgid設置為自己或者它所在組的其他成員,這樣的操作可能不會改變其他任意進程的進程組,即使這個進程有root權限。
  2. 會話頭進程(第二部分會介紹)不能改變自己所在的進程組。
  3. 一個進程不能被加入到另一個會話中的進程組中,換句話說,setpgid只能在一個會話中使用。由於setpgid()只能將進程在本會話中“移動”,所以兩個會話不可能有相同的進程組或者進程。

一個進程可以通過 getpgrp()系統調用獲得自己所在組的ID,也可以通過 getpgid(p) 獲得pid為p的進程所在組的組ID,當p為0時,獲得本進程的組ID:

pid_t getpgid(pid_t pid)
pid_t getpgrp(void)

下面是一個簡單的示例圖:


#### 斷開連接(我對於Linux下的終端、shell通信機制不了解,這個地方暫時貼上參考資料里的原文,以后懂了再翻譯)

If the terminal goes away by modem hangup, and the line was not local, then a SIGHUP is sent to the session leader. Any further reads from the gone terminal return EOF. (Or possibly -1 with errno set to EIO.)

If the terminal is the slave side of a pseudotty, and the master side is closed (for the last time), then a SIGHUP is sent to the foreground process group of the slave side.

When the session leader dies, a SIGHUP is sent to all processes in the foreground process group. Moreover, the terminal stops being the controlling terminal of this session (so that it can become the controlling terminal of another session).

Thus, if the terminal goes away and the session leader is a job control shell, then it can handle things for its descendants, e.g. by sending them again a SIGHUP. If on the other hand the session leader is an innocent process that does not catch SIGHUP, it will die, and all foreground processes get a SIGHUP.


1.2 孤兒進程組

現在我們來討論當會話消失的時候進程是如何終止的。

假設有一個在終端下運行的會話,其會話頭是一個shell。當這個shell存在時,會話中的進程組處於不同的環境中,可能在運行,也可能被掛起了。當終端關閉時,如果它正在運行,當終端關閉后它就無法讀入或者輸出了;如果它被掛起了,則它可能永遠不會被喚醒(也不會終止)。在這種情況下,原會話的進程組就被稱為“孤兒進程組”。POSIX定義為該進程組的父進程也是該進程組的成員或者是別的會話的成員。總之,只要一個進程組的父進程在同一會話的不同組中,它就不是孤兒進程組。

當一個進程組成為孤兒進程組之后,在這個進程組中的每一個進程都會被發送一個SIGHUP信號——通常進程將會被正常關閉。對於收到SIGHUP信號后,選擇不終止的程序將會被發送一個SIGCONT,這個信號將會重啟任何被掛起的進程。這個信號流程能夠關閉大多數的進程並保證剩下的是正在運行的進程(不被掛起)。

當進程被“遺棄”后,它被強制和控制終端分離(其他的用戶便可以使用這個終端)。原來的會話ID繼續被保留(不作為新生進程的PID)直到每一個會話中的程序退出。


2.1 會話

當一個用戶注銷的時候,內核會終止用戶之前啟動的所有進程(不然這些進程會在那一直運行並等待輸入的到來)。為了簡化這個任務,內核將幾個進程組並為一個“會話”。會話的ID就是通過setsid()啟動這個會話的進程的PID (也就是這個會話的第一個進程,通常是用戶的shell),這個進程也稱為“會話頭”,它隨后產生的所有子孫進程都默認在這個會話里。

進程也可以使用setsid使自己離開自己的會話,其參數為空,返回新的會話的ID:

#include <unistd.h>

pid_t setsid(void);


2.2 控制終端

每一個會話有且僅有一個對應的終端,會話中的進程從這個終端得到輸入並輸出,該終端被稱為“控制終端”(controlling terminal)。這個終端可能是機器本地的控制台、桌面環境的偽終端、網絡上的偽終端等等。

雖然一個會話對應的控制終端是可以改變的,但這通常都是由初始化用戶登錄環境的那個進程設定的。




主要參考:

  1. The Process Model of Linux Application Development
  2. Processes (里面有關於進程和線程的簡略介紹)
  3. 《深入理解計算機系統》第三版


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM