#include <sys/types.h>
#include <sys/wait.h>
?
pid_t wait(int *status);
?
僵尸進程。進程結束后放棄了幾乎所有的內存空間,沒有任何可以執行的代碼,也不能被調度,僅僅在進程列表中保留著一個位置,記載著該進程的退出狀態等信息供其他進程收集,除此之外僵尸進程不再占有任何內存空間。
當進程終止時,操作系統的隱式回收機制會:1.關閉所有文件描述符 2. 釋放用戶空間分配的內存。內核的PCB仍存在。其中保存該進程的退出狀態,此時是一個僵尸進程。(正常終止→退出值;異常終止→終止信號)
一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的PCB還保留著,內核在其中保存了一些信息:如果是正常終止則保存著退出狀態,如果是異常終止則保存著導致該進程終止的信號是哪個。這個進程的父進程可以調用wait或waitpid獲取這些信息,然后徹底清除掉這個進程。
在shell終端執行的命令和程序,都是shell通過fork產生的子進程來完成的,因此在命令和程序結束后,shell終端進程也可以獲取子進程的結束狀態,通過特殊命令$?查看,如果為0,表示正常結束,如果為非0值,表示出現了錯誤。Shell調用wait或waitpid得到它的退出狀態同時徹底清除掉這個進程。
父進程調用wait函數可以回收子進程終止信息。該函數有三個功能:1.阻塞等待子進程退出(父進程調用該函數后,如果子進程沒死,則父進程阻塞并等待子進程結束);2. 獲取子進程結束狀態(退出原因);3.回收子進程殘留資源 (回收子進程結束后殘留在內核中的PCB資源),然后徹底清除掉這個進程。
進程的一生。隨著fork的成功執行,一個新的子進程產生,但此時它還只是父進程的克隆。然后隨著exec函數族,新進程脫胎換骨,開始獨立執行一個全新的程序,并完全替代了原有的父進程。進程結束,可以是自殺,也可以是他殺。自殺:遇到main函數的最后一個“}”、在main函數中使用return或者調用exit和_exit;他殺:被其它進程通過另外一種方式殺掉。進程自殺(屬于正常死亡),即使程序出錯,但是進程自身結束了自己,可以通過return和exit賦給進程結束后一個值,通過wait或waitpid函數來獲取該值,從而知道該進程自殺的原因,是否出錯(一般0代表正常,非0代表出錯),當然可以返回任何值,由wait函數可以獲取到。進程如果屬于他殺,則為異常結束,在linux操作系統中,所有的異常終止都是由于某一個信號導致的(通過kill –l參數可以查看所有的信號及信號編號),如在程序中給一個常量賦值,會發生段錯誤,此時進程會收到段錯誤相應的信號,從而被終止;除數為0時,進程也會收到一個信號(浮點數例外),從而被終止。而終止進程的信號會記錄在終止信息中,供父進程調用wait函數獲取。進程死掉后,會留下一具僵尸,wait和waitpid則去收集信息,清理尸體。
exit和_exit這兩個函數(都是系統調用)都有一個整型形參,用于傳遞進程結束時的狀態(正常結束還是出錯結束)。一般,0表示正常結束,非0值(如1)表示出現了錯誤,非正常結束。可以利用wait系統調用接收子進程的返回值,從而針對不同的情況進行不同的處理。
?
pid_t wait(int *status);
status為傳出參數,用于獲取子進程結束時的狀態(原因)。函數執行成功,則返回清理掉的子進程ID,如果失敗則返回-1。進程一旦調用了wait,就立即阻塞自己,由wait自動分析該進程的某個子進程是否已經退出,如果找到了一個已經結束的子進程,則收集其信息,并將其清除掉并返回該子進程ID;如果沒有找到這樣的一個子進程,則會一直阻塞到那里,直到有一個出現為止。如果父進程沒有子進程,則直接返回-1,表示出錯。
如果對子進程如何死掉的不關心(不需要收集其殘留的信息),只想把僵尸進程消滅掉,可以將函數形參設置為NULL,即pid=wait(NULL);
?
可使用wait函數傳出參數status(整型值)來保存進程的退出狀態。借助宏函數來進一步判斷進程終止的具體原因。宏函數可分為如下三組:
?1. WIFEXITED(status) 為非0?? → 表示進程正常結束
WEXITSTATUS(status) 如上宏為真(進程正常結束),使用此宏 → 獲取進程退出狀態 (exit/_exit/return的參數)
?2. WIFSIGNALED(status) 為非0 → 表示進程異常終止
?????? WTERMSIG(status) 如上宏為真,使用此宏 → 取得使進程終止的那個信號的編號。可以通過kill –l命令查看編號的詳細含義。
3. WIFSTOPPED(status) 為非0 → 表示進程處于暫停狀態(進程沒有終止,只是暫停運行,如掛起或阻塞)。
WSTOPSIG(status) 如上宏為真,使用此宏 → 取得使進程暫停的那個信號的編號。
WIFCONTINUED(status) 為真 → 進程暫停后已經繼續運行
?
//代碼舉例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main(void)
{pid_t pid, wpid;int status;pid = fork( );if(pid == 0){printf("---child, my parent= %d, going to sleep 10s\n",getppid( ));sleep(10);printf("-------------child die--------------\n");exit(77); //return 77; _exit(77); //子進程正常結束,置其結束狀態為77}else if(pid > 0){wpid = wait(&status); //父進程阻塞等待子進程結束if(wpid==-1){perror("wait");exit(1); //置結束狀態為1}if( WIFEXITED(status) ) //如果子進程正常結束{printf( "The child process exit with %d\n",WEXITSTATUS(status));}else if( WIFSIGNALED(status) ) //如果子進程異常結束{printf( "The child process was killed by %dth signal\n",WTERMSIG(status));}while (1){printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);sleep(1);} //父進程一直運行}else{perror("fork");exit(1);}return 0;
}
[root@localhost wait]# ./wait_test
---child, my parent= 30770, going to sleep 10s
-------------child die--------------
The child process exit with 77?? //獲取了結束狀態為77
I am parent, pid = 30770, myson = 30771
I am parent, pid = 30770, myson = 30771
I am parent, pid = 30770, myson = 30771
[root@localhost wait]#ps aux
root????? 30770? 0.0? 0.0?? 4164?? 356 pts/0??? S+?? 18:11?? 0:00 ./wait_test
root????? 30791? 0.0? 0.0 123360? 1380 pts/2??? R+?? 18:12?? 0:00 ps aux
可見,只有父進程在一直運行,子進程不存在了,也不存在僵尸進程,子進程痕跡被完全清除。
?
在子進程運行期間,利用kill -9 將其殺掉,則為異常結束:
[root@localhost wait]#ps aux
[root@localhost wait]# kill 30830
[root@localhost wait]# ./wait_test
---child, my parent= 30829, going to sleep 60s
The child process was killed by 15th signal
I am parent, pid = 30829, myson = 30830
I am parent, pid = 30829, myson = 30830
I am parent, pid = 30829, myson = 30830
I am parent, pid = 30829, myson = 30830
利用kill -l 命令可以查看編號為15的信號為???? 15) SIGTERM
?
總結,進程正常結束,父進程利用wait函數回收其結束狀態,狀態值通過return、exit和_exit返回(一般,如果程序正常則為0,出錯設非0,但不是必須的),如果進程結束沒有指明其值,如main函數中沒有return語句且正常結束,則會有默認值(正常0,出錯非0)。注意,return、exit和_exit返回的值必須不超過128,可通過wait函數獲取。如果函數異常結束,wait函數回收其終止原因(信號編號)。