對話期和進程組有一些其他特性:
這些特性見下圖
登錄時會自動建立控制終端
以下兩個函數用來通知內核哪一個進程組是前台進程組,這樣,終端設備驅動程序就能了解將終端輸入和終端產生的信號送到何處。
#include <sys/types.h>
#include <unistd.h>
pid_t tcgetpgrp(int filedes);
返回值:成功返回前台進程組ID,出錯-1
int tcsetpgrp(int filedes, pid_t pgrpid);
返回值:成功為0,出錯為-1
函數tcgetpgrp返回前台進程組ID,它與在filedes上打開的終端相關。
如果進程有一個控制終端,則該進程可以調用tcsetpgrp將前台進程組ID設置為pgrpid。pgrpid值應當是在同一會話期中的一個進程組的ID。filedes必須引用該對話期的控制終端。
大多數程序並不直接調用這兩個函數。它們通常由作業控制shell調用。只有定義了—_POSIX_JOB_CONTROL,這兩個函數才被定義了。否則它們返回出錯。
作業控制允許在一個控制終端上啟動多個作業(進程組),控制哪一個作業可以存取該終端,以及哪些作業在後台運行。作業控制要求三種形式的支持:
一個作業只是幾個進程的集合,通常是一個進程管道。例如:
vim abc.c
在前台啟動了只有一個進程的一個作業。下面的命令:
pr *.c | lpr &
make all &
在後台啟動了兩個作業。這兩個作業所調用的進程都在後台運行。 當啟動一個後台作業時,shell賦予它一個作業標識。並打印一個或幾個進程ID。下面的操作過程顯示了KornShell是如何處理這一點的。
$ make all > Make.out &
[1] 1475
$ pr *.c | lpr &
[2] 1490
$ 鍵入回車
[2] + Done pr *.c | lpr &
[1] + Done make all > Make.out &
make是作業號1,所啟動的進程ID是1475.下一個管道線是作業號2,其中第一個進程的進程ID是1490。當作業已完成並且鍵入回車時,shell通知我們作業已完成。鍵入回車是為了讓shell打印其提示符。shell並不在任意時間打印後台作業的狀態改變,它只在打印其提示符之前這樣做。
有三個特殊字符可使終端驅動程序產生信號,並將他們送至前台進程組,後台進程組作業不受影響。它們是:
如果後台作業試圖讀終端,終端驅動程序會檢測這種情況,並且發送一個特定信號SIGTTIN給後台作業。這通常會停止此後台作業,而有關用戶則會得到這種情況的通知,然後就可以將此作業轉為前台作業運行,於是它就可以讀終端。下面操作過程展示了這種情況:
$ cat > temp.foo & 在後台啟動,但將從標准輸入讀
[1] 1681
$ 鍵入回車
[1] + Stopped (tty input) cat > temp.out &
$ fg &1 使1號作業成為前台作業
cat > temp.foo shell告訴我們現在哪一個作業在前台
hello, world 輸入1行
^D 鍵入文件描述符
$ cat temp.foo 檢查該行已送入文件
hello, world
shell在後台啟動cat進程,但是當cat試圖讀其標准輸入(控制終端)時,終端驅動程序知道它是後台作業,於是將SIGTTIN信號送至該後台作業。shell檢測到其子進程的狀態變化,並通知我們該作業已被停止。然後,用shell的fg命令將次停止的作業送入前台運行。這樣做使shell將此作業轉為前台進程組(tcsetpgrp),並將繼續信號(SIGCONT)送給該進程組。因為該作業現在在前台進程組中,所以它可以讀控制終端。
如果後台進程輸出到控制終端會發生什麼呢?這是一個可以允許或禁止的選擇項。通常,可以用stty命令來改變這一選項
$ cat temp.foo & 在後台運行
[1] 1719
$ hello, world 在提示符出現後台作業的輸出
鍵入回車
[1] + Done cat temp.foo &
$ stty tostop 禁止後台作業向控制終端輸出
$ cat temp.foo & 在後台再次運行
[1] 1721
$ 鍵入回車,發現作業已停止
[1] + Stopped(tty output) cat temp.foo &
$ fg %1 將停止的作業恢復為前台作業
cat temp.foo shell告訴我們現在哪一個作業在前台
hello, world 該作業的輸出
下圖摘錄了我們已說明的作業控制的某些功能。穿過終端驅動程序的實線表示:終端I/O和終端產生的信號總是從前台進程組連接到實際終端。對應於SIGTTOU信號的虛線表示後台進程組進程的輸出是否出現在終端是可選擇的。
首先使用不支持作業控制的經典的Bourne shell。如果執行:
ps -xj
則其輸出為: PPID PID PGID SID TPGID COMMAND 1 163 163 163 163 -sh 163 163 163 163 163 ps 結果略去了現在無關的列。shell和ps命令兩者位於同一對話期和前台進程組(163)中。因為163是在TGPID列中顯示的進程組,所以稱其為前台進程組。
說進程與終端進程組ID(TPGID列)相關聯並不當。進程並沒有終端進程控制組。進程屬於一個進程組,而進程組屬於一個對話期。對話期可能有,也可能沒有控制終端。如果它確有一個控制終端,則此終端設備知道其前台進程的進程組ID。這一值可以用tcsetpgrp函數在終端驅動程序中設置。前台進程組ID是終端的一個屬性,而不是進程的屬性。取自終端設備驅動程序的該值是ps在TPGID列中打印的值。如果ps發現此對話期沒有控制終端,則它在該列打印1。
如果在後台執行命令:
ps -xj &
則唯一改變的值是命令的進程ID。
PPID PID PGID SID TPGID COMMAND
1 163 163 163 163 -sh
163 163 163 163 163 ps
因為這種shell不知道作業控制,所以後台作業沒有構成另一個進程組,也沒有從後台作業處取走控制終端。
看一下Bourne shell如何處理管道線。執行下列命令:
ps -xj | cat1
其輸出是:
PPID PID PGID SID TPGID COMMAND
1 163 163 163 163 -sh
163 200 163 163 163 cat1
200 201 163 163 163 ps
(程序cat1只是標准cat程序的一個副本,但名字不同)管道中最後一個進程是shell的子進程,該管道中的第一個進程則是最後一個進程的子進程。從中可以看出,shell fork一個它的副本,然後此副本再為管道線中的每條命令各fork一個進程。
&nsbp;如果在後台執行此管道線:
ps -xj | cat1 &
則只有進程ID改變了。因為shell並不處理作業控制,後台進程的進程組ID仍是163,如果終端進程組ID一樣。
在沒有作業控制時如果後台作業試圖讀控制終端,其處理方法是:如果該進程自己不重新定向標准輸入,則shell自動將後台進程的標准輸入重新定向到/dev/null。讀/dev/null則產生一個文件結束。這就意味著後台cat進程立即讀到文件尾,並正常結束。
在一條管道中執行三個進程:
ps -xj | cat1 | cat2
該管道中的最後一個進程是shell的子進程,而執行管道中其他命令的進程則是該最後進程的子進程。下圖展示了所發生的情況:
一個父進程已終止的子進程稱為孤兒進程(orphan process),這種進程由init進程收養。整個進程組也可以成為孤兒。 考慮一個進程,它fork了一個子進程然後終止。這在系統中是進場發生的,但是在父進程終止時,如果該子進程停止(用作業控制)該如何?下面的程序就是這種情況的一個例子。下圖顯示了程序已經啟動,父進程已經fork了子進程之後的情況。
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include "ourhdr.h"
static void sig_hup(int);
static void pr_ids(char *);
int main(void)
{
char c;
pid_t pid;
pr_ids("parent");
if ( (pid = fork()) < 0) {
fprint(stderr, "fork error\n");
exit(1);
} else if (pid > 0) {
sleep(5); // sleep 5等待子進程退出
exit(0); // 父進程退出
} else { // 子進程
pr_ids("child");
signal(SIGHUP, sig_hup); //
kill(getpid(), SIGTSTP);
pr_ids("child");
if (read(0, &c, 1) != 1) {
printf("read error from control terminal,errno = %d\n", errno);
}
exit(0);
}
}
static void sig_hup(int signo) {
printf("SIGHUP received, pid = %d\n, getpid()");
return ;
}
static void pr_ids(char *name) {
printf("%s: pid = %d, ppid = %d, pgrp = %d\n", name, getpid(), getppid(), getpgrp());
fflush(stdout);
}
這裡假定使用了一個作業控制shell。shell將前台進程放在一個進程組中(本例是512),shell則留在自己的組內(442)。子進程繼承其父進程512進程組。在fork後:
下面是程序的輸出:
因為兩個進程,登錄shell和子進程都寫向終端,所以shell提示符和子進程的輸出一起出現。
在子進程中調用pr_ids後程序企圖讀標准輸入。正如前述,當後台進程組試圖讀控制終端時,則對該後台進程組產生SIGTTIN。但在這裡這是一個孤兒進程組,如果內核用此信號終止它,則此進程組中的進程就再也不會繼續。POSIX.1規定,read返回出錯,其errno設置為EIO。
在父進程終止時,子進程變成後台進程組,因為父進程是由shell作為前台作業執行的。
http://www.bkjia.com/Linuxjc/1195855.htmlwww.bkjia.comtrue