linux下的殭屍進程處理SIGCHLD信號

什麼是殭屍進程

首先內核會釋放終止進程(調用了exit系統調用)所使用的所有存儲區,關閉所有打開的文件等,但內核爲每一個終止子進程保存了一定量的信息。這些信息至少包括進程ID,進程的終止狀態,以及該進程使用的CPU時間,所以當終止子進程的父進程調用wait或waitpid時就可以得到這些信息。

而殭屍進程就是指:一個進程執行了exit系統調用退出,而其父進程並沒有爲它收屍(調用wait或waitpid來獲得它的結束狀態)的進程。

任何一個子進程(init除外)在exit後並非馬上就消失,而是留下一個稱外殭屍進程的數據結構,等待父進程處理。這是每個子進程都必需經歷的階段。另外子進程退出的時候會向其父進程發送一個SIGCHLD信號。

 

殭屍進程的目的?

設置僵死狀態的目的是維護子進程的信息,以便父進程在以後某個時候獲取。這些信息至少包括進程ID,進程的終止狀態,以及該進程使用的CPU時間,所以當終止子進程的父進程調用wait或waitpid時就可以得到這些信息。如果一個進程終止,而該進程有子進程處於殭屍狀態,那麼它的所有殭屍子進程的父進程ID將被重置爲1(init進程)。繼承這些子進程的init進程將清理它們(也就是說init進程將wait它們,從而去除它們的殭屍狀態)。

 

如何避免殭屍進程?

  1. 通過signal(SIGCHLD, SIG_IGN)通知內核對子進程的結束不關心,由內核回收。如果不想讓父進程掛起,可以在父進程中加入一條語句:signal(SIGCHLD,SIG_IGN);表示父進程忽略SIGCHLD信號,該信號是子進程退出的時候向父進程發送的。
  2. 父進程調用wait/waitpid等函數等待子進程結束,如果尚無子進程退出wait會導致父進程阻塞waitpid可以通過傳遞WNOHANG使父進程不阻塞立即返回
  3. 如果父進程很忙可以用signal註冊信號處理函數,在信號處理函數調用wait/waitpid等待子進程退出。
  4. 通過兩次調用fork。父進程首先調用fork創建一個子進程然後waitpid等待子進程退出,子進程再fork一個孫進程後退出。這樣子進程退出後會被父進程等待回收,而對於孫子進程其父進程已經退出所以孫進程成爲一個孤兒進程,孤兒進程由init進程接管,孫進程結束後,init會等待回收。

第一種方法忽略SIGCHLD信號,這常用於併發服務器的性能的一個技巧因爲併發服務器常常fork很多子進程,子進程終結之後需要服務器進程去wait清理資源。如果將此信號的處理方式設爲忽略,可讓內核把殭屍子進程轉交給init進程去處理,省去了大量殭屍進程佔用系統資源。

 

殭屍進程處理辦法

1 wait()函數

#include <sys/types.h> 
#include <sys/wait.h>

pid_t wait(int *status);

進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成殭屍的子進程,wait就會收集這個子進程的信息,並把它徹底銷燬後返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這裏,直到有一個出現爲止。 
參數status用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。但如果我們對這個子進程是如何死掉的毫不在意,只想把這個殭屍進程消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個參數爲NULL,就象下面這樣:

  pid = wait(NULL);

如果成功,wait會返回被收集的子進程的進程ID,如果調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置爲ECHILD。

  • wait系統調用會使父進程暫停執行,直到它的一個子進程結束爲止。
  • 返回的是子進程的PID,它通常是結束的子進程
  • 狀態信息允許父進程判定子進程的退出狀態,即從子進程的main函數返回的值或子進程中exit語句的退出碼。
  • 如果status不是一個空指針,狀態信息將被寫入它指向的位置

可以上述的一些宏判斷子進程的退出情況:

QQ截圖20130713110230

 

2 waitpid()函數

#include <sys/types.h> 
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

參數:

status:如果不是空,會把狀態信息寫到它指向的位置,與wait一樣

options:允許改變waitpid的行爲,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者的執行掛起

The value of options is an OR of zero or more  of  the  following  con- 
stants:

WNOHANG     return immediately if no child has exited.

