linux系統程式設計之程式(六):父程式查詢子程式的退出,wait,waitpid

mickole發表於2013-07-13

本節目標:

  • 僵程式
  • SIGCHLD
  • wait
  • waitpid

一,殭屍程式

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

子程式退出時,核心將子程式置為殭屍狀態,這個程式稱為殭屍程式,它只保留最小的一些核心資料結構,以便父程式查詢子程式的退出狀態。

程式表中代表子程式的資料項是不會立刻釋放的,雖然不再活躍了,可子程式還停留在系統裡,因為它的退出碼還需要儲存起來以備父程式中後續的wait呼叫使用。它將稱為一個“僵程式”。

二,如何避免殭屍程式

  • 呼叫wait或者waitpid函式查詢子程式退出狀態,此方法父程式會被掛起。
  • 如果不想讓父程式掛起,可以在父程式中加入一條語句:signal(SIGCHLD,SIG_IGN);表示父程式忽略SIGCHLD訊號,該訊號是子程式退出的時候向父程式傳送的。

三,SIGCHLD訊號

當子程式退出的時候,核心會向父程式傳送SIGCHLD訊號,子程式的退出是個非同步事件(子程式可以在父程式執行的任何時刻終止)

如果不想讓子程式程式設計殭屍程式可在父程式中加入:signal(SIGCHLD,SIG_IGN);

如果將此訊號的處理方式設為忽略,可讓核心把殭屍子程式轉交給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;
}

結果:

QQ截圖20130713104153

可知,雖然子程式先退出了,但程式表中已經不存在子程式的殭屍狀態

 

三,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。

man幫助:

DESCRIPTION
       All of these system calls are used to wait for state changes in a child
       of the calling process, and obtain information about  the  child  whose
       state  has changed.  A state change is considered to be: the child ter-
       minated; the child was stopped by a signal; or the child was resumed by
       a  signal.  In the case of a terminated child, performing a wait allows
       the system to release the resources associated with  the  child;  if  a
       wait  is not performed, then the terminated child remains in a "zombie"
       state (see NOTES below).

       If a child has already changed state, then these calls  return  immedi-
       ately.   Otherwise  they  block until either a child changes state or a
       signal handler interrupts the call (assuming that system calls are  not
       automatically restarted using the SA_RESTART flag of sigaction(2)).  In
       the remainder of this page, a child whose state has changed  and  which
       has  not  yet  been  waited upon by one of these system calls is termed
       waitable.

wait() :
    The wait() system call suspends execution of the calling process  until
    one  of  its children terminates.  The call wait(&status) is equivalent
    to:

        waitpid(-1, &status, 0);

If status is not NULL, wait() and waitpid() store status information in
      the  int  to  which  it points.  This integer can be inspected with the
      following macros (which take the integer itself as an argument,  not  a
      pointer to it, as is done in wait() and waitpid()!):

      WIFEXITED(status)
             returns true if the child terminated normally, that is, by call-
             ing exit(3) or _exit(2), or by returning from main().

      WEXITSTATUS(status)
             returns the exit status of the  child.   This  consists  of  the
             least  significant  8 bits of the status argument that the child
             specified in a call to exit(3) or _exit(2) or  as  the  argument
             for  a  return  statement  in main().  This macro should only be
             employed if WIFEXITED returned true.

      WIFSIGNALED(status)
             returns true if the child process was terminated by a signal.

    WTERMSIG(status)
             returns the number of the signal that caused the  child  process
             to terminate.  This macro should only be employed if WIFSIGNALED
             returned true.

      WCOREDUMP(status)
             returns true if the child produced  a  core  dump.   This  macro
             should  only  be  employed  if  WIFSIGNALED returned true.  This
             macro is not specified in POSIX.1-2001 and is not  available  on
             some  Unix  implementations  (e.g.,  AIX, SunOS).  Only use this
             enclosed in #ifdef WCOREDUMP ... #endif.

      WIFSTOPPED(status)
             returns true if the child process was stopped by delivery  of  a
             signal;  this  is  only possible if the call was done using WUN-
             TRACED or when the child is being traced (see ptrace(2)).

      WSTOPSIG(status)
             returns the number of the signal which caused the child to stop.
             This  macro should only be employed if WIFSTOPPED returned true.


   WIFCONTINUED(status)
       (since Linux 2.6.10) returns  true  if  the  child  process  was
       resumed by delivery of SIGCONT.

  • wait系統呼叫會使父程式暫停執行,直到它的一個子程式結束為止。
  • 返回的是子程式的PID,它通常是結束的子程式
  • 狀態資訊允許父程式判定子程式的退出狀態,即從子程式的main函式返回的值或子程式中exit語句的退出碼。
  • 如果status不是一個空指標,狀態資訊將被寫入它指向的位置

可以上述的一些巨集判斷子程式的退出情況:

QQ截圖20130713110230

示例程式:

#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;
}

結果:

QQ截圖20130713111249

當子程式正常退出時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;
}

結果:

QQ截圖20130713111642

當子程式異常退出時,WIFSIGNALED(status)為真,可用WTERMSIG(status)獲得訊號

 

四,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把呼叫者的執行掛起

The value of options is an OR of zero or more  of  the  following  con-
stants:

WNOHANG     return immediately if no child has exited.

WUNTRACED   also  return  if  a  child  has stopped (but not traced via
            ptrace(2)).  Status for traced children which have  stopped
            is provided even if this option is not specified.

WCONTINUED (since Linux 2.6.10)
            also return if a stopped child has been resumed by delivery
            of SIGCONT.

返回值:如果成功返回等待子程式的ID,失敗返回-1

對於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;
}

結果:

QQ截圖20130713112838

可知,option設為WNOHANG,父程式不會等到子程式的退出,即不會阻塞,如果沒有子程式退出則立即返回-1,

相關文章