在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訊號給子進程,使子進程重新運行,由於這是子進程已經屬於前台進程組了,因此可以成功的讀入終端字元並顯示出來。