WUNTRACED   also  return  if  a  child  has stopped (but not traced via 
            ptrace(2)).  Status for traced children which have  stopped 
            is provided even if this option is not specified.

WCONTINUED (since Linux 2.6.10) 
            also return if a stopped child has been resumed by delivery 
            of SIGCONT.

返回值:如果成功返回等待子進程的ID,失敗返回-1

對於waitpid的p i d參數的解釋與其值有關:

pid == -1 等待任一子進程。於是在這一功能方面waitpid與wait等效。

pid > 0 等待其進程I D與p i d相等的子進程。

pid == 0 等待其組I D等於調用進程的組I D的任一子進程。換句話說是與調用者進程同在一個組的進程。

pid < -1 等待其組I D等於p i d的絕對值的任一子進程

wait與waitpid區別:

  • 在一個子進程終止前, wait 使其調用者阻塞,而waitpid 有一選擇項,可使調用者不阻塞。
  • waitpid並不等待第一個終止的子進程—它有若干個選擇項,可以控制它所等待的特定進程。
  • 實際上wait函數是waitpid函數的一個特例。waitpid(-1, &status, 0);

 

示例:

如以下代碼會創建100個子進程,但是父進程並未等待它們結束,所以在父進程退出前會有100個殭屍進程。

複製代碼
#include <stdio.h>  
#include <unistd.h>  
   
int main() {  
   
  int i;  
  pid_t pid;  
   
  for(i=0; i<100; i++) {  
    pid = fork();  
    if(pid == 0)  
      break;  
  }  
   
  if(pid>0) {  
    printf("press Enter to exit...");  
    getchar();  
  }  
   
  return 0;  
}  
複製代碼

其中一個解決方法即是編寫一個SIGCHLD信號處理程序來調用wait/waitpid來等待子進程返回。

 

複製代碼
#include <stdio.h>  
#include <unistd.h>  
#include <signal.h>  
#include <sys/types.h>  
#include <sys/wait.h>  
   
void wait4children(int signo) {  
   
  int status;  
  wait(&status);  
   
}  
   
int main() {  
   
  int i;  
  pid_t pid;  
   
  signal(SIGCHLD, wait4children);  
   
  for(i=0; i<100; i++) {  
    pid = fork();  
    if(pid == 0)  
      break;  
  }  
   
  if(pid>0) {  
    printf("press Enter to exit...");  
    getchar();  
  }  
   
  return 0;  
}  
複製代碼

但是通過運行程序發現還是會有殭屍進程,而且每次殭屍進程的數量都不定。這是爲什麼呢?其實主要是因爲Linux的信號機制是不排隊的,假如在某一時間段多個子進程退出後都會發出SIGCHLD信號,但父進程來不及一個一個地響應,所以最後父進程實際上只執行了一次信號處理函數。但執行一次信號處理函數只等待一個子進程退出,所以最後會有一些子進程依然是殭屍進程。

雖然這樣但是有一點是明瞭的,就是收到SIGCHLD必然有子進程退出,而我們可以在信號處理函數裏循環調用waitpid函數來等待所有的退出的子進程。至於爲什麼不用wait,主要原因是在wait在清理完所有殭屍進程後再次等待會阻塞。

 

所以最佳方案如下:

複製代碼
#include <stdio.h>  
#include <unistd.h>  
#include <signal.h>  
#include <errno.h>  
#include <sys/types.h>  
#include <sys/wait.h>  
   
void wait4children(int signo) {  
  int status;  
  while(waitpid(-1, &status, WNOHANG) > 0);  
}  
   
int main() {  
   
  int i;  
  pid_t pid;  
   
  signal(SIGCHLD, wait4children);  
   
  for(i=0; i<100; i++) {  
    pid = fork();  
    if(pid == 0)  
      break;  
  }  
   
  if(pid>0) {  
    printf("press Enter to exit...");  
    getchar();  
  }  
   
  return 0;  
}  
複製代碼

這裏使用waitpid而不是使用wait的原因在於:我們在一個循環內調用waitpid,以獲取所有已終止子進程的狀態。我們必須指定WNOHANG選項,它告訴waitpid在有尚未終止的子進程在運行時不要阻塞。我們不能在循環內調用wait,因爲沒有辦法防止wait在正運行的子進程尚有未終止時阻塞。

 

 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章