當一個進程正常或異常終止時會向父進程發送SIGCHLD信號。對於這種信號系統默認會忽略。調用wait/waidpid的進程可能會:shell
#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就當即返回。由於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
對於wait,其惟一的出錯是沒有子進程(函數調用被一個信號中斷,也可能返回另外一種出錯)。對於waitpid, 若是指定的進程或進程組不存在,或者調用進程沒有子進程都能出錯。 options參數使咱們能進一步控制waitpid的操做。此參數或者是0,或者是下表中常數的逐位或運算。
命令行
當多個進程都企圖對某共享數據進行某種處理,而最後的結果又取決於進程運行的順序,則咱們認爲這發生了競態條件(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);
當進程調用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做爲參數時:
若是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沒改變。除此以外,執行新程序的進程還保持了原進程的下列特徵:
對打開文件的處理與每一個描述符的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個是庫函數