歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> 學習Linux

[APUE]進程關系(下),apue進程關系

[APUE]進程關系(下),apue進程關系


熱度4 評論 108 www.BkJia.Com  網友分享於:  2017-02-27 04:02:38     浏覽數504次

[APUE]進程關系(下),apue進程關系


一、控制終端

  對話期和進程組有一些其他特性:

  • 一個對話期可以有一個單獨的控制終端。通常是我們在其上登錄的終端設備或偽終端設備。
  • 建立與控制終端連接的對話期首進程,被稱之為控制進程
  • 一個對話期中的幾個進程組可以被分成一個前台進程組以及一個或幾個後台進程組
  • 如果一個對話期有一個控制終端,則它有一個前台進程組,其他進程組則為後台進程組。
  • 無論何時鍵入終端鍵(Ctrl-C)或退出鍵(Ctrl-\),就會造成中斷信號或退出信號送至前台進程組的所有進程。
  • 如果終端界面檢測到調制解調器已經脫開連接,則將掛斷信號送至控制進程。

  這些特性見下圖
  


  登錄時會自動建立控制終端

二、tcgetpgrp和tcsetpgrp函數

  以下兩個函數用來通知內核哪一個進程組是前台進程組,這樣,終端設備驅動程序就能了解將終端輸入和終端產生的信號送到何處。

#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,這兩個函數才被定義了。否則它們返回出錯。

三、作業控制

  作業控制允許在一個控制終端上啟動多個作業(進程組),控制哪一個作業可以存取該終端,以及哪些作業在後台運行。作業控制要求三種形式的支持:

  • 支持作業控制shell。
  • 內核中的終端驅動程序必須支持作業控制
  • 必須提供對某些作業控制信號的支持

  一個作業只是幾個進程的集合,通常是一個進程管道。例如:

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並不在任意時間打印後台作業的狀態改變,它只在打印其提示符之前這樣做。
  有三個特殊字符可使終端驅動程序產生信號,並將他們送至前台進程組,後台進程組作業不受影響。它們是:

  • 終端字符(一般用DELETE或者Ctrl-C)產生SIGINT
  • 退出字符(一般用Ctrl-\)產生SIGOUT
  • 掛起字符(一般采用Ctrl-Z)產生SIGTSTP

  如果後台作業試圖讀終端,終端驅動程序會檢測這種情況,並且發送一個特定信號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信號的虛線表示後台進程組進程的輸出是否出現在終端是可選擇的。

  

四、shell執行程序

  首先使用不支持作業控制的經典的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後:

  • 父進程睡眠5秒,讓子進程在父進程終止之前運行
  • 子進程為掛斷信號(SIGHUP)建立信號處理程序。
  • 子進程用kill函數向其自身發送停止信號SIGTSTP。這停止了子進程,類似於用終端掛起字符(Ctrl-Z)停止一個前台作業。
  • 當父進程終止時,該子進程成為孤兒進程,其父進程ID成為1,也就是init進程ID。
  • 現在,子進程成為一個孤兒進程組的成員。POSIX.1將孤兒進程組定義為:該組中每一個成員的父進程或者是該組中的一個成員,或者不是該組所屬對話期的成員。
  • 因為在父進程終止後,進程組成為孤兒進程組,POSIX.1要求向新孤兒進程組中處於停止狀態的每一個進程發送掛斷信號(SIGHUP),接著又向其發送繼續信號(SIGCONT)。
  • 在處理了掛斷信號後,子進程繼續。對掛斷信號的系統默認動作是終止該進程,為此必須提供一個信號處理函數來捕捉此信號。因此我們期望sig_hup函數中的printf會在pr_id函數中的printf之前執行。

  下面是程序的輸出:
  


  因為兩個進程,登錄shell和子進程都寫向終端,所以shell提示符和子進程的輸出一起出現。
  在子進程中調用pr_ids後程序企圖讀標准輸入。正如前述,當後台進程組試圖讀控制終端時,則對該後台進程組產生SIGTTIN。但在這裡這是一個孤兒進程組,如果內核用此信號終止它,則此進程組中的進程就再也不會繼續。POSIX.1規定,read返回出錯,其errno設置為EIO。
  在父進程終止時,子進程變成後台進程組,因為父進程是由shell作為前台作業執行的。

http://www.bkjia.com/Linuxjc/1195855.htmlwww.bkjia.comtrue

Copyright © Linux教程網 All Rights Reserved