/* $Id: signal.c 449 2009-05-22 15:45:18Z kate $ */ /* * Fork a child and catch signals sent from the child to its parent. * * This is a simple example of using signal() and wait() whilst (hopefully!) * avoiding a few of the race conditions commonly introduced. * * One practical caveat is that signal() does not have consistent semantics * over different systems (e.g. NetBSD's signal() piles up handlers, instead * of replacing them). Generally signal() is best used only for SIG_DFL and * SIG_IGN, and sigaction() used for real work. */ #include #include #include #include #include #include #include #include #include static void handler(int s) { /* * The set of functions which may be reliably used inside a signal handler * is rather small, being limited to either re-entrant or non-interruptable * functions; here write() is used as printf() is neither. * * In practise, for anything more significant than an example of this size, * the handler would set a sigatomic_t global flag which may be checked at * a convenient point back in the main program. */ write(STDOUT_FILENO, "parent: caught signal\n", strlen("parent: caught signal\n")); } int main(void) { /* A handler is installed pre-fork to avoid racing */ if (SIG_ERR == signal(SIGUSR1, handler)) { perror("signal"); return EXIT_FAILURE; } switch (fork()) { case -1: perror("fork"); return EXIT_FAILURE; case 0: /* Child */ /* * When exiting from child processes, _exit() is used as opposed to exit(). * One reason is that file buffers for open streams are inherited across a * fork() and so are their buffers. Calling exit() proper would flush these * buffers from the child, giving the effect of them being flushed twice. */ /* Re-set the default handler; we're not expecting to receive SIGUSR1 here */ if (SIG_ERR == signal(SIGUSR1, SIG_DFL)) { perror("signal"); _exit(EXIT_FAILURE); } /* Send a random number of signals to the parent */ { int n; srand(time(NULL)); n = 5.0 * rand() / RAND_MAX; printf("child: sending %d signal%s\n", n, n == 1 ? "" : "s"); while (n--) { if (-1 == kill(getppid(), SIGUSR1)) { perror("kill"); _exit(EXIT_FAILURE); } printf("child: sent signal\n"); } } _exit(EXIT_SUCCESS); /* NOTREACHED */ default: /* Parent */ /* * Wait for the child process to terminate. This call may be interrupted * by a signal (in our case, this is most likely to be SIGUSR1 from the * child, but it could be any other signal sent from elsewhere, too). * * If wait() returns successfully, the child has exited. If not, it was * either interrupted (in which case we can go back to waiting for the * child to exit), or there was a genuine error. */ while (-1 == wait(NULL)) { switch (errno) { case EINTR: /* Interrupted */ continue; default: /* TODO: Possible zombie here */ perror("wait"); return EXIT_FAILURE; } } /* * One misconception here is to think that the kernel may have been lax * in delivering signals to the parent, and that the child could have * called kill() and exited (hence the parent stops its wait() loop) * before the signal is delivered. * * However, this is not true; signals are delivered during syscalls, * and so the child is unable to exit (which is a syscall) without its * signal being delivered. Signals are not queued, and so it is not * possible for the parent to return from wait() without having * received and handled the signal sent. */ /* Successfully waited: the child died */ break; } /* Parent */ return EXIT_SUCCESS; }