Why does a sigaction handler not handle every sign

2019-05-29 00:50发布

问题:

I have a parent process that spawns ten child processes and uses a SIGCHILD handler to notify when a child process has died. The child processes will notify when they have started and will then exit immediately.
I use the SA_NODEFER flag to prevent SIGCHLD signals to get discarded when too many are coming in. The children are all being correctly terminated and reaped, but in this script and also when terminating two at the same time via the console signals send at the same time always appear as one.

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void CHLDHandler(int sig)
{
  char child[] = "Child finished!\n";
  write(1, &child, sizeof(child));
}

int main(int argc, char const *argv[]) {

  struct sigaction sa;
  sa.sa_handler = CHLDHandler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_NODEFER;

  sigaction(SIGCHLD,&sa,NULL);

  for (size_t i = 0; i < 10; i++) {
    int pid = fork();

    if (pid == 0)
    {
      int pid = getpid();
      printf("I'm a child with pid %d!\n", pid);
      return 0;
    }
  }
  while(1)
  {
    wait(NULL);
  }

  return 0;
}

回答1:

Basic UNIX signals do not queue — only one can be pending (for a given thread) at a time.

If two child processes terminate at effectively the same time and thus deliver their SIGCHLD at the "same" time, your process will have only one signal pending.

waiting in a loop after receipt of a SIGCHLD is a long-established technique to compensate for the fact that a SIGCHLD may be "lost". Move the "Child finished!\n" announcement to the loop that calls wait, and you'll have an accurate count.

UPDATE

If you must reap within your handler, you may call wait within the handler, since it is async-signal-safe:

static void CHLDHandler(int sig)
{
  static char child[] = "Child finished!\n";
  int save_errno = errno;

  while (waitpid((pid_t)-1, NULL, WNOHANG) > 0) {
    write(STDERR_FILENO, &child, sizeof(child) - 1);  // don't write the terminating NULL
  }

  errno = save_errno;
}

In this case I would not set SA_NODEFER, since another SIGCHLD might interrupt (EINTR) the waitpid or the write calls.



标签: c signals