[APUE]進程控制(中)

1、wait和waitpid函數

  當一個進程正常或異常終止時會向父進程發送SIGCHLD信號。對於這種信號系統默認會忽略。調用wait/waidpid的進程可能會:shell

  • 阻塞(若是其子進程都還在運行);
  • 當即返回子進程的終止狀態(若是一個子進程已經終止正等待父進程存取其終止狀態);
  • 出錯當即返回(若是它沒有任何子進程);
    若是進程因爲收到SIGCHLD信號而調用wait,則可指望wait會當即返回。可是在任一時刻調用則進程可能阻塞。
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int options); 返回值: 成功返回進程ID, 出錯-1.

  這兩個函數區別:數組

  • wait若是在子進程終止前調用則會阻塞,而waitpid有一選項可使調用者不阻塞。
  • waitpid並不等待第一個終止的子進程--它有多個選項,能夠控制它所等待的進程。

若是調用者阻塞並且它有多個子進程,則在其一個子進程終止時,wait就當即返回。由於wait返回子進程ID,因此調用者知道是哪一個子進程終止了。
  參數statloc是一個整型指針。若是statloc不是一個空指針,則終止狀態就存放到它所指向的單元內。若是不關心終止狀態則將statloc設爲空指針。
  這兩個函數返回的整型狀態由實現定義。其中某些位表示退出狀態(正常退出),其餘位則指示信號編號(異常返回),有一位指示是否產生了一個core文件等等。POSIX.1規定終止狀態用定義在<sys/wait.h>中的各個宏來查看。有三個互斥的宏可用來取得進程終止的緣由,它們的名字都已WIF開始。基於這三個宏中哪個值是真,就可選用其餘宏(這三個宏以外的其餘宏)來取得終止狀態、信號編號等。
  函數

  下面的程序中pr_exit函數使用上表中的宏以打印進程的終止狀態。測試

#include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> void pr_exit(int status) { if (WIFEXITED(status)) { printf("normal termination, exit status=%d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("abnormal termination, signal number = %d\n", WTERMSIG(status), #ifdef WCOREDUMP WCOREDUMP(status) ? "(core file generated)" : ""); #else ""); #endif } else if (WIFSTOPPED(status)) { printf("child stopped, signal number = %d\n", WSTOPSIG(status)); } } int main(void) { pid_t pid; int status; if ((pid = fork()) < 0) { fprintf(stderr, "fork error"); } else if (pid == 0) { exit(7); } if (wait(&status) != pid) { fprintf(stderr, "wait error"); } pr_exit(status); if ((pid = fork()) < 0) { fprintf(stderr, "fork error"); } else if (pid == 0) { abort(); } if (wait(&status) != pid) { fprintf(stderr, "wait error"); } pr_exit(status); if ((pid = fork()) < 0) { fprintf(stderr, "fork error"); } else if (pid == 0) { status /= 0; } if (wait(&status) != pid) { fprintf(stderr, "wait error"); } pr_exit(status); return 0; }

  編譯運行結果:ui

     

  wait是隻要有一個子進程終止就返回,waitpid能夠指定子進程等待。對於waitpid的pid參數:spa

  • pid == -1, 等待任一子進程。這時waitpid與wait等效。
  • pid > 0, 等待子進程ID爲pid。
  • pid == 0, 等待其組ID等於調用進程的組ID的任一子進程。
  • pid < -1 等待其組ID等於pid的絕對值的任一子進程。

  對於wait,其惟一的出錯是沒有子進程(函數調用被一個信號中斷,也可能返回另外一種出錯)。對於waitpid, 若是指定的進程或進程組不存在,或者調用進程沒有子進程都能出錯。   options參數使咱們能進一步控制waitpid的操做。此參數或者是0,或者是下表中常數的逐位或運算。
  命令行

2、 競態條件

  當多個進程都企圖對某共享數據進行某種處理,而最後的結果又取決於進程運行的順序,則咱們認爲這發生了競態條件(race condition)。若是在fork以後的某種邏輯顯式或隱式地依賴於在fork以後是父進程先運行仍是子進程先運行,那麼fork函數就會是競態條件活躍的孽生地。
  若是一個進程但願等待一個子進程終止,則它必須調用wait函數。若是一個進程要等待其父進程終止,則可以使用下列形式的循環:指針

