孤兒進程僵屍進程及其回收


孤兒進程僵屍進程及其回收是進程的經典知識了。

 

什么是孤兒進程?

孤兒進程: 父進程先於子進程結束,則子進程成為孤兒進程,子進程的父進程成為 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 }
wait回收子進程

 

 

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 }
waitpid回收所以子進程

 

 

更加美妙的回收方式是使用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 }
SIGCHLD信號回收子進程

 


免責聲明!

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



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