孤兒進程僵屍進程及其回收是進程的經典知識了。
什么是孤兒進程?
孤兒進程: 父進程先於子進程結束,則子進程成為孤兒進程,子進程的父進程成為 init進程,稱為 init 進程領養孤兒進程。
什么是僵屍進程?
僵屍進程: 進程終止,父進程尚未回收,子進程殘留資源(PCB)存放於內核中,變成僵屍(Zombie)進程。
特別注意,僵屍進程是不能使用 kill 命令清除掉的。因為 kill 命令只是用來終止進程的,而僵屍進程已經終止。
孤兒進程有init進程回收,但是僵屍進程要等父進程來回收,如果父進程不回收,僵屍進程會已經死亡得不到回收卻占用進程號,如果大量的產生僵屍進程,將因為沒有可用的進程號而導致系統不能產生新的進程. 此即為僵屍進程的危害,應當避免。
相關函數:
一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的 PCB還保留着,內核在其中保存了一些信息:如果是正常終止則保存着退出狀態,如果是異常終止則保存着導致該進程終止的信號是哪個。
這個進程的父進程可以調用 wait 或 waitpid 獲取這些信息,然后徹底清除掉這個進程。我們知道一個進程的退出狀態可以在 Shell 中用特殊變量$?查看,因為 Shell 是它的父進程,當它終止時 Shell 調用 wait 或 waitpid 得到它的退出狀態同時徹底清除掉這個進程。
wait 函數
pid_t wait(int *status);
父進程調用 wait 函數可以回收子進程終止信息。該函數有三個功能:
① 阻塞等待子進程退出 ② 回收子進程殘留資源 ③ 獲取子進程結束狀態(退出原因)。
函數返回值:成功:清理掉的子進程 ID;失敗:-1 (沒有子進程)
當進程終止時,操作系統的隱式回收機制會:1.關閉所有文件描述符 2. 釋放用戶空間分配的內存。內核的 PCB 仍存在。其中保存該進程的退出狀態。(正常終止→退出值;異常終止→終止信號)
status判斷終止原因
可使用 wait 函數傳出參數 status 來保存進程的退出狀態。借助宏函數來進一步判斷進
程終止的具體原因。宏函數可分為如下三組:
1. WIFEXITED(status) 為非 0 → 進程正常結束
WEXITSTATUS(status) 如上宏為真,使用此宏 → 獲取進程退出狀態 (exit 的參數)
2. WIFSIGNALED(status) 為非 0 → 進程異常終止
WTERMSIG(status) 如上宏為真,使用此宏 → 取得使進程終止的那個信號的編號。
*3. WIFSTOPPED(status) 為非 0 → 進程處於暫停狀態
WSTOPSIG(status) 如上宏為真,使用此宏 → 取得使進程暫停的那個信號的編號。
WIFCONTINUED(status) 為真 → 進程暫停后已經繼續運行

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<sys/wait.h> 5 6 int main() 7 { 8 pid_t pid,wpid; 9 int status; 10 11 pid=fork(); 12 if (pid==0) { //子進程 13 printf("i am child, pid is %d, sleep 10s ",pid); 14 sleep(20); 15 printf("i am diying"); 16 return 73; 17 } else if (pid>0) { //父進程 18 //wait函數會阻塞,如果沒有一個子進程死亡 19 //wpid=wait(NULL); //不關心子進程如何死亡 20 wpid=wait(&status); //關心子進程死亡,寫到status里面 21 if (wpid==-1) { 22 perror("wait error"); 23 exit(1); 24 } 25 26 if (WIFEXITED(status)) //為真,子進程正常終止 27 printf("child exit with %d\n",WEXITSTATUS(status)); 28 29 if (WIFSIGNALED(status)) //為假,子進程被信號終止 30 printf("child kill with signal %d\n",WTERMSIG(status)); 31 32 printf("wait successful, %d died",pid); 33 34 } else { 35 perror("fork error"); //fork出錯了 36 return 1; 37 } 38 return 0; 39 }
waitpid 函數
pid_t waitpid(pid_t pid, int *status, in options); 作用同 wait,但可指定 pid 進程清理,可以不阻塞。
特殊參數和返回情況:
參數 pid:> 0 回收指定 ID 的子進程 -1 回 回於 收任意子進程(相當於 wait ) 0 回收和當前調用 waitpid 一個組的所有子進程 < -1 回收指定進程組內的任意子進程
參數status:和wait函數的status一樣,判斷進程終止原因
參options: 0 :(相當於wait)阻塞回收 WBNIOHANG:非阻塞回收(輪詢)
返回值:成功:返回清理掉的子進程 ID;失敗:-1(無子進程)
特殊的返回 0:參 3 為 WNOHANG,且子進程正在運行。
注意:次 一次 wait 或 或 waitpid 調用只能清理一個子進程,清理多個子進程應使用循環while。

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<sys/wait.h> 6 #include<pthread.h> 7 8 int main(int argc,char *argv[]) 9 { 10 int i; 11 pid_t pid,wpid; 12 13 for (i=0;i<5;i++) { 14 pid=fork(); 15 if (pid==0) break; 16 } 17 18 if (i==5) { //父進程 19 20 //這里不斷while循環等待死亡的子進程 21 while ((wpid=waitpid(-1,NULL,WNOHANG))!=-1) { 22 if (wpid>0) { //回收成功 23 printf("wait child %d \n",wpid); 24 } else if (wpid==0) { //回收失敗 25 sleep(1); //sleep一秒之后再嘗試回收 26 continue; 27 } 28 } 29 } else { //5個子進程 30 sleep(i); //子進程sleep以一下 31 printf("I'm child %d ,i am dying",i); 32 } 33 return 0; 34 }
更加美妙的回收方式是使用SIGCHLD信號回收子進程,這樣父進程不阻塞可以干自己的事情:https://www.cnblogs.com/clno1/p/12941316.html

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<unistd.h> 5 #include<signal.h> 6 #include<sys/wait.h> 7 #include<errno.h> 8 #include<pthread.h> 9 10 void sys_err(const char *str) { 11 perror(str); 12 exit(1); 13 } 14 15 void catch_child(int signo) { 16 pid_t wpid; 17 int status; 18 19 //這里的while非常重要,當接受到SIGCHLD信號時候,有多個子進程同時死亡,這時候就需要while來把這段時間的死亡子全部回收 20 while ((wpid=waitpid(-1,&status,0))!=-1) { 21 if (WIFEXITED(status)) 22 printf("-------------catch child id %d, ret=%d \n",wpid,WEXITSTATUS(status)); 23 } 24 return; 25 } 26 27 int main(int argc,char *argv[]) 28 { 29 pid_t pid; 30 int i; 31 32 for (i=0;i<15;i++) 33 if ((pid=fork())==0) //創建15個子進程 34 break; 35 36 if (i==15) { //父進程 37 38 //信號結構體,三個參數重要 39 struct sigaction act; 40 41 act.sa_handler=catch_child; //注冊函數 42 sigemptyset(&act.sa_mask); //在執行期間,sa_mask會替換原mask 43 act.sa_flags=0; //設為0,在該信號處理函數期間,再次收到同樣信號就屏蔽 44 45 sigaction(SIGCHLD,&act,NULL); 46 47 printf("I'm parent, pid=%d \n",getpid()); 48 49 while(1); 50 51 } else { //子進程 52 printf("I'm child pid= %d\n",getpid()); 53 return i; 54 } 55 return 0; 56 }