while(getppid() != 1) sleep(1);

  這種形式的循環(稱爲按期詢問(polling))的問題是它浪費了CPU時間,由於調用者每隔1秒都被喚醒,而後進行條件測試。
  爲了不競態條件和按期詢問,在多個進程之間須要有某種形式的信號機制。在UNIX中可使用信號機制,各類形式的進程間通訊(IPC)也可以使用。
  在父、子進程的關係中,經常有如下狀況:在fork以後,父、子進程都有一些事情要作。例如:父進程可能以子進程ID更新日誌文件中的一個記錄,而子進程則可能要爲父進程建立一個文件。在本例中,要求每一個進程在執行完它的一套初始化操做後要通知對方,而且在繼續運行以前,要等待另外一方完成其初始化操做。這種狀況能夠描述爲以下:日誌

TELL_WAIT();

if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { TELL_PARENT(getppid()); WAIT_PARENT(); exit(0); } TELL_CHILD(pid); WAIT_CHILD(); exit(0);

3、exec函數

  當進程調用exec函數時,該進程徹底由新進程代換,而新程序則從其main函數開始執行。由於調用exec並不建立新進程,因此先後的進程ID不會改變。exec只是用另外一個程序替換了當前進程的正文、數據、堆和棧段。excel

#include <unistd.h> int execl(const char *pathname, const char *arg0, ... /* (char *) 0 */); int execv(const char *pathname, char *const argv[]); int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */); int execve(const char *pathname, char *const argv[], char *const envp[]); int execlp(const char *filename, const char *arg0, ... /* (char *) 0 */); int execvp(const char *filename, char *const argv[]); 返回值:出錯-1,若成功不返回

  這些函數之間的第一個區別是前四個取路徑名做爲參數,後兩個取文件名做爲參數。當制定filename做爲參數時:

  • 若是filename中包含/,則就將其視爲路徑名。
  • 不然按PATH環境變量。

  若是excelp和execvp中的任意一個使用路徑前綴中的一個找到了一個可執行文件,可是該文件不是機器可執行代碼文件,則就認爲該文件是一個shell腳本,因而試着調用/bin/sh,並以該filename做爲shell的輸入。
  第二個區別與參數表的傳遞有關(l 表示表(list),v 表示矢量(vector))。函數execl、execlp和execle要求將新程序的每一個命令行參數都說明爲一個單獨的參數。這種參數表以空指針結尾。另外三個函數execv,execvp,execve則應先構造一個指向個參數的指針數組,而後將該數組地址做爲這三個函數的參數。
  最後一個區別與向新程序傳遞環境表相關。以 e 結尾的兩個函數excele和exceve能夠傳遞一個指向環境字符串指針數組的指針。其餘四個函數則使用調用進程中的environ變量爲新程序複製現存的環境。
  六個函數之間的區別:
  

  每一個系統對參數表和環境表的總長度都有一個限制。當使用shell的文件名擴充功能產生一個文件名錶時,可能會收到此值的限制。例如,命令:

grep _POSIX_SOURCE /usr/include/*/*.h

在某些系統上可能產生下列形式的shell錯誤。

arg list too long

  執行exec後進程ID沒改變。除此以外,執行新程序的進程還保持了原進程的下列特徵:

  • 進程ID和父進程ID。
  • 實際用戶ID和實際組ID。
  • 添加組ID。
  • 進程組ID。
  • 對話期ID。
  • 控制終端。
  • 鬧鐘尚餘留的時間。
  • 當前工做目錄。
  • 根目錄。
  • 文件方式建立屏蔽字。
  • 文件鎖。
  • 進程信號屏蔽。
  • 未決信號。
  • 資源限制。
  • tms_utime,tms_stime,tms_cutime以及tms_ustime值。

  對打開文件的處理與每一個描述符的exec關閉標誌值有關。進程中每一個打開描述符都有一個exec關閉標誌。若此標誌設置,則在執行exec時關閉該文件描述符,不然該描述符仍打開。除非特意用fcntl設置了該標誌,不然系統的默認操做是在exec後仍保持這種描述符打開。
  POSIX.1明確要求在exec時關閉打開目錄流。這一般是由opendir函數實現的,它調用fcntl函數爲對應於打開目錄流的描述符設置exec關閉標誌。
  在exec先後實際用戶ID和實際組ID保持不變,而有效ID是否改變則取決於所執行程序的文件的設置-用戶-ID位和設置-組-ID位是否設置。若是新程序的設置-用戶-ID位已設置,則有效用戶ID變成程序文件的全部者的ID,不然有效用戶ID不變。對組ID的處理方式與此相同。

  在不少UNIX實現中,這六個函數只有一個execve是系統調用。另外5個是庫函數

相關文章
相關標籤/搜索