2011-12-06 33 views
0

我有一個程序,產生大量的孩子,並運行很長一段時間。該程序包含一個SIGCHLD處理程序以收穫不存在的進程。偶爾,這個程序會凍結。我相信pstack表明了一種死鎖情況。這是對這個輸出的正確解釋嗎?尋找死鎖場景的指導

10533: ./asyncsignalhandler 
ff3954e4 lwp_park (0, 0, 0) 
ff391bbc slow_lock (ff341688, ff350000, 0, 0, 0, 0) + 58 
ff2c45c8 localtime_r (ffbfe7a0, 0, 0, 0, 0, 0) + 24 
ff2ba39c __posix_ctime_r (ffbfe7a0, ffbfe80e, ffbfe7a0, 0, 0, 0) + c 
00010bd8 gettimestamp (ffbfe80e, ffbfe828, 40, 0, 0, 0) + 18 
00010c50 sig_chld (12, 0, ffbfe9f0, 0, 0, 0) + 30 
ff3956fc __sighndlr (12, 0, ffbfe9f0, 10c20, 0, 0) + c 
ff38f354 call_user_handler (12, 0, ffbfe9f0, 0, 0, 0) + 234 
ff38f504 sigacthandler (12, 0, ffbfe9f0, 0, 0, 0) + 64 
--- called from signal handler with signal 18 (SIGCLD) --- 
ff391c14 pthread_mutex_lock (20fc8, 0, 0, 0, 0, 0) + 48 
ff2bcdec getenv (ff32a9ac, 770d0, 0, 0, 0, 0) + 1c 
ff2c6f40 getsystemTZ (0, 79268, 0, 0, 0, 0) + 14 
ff2c4da8 ltzset_u (4ede65ba, 0, 0, 0, 0, 0) + 14 
ff2c45d0 localtime_r (ffbff378, 0, 0, 0, 0, 0) + 2c 
ff2ba39c __posix_ctime_r (ffbff378, ffbff402, ffbff378, ff33e000, 0, 0) + c 
00010bd8 gettimestamp (ffbff402, ffbff402, 2925, 29a7, 79c38, 10b54) + 18 
00010ae0 main  (1, ffbff4ac, ffbff4b4, 20c00, 0, 0) + 190 
00010928 _start (0, 0, 0, 0, 0, 0) + 108 

我真的不喜歡自己的C編碼器,並不熟悉語言的細微差別。我特別在程序中使用ctime(_r)的重入版本。爲什麼這仍然是僵局?

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#include <time.h> 

// import pid_t type 
#include <sys/types.h> 

// import _exit function 
#include <unistd.h> 

// import WNOHANG definition 
#include <sys/wait.h> 

// import errno variable 
#include <errno.h> 

// header for signal functions 
#include <signal.h> 

// function prototypes 
void sig_chld(int); 
char * gettimestamp(char *); 

// begin 
int main(int argc, char **argv) 
{ 
    time_t sleepstart; 
    time_t sleepcheck; 
    pid_t childpid; 
    int i; 
    unsigned int sleeptime; 
    char sleepcommand[20]; 
    char ctime_buf[26]; 

    struct sigaction act; 

    /* set stdout to line buffered for logging purposes */ 
    setvbuf(stdout, NULL, _IOLBF, BUFSIZ); 

    /* Assign sig_chld as our SIGCHLD handler */ 
    act.sa_handler = sig_chld; 

    /* We don't want to block any other signals */ 
    sigemptyset(&act.sa_mask); 

    /* 
    * We're only interested in children that have terminated, not ones 
    * which have been stopped (eg user pressing control-Z at terminal) 
    */ 
    act.sa_flags = SA_NOCLDSTOP; 

    /* Make these values effective. */ 
    if (sigaction(SIGCHLD, &act, NULL) < 0) 
    { 
     printf("sigaction failed\n"); 
     return 1; 
    } 

    while (1) { 
     for (i = 0; i < 20; i++) { 
     /* fork/exec child program        */ 
     childpid = fork(); 
     if (childpid == 0) // child 
     { 
      //sleeptime = 30 + i; 
      sprintf(sleepcommand, "sleep %d", i); 

      printf("\t[%s][%d] Executing /bin/sh -c %s\n", gettimestamp(ctime_buf), getpid(), sleepcommand); 

      execl("/bin/sh", "/bin/sh", "-c", sleepcommand, NULL); 

      // only executed if exec fails 
      printf("[%s][%d] Error executing program, errno: %d\n", gettimestamp(ctime_buf), getpid(), errno); 
      _exit(1); 
     } 
     else if (childpid < 0) // error 
     { 
      printf("[%s][%d] Error forking, errno: %d\n", gettimestamp(ctime_buf), getpid(), errno); 
     } 
     else // parent 
     { 
      printf("[%s][%d] Spawned child, pid: %d\n", gettimestamp(ctime_buf), getpid(), childpid); 
     } 
     } 

     // sleep is interrupted by SIGCHLD, so we can't simply sleep(5) 
     printf("[%s][%d] Sleeping for 5 seconds\n", gettimestamp(ctime_buf), getpid()); 
     time(&sleepstart); 
     while (1) { 
     time(&sleepcheck); 
     if (difftime(sleepcheck, sleepstart) < 5) { 
      sleep(1); 
     } else { 
      break; 
     } 
     } 
    } 


    return(0); 
} 

