進程間關係:進程、殭屍進程、孤兒進程、進程組、前臺進程組、後臺進程組、孤兒進程組、會話、控制終端

不同的shell對使用管道線時創建子進程的順序不同,本文以bash爲例,它是支持作業控制的shell的典型代表。

殭屍進程與孤兒進程

殭屍進程:先於父進程終止,但是父進程沒有對其進行善後處理(獲取終止子進程有關信息,釋放它仍佔有的資源)。消滅殭屍進程的唯一方法是終止其父進程。
孤兒進程:該進程的父進程先於自身終止。其特點是PPID=1(init進程的ID)。一個孤兒進程可以自成孤兒進程組。

文中用到的縮寫

PID = 進程ID (由內核根據延遲重用算法生成)
PPID = 父進程ID(只能由內核修改)
PGID = 進程組ID(子進程、父進程都能修改)
SID = 會話ID(進程自身可以修改,但有限制,詳見下文)
TPGID= 控制終端進程組ID(由控制終端修改,用於指示當前前臺進程組)

進程、進程組、會話之間的關係

總體關係

進程屬於一個進程組,進程組屬於一個會話,會話可能有也可能沒有控制終端

會話

  • 會話首進程:
    新建會話時,會話中的唯一進程,其PID=SID。它通常是一個登陸shell,也可以在成爲孤兒進程後調用setsid()成爲一個新會話。
  • 會話:
    一個或多個進程組的集合。一個登陸shell發起的會話,一般由一個會話首進程、一個前臺進程組、一個後臺進程組組成。

進程組

一個或多個進程的集合,進程組屬於一個會話。fork()並不改變進程組ID。

  • 進程組組長:
    PID與PGID相等的進程。組長可以改變子進程的進程組ID,使其轉移到另一進程組。
    例如一個shell進程(下文均以bash爲例),當使用管道線時,如echo "hello" | cat,bash以第一個命令的進程ID爲該管道線內所有進程設置進程組ID。此時echocat的進程組ID都設置成echo的進程ID。
  • 前臺進程組
    該進程組中的進程能夠向終端設備進行讀、寫操作的進程組。
    登陸shell(例如bash)通過調用tcsetpgrp()函數設置前臺進程組,該函數將終端設備的fd(文件描述符)與指定進程組關聯。成爲前臺進程組的進程其TPGID=PGID,常常可以通過比較他們來判斷前後臺進程組。
  • 後臺進程組
    一個會話中,除前臺進程組、會話首進程以外的所有進程組。該進程組中的進程能夠向終端設備寫,但是當試圖讀終端設備時,將會收到SIGTTIN信號,並停止。登錄shell可以根據設置在終端上發出一條消息[1]通知用戶有進程欲求讀終端。
    前臺進程組ID只能有一個,而後臺進程組同時可存在多個。後臺進程組的PGID≠TPGID。

孤兒進程組

  • 定義1
    該組中的每個成員的父進程要麼是該組的一個成員,要麼不是該組所屬會話的成員
  • 定義2
    不是孤兒進程組的條件是,該組中有一個進程,其父進程屬於同一會話的另一個組中。
    也就是說,將該父進程終止就能使該進程組成爲殭屍進程孤兒進程(感謝網友”hello”的指正)。這個父進程通常是這個進程組的組長進程,因爲只有它的父進程在這個進程組外,而其他進程(組長的子進程)的父進程都是組長進程的ID。

解析:產生一個孤兒進程(組)並讀終端

  1. 由組長fork()產生的子進程其進程組ID不變(因爲fork()不改變進程組ID)
  2. 若組長是bash,則將子進程的進程組ID設置成第一個命令的PID,即由第一個命令當組長,併成爲一個新的進程組
  3. 由bash產生的新進程組中,至少要有一個進程的PPID指向該bash,否則該進程組成爲孤兒進程組,無法將進程狀態的改變通知bash
  4. bash通過wait函數族檢測子進程(新的進程組)的狀態,從而決定如何設置前臺進程組ID(給指定的終端設備)
  5. 後臺進程(組)試圖讀控制終端設備時,終端驅動程序向其發送SIGTTIN信號,此時應當由bash喚醒該進程(組),使之進入前臺
  6. 對於孤兒進程(組),bash無法知曉其狀態,因爲bash不知道其PID,而唯一知道其PID的進程已經終止,也就無法知曉其組ID,從而不能將其組ID放入前臺。如果孤兒進程(組)試圖讀取終端,read()調用將失敗,並將errno置爲EIO。

註釋

[1] 可以通過使用stty tostop命令禁止後臺進程組向終端進行寫操作,當發出寫請求時,將會收到SIGTTOU信號。

 

轉自:http://lesca.me/archives/process-relationship.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章