C 寻找关于死锁场景的指导

C 寻找关于死锁场景的指导,c,signals,deadlock,C,Signals,Deadlock,我有一个程序,产生了很多孩子,并运行了很长一段时间。该程序包含一个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,

我有一个程序,产生了很多孩子,并运行了很长一段时间。该程序包含一个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的可重入版本。为什么这仍然是僵局

#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;
   }
}

程序中有缺陷吗?有没有更好的方法来处理来自信号处理程序的日志记录(带有时间戳?

您正在从信号处理程序中调用非异步信号安全的函数(请参阅unix规范的部分)-在本例中,
ctime\u r()
printf()
(死锁似乎是由于
ctime\u r()使用的锁而发生的)
在您显示的堆栈跟踪中)。这些函数可能具有锁,并且由于信号处理程序可能随时被调用,因此锁可能已经被持有,从而导致死锁

通常,在信号处理程序中,您只需做一个注释,供主线程稍后检查。例如,您可以将
write()
(这是一个异步信号安全函数)写入
pipe()
创建的文件描述符,并让主循环(或另一个线程)执行选择循环,以等待某些数据显示在该管道上


还要注意,线程安全与异步信号安全不同
ctime\u r
是线程安全的—它需要锁来确保线程不会相互碰撞,并且它使用传入的缓冲区而不是静态缓冲区。但它不是异步信号安全的,因为它不能容忍在其执行过程中的任何任意点被重入调用。

您正在从信号处理程序中调用非异步信号安全的函数(请参阅unix规范的部分)-在本例中,
ctime\u r()
printf()
(死锁似乎是由于您显示的stacktrace中的
ctime\u r()
所使用的锁而发生的)。这些函数可能会使用锁,并且由于信号处理程序可能随时被调用,锁可能已经被持有,从而导致死锁

通常,在信号处理程序中,您只需为主线程做一个注释,以便稍后进行检查。例如,您可以将
写入
管道()
-创建的文件描述符中,并将主循环(或另一个线程)执行select循环以等待某些数据显示在该管道上


还要注意的是,线程安全与异步信号安全不同。
ctime\u r
是线程安全的-它需要锁来确保线程之间不会相互步进,并且它使用传入的缓冲区而不是静态缓冲区。但它不是异步信号安全的,因为它不能容忍在执行过程中的任意点被重入调用

printf
不是异步信号安全的,因此您不应该在信号处理程序中使用它…使用
write
而不是使用
fileno(stdout)
。虽然我承认printf()可能是个问题,但堆栈跟踪并没有告诉我这一点。如何获得printf()作为死锁的来源?
printf
不是异步信号安全的,因此您不应该在信号处理程序中使用它……使用
write
而不是使用
fileno(stdout)
。虽然我承认printf()可能是个问题,但堆栈跟踪并没有告诉我这一点。您如何获得printf()作为死锁的来源?我没有调用localtime()。我调用的是ctime\u r(),而ctime\u r()则是根据堆栈跟踪调用localtime\u r()。啊,糟糕。但同样的事情-
ctime\u r()
没有列为异步信号安全。ctime\u r()是显式可重入的。我认为这是线程安全的最高级别?可重入并不意味着异步信号安全?这是正确的。异步信号安全比线程安全要高得多(也称为“可重入”)。实际上,对于
*\r
函数来说,可重入是一个非常糟糕的名字。通常它们唯一的保证是不使用静态存储,但它们可能在内部使用非可重入锁定。我不是在调用localtime()。我在调用ctime\u r(),它反过来又在堆栈跟踪中调用localtime\u r()。啊,糟糕。但同样的事情-
ctime\u r()
未列为异步信号安全。ctime\u r()是显式可重入的。我认为这是线程安全的最高级别?可重入并不意味着异步信号安全?这是正确的。异步信号安全比线程安全(也称为“可重入”)要高得多。实际上,对于
*\r
函数来说,可重入是一个非常糟糕的名字。通常,它们所做的唯一保证是不使用静态存储,但它们可能在内部使用非可重入锁定。
cc -o asyncsignalhandler asyncsignalhandler.c -mt -D_POSIX_PTHREAD_SEMANTICS