什麼是殭屍進程?服務器
首先內核會釋放終止進程(調用了exit系統調用)所使用的全部存儲區,關閉全部打開的文件等,但內核爲每個終止子進程保存了必定量的信息。這些信息至少包括進程ID,進程的終止狀態,以及該進程使用的CPU時間,因此當終止子進程的父進程調用wait或waitpid時就能夠獲得這些信息。數據結構
而殭屍進程就是指:一個進程執行了exit系統調用退出,而其父進程並無爲它收屍(調用wait或waitpid來得到它的結束狀態)的進程。併發
任何一個子進程(init除外)在exit後並不是立刻就消失,而是留下一個稱外殭屍進程的數據結構,等待父進程處理。這是每一個子進程都必需經歷的階段。另外子進程退出的時候會向其父進程發送一個SIGCHLD信號。ide
殭屍進程的目的?函數
設置僵死狀態的目的是維護子進程的信息,以便父進程在之後某個時候獲取。這些信息至少包括進程ID,進程的終止狀態,以及該進程使用的CPU時間,因此當終止子進程的父進程調用wait或waitpid時就能夠獲得這些信息。若是一個進程終止,而該進程有子進程處於殭屍狀態,那麼它的全部殭屍子進程的父進程ID將被重置爲1(init進程)。繼承這些子進程的init進程將清理它們(也就是說init進程將wait它們,從而去除它們的殭屍狀態)。性能
如何避免殭屍進程?this
第一種方法忽略SIGCHLD信號,這經常使用於併發服務器的性能的一個技巧由於併發服務器經常fork不少子進程,子進程終結以後須要服務器進程去wait清理資源。若是將此信號的處理方式設爲忽略,可以讓內核把殭屍子進程轉交給init進程去處理,省去了大量殭屍進程佔用系統資源。spa
殭屍進程處理辦法指針
#include <sys/types.h>
#include <sys/wait.h>code
pid_t wait(int *status);
進程一旦調用了wait,就當即阻塞本身,由wait自動分析是否當前進程的某個子進程已經退出,若是讓它找到了這樣一個已經變成殭屍的子進程,wait就會收集這個子進程的信息,並把它完全銷燬後返回;若是沒有找到這樣一個子進程,wait就會一直阻塞在這裏,直到有一個出現爲止。
參數status用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。但若是咱們對這個子進程是如何死掉的絕不在乎,只想把這個殭屍進程消滅掉,(事實上絕大多數狀況下,咱們都會這樣想),咱們就能夠設定這個參數爲NULL,就象下面這樣:
pid = wait(NULL);
若是成功,wait會返回被收集的子進程的進程ID,若是調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置爲ECHILD。
能夠上述的一些宏判斷子進程的退出狀況:
#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
pid == -1 等待任一子進程。因而在這一功能方面waitpid與wait等效。
pid > 0 等待其進程I D與p i d相等的子進程。
pid == 0 等待其組I D等於調用進程的組I D的任一子進程。換句話說是與調用者進程同在一個組的進程。
pid < -1 等待其組I D等於p i d的絕對值的任一子進程
示例:
如如下代碼會建立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在正運行的子進程尚有未終止時阻塞。