HOWTO · C

使用 C 語言中的 waitpid 函式

本文介紹瞭如何在 C 語言中使用 waitpid 函式。

本頁內容

父程序需要回收子程序狀態時,可使用 waitpid 等待指定的子程序,或等待任何符合條件的子程序。其引數會選擇子程序及要回報的狀態變化;傳回的狀態必須使用 <sys/wait.h> 中的巨集解碼,不能直接視為結束碼。

使用 waitpid 等待特定子程序

程序可以透過 fork 建立子程序。子程序會從呼叫的位置繼續執行,而父程序會取得子程序的程序 ID。子程序終止後,父程序應呼叫 wait 系列函式之一,取得子程序的終止狀態並回收該程序。

呼叫形式為 waitpid(pid, &status, options)。正數 pid 會選取該子程序。-1 會選取狀態符合選項的任何子程序,這也是 wait 使用的選取方式;0 會選取呼叫者所屬程序群組中的子程序;小於 -1 的值則會選取程序群組 ID 等於 pid 絕對值的子程序。因此,waitpid 適合用來指定要等待的子程序,但它並非 wait 系列中唯一可用來等待子程序的介面。

第二個引數是指向整數的指標;有子程序狀態需要回報時,核心會將編碼後的狀態寫入該整數。若需要取得狀態,請傳入有效的指標。第三個引數是選項的位元遮罩。選項設為 0 時,呼叫會等待所選子程序終止;如果目前沒有符合條件的狀態變化,呼叫就會阻塞。

選擇要回報的子程序事件

WNOHANG 會讓呼叫不阻塞:如果所選子程序沒有可回報的狀態變化,waitpid 會立即傳回 0。如此一來,父程序可以先處理其他工作,稍後再檢查。正數傳回值是狀態已寫入的子程序之程序 ID。WUNTRACED 也會回報尚未回報的已停止子程序;WCONTINUED 則會回報停止後又恢復執行的子程序。這些選項決定哪些狀態變化能讓這次呼叫取得回應;單次呼叫不會持續監看後續的每一次狀態轉變。旗標可以使用位元 OR 運算子組合,例如 WUNTRACED | WCONTINUED。

解讀 waitpid 傳回的狀態

狀態整數經過編碼,應使用相應的巨集判斷。WIFEXITED(status) 用來判斷子程序是否正常終止;若是,接著以 WEXITSTATUS(status) 取得結束碼。WIFSIGNALED(status) 用來判斷子程序是否因訊號而終止,而 WTERMSIG(status) 會取得該訊號。若要求回報已停止的子程序,WIFSTOPPED(status) 和 WSTOPSIG(status) 可用來判斷停止狀態及其訊號。在系統支援且呼叫要求回報的情況下,WIFCONTINUED(status) 可用來判斷子程序是否已恢復執行。部分系統提供 WCOREDUMP;使用它的程式應以預處理器檢查保護相關程式碼。這些巨集宣告於 <sys/wait.h>。

只有在 WIFEXITED 為真時才能呼叫 WEXITSTATUS;只有在 WIFSIGNALED 為真時才能呼叫 WTERMSIG。該整數本身不是子程序的結束碼;使用這些巨集可避免依賴實作特定的位元配置。

處理傳回值與錯誤

waitpid 傳回大於零的值時,表示已取得該子程序的狀態。傳回 0 表示目前沒有符合條件的狀態可供回報,這種情況會出現在使用 WNOHANG 時。傳回 -1 表示發生錯誤;請檢查 errno 以診斷原因。例如,ECHILD 表示沒有符合所選條件的子程序;EINTR 表示等待期間收到的訊號中斷了呼叫。如果程式在 EINTR 後應繼續等待,就重試呼叫;否則,請依照應用程式的訊號處理策略處理中斷。若未使用 WNOHANG,而所選子程序持續執行且沒有發生指定要回報的狀態變化,呼叫也可能無限期阻塞。

執行完整的子程序範例

這個 POSIX 範例會建立一個子程序,等待該子程序終止;若訊號中斷等待,就會重試,並印出解碼後的結束狀態。範例使用 fork 和 waitpid,不需要另一個程式或互動式輸入。此程式碼適用於具備 C 編譯器的 POSIX 環境,並非可攜式 ISO C 範例。提供的執行證據記錄了成功執行,結束碼為 0,並產生下方所示的輸出;輸出區塊中的英文標籤會維持原樣。

執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(void) {
    pid_t child = fork();
    if (child == -1) {
        perror("fork");
        return EXIT_FAILURE;
    }

    if (child == 0) {
        _exit(7);
    }

    int status;
    pid_t result;
    do {
        result = waitpid(child, &status, 0);
    } while (result == -1 && errno == EINTR);

    if (result == -1) {
        perror("waitpid");
        return EXIT_FAILURE;
    }

    if (WIFEXITED(status)) {
        printf("child exit status: %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("child terminated by signal: %d\n", WTERMSIG(status));
    }

    return EXIT_SUCCESS;
}
child exit status: 7