孤兒/僵屍進程——回收子進程
參考博客:https://blog.csdn.net/qq_35396127/article/details/78725915
:https://www.cnblogs.com/Anker/p/3271773.html
在Linux下,子進程可由父進程創建,子進程也可以創建新的進程。但是父進程無法預測子進程的運行狀態,不知道子進程何時會結束。由此會產生孤兒進程與僵屍進程。所以當一個進程結束后,它的父進程需要調用wait(),waitpid()系統調用獲取子進程終止狀態,回收子進程。
什么是孤兒進程與僵屍進程?
孤兒進程:
父進程先於子進程結束,則子進程失去父進程,子進程就會被init 進程收養,子進程就成為孤兒進程。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 5 int main(void) 6 { 7 pid_t pid; 8 int i=0; 9 //創建一個子進程 10 pid = fork(); 11 12 if(pid == -1) 13 { 14 perror("fork"); 15 exit(1); //1:異常退出 0:正常退出 16 } 17 else if(pid>0) 18 { 19 printf("I am parent, my pid=%d\n",getpid()); 20 sleep(4); //父進程運行4秒后結束 21 printf("---------parent going to die-----------\n"); 22 23 } 24 else 25 { 26 while(i<80) 27 { 28 //待父進程結束后會被init收養 29 printf("I am child, pid = %d, parentpid = %d\n",getpid(),getppid()); 30 sleep(1); 31 i++; 32 } 33 } 34 return 0; 35 }
結果:
有的Ubuntu版本,會設置user init 進程專門處理孤兒進程
僵屍進程:
子進程終止,父進程未回收子進程的資源PCB,使其變成僵屍進程。
測試程序:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 6 int main(){ 7 8 pid_t pid; 9 pid = fork(); 10 if(pid < 0) 11 { 12 perror("fork error:"); 13 exit(1); 14 } 15 else if (pid == 0) 16 { 17 printf("I am child, I am exiting.\n"); 18 exit(0); 19 } 20 21 printf("I am parent,I will sleep 2s\n"); 22 //等待子進程先退出 23 sleep(2); 24 //輸出進程信息 25 system("ps -o pid,ppid,state,tty,command"); 26 printf("father process exiting\n"); 27 28 return 0; 29 } 30 31 源自:https://www.cnblogs.com/Anker/p/3271773.html
結果:
孤兒進程與僵屍進程的危害
在Linux中,每個進程退出時,內核會釋放該進程所有資源,包括打開的文件,占用的內存等,但仍為之保留一定信息,包括:進程ID,退出狀態,運行時間等。直到父進程通過wait/waitpid來取時,才會釋放。因此,只要進程一直調用wait與waitpid,進程占用的資源就不會釋放,進程號也不會釋放,由於系統能使用的進程號是有限的,就可能因為大量僵屍進程占用進程號而不能產生新進程。當系統中產生大量僵屍進程時,應該把產生僵屍進程的父進程給殺死掉。可以通過kill發送SIGTERM或者SIGKILL信號,之后僵屍進程會因為沒了父進程變成孤兒,被init收養再釋放。
對於孤兒進程,會被init進程收養,而且init進程會循環地wait()它收養的子進程。所以孤兒進程並無危害。
通過信號機制解決僵屍進程
子進程退出時會向父進程發送SIGCHLD信號,父進程調用信號處理函數,進而調用wait處理僵屍進程。測試程序:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 #include <signal.h> 6 7 static void handlefunc(int sig) 8 { 9 pid_t pid; 10 int stat; 11 12 //處理僵屍進程 13 //waitpid(-1:回收任一子進程,子進程結束狀態,不阻塞父進程) 14 //waitpid成功返回子進程pid 15 while((pid = waitpid(-1, &stat, WNOHANG))>0) 16 printf("child %d terminated. \n",pid); 17 } 18 19 int main() 20 { 21 pid_t pid; 22 //創建signal,捕捉子進程退出信號 23 signal(SIGCHLD,handlefunc); 24 pid = fork(); 25 if(pid<0) 26 { 27 perror("fork error:"); 28 exit(1); 29 } 30 else if(pid == 0) 31 { 32 printf("I am chid, pid=%d. I exiting\n",getpid()); 33 exit(0); 34 } 35 printf("I am parent. I sleep 3S\n"); 36 //等待子進程退出 37 sleep(3); 38 //輸出進程信息 39 system("ps -o pid,ppid,state,tty,command"); 40 printf("parent exiting"); 41 42 return 0; 43 44 }
結果:僵屍進程消失
signal(SIGCLD,SIG_IGN);
因為並發服務器常常fork很多子進程,子進程終結之后需要服務器進程去wait清理資源。如果將此信號的處理方式設為忽略,可讓內核把僵屍子進程轉交給init進程去處理,省去了大量僵屍進程占用系統資源。
詳見:https://blog.csdn.net/u012317833/article/details/39253793