2010-03-04 163 views
16

如何在不讓父進程等待子進程死亡的情況下追蹤子進程的死亡?追蹤子進程的死亡

我正在嘗試一個客戶端 - 服務器場景,其中服務器接受來自客戶端的連接,併爲其接受的每個連接分配一個新進程。

我忽略了SIGCHLD信號來防止殭屍創建。

signal(SIGCHLD, SIG_IGN); 
while(1) 
{ 
    accept(); 
    clients++; 
    if(fork() ==0) 
    { 
    childfunction(); 
    clients--; 
    } 
    else 
    { 
    } 
} 

在上述情況下的問題是,如果子進程得到的childfunction()功能被殺,全局變量clients是沒有得到遞減。

注:我找不使用SIGCHLD信號的解決方案......如果可能的話

+0

你可以在SIGCHLD – tristan 2010-03-04 08:40:49

+0

的信號處理程序中做些什麼我已經提到過... SIGCHLD信號被忽略..? – codingfreak 2010-03-04 08:45:45

+7

+1的瘋狂戲劇題目和開頭句子。 D: – 2010-03-04 09:22:48

回答

22

通常你寫SIGCHLD處理程序,其對PID -1呼籲waitpid()。你可以使用它的返回值來確定哪個pid死掉了。例如:

void my_sigchld_handler(int sig) 
{ 
    pid_t p; 
    int status; 

    while ((p=waitpid(-1, &status, WNOHANG)) != -1) 
    { 
     /* Handle the death of pid p */ 
    } 
} 

/* It's better to use sigaction() over signal(). You won't run into the 
* issue where BSD signal() acts one way and Linux or SysV acts another. */ 

struct sigaction sa; 

memset(&sa, 0, sizeof(sa)); 
sa.sa_handler = my_sigchld_handler; 

sigaction(SIGCHLD, &sa, NULL); 

另外,您可以撥打waitpid(pid, &status, 0)與指定的子進程ID,並同步等待它死了。或者使用WNOHANG檢查其狀態而不阻塞。

+0

但在我的例子中,SIGCHLD是IGNORED? – codingfreak 2010-03-04 09:54:27

+1

@codingfreak我建議你可能想重新評估一下。你不需要忽略它來避免殭屍。當你waitpid()時,「殭屍」會消失。 – asveikau 2010-03-04 09:58:16

+0

@asveikau - 我想創建一個守護進程..並使用SIGCHLD處理程序破壞了應用程序的穩定性.. 所有的時間父進程應該在SIGCHLD處理程序中等待下來.. – codingfreak 2010-03-04 10:06:49

2

你不想要一個殭屍。如果子進程死亡,並且父進程仍處於運行狀態,但從不發出調用來獲取狀態,則系統不會釋放與子進程相關的資源,並且在進程表中留下殭屍/停止進程。

試着改變你的SIGCHLD處理程序更接近於以下內容:


void chld_handler(int sig) { 
    pid_t p; 
    int status; 

    /* loop as long as there are children to process */ 
    while (1) { 

     /* retrieve child process ID (if any) */ 
     p = waitpid(-1, &status, WNOHANG); 

     /* check for conditions causing the loop to terminate */ 
     if (p == -1) { 
      /* continue on interruption (EINTR) */ 
      if (errno == EINTR) { 
       continue; 
      } 
      /* break on anything else (EINVAL or ECHILD according to manpage) */ 
      break; 
     } 
     else if (p == 0) { 
      /* no more children to process, so break */ 
      break; 
     } 

     /* valid child process ID retrieved, process accordingly */ 
     ... 
    } 
} 

使用sigprocmask()信號處理程序的執行過程中,您可以選擇性地屏蔽/塊額外SIGCHLD信號。當信號處理程序完成時,屏蔽掩碼必須返回到其原始值。

如果您確實不想使用SIGCHLD處理程序,則可以嘗試將子處理循環添加到定期調用的某個位置並輪詢已終止的子項。

+0

按照您所說的進行更改後..我沒有看到任何創建的殭屍..讓我看看它如何在HTTP服務器守護進程中運行...... – codingfreak 2010-03-05 08:49:21

+0

