Linux 進程--父進程查詢子進程的退出狀態



僵屍進程

當一個子進程先於父進程結束運行時,它與其父進程之間的關聯還會保持到父進程也正常地結束運行,或者父進程調用了wait才告終止。

子進程退出時,內核將子進程置為僵屍狀態,這個進程稱為僵屍進程,它只保留最小的一些內核數據結構,以便父進程查詢子進程的退出狀態。

進程表中代表子進程的數據項是不會立刻釋放的,雖然不再活躍了,可子進程還停留在系統里,因為它的退出碼還需要保存起來以備父進程中后續的wait調用使用。它將稱為一個“僵進程”。

如何避免僵屍進程

  1. 調用wait或者waitpid函數查詢子進程退出狀態,此方法父進程會被掛起。
  2. 如果不想讓父進程掛起,可以在父進程中加入一條語句: signal(SIGCHLD,SIG_IGN);表示父進程忽略SIGCHLD信號,該信號是子進程退出的時候向父進程發送的
  3. 注冊信號處理函數,在信號處理函數總調用 wait 函數。

SIGCHLD 信號

當子進程退出的時候,內核會向父進程發送SIGCHLD信號,子進程的退出是個異步事件(子進程可以在父進程運行的任何時刻終止)

如果不想讓子進程編程僵屍進程可在父進程中加入:signal(SIGCHLD,SIG_IGN);

如果將此信號的處理方式設為忽略,可讓內核把僵屍子進程轉交給init進程去處理,省去了大量僵屍進程占用系統資源。(父進程忽略了 SIGCHLD信號之后,會將僵屍子進程轉交給init進程給處理,這樣子就不保存父子關系了嗎?)。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main(void)
{
    pid_t pid;
    if(signal(SIGCHLD,SIG_IGN) == SIG_ERR)
    {
        perror("signal error");
        exit(EXIT_FAILURE);
    }
    pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0)
    {
        printf("this is child process\n");
        exit(0);
    }
    if(pid > 0)
    {
        sleep(100);
        printf("this is parent process\n");
    }
    return 0;
}

結果是:
這里寫圖片描述

可以看到,雖然子進程先退出了,但是進程表中已經不存在子進程的僵屍狀態了。(因為被 init 進程處理掉了)

wait() 函數

#include <sys/types.h> 
#include <sys/wait.h>
pid_t wait(int *status);

進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成僵屍的子進程,wait就會收集這個子進程的信息,並把它徹底銷毀后返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這里,直到有一個出現為止。

參數status用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。但如果我們對這個子進程是如何死掉的毫不在意,只想把這個僵屍進程消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個參數為NULL,就象下面這樣:

                    `pid = wait(NULL)`

如果成功,wait會返回被收集的子進程的進程ID,如果調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置為ECHILD

  1. wait系統調用會使父進程暫停執行,直到它的一個子進程結束為止。
  2. 返回的是子進程的PID,它通常是結束的子進程
  3. 狀態信息允許父進程判定子進程的退出狀態,即從子進程的main函數返回的值或子進程中exit語句的退出碼。
  4. 如果status不是一個空指針,狀態信息將被寫入它指向的位置

可以用一些宏判斷子進程的退出情況:
這里寫圖片描述

示例程序如下:

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        printf("this is child process\n");
        exit(100);
    }
    int status;
    pid_t ret;
    ret = wait(&status);
    if(ret <0){
        perror("wait error");
        exit(EXIT_FAILURE);
    }
        printf("ret = %d pid = %d\n", ret, pid);
    if (WIFEXITED(status))
        printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
    else if (WIFSTOPPED(status))
        printf("child stoped signal number=%d\n", WSTOPSIG(status));
    return 0;
}

上述程序正常退出exit(100),程序返回值為100。

這里寫圖片描述

當子進程正常退出的時候, wait返回子進程的 pid, 並且 WIFEXITED(status) 為真, WEXITSTATUS(status)獲得返回碼。

實例2:

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        printf("this is child process\n");
        //exit(100);
        abort();
    }
    int status;
    pid_t ret;
    ret = wait(&status);
    if(ret <0){
        perror("wait error");
        exit(EXIT_FAILURE);
    }
        printf("ret = %d pid = %d\n", ret, pid);
    if (WIFEXITED(status))
        printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
    else if (WIFSTOPPED(status))
        printf("child stoped signal number=%d\n", WSTOPSIG(status));
    return 0;
}

上述代碼中,程序通過 abort() 系統調用返回,發送 SIGABRT 信號,對此信號的默認動作是終止進程。 並且系統終止的時候,不會 without destroying any objecty and without calling any of the function passed to atexit or at_quick_exit

結果如下:
這里寫圖片描述

當程序異常退出的時候, WIFSIGNALED(status) 為真,可用 WTERMSIG(status) 返回相應的信號代碼。

(另外,WIFSTOPPED,子進程被暫停也會被父進程捕捉到,但是並不代表子進程已經是處於退出狀態吧?? 不太確定)

waitpid() 函數

waitpid() 是一個非常有用的函數,不單單可以等待子進程。 還可以等待進程組中的任意一個進程。 可以看到 wait() 函數實際上是 waitpid() 函數的特例。

#include <sys/types.h> 
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
參數:
status:如果不是空,會把狀態信息寫到它指向的位置,與wait一樣
options:允許改變waitpid的行為,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者的執行掛起 (也就是不阻塞父進程)

對於waitpid的p i d參數的解釋與其值有關:
pid == -1 等待任一子進程。於是在這一功能方面waitpid與wait等效。

pid > 0 等待其進程I D與p i d相等的子進程。

pid == 0 等待其組I D等於調用進程的組I D的任一子進程。換句話說是與調用者進程同在一個組的進程。

pid < -1 等待其組I D等於p i d的絕對值的任一子進程

wait與waitpid區別:

在一個子進程終止前, wait 使其調用者阻塞,而waitpid 有一選擇項,可使調用者不阻塞。
waitpid並不等待第一個終止的子進程—它有若干個選擇項,可以控制它所等待的特定進程。
實際上wait函數是waitpid函數的一個特例。waitpid(-1, &status, 0);

代碼如下:

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        printf("this is child process\n");
        sleep(5);
        exit(100);
    }
    int status;
    pid_t ret;
    ret = waitpid(pid,&status,WNOHANG);
    if(ret <0){
        perror("wait error");
        exit(EXIT_FAILURE);
    }
        printf("ret = %d pid = %d\n", ret, pid);
    if (WIFEXITED(status))
        printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
    else if (WIFSTOPPED(status))
        printf("child stoped signal number=%d\n", WSTOPSIG(status));
    return 0;
}

結果如下:
這里寫圖片描述

這里可以看到,首先 option 設置為了 WNOHANG 之后,父進程不會等待子進程的退出,也就是不會阻塞,如果沒有子進程的退出立即返回-1。

上述中的代碼是有問題的: 首先 ret = 0, 是什么意思? 如果沒有子進程的退出了,那么上述代碼中檢測 WIFEXITED(status) 就失去了意義,所以還需要增加一個判斷條件,判斷是否有子進程退出。也就是ret > 0的選項。

關於父子進程之間還有很多話題可以講述,以后慢慢的一一道來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM