会话、进程组与僵死进程
1. ? 終端
????? 在Linux系統中,用戶通過終端登錄系統后得到一個Shell進程,這個終端成為Shell進程的控制終端(Controlling Terminal),Shell進程啟動的其他進程的控制終端也是這個終端。默認情況下(沒有重定向),每個進程的標準輸入、標準輸出和標準錯誤輸出都指向控制終端,進程從標準輸入讀也就是讀用戶的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上。此外在控制終端輸入一些特殊的控制鍵可以給前臺進程發信號,例如Ctrl+C表示SIGINT,Ctrl+\表示SIGQUIT。
2. ? 會話與進程組
???? 每次用戶登錄終端時會產生一個會話(session)。從用戶登錄開始到用戶退出為止,這段時間內在該終端執行的進程都屬于這一個會話。
????? 每個進程除了有一進程ID之外,還屬于一個進程組(Process Group)。進程組是一個或多個進程的集合,每個進程組有一個唯一的進程組ID。多個進程屬于進程組的情況是多個進程用管道“|”號連接進行執行。如果在命令行執行單個進程時這個進程組只有這一個進程。
3. ? 控制終端
????? 在終端(包括telnet等偽終端)登錄就會產生一個會話,此會話擁有這一個單獨的控制終端。
????? 建立與控制終端連接的會話首進程,被稱之為控制進程,也就是Shell進程。
一個會話中的幾個進程組可被分成一個前臺進程組以及一個或幾個后臺進程組。
4. ? 作業控制
????? 前后臺運行的進程組又稱為作業(Job),一個前臺作業可以由多個進程組成,一個后臺作業也可以由多個進程組成,Shell進程可以同時運行一個前臺作業和任意多個后臺作業,這稱為作業控制(Job Control)。
????? 作業控制允許在一個終端上起動多個作業(進程組),控制哪一個作業可以存取該終端,以及哪些作業在后臺運行。
??????從Shell進程使用作業控制功能角度觀察,可以在前臺啟動一個作業或后臺啟動多個作業。一個作業只是幾個進程的集合,通常用管道連接各進程。
????? 例如下面的命令在后臺(&表示在后臺運行)啟動了兩個作業,這兩個后臺作業所調用的進程都在后臺運行。
cat?? *.c?? | pg?? &
make?? all?? &
???? 實際上有三個特殊字符可使終端驅動程序產生信號,信號將送至前臺進程組的所有進程,而后臺進程組作業則不受影響,它們是:
①??? 中斷字符(DELETE或Ctrl-C)產生SIGINT信號。
②??? 退出字符(Ctrl-\)產生SIGQUIT信號。
③??? 掛起字符(Ctrl-Z)產生SIGTSTP信號。
5. ? 會話與進程組的關系
??? 例如用以下命令啟動5個進程。
$ proc1 | proc2 &
$ proc3 | proc4 | proc5
???? 其中proc1和proc2屬于同一個后臺進程組,proc3、proc4、proc5屬于同一個前臺進程組,Shell進程本身屬于一個單獨的進程組。這些進程組的控制終端相同,它們屬于同一個session。其進程、進程組、session的關系如下圖12-12所示。
?????????????????????????????????? 圖12-12 會話與進程組關系圖
?????? 現在從session和進程組的角度重新來看登錄和執行命令的過程。在上面的例子中,proc3、proc4、proc5被Shell放到同一個前臺進程組,其中proc3進程是該進程組的組長,Shell進程調用wait等待它們運行結束,一旦它們全部運行結束,Shell進程就調用tcsetpgrp函數將自己提到前臺繼續接收命令。但是注意,如果proc3、proc4、proc5中的某個進程又fork出子進程,子進程也屬于同一進程組,但是Shell進程并不知道子進程的存在,也不會調用wait等待它結束。換句話說,proc3 | proc4 | proc5是Shell進程的作業,而這個子進程不是,這是作業和進程組在概念上的區別。一旦作業運行結束,Shell進程就把自己提到前臺。
6. ? 進程組和會話實例說明
(1)進程組與會話實例一
$ ps -o pid,ppid,pgrp,session,tpgid,comm | cat
? PID? PPID? PGRP? SESS TPGID COMMAND
?6994? 6989? 6994? 6994? 8762 bash
?8762? 6994? 8762? 6994? 8762 ps
?8763? 6994? 8762? 6994? 8762 cat
這個作業由ps和cat兩個進程組成,在前臺運行。從PPID列可以看出這兩個進程的父進程是bash;從PGRP列可以看出,bash在進程組ID為6994的進程組中,這個進程組ID等于bash的進程ID,所以它是進程組的組長;而兩個子進程在8762的進程組中,ps是這個進程組的組長;從SESS列可以看出這三個進程都在同一session中,會話ID為6994,bash是會話首進程;從TPGID列可以看出,前臺進程組ID是8762,也就是ps和cat這兩個進程所在的進程組,其中ps進程為進程組的組長。
?
(2)進程組與會話實例二
$ ps -o pid,ppid,pgrp,session,tpgid,comm | cat &
[1] 8835
$?? PID? PPID? PGRP? SESS TPGID COMMAND
?6994? 6989? 6994? 6994? 6994 bash
?8834? 6994? 8834? 6994? 6994 ps
?8835? 6994? 8834? 6994? 6994 cat
這個作業由ps和cat兩個進程組成,在后臺運行。bash不等作業結束就打印提示信息“[1] 8835”,然后給出提示符接受新的命令,“1”是作業的編號,如果同時運行多個作業可以用這個編號區分,“8835”是該作業中某個進程的進程ID。
7. ? 進程組函數
(1)進程組函數說明
每個進程組有一個組長進程,組長進程ID等于其進程組ID。只要在某個進程組中有一個進程存在,則該進程組就存在,這與其組長進程是否終止無關。從進程組創建開始到其中最后一個進程離開為止的時間區間稱為進程組的生命期。
?
(2)取進程組ID函數原型
?getpgrp(取得進程組識別碼)
?
| 所需頭文件 | #include <unistd.h> |
| 函數說明 | getpgrp()用來取得目前進程所屬的組識別碼。此函數相當于調用getpgid(0) |
| 函數原型 | pid_t getpgrp(void) |
| 函數返回值 | 返回目前進程所屬的組識別碼 |
(3)設置進程組ID函數原型
?setpgrp(設置進程組識別碼)
| 所需頭文件 | #include <unistd.h> |
| 函數說明 | setpgid()將參數pid 指定進程所屬的組識別碼設為參數pgid 指定的組識別碼。如果參數pid 為0,則會用來設置目前進程的組識別碼,如果參數pgid為0,則會以目前進程的進程識別碼來取代 |
| 函數原型 | int setpgid(pid_t pid,pid_t pgid) |
| 函數返回值 | 執行成功則返回組識別碼,如果有錯誤則返回-1,錯誤原因存于errno中 |
| 錯誤代碼 | EINVAL:參數pgid小于0 EPERM:進程權限不足,無法完成調用 ESRCH:找不到符合參數pid指定的進程 |
進程調用setpgid(pid, pgid)可以參加一個現存的組或者創建一個新進程組,這時pid進程的進程組ID設置為pgid。如果這兩個參數相等,則pid指定的進程變成進程組組長。
一個進程只能為它自己或它的子進程設置進程組ID。在它的子進程調用了 exec后,它就不再能改變該子進程的進程組ID。如果設置的pid等于0,則使用調用者的進程ID;如果設置pgid等于0,則使用pid進程的進程組ID。
在大多數作業控制中,在fork之后調用此函數,使父進程設置其子進程的進程組ID,然后使子進程設置其自己的進程組ID。這些調用中有一個是冗余的,但這樣做可以保證父、子進程在進一步操作之前,子進程都進入了該進程組。如果不這樣做的話,那么就產生一個競態條件,因為它依賴于哪一個進程先執行。
在發送一個信號時,信號可以發送給一個進程或送給一個進程組。
8. ? 會話函數
(1)會話函數原型
?????? setsid(創建一個新的會話)
| 所需頭文件 | #include <sys/types.h> #include <unistd.h> |
| 函數說明 | 創建一個新的會話 |
| 函數原型 | pid_t setsid(void) |
| 函數返回值 | 若成功則為進程組 ID,若出錯則為-1 |
?
(2)會話說明
用戶每次用戶登錄會產生一個會話。如果調用setsid此函數的進程不是一個進程組的組長,則此函數會創建一個新會話,所產生的結果如下:
此進程變成該新會話的會話首進程(會話首進程是創建該會話的進程),此進程是該新會話中的唯一進程。
此進程成為一個新進程組的組長進程,新進程組ID是此進程的進程ID。
此進程沒有控制終端,如果在調用setsid之前,此進程有一個控制終端,那么這種聯系也被解除。
如果此調用進程已經是一個進程組的組長,則此函數返回出錯。為了保證不處于這種情況,通常先調用fork,然后使其父進程終止,而子進程則繼續。因為子進程繼承了父進程的進程組ID,而其進程ID則是新分配的,兩者不可能相等,所以這就保證了子進程不是一個進程組的組長。
?
(3)setsid函數的作用如下
①??? 讓進程擺脫原會話的控制。
②??? 讓進程擺脫原進程組的控制。
③??? 讓進程擺脫原控制終端的控制。
9. ? 孤兒進程
一個父進程已終止的進程稱為孤兒進程 (orphan process),這種進程由1號進程init收養。
10. ? 僵尸進程
(1)僵尸進程的產生
一個進程在調用exit函數結束自己生命的時候,其實它并沒有真正的被完全銷毀,而是留下一個稱為僵尸進程(Zombie)的數據結構。
在Linux進程的狀態中,僵尸進程是非常特殊的一種,它已經釋放了幾乎所有內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個表項,記載該進程的退出狀態等信息供其他進程收集。除此之外,僵尸進程不再占有任何內存空間。它需要它的父進程來為它收尸,如果它的父進程沒有設置SIGCHLD信號處理函數、或者沒有設置SIGCHLD信號為忽略(SIG_IGN)、或者沒有調用wait(或waitpid)等待子進程結束,那么它就一直保持僵尸狀態。如果這時父進程結束了,那么init進程會自動接手這個子進程,僵尸進程消失。但是如果如果父進程是一個循環,不會結束,那么子進程就會一直保持僵尸狀態,這就是為什么系統中有時會有很多的僵尸進程。
?
(2)怎樣來清除僵尸進程
??? 清除僵尸進程有三種處理方法,具體說明如下:
改寫父進程,在子進程死后為它收尸。具體做法是接管SIGCHLD信號,子進程死后,會發送SIGCHLD信號給父進程,父進程調用wait(或waitpid)函數為子進程收尸。
在父進程中設置SIGCHLD信號處理函數或者設置SIGCHLD信號為忽略(SIG_IGN)。
把父進程殺掉,父進程死后,僵尸進程成為“孤兒進程”,過繼給1號進程init,init始終會負責清理僵尸進程,它產生的所有僵尸進程也跟著消失。
轉載于:https://www.cnblogs.com/xiayong123/archive/2011/10/11/3717212.html
總結
以上是生活随笔為你收集整理的会话、进程组与僵死进程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux Java连接MySQL数据库
- 下一篇: jQuery EasyUI API 中文