我不認爲在守護進程中使用此代碼會有任何主要問題。 – jschmier 2010-03-08 16:30:39

0

變量'clients'在fork()之後位於不同的進程地址空間中,當您在子級中減小變量時,這不會影響父級中的值。我認爲你需要處理SIGCHLD來正確處理計數。

4

到目前爲止,沒有任何解決方案提供了一種不使用SIGCHLD作爲問題請求的方法。這是因爲在this answer概述使用poll另一種方法的實現(這也解釋了爲什麼你應該避免使用在這樣的情況下SIGCHLD):

確保您從您創建的每個子進程有一個管/ 。它可以是他們的stdin/stdout/stderr或者只是一個額外的虛擬fd。當子進程終止時,管道的末端將被關閉,並且主事件循環將檢測該文件描述符上的活動。從它關閉的事實中,你認識到子進程已經死亡,並且調用waitpid來獲得殭屍。

(注意:我省略了一些最佳實踐,如錯誤檢查和清理爲了簡潔文件描述符)

/** 
* Specifies the maximum number of clients to keep track of. 
*/ 
#define MAX_CLIENT_COUNT 1000 

/** 
* Tracks clients by storing their process IDs and pipe file descriptors. 
*/ 
struct process_table { 
    pid_t clientpids[MAX_CLIENT_COUNT]; 
    struct pollfd clientfds[MAX_CLIENT_COUNT]; 
} PT; 

/** 
* Initializes the process table. -1 means the entry in the table is available. 
*/ 
void initialize_table() { 
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
     PT.clientfds[i].fd = -1; 
    } 
} 

/** 
* Returns the index of the next available entry in the process table. 
*/ 
int get_next_available_entry() { 
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
     if (PT.clientfds[i].fd == -1) { 
      return i; 
     } 
    } 
    return -1; 
} 

/** 
* Adds information about a new client to the process table. 
*/ 
void add_process_to_table(int i, pid_t pid, int fd) { 
    PT.clientpids[i] = pid; 
    PT.clientfds[i].fd = fd; 
} 

/** 
* Removes information about a client from the process table. 
*/ 
void remove_process_from_table(int i) { 
    PT.clientfds[i].fd = -1; 
} 

/** 
* Cleans up any dead child processes from the process table. 
*/ 
void reap_zombie_processes() { 
    int p = poll(PT.clientfds, MAX_CLIENT_COUNT, 0); 

    if (p > 0) { 
     for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
      /* Has the pipe closed? */ 
      if ((PT.clientfds[i].revents & POLLHUP) != 0) { 
       // printf("[%d] done\n", PT.clientpids[i]); 
       waitpid(PT.clientpids[i], NULL, 0); 
       remove_process_from_table(i); 
      } 
     } 
    } 
} 

/** 
* Simulates waiting for a new client to connect. 
*/ 
void accept() { 
    sleep((rand() % 4) + 1); 
} 

/** 
* Simulates useful work being done by the child process, then exiting. 
*/ 
void childfunction() { 
    sleep((rand() % 10) + 1); 
    exit(0); 
} 

/** 
* Main program 
*/ 
int main() { 
    /* Initialize the process table */ 
    initialize_table(); 

    while (1) { 
     accept(); 

     /* Create the pipe */ 
     int p[2]; 
     pipe(p); 

     /* Fork off a child process. */ 
     pid_t cpid = fork(); 

     if (cpid == 0) { 
      /* Child process */ 
      close(p[0]); 
      childfunction(); 
     } 
     else { 
      /* Parent process */ 
      close(p[1]); 
      int i = get_next_available_entry(); 
      add_process_to_table(i, cpid, p[0]); 
      // printf("[%d] started\n", cpid); 
      reap_zombie_processes(); 
     } 
    } 

    return 0; 
} 

這裏是從與printf語句註釋掉運行該程序的一些示例輸出:

[31066] started 
[31067] started 
[31068] started 
[31069] started 
[31066] done 
[31070] started 
[31067] done 
[31068] done 
[31071] started 
[31069] done 
[31072] started 
[31070] done 
[31073] started 
[31074] started 
[31072] done 
[31075] started 
[31071] done 
[31074] done 
[31081] started 
[31075] done