⼀個進程在終⽌時會關閉所有⽂件描述符,釋放在⽤戶空間分配的內存,但它的PCB還 保留着,內核在其中保存了⼀些信息:如果是正常終⽌則保存着退出狀態,如果是異常終 ⽌則保存着導致該進程終⽌的信號是哪個。這個進程的⽗進程可以調⽤wait或waitpid 獲取這些信息,然后徹底清除掉 這個進程。我們知道⼀個進程的退出狀態可以在Shell中 ⽤特殊變量$?查看,因為Shell是它的⽗進程,當它終⽌時Shell調⽤wait或waitpid得到它的退出狀態同時徹底清除掉這個進程。
父進程調用wait()和waitpid()函數等待子進程時,⽗進程可以阻塞等待⼦進程結束,也可以⾮阻塞地查詢是否有⼦進程結束等待清理(也就是輪詢的⽅式)。采⽤第⼀種⽅式,⽗進程阻塞了就不 能處理⾃⼰的⼯作了;采⽤第⼆種⽅式,⽗進程在處理⾃⼰的⼯作的同時還要記得時不時地輪詢⼀ 下,程序實現復雜。
1.wait()和waitpid()
(1)wait 函數:用來等待任何一個子進程退出,由父進程調用。
1 #include<sys/types.h> 2 #include<sys/wait.h> 3 pid_t wait(int* status);
返回值:成功返回被等待子進程的pid,失敗返回-1;
status:輸出型參數,拿回子進程的退出信息。 不關⼼則可以設置成為NULL,如果參數status不為空,則進程終止狀態被保存於其中;
wait方式:阻塞式等待,等待的子進程不退出時,父進程一直不退出;
目的:回收子進程,系統回收子進程的空間。
依據傳統,返回的整形狀態字是由實現定義的,其中有些位表示退出狀態(正常返回),其他位表示信號編號(異常返回),有的位表示是否產生了一個core文件等。 終止狀態是定義在 sys/wait.h中的各個宏,有四個可互斥的宏可以用來取得進程終止的原因。
WIFEXITED:若為正常終⽌⼦進程返回的狀態,則為真。可以執行宏函數 WEXITSTATUS獲取子進程狀態傳送給exit、_exit或_Exit的參數的低8位。(查看進程是否正常退出)
WIFSIGNALED:若為異常終⽌⼦進程返回的狀態
(收到⼀個未捕捉的信號),則為真。可以執行宏函數WTERMSIG取得子進程終止的信號編號。另外,對於一些定義有宏WCOREDUMP宏,若以正常終止進程的core文件,則為真。
WIFSTOPPED :若為當前暫停子進程的返回的狀態,則為真,可執行WSTOPSIG取得使子進程暫停的信號編號。
WIFCONTINUED:若在作業控制暫停后已經繼續的子進程返回了狀態,則為真,僅用於waitpid。
舉例: 1. 正常創建⽗⼦進程,⼦進程正常退出,⽗進程等待,並獲取退出狀態status。調⽤該宏,查看輸出結果(正常為⾮0,或1)。 2. 正常創建⽗⼦進程,⼦進程pause(),⽗進程等待,並設置獲取退出狀態status, kill殺掉⼦進程。調⽤該宏,查看輸出結果(結果為0)。2. 若WIFEXITED⾮零,返回⼦進程退出碼,提取進程退出返回值,如果⼦進程調⽤exit(7),WEXITSTATUS(status)就會返回7。請注意,如果進程不是正常退出的,也就是說,WIFEXITED返回0,這個值就毫⽆意義.。
(2)waitpid:
1 #include<sys/types.h> 2 #include<sys/wait.h> 3 pid_t waitpid(pid_t pid,int* status,int options);
1>參數
從參數的名字pid和類型pid_t中就可以看出,這里需要的是一個進程ID。但當pid取不同的值時,在這里有不同的意義。
pid>0時,只等待進程ID等於pid的子進程,不管其它已經有多少子進程運行結束退出了,只要指定的子進程還沒有結束,waitpid就會一直等下去。
pid=-1時,等待任何一個子進程退出,沒有任何限制,此時waitpid和wait的作用一模一樣。
pid=0時,等待同一個進程組中的任何子進程,如果子進程已經加入了別的進程組,waitpid不會對它做任何理睬。
pid<-1時,等待一個指定進程組中的任何子進程,這個進程組的ID等於pid的絕對值。
status參數與wait()函數的基本相同。
options參數 當options參數為0時,與wait功能相同,仍是阻塞式等待,不提供額外功能,如果為下列常量按位或則提供更多功能:
WCONTINUED:若實現支持作業控制,那么由pid指定的任一子進程在暫停后已經繼續,但狀態尚未報告,則返回狀態
WNOHANG:若由pid指定的子進程並不是立即結束,則waitpid不阻塞,即此時以非阻塞方式(輪詢式訪問的必要條件)等待子進程,並且返回0。
若正常結束,則返回該⼦進程的ID。
WUNTRACED:若實現支持作業控制,而pid指定的任一子進程已經暫停,且其狀態尚未報告,則返回其狀態。
說明: status 並不簡簡單單是⼀個整形變量,⽗進程和⼦進程之間所有的狀態交互都要通過這個int來表⽰,所以這個int的若⼲bit位都是有特殊的含義的,那么這個“int”如何編碼就⽐較重要了,和IP地址⼀樣,它是⽐較緊湊的,或者說是⽐較擁擠的。status指出了⼦進程是正常退出還是被⾮正常結束的(⼀個進程也可以被其他進程⽤信號結束),以及正常結束時的返回值,或被哪⼀個信號結束或進程的退出碼是多少等信息,這些信息都被放在整數的不同⼆進制中,所以⽤常規的⽅法讀取會⾮常⿇煩,所以開發者就設計了⼀套專門的宏 來完成這項⼯作。
2>返回值:
當正常返回的時候,waitpid返回收集到的子進程的進程ID;
如果設置了選項WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0;
如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在;
當pid所指示的子進程不存在,或此進程存在,但不是調用進程的子進程,waitpid就會出錯返回,這時errno被設置為ECHILD;
3>waitpid提供了三個wait所沒有的功能:
1. waitpid可等待一個特定的進程
2. waitpid提供了一個wait的非阻塞版本
3. waitpid支持作業控制
2.關於SIGCHLD信號
其實,⼦進程在終⽌時會給⽗進程發SIGCHLD信號,該信號的默認處理動作是忽略,⽗進程可以⾃ 定義SIGCHLD信號的處理函數,這樣⽗進程只需專⼼處理⾃⼰的⼯作,不必關⼼⼦進程了,⼦進程 終⽌時會通知⽗進程,⽗進程在信號處理函數中調⽤wait清理⼦進程即可。一般情況下父進程收到這個信號的默認處理是忽略這個信號,即就是不做任何處理,但是我們可以通過系統調用API:signal()來進行自定義處理句柄,進行驗證,具體代碼如下:
(1)驗證子進程退出時會給父進程發送SIGCHLD信號
程序完成以下功能:⽗進程fork出⼦進程,⼦進程調⽤exit(1) 終⽌,⽗進程⾃定義SIGCHLD信號的處理函數,在其中調⽤wait獲得⼦進程的退出狀態並打印。
用kill -l指令查看17號信號:
3.子進程的異步等待方式
(1)異步回收僵屍進程:
fork()之后,非阻塞(異步)等待子進程(回收僵屍)。
fork()之后,子進程和父進程分叉執行,僵屍進程的產生是因為父進程沒有給子進程“收屍”造成的,又可以根據危害程度分為下述兩類:
總體來說:當子進程結束之后,但父進程未結束之前,子進程將成為僵屍進程。
1)當子進程結束之后,但父進程未結束之前,子進程將成為僵屍進程,父進程結束后僵屍被init進程回收。
2)如果子進程結束了,但是父進程始終沒有結束,那么這個僵屍將一直存在,而且隨着exec,僵屍越來越多。
(2)實現代碼
1 #include<stdio.h>
2 #include<stdlib.h> 3 #include<signal.h> 4 #include<unistd.h> 5 #include<sys/types.h> 6 #include<sys/wait.h> 7 void catchSig(int sig) 8 { 9 printf("father is catching,child is quit\n"); 10 //以非阻塞方式等待所有異常退出的子進程 11 pid_t id; 12 while((id = waitpid(-1,NULL,WNOHANG)) > 0) 13 { 14 printf("wait child success:%d\n",id); 15 } 16 } 17 int main() 18 { 19 signal(SIGCHLD,catchSig); 20 pid_t pid1 = fork(); 21 if(pid1 == 0)//child1 22 { 23 printf("child1:my pid is %d\n",getpid()); 24 exit(-1);//子進程1異常終止 25 } 26 pid_t pid2 = fork();//child2 27 if(pid2 == 0) 28 { 29 printf("child2:my pid is %d\n",getpid()); 30 exit(-1);//子進程2異常退出 31 } 32 pid_t pid3 = fork();//child3 33 if(pid3 == 0) 34 { 35 printf("child3:my pid is %d\n",getpid());//子進程3正常運行 36 } 37 while(1) 38 { 39 printf("father is waiting...\n"); 40 sleep(1); 41 } 42 return 0;
運行結果: