子進程的異步等待方式


  ⼀個進程在終⽌時會關閉所有⽂件描述符,釋放在⽤戶空間分配的內存,但它的PCB 保留着,內核在其中保存了⼀些信息:如果是正常終⽌則保存着退出狀態,如果是異常終 ⽌則保存着導致該進程終⽌的信號是哪個。這個進程的⽗進程可以調⽤waitwaitpid 獲取這些信息,然后徹底清除掉 這個進程。我們知道⼀個進程的退出狀態可以在Shell ⽤特殊變量$?查看,因為Shell是它的⽗進程,當它終⽌時Shell調⽤waitwaitpid得到它的退出狀態同時徹底清除掉這個進程。

  父進程調用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(),⽗進程等待,並設置獲取退出狀態statuskill殺掉⼦進程。調⽤該宏,查看輸出結果(結果為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; 

運行結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM