當(dāng)一個(gè)進(jìn)程正常或異常終止時(shí),內(nèi)核就向其父進(jìn)程發(fā)送SIGCHLD信號(hào)。因?yàn)樽舆M(jìn)程終止是個(gè)異步事件(這可以在父進(jìn)程運(yùn)行的任何時(shí)候發(fā)生),所以這種信號(hào)也是內(nèi)核向父進(jìn)程發(fā)的異步通知。父進(jìn)程可以選擇忽略該信號(hào),或者提供一個(gè)該信號(hào)發(fā)生時(shí)即被調(diào)用執(zhí)行的函數(shù)(信號(hào)處理程序)。對(duì)于這種信號(hào)的系統(tǒng)默認(rèn)動(dòng)作是忽略它)。
調(diào)用wait或waitpid的進(jìn)程可能會(huì)發(fā)生的情況:
如果進(jìn)程由于接收到SIGCHLD信號(hào)而調(diào)用wait,則可期望wait會(huì)立即返回。但是如果在任意時(shí)刻調(diào)用wait,則進(jìn)程可能會(huì)阻塞。
#include <sys/wait.h>pid_t wait( int *statloc );返回值:若成功則返回已終止子進(jìn)程ID,若出錯(cuò)則返回-1pid_t waitpid( pid_t pid, int *statloc, int options );返回值:若成功則返回狀態(tài)改變的子進(jìn)程ID,若出錯(cuò)則返回-1,若指定了WNOHANG選項(xiàng)且pid指定的子進(jìn)程狀態(tài)沒(méi)有發(fā)生改變則返回0
這兩個(gè)函數(shù)的區(qū)別如下:
如果一個(gè)子進(jìn)程已經(jīng)終止,并且是一個(gè)僵死進(jìn)程,則wait立即返回并取得該子進(jìn)程的狀態(tài),否則wait使其調(diào)用者阻塞直到一個(gè)子進(jìn)程終止。如果調(diào)用者阻塞而且它有多個(gè)子進(jìn)程,則在其一個(gè)子進(jìn)程終止時(shí),wait就立即返回。因?yàn)閣ait返回終止子進(jìn)程的進(jìn)程ID,所以它總能了解是哪一個(gè)子進(jìn)程終止了。
這兩個(gè)函數(shù)的參數(shù)statloc是一個(gè)整型指針。如果statloc不是一個(gè)空指針,則終止進(jìn)程的終止?fàn)顟B(tài)就存放在它所指向的單元內(nèi)。如果不關(guān)心終止?fàn)顟B(tài),則可將該參數(shù)指定為空指針。
依據(jù)傳統(tǒng),這兩個(gè)函數(shù)返回的整型狀態(tài)字是由實(shí)現(xiàn)定義的。其中某些位表示退出狀態(tài)(正常返回),其他位則指示信號(hào)編號(hào)(異常返回),有一位指示是否產(chǎn)生了一個(gè)core文件等。POSIX.1規(guī)定狀態(tài)用定義在<sys/wait.h>中的各個(gè)宏來(lái)查看。有四個(gè)互斥的宏可用來(lái)取得進(jìn)程終止的原因,它們的名字都以WIF開(kāi)始。基于這四個(gè)宏中哪一個(gè)值為真,就可選用其他宏(表8-1說(shuō)明欄中下劃線標(biāo)注的宏)來(lái)取得終止?fàn)顟B(tài)、信號(hào)編號(hào)等。這四個(gè)互斥的宏示于表8-1中。
表8-1 檢查wait和waitpid所返回的終止?fàn)顟B(tài)的宏
宏 | 說(shuō)明 |
WIFEXITED(status) | 若為正常終止子進(jìn)程返回的狀態(tài),則為真。對(duì)于這種情況可執(zhí)行WEXITSTATUS(status),取子進(jìn)程傳送給exit、_exit或_Exit參數(shù)的低8位 |
WIFSIGNALED(status) | 若為異常終止子進(jìn)程返回的狀態(tài),則為真(接到一個(gè)不捕捉的信號(hào))。對(duì)于這種情況,可執(zhí)行WTERMSIG(status),取使子進(jìn)程終止的信號(hào)編號(hào)。另外,有些實(shí)現(xiàn)定義宏WCOREDUMP(status),若已產(chǎn)生終止進(jìn)程的core文件,則它返回真 |
WIFSTOPPED(status) | 若為當(dāng)前暫停子進(jìn)程的返回狀態(tài),則為真。對(duì)于這種情況,可執(zhí)行WSTOPSIG(status),取使子進(jìn)程暫停的信號(hào)編號(hào) |
WIFCONTINUED(status) | 若在作業(yè)控制暫停后已經(jīng)繼續(xù)的子進(jìn)程返回了狀態(tài),則為真。(POSIX.1的XSI擴(kuò)展;僅用于waitpid。) |
程序清單8-3 打印exit狀態(tài)的說(shuō)明
[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值(調(diào)用prog8-3中的pr_exit函數(shù))
[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);}
運(yùn)行該程序可得:
[root@localhost apue]# ./prog8-4normal termination, exit status = 7abnormal termination, signal number = 6abnormal termination, signal number = 8
不幸的是,沒(méi)有一種可移植的方法將WTERMSIG得到的信號(hào)編號(hào)映射為說(shuō)明性的名字。我們必須查看<signal.h>頭文件才能知道SIGABRT的值是6,SIGFPE的值是8.
正如前面所述,如果一個(gè)進(jìn)程有幾個(gè)子進(jìn)程,那么只要有一個(gè)子進(jìn)程終止,wait就返回。如果要等待一個(gè)指定的進(jìn)程終止(如果知道要等待進(jìn)程的ID),那么該如何做呢?POSIX.1定義了waitpid函數(shù)以提供這種功能(以及其他一些功能)。
對(duì)于waitpid函數(shù)中pid參數(shù)的作用解釋如下:
pid == –1 | 等待任一子進(jìn)程。就這一方面而言,waitpid與wait等效。 |
pid > 0 | 等待其進(jìn)程ID與pid相等的子進(jìn)程。 |
pid == 0 | 等待其組ID等于調(diào)用進(jìn)程組ID的任一子進(jìn)程。 |
pid < –1 | 等待其組ID等于pid絕對(duì)值的任一子進(jìn)程。 |
waitpid函數(shù)返回終止子進(jìn)程的進(jìn)程ID,并將該子進(jìn)程的終止?fàn)顟B(tài)存放在由status指向的存儲(chǔ)單元中。對(duì)于wait,其唯一的出錯(cuò)是調(diào)用進(jìn)程沒(méi)有子進(jìn)程(函數(shù)調(diào)用被一個(gè)信號(hào)中斷時(shí),也可能返回另一種出錯(cuò))。但是對(duì)于waitpid,如果指定的進(jìn)程或進(jìn)程組不存在,或者參數(shù)pid指定的進(jìn)程不是調(diào)用進(jìn)程的子進(jìn)程則都將出錯(cuò)。
options參數(shù)使我們能進(jìn)一步控制waitpid的操作。此參數(shù)可以是0,或者是表8-2中常量按位“或”運(yùn)算的結(jié)果。
表8-2 waitpid的options常量
常量 | 說(shuō)明 |
WCONTINUED | 若實(shí)現(xiàn)支持作業(yè)控制,那么由pid指定的任一子進(jìn)程在暫停后已經(jīng)繼續(xù),但其狀態(tài)尚未報(bào)告,則返回其狀態(tài) |
WNOHANG | 若由pid指定的子進(jìn)程并不是立即可用的,則waitpid不阻塞,此時(shí)其返回值為0 |
WUNTRACED | 若某實(shí)現(xiàn)支持作業(yè)控制,而由pid指定的任一子進(jìn)程已處于暫停狀態(tài),并且其狀態(tài)自暫停以來(lái)還未報(bào)告過(guò),則返回其狀態(tài)。WIFSTOPPED宏確定返回值是否對(duì)應(yīng)于一個(gè)暫停子進(jìn)程 |
waitpid函數(shù)提供了wait函數(shù)沒(méi)有提供的三個(gè)功能:
(1)waitpid可等待一個(gè)特定的進(jìn)程,而wait則返回任一終止子進(jìn)程的狀態(tài)。
(2)waitpid提供了一個(gè)wait的非阻塞版本。有時(shí)用戶希望取得一個(gè)子進(jìn)程的狀態(tài),但不想阻塞。
(3)waitpid支持作業(yè)控制(利用WUNTRACED和WCONTINUED選項(xiàng))。
如果一個(gè)進(jìn)程fork一個(gè)子進(jìn)程,但不要等待子進(jìn)程終止,也不希望子進(jìn)程處于僵死狀態(tài)直到父進(jìn)程終止,實(shí)現(xiàn)這一要求的技巧是調(diào)用fork兩次。
程序清單8-5 調(diào)用fork兩次以避免僵死進(jìn)程
[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);}
第二個(gè)子進(jìn)程調(diào)用sleep以保證在打印父進(jìn)程ID時(shí)第一個(gè)子進(jìn)程已終止。在fork之后,父、子進(jìn)程都可繼續(xù)執(zhí)行,并且我們無(wú)法預(yù)知哪一個(gè)會(huì)先執(zhí)行。在fork之后,如果不使第二個(gè)子進(jìn)程休眠,那么它可能比其父進(jìn)程先執(zhí)行,于是它打印的父進(jìn)程ID將是創(chuàng)建它的父進(jìn)程,而不是init進(jìn)程(進(jìn)程ID 1)。
執(zhí)行結(jié)果:
[root@localhost apue]# ./prog8-5[root@localhost apue]# second child, parent pid = 1
注意,當(dāng)原先的進(jìn)程(也就是exec本程序的進(jìn)程)終止時(shí),shell打印其提示符,這在第二個(gè)子進(jìn)程打印其父進(jìn)程ID之前。
本篇博文內(nèi)容摘自《UNIX環(huán)境高級(jí)編程》(第二版),僅作個(gè)人學(xué)習(xí)記錄所用。關(guān)于本書可參考:http://www.apuebook.com/。
新聞熱點(diǎn)
疑難解答
圖片精選