當一個進程正常或異常終止時,內核就向其父進程發送SIGCHLD信號。因為子進程終止是個異步事件(這可以在父進程運行的任何時候發生),所以這種信號也是內核向父進程發的異步通知。父進程可以選擇忽略該信號,或者提供一個該信號發生時即被調用執行的函數(信號處理程序)。對于這種信號的系統默認動作是忽略它)。
調用wait或waitpid的進程可能會發生的情況:
如果進程由于接收到SIGCHLD信號而調用wait,則可期望wait會立即返回。但是如果在任意時刻調用wait,則進程可能會阻塞。
#include <sys/wait.h>pid_t wait( int *statloc );返回值:若成功則返回已終止子進程ID,若出錯則返回-1pid_t waitpid( pid_t pid, int *statloc, int options );返回值:若成功則返回狀態改變的子進程ID,若出錯則返回-1,若指定了WNOHANG選項且pid指定的子進程狀態沒有發生改變則返回0
這兩個函數的區別如下:
如果一個子進程已經終止,并且是一個僵死進程,則wait立即返回并取得該子進程的狀態,否則wait使其調用者阻塞直到一個子進程終止。如果調用者阻塞而且它有多個子進程,則在其一個子進程終止時,wait就立即返回。因為wait返回終止子進程的進程ID,所以它總能了解是哪一個子進程終止了。
這兩個函數的參數statloc是一個整型指針。如果statloc不是一個空指針,則終止進程的終止狀態就存放在它所指向的單元內。如果不關心終止狀態,則可將該參數指定為空指針。
依據傳統,這兩個函數返回的整型狀態字是由實現定義的。其中某些位表示退出狀態(正常返回),其他位則指示信號編號(異常返回),有一位指示是否產生了一個core文件等。POSIX.1規定狀態用定義在<sys/wait.h>中的各個宏來查看。有四個互斥的宏可用來取得進程終止的原因,它們的名字都以WIF開始?;谶@四個宏中哪一個值為真,就可選用其他宏(表8-1說明欄中下劃線標注的宏)來取得終止狀態、信號編號等。這四個互斥的宏示于表8-1中。
表8-1 檢查wait和waitpid所返回的終止狀態的宏
宏 | 說明 |
WIFEXITED(status) | 若為正常終止子進程返回的狀態,則為真。對于這種情況可執行WEXITSTATUS(status),取子進程傳送給exit、_exit或_Exit參數的低8位 |
WIFSIGNALED(status) | 若為異常終止子進程返回的狀態,則為真(接到一個不捕捉的信號)。對于這種情況,可執行WTERMSIG(status),取使子進程終止的信號編號。另外,有些實現定義宏WCOREDUMP(status),若已產生終止進程的core文件,則它返回真 |
WIFSTOPPED(status) | 若為當前暫停子進程的返回狀態,則為真。對于這種情況,可執行WSTOPSIG(status),取使子進程暫停的信號編號 |
WIFCONTINUED(status) | 若在作業控制暫停后已經繼續的子進程返回了狀態,則為真。(POSIX.1的XSI擴展;僅用于waitpid。) |
程序清單8-3 打印exit狀態的說明
[root@localhost apue]# cat PRog8-3.c#include "apue.h"#include <sys/wait.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%s/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));}
程序清單8-4 演示不同的exit值(調用prog8-3中的pr_exit函數)
[root@localhost apue]# cat prog8-4.c#include "apue.h"#include <sys/wait.h>intmain(void){ pid_t pid; int status; if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) exit(7); /* child */ if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ abort(); if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ if ((pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* child */ status /= 0; /* divide by 0 generates SIGPE */ if (wait(&status) != pid) /* wait for child */ err_sys("wait error"); pr_exit(status); /* and print its status */ exit(0);}
運行該程序可得:
[root@localhost apue]# ./prog8-4normal termination, exit status = 7abnormal termination, signal number = 6abnormal termination, signal number = 8
不幸的是,沒有一種可移植的方法將WTERMSIG得到的信號編號映射為說明性的名字。我們必須查看<signal.h>頭文件才能知道SIGABRT的值是6,SIGFPE的值是8.
正如前面所述,如果一個進程有幾個子進程,那么只要有一個子進程終止,wait就返回。如果要等待一個指定的進程終止(如果知道要等待進程的ID),那么該如何做呢?POSIX.1定義了waitpid函數以提供這種功能(以及其他一些功能)。
對于waitpid函數中pid參數的作用解釋如下:
pid == –1 | 等待任一子進程。就這一方面而言,waitpid與wait等效。 |
pid > 0 | 等待其進程ID與pid相等的子進程。 |
pid == 0 | 等待其組ID等于調用進程組ID的任一子進程。 |
pid < –1 | 等待其組ID等于pid絕對值的任一子進程。 |
waitpid函數返回終止子進程的進程ID,并將該子進程的終止狀態存放在由status指向的存儲單元中。對于wait,其唯一的出錯是調用進程沒有子進程(函數調用被一個信號中斷時,也可能返回另一種出錯)。但是對于waitpid,如果指定的進程或進程組不存在,或者參數pid指定的進程不是調用進程的子進程則都將出錯。
options參數使我們能進一步控制waitpid的操作。此參數可以是0,或者是表8-2中常量按位“或”運算的結果。
表8-2 waitpid的options常量
常量 | 說明 |
WCONTINUED | 若實現支持作業控制,那么由pid指定的任一子進程在暫停后已經繼續,但其狀態尚未報告,則返回其狀態 |
WNOHANG | 若由pid指定的子進程并不是立即可用的,則waitpid不阻塞,此時其返回值為0 |
WUNTRACED | 若某實現支持作業控制,而由pid指定的任一子進程已處于暫停狀態,并且其狀態自暫停以來還未報告過,則返回其狀態。WIFSTOPPED宏確定返回值是否對應于一個暫停子進程 |
waitpid函數提供了wait函數沒有提供的三個功能:
(1)waitpid可等待一個特定的進程,而wait則返回任一終止子進程的狀態。
(2)waitpid提供了一個wait的非阻塞版本。有時用戶希望取得一個子進程的狀態,但不想阻塞。
(3)waitpid支持作業控制(利用WUNTRACED和WCONTINUED選項)。
如果一個進程fork一個子進程,但不要等待子進程終止,也不希望子進程處于僵死狀態直到父進程終止,實現這一要求的技巧是調用fork兩次。
程序清單8-5 調用fork兩次以避免僵死進程
[root@localhost apue]# cat prog8-5.c#include "apue.h"#include <sys/wait.h>intmain(void){ pid_t pid; if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) /* first child */ { if ((pid = fork()) < 0) err_sys("fork error"); else if (pid > 0) exit(0); /* parent from second fork == first child */ /* * We're the second child; our parent become init as soon * as our real parent calls exit() in the statement above. * Here's where we'd continue executing, knowing that when * we're done, init will reap our status. */ sleep(2); printf("second child, parent pid = %d/n", getppid()); exit(0); } if (waitpid(pid, NULL, 0) != pid) /* wait for first child */ err_sys("waitpid error"); /* * We're the parent ( the original process ); we continue executing, * knowing that we're not the parent of the second child. */ exit(0);}
第二個子進程調用sleep以保證在打印父進程ID時第一個子進程已終止。在fork之后,父、子進程都可繼續執行,并且我們無法預知哪一個會先執行。在fork之后,如果不使第二個子進程休眠,那么它可能比其父進程先執行,于是它打印的父進程ID將是創建它的父進程,而不是init進程(進程ID 1)。
執行結果:
[root@localhost apue]# ./prog8-5[root@localhost apue]# second child, parent pid = 1
注意,當原先的進程(也就是exec本程序的進程)終止時,shell打印其提示符,這在第二個子進程打印其父進程ID之前。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答