前言
什么是僵尸行程?
當子行程比父行程先結束,而父行程又沒有回收子行程,釋放子行程占用的資源,此時子行程將成為一個僵尸行程,直到父行程結束,僵尸行程的資源才會被釋放,
僵尸行程產生的原因
父子行程運行是一個異步的程序,為了使得父行程在子行程退出時,仍可以獲得子行程的狀態資訊,Unix不會直接釋放行程的所有資源,當子行程退出時,內核釋放子行程的用戶資源,包括打開的檔案,占用的記憶體等,但是仍然為其保留一定的資訊,包括行程號(Process ID),退出狀態(Termination Status),運行時間(Runtime)等,
這樣,父行程就可以通過呼叫wait/waitpid來獲取子行程的狀態資訊同時釋放子行程資源,但如果不呼叫wait/waitpid,系統資源將被占用,可能導致系統無法產生新的行程,
正文
正如上文提到的,可以使用wait/waitpid來將僵尸行程資源回收
可以在父行程中呼叫wait/waitpid,或者捕獲SIGCHLD再呼叫wait/waitpid
需要注意:
- 呼叫wait/waitpid函式時,如果沒有僵尸子行程,但仍有子行程,父行程會阻塞,直到有子行程死亡,變成僵尸行程
- wait/waitpid 一次回傳一個子行程的狀態資訊,有多個行程需要呼叫多次
- 多執行緒阻塞在wait/waitpid時,僅有一個執行緒會回傳子行程狀態資訊
大多時候我們都希望父子行程異步作業,而不是阻塞等待子行程結束,下面是捕獲SIGCHLD的示例:
void handle(int sig){
printf("catch signal %d\n",sig);
wait(NULL); // 可以傳入結構體,并呼叫不同函式獲取特定的資訊,具體看手冊
printf("Child process exit\n");
}
int main(){
int pid = fork();
if(pid > 0){
signal(SIGCHLD,handle);
// 父行程作業
}else if(pid == 0){
// 子行程作業
}
}
但是這里忽略了SIGCHLD信號的產生條件,SIGCHLD信號在子行程終止,掛起,被喚醒時會向父行程發出
如果子行程接收到掛起信號,父行程進入handle處理函式,父行程將阻塞在wait/waitpid
如果我們不關心子行程的執行情況,最簡單直接的方法就是忽略SIGCHLD信號,讓子行程結束后由init接管,init行程會釋放掉所有僵尸行程的資源,
signal(SIGCHLD,SIG_IGN);
思考一下,忽略SIGCHLD信號是否對程式有影響,SIGCHLD信號產生條件:終止,掛起,喚醒
其中掛起信號(SIGSTOP),SIGKILL信號無法捕獲,所以忽略SIGCHLD信號對這兩個沒影響
但是假如我們希望捕獲SIGCONT信號,生成日志,那么我們就不能簡單忽略SIGCHLD信號
個人認為更科學的做法是
使用sigaction,并設定sa_flags標志位為SA_NOCLDWAIT
這樣,當子行程終止(terminate)時,子行程不會被設定為僵尸行程
struct sigaction sa;
memset(&sa,0,sizeof(sa));
sa.sa_handler = SIG_DFL; // 也可以自定義handler函式
sa.sa_flags = SA_NOCLDWAIT;
sigaction(SIGCHLD,&sa,NULL);
tips: 當使用 -std=c99 編譯時,會找不到sigaction結構體,解決方法:添加頭檔案<bits/sigaction.h>
另外也有別的技巧,呼叫兩次fork函式,然后子行程退出,孫行程由系統接管
int pid;
if ((pid = fork()) == 0){
if ((pid = fork()) > 0)
exit(0); // 子行程退出
else{
// 孫行程作業
}
}
else{
// 父行程作業
}
僅做記錄,最好的方法還是使用sigaction

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/393978.html
標籤:其他
