在UNIX系統中,作業控制允許在一個終端上啓動多個作業(進程組),控制哪一個作業可以存取該終端,以及哪些作業在後臺運行。
爲了支持作業控制,引入了進程組,會話期,控制終端等概念,還需要內核以一定的信號支持。
一·進程組。
每一個進程除了有一個進程PID之外,還屬於一個進程組,用進程組ID表示。返回當前進程組ID的系統調用爲:
pid_t getpgrp();
每個進程組都有一個組長進程,組長進程的標識是進程組ID等於其進程ID。
進程可以調用setpgid系統調用參加一個現存的組或者創建一個新的進程組。
int setpgid(pid_t pid, pid_t pgid);
這將pid進程的進程組ID設置爲pgid,如果兩者相等,則pid變爲進程組的組長。
一個進程只能爲它自己或者它的子進程改變進程組ID,如果pid爲0,則代表自己,如果pgid爲0,則由pid指定的進程ID作爲進程組ID。
如果pid和pgid不等,而目前系統中不存在pgid的進程組,則出錯。
當用fork()產生一個子進程後,子進程將繼承父進程的進程組ID,也就是子進程和父進程屬於同一個進程組。
二·對話期(session)
對話期是一個或多個進程組的集合,對話期可以有一個控制終端。例如,可以由以下的安排:
進程調用setsid函數可以創建一個新的對話期。
pid_t setsid();
如果調用此函數的進程是一個進程組的組長,則出錯。否則該函數創建一個新的對話期,結果爲:
1)該進程變爲新的對話期的首進程。
2)此進程成爲一個新進程組的組長進程。新進程組的ID爲調用進程的進程ID。
3)此進程沒有控制終端。
三。前臺進程組,後臺進程組
一個對話期的幾個進程組可以被分成一個前臺進程組以及一個或幾個後臺進程組。
如果一個對話期有一個控制終端,那麼它有一個前臺進程組,其他進程組爲後臺進程組。
無論何時鍵入中斷鍵(Ctrl-C)或者退出鍵(Ctrl-/),就會造成中斷信號SIGINT或者退出信號SITQUIT送至前臺進程組中的所有進程。
只有前臺進程組中的進程可以接受終端輸入,如果後臺進程組的進程試圖讀終端,那麼內核會發送一個特定的信號SIGTTIN給後臺作業,這通常會停止(掛起)次後臺作業。當用將次後臺進程轉爲前臺進程後(移入前臺進程組),會發送一個SIGCONT信號給該進程,使該進程繼續運行。
四·測試
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> int main() { setbuf(stdout, NULL); printf("main: %d %d/n", getpid(), getpgrp()); pid_t pid = fork(); if(pid < 0) { perror("fork"); return 1; } setpgid(pid, 0); if(pid > 0) { sleep(5); setpgid(pid, getpgrp()); kill(pid, SIGCONT); waitpid(pid, NULL, 0); return 0; } char buf[1024]; printf("child: %d %d/n", getpid(), getpgrp()); while(fgets(buf, 1024, stdin)) { fputs(buf, stdout); } return 0; }
該程序首先打印父進程的PID和進程組ID,由於該進程由shell創建,所以會將該進程的進程組ID設置爲進程PID,是的該進程組屬於一個新進程組,並且爲前臺進程組。然後fork出一個子進程,此時子進程應該繼承父進程的進程組ID,和父進程同屬於前臺進程組,然後父子進程下一步都調用setpgid(pid, 0),這會確保把子進程設爲一個新的進程組的組長,並且該進程組爲後臺進程組。這個時候子進程打印自己的進程PID和進程組ID,然後開始循環從終端讀入一行數據,並原樣輸入到終端,由於這個子進程屬於後臺進程組,這會導致子進程被掛起(停止),所以屏幕上不會有什麼輸出。父進程先睡眠5秒鐘(給我足夠的時間來敲幾行字符演示子進程確實沒有輸出),然後設置子進程的組ID爲自己的組ID,也就是將子進程移入前臺進程組,然後發送SIGCONT信號給子進程,使子進程重新運行,由於這是子進程已經屬於前臺進程組了,因此可以成功的讀入終端字符並顯示出來。