fork子進程僵屍問題及解決方案


額,原來用 c 寫 cgi 的時候用過 fork 。那時候 cgi 的生命很短,所以遇到的問題壓根沒出現過。這次也是更加深入的對 fork 機制進行了一下了解。

參考這里的文檔:http://ju.outofmemory.cn/entry/98971

 

1. 我們都是小僵屍

下面是這次應用的一個 fork 的例子。主進程繼續進行數據處理,一定時間后用下面的代碼開新進程,並將處理結果發送出去。看起來似乎沒什么問題,但是,一定時間后,fork 總是failed。。。用 ps aux 查看進程列表,列表被僵屍進程占滿了!!

...
        if ((pid=fork())<0)
        {
            printf("fork failed \n");
        } 
        else if ( 0 == pid)
        {
            test_result.error_1 = (double)error_1 / test_result.total * 10000;
            test_result.error_2 = (double)error_2 / test_result.total * 10000;
            test_result.error_4 = (double)error_4 / test_result.total * 10000;
            test_result.error_6 = (double)error_6 / test_result.total * 10000;
            
            n = sendto(sock, &test_result, sizeof(test_result), 0, 
                        (struct sockaddr *)&addr_to, sizeof(addr_to));    
            exit(0);
        }
...

好吧,難道 exit(0) 之后,它還在留戀什么?所以,又回來繼續摸索什么是最有可能性。

 

2. 小僵屍找爸爸

好吧,最后目光落到了這里 SIGCHLD。通常,父進程不會始終處於等待狀態,它還需要執行其它代碼,因此“等待”的工作會使用信號機制來完成;或者說,子進程在處理完任務以后,內核會發送一個SIGCHLD信號。。。對,這孩子還要向它爸做最后的告別。

那孩它爸能做些什么呢?

1)孩它爸也不在了。

2)孩它爸在等待關於這孩子的消息,完成告別儀式,各安天命。

3)孩它爸發布了告示“我沒有兒子,你們都別來煩我”。

恩,第一個這是肯定的,因為fork機制天生的屬性,如果孩它爸不在了,它會被送給init來監管撫養。。。init是個負責任的好爸爸,他會處理好一切。那接下來兩個是怎么回事呢?

 

3.小僵屍和爸爸最后的告別

用於監督子進程的完成情況,fork配套使用的有waitpid()和wait()兩個函數。waitpid()的功能和wait()類似,但waitpid()提供了額外的選項(wait(NULL)等價於waitpid(-1, NULL, 0))。如,wait()函數是阻塞的,而waitpid()提供了WNOHANG選項,調用后會立刻返回,可根據返回值判斷等待結果。

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <malloc.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <unistd.h>
#include <fcntl.h>

#include <signal.h>

void signal_handler(int signo) {
    if (signo == SIGCHLD) {
        pid_t pid;
        while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
            printf("SIGCHLD pid %d\n", pid);
        }
    }
}
void mysleep(int sec) {
    time_t start = time(NULL), elapsed = 0;
    while (elapsed < sec) {
        sleep(sec - elapsed);
        elapsed = time(NULL) - start;
    }
}
int main(int argc, char **argv) {
    signal(SIGCHLD, signal_handler);
    while (1) {
        pid_t pid = fork();
        if (pid > 0) {
            // parent process
            mysleep(5);
        } else if (pid == 0) {
            // child process
            printf("child pid %d\n", getpid());
            return 0;
        } else {
            fprintf(stderr, "fork error\n");
            return 2;
        }
    }
}

比如上面的代碼。我們在信號處理中使用了一個循環體,不斷調用waitpid(),直到失敗為止。那是因為在系統繁忙時,信號可能會被合並,即兩個子進程結束只會發送一次SIGCHLD信號,如果只wait()一次,就會產生僵屍進程。(由於默認的sleep()函數會在接收到信號時立即返回,因此為了方便演示,這里定義了mysleep()函數)。

 

4.做個不負責任的爸爸

很簡單,用下面這句話,告訴操作系統,你不關心所有那些子進程的死活。

signal(SIGCHLD, SIG_IGN);

但是,這樣做的問題是,有些BSD系統不支持這樣的用法。所以,更為廣泛的,還是使用wait。

 

perl中

下面是perl中使用fork-waitpid的代碼,也是也不是我寫的:

#!/usr/bin/perl
sub REAPER { my $pid;
while (($pid = waitpid(-1, WNOHANG)) > 0) { print "SIGCHLD pid $pid\n"; } }
$SIG{CHLD}
= \&REAPER; my $pid = fork(); if ($pid > 0) { print "[Parent] child pid $pid\n"; sleep(10); } elsif ($pid == 0) { print "[Child] pid $$\n"; exit; }

這個和c基本一樣的。如果想要忽略SIGCHLD,可使用$SIG{CHLD} = 'IGNORE'


免責聲明!

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



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