char * gettimestamp(char *ctime_buf) 
{ 
    time_t now; 

    time(&now); 

    // format the timestamp and chomp the newline 
    ctime_r(&now, ctime_buf); 
    ctime_buf[strlen(ctime_buf) - 1] = '\0'; 

    return ctime_buf; 
} 

/* 
* The signal handler function -- only gets called when a SIGCHLD 
* is received, ie when a child terminates. 
*/ 
void sig_chld(int signo) 
{ 
    pid_t childpid; 
    int childexitstatus; 
    char ctime_buf[26]; 

    while (1) { 
     childpid = waitpid(-1, &childexitstatus, WNOHANG); 
     if (childpid > 0) 
     printf("[%s][%d] Reaped child, pid: %d, exitstatus: %d\n", gettimestamp(ctime_buf), getpid(), childpid, WEXITSTATUS(childexitstatus)); 
     else 
     return; 
    } 
} 

我在Solaris 9環境中運行。該方案是與Sun講習班6更新2編譯的C 5.3補丁111679-15 2009/09/10使用的語法如下:

cc -o asyncsignalhandler asyncsignalhandler.c -mt -D_POSIX_PTHREAD_SEMANTICS 

是否有程序中的漏洞?是否有更好的方法來處理來自信號處理程序的日誌記錄(帶時間戳)?

+0

'printf'不是異步信號安全的,所以你不應該在信號處理程序中使用它......使用'write'來代替使用'fileno(stdout)'。 – Jason

+0

雖然我會承認printf()可能是一個問題,但這不是堆棧跟蹤告訴我的。你如何到達printf()作爲死鎖的源頭? – user255205

回答

3

您撥打的不是異步信號安全(見UNIX規範的section 2.4.3)從信號處理程序中的功能 - 在這種情況下,ctime_r()printf()(僵局似乎發生了由於使用的鎖在您顯示的堆棧跟蹤中輸入ctime_r())。這些函數可能會佔用鎖,並且由於可能會在任何時候調用信號處理程序,所以鎖可能已被佔用,從而導致死鎖。

一般來說,在一個信號處理程序中,你應該這樣做的是爲主線程做一個筆記以便稍後檢查。例如,您可以將write()(這是一個異步信號安全函數)創建爲pipe()創建的文件描述符,並讓您的主循環(或另一個線程)執行select循環,以等待某些數據顯示在該管道上。

還要注意線程安全異步信號安全不一樣。 ctime_r是線程安全的 - 它會鎖定線程以確保線程不會互相踩在一起,並且它使用傳入的緩衝區而不是靜態緩衝區。但它不是異步信號安全的,因爲它不能容忍在執行過程中的任意點被重複調用。

+0

我沒有調用localtime()。我調用ctime_r(),然後每個堆棧跟蹤調用localtime_r()。 – user255205

+0

啊,哎呀。但同樣的事情 - 'ctime_r()'沒有列爲異步信號安全。 – bdonlan

+0

ctime_r()明確重入。我認爲這是線程安全的最高順序?重入並不會使其成爲異步信號安全的? – user255205