First pass
Here's an amended version of your code which seems to work. Note that no process can ever block SIGKILL or SIGSTOP. I removed the SIGTERM code since is not being used. The signal handler calls write()
rather than printf()
since write()
is async-signal-safe. It hand-encodes the signal number into the message since functions such as sprintf()
are not async-signal-safe (neither is strlen()
, officially) — see How to avoid using printf()
in a signal handler for more information.
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
static void handler_sigusr1(int signum)
{
char msg[] = "Signal ?? SIGUSR1\n";
msg[7] = (signum / 10) + '0';
msg[8] = (signum % 10) + '0';
write(2, msg, sizeof(msg) - 1);
}
int main(void)
{
struct sigaction action;
action.sa_flags = 0;
action.sa_handler = handler_sigusr1;
sigemptyset(&action.sa_mask);
sigaction(SIGUSR1, &action, NULL);
int child1 = fork();
if (child1 == 0)
{
sigset_t new_mask, oldmask;
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGUSR1);
sigprocmask(SIG_BLOCK, &new_mask, &oldmask);
sigsuspend(&new_mask);
printf("Child (%d) unsuspended\n", (int)getpid());
}
if (child1 != 0)
{
kill(child1, SIGUSR1);
sleep(1);
kill(child1, SIGKILL);
int status;
int corpse = wait(&status);
printf("Child (%d) - corpse = %d (0x%.4X)\n", child1, corpse, status);
}
return 0;
}
Example run:
Signal 30 SIGUSR1
Child (41875) - corpse = 41875 (0x0009)
The first message is from the signal handler in the child, obviously. It indicates that SIGUSR1 was received. The second message is from the parent. The hex number indicates that the child died from signal 9 (aka SIGKILL).
Second pass
What is that code in the child process doing? Answer: confusing me (and probably you too). I added an extra printf()
before sigsuspend()
, and ran the code and it shows:
Signal 30 SIGUSR1
Child (42688) suspending
Child (42688) - corpse = 42688 (0x0009)
The sigprocmask()
call blocks SIGUSR1, but the parent on my machine is too quick and gets the signal delivered before that takes effect. The sigsuspend()
then goes to sleep with SIGUSR1 blocked; the process dies because of the SIGKILL.
This variant of the code doesn't use sigprocmask()
at all. It has a very different effect:
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
static void signal_handler(int signum)
{
char msg[] = "Signal ??\n";
msg[7] = (signum / 10) + '0';
msg[8] = (signum % 10) + '0';
write(2, msg, sizeof(msg) - 1);
}
int main(void)
{
struct sigaction action;
action.sa_flags = 0;
action.sa_handler = signal_handler;
sigemptyset(&action.sa_mask);
sigaction(SIGUSR1, &action, NULL);
sigaction(SIGTERM, &action, NULL);
int child1 = fork();
if (child1 == 0)
{
sigset_t new_mask;
sigemptyset(&new_mask);
printf("Child (%d) suspending\n", (int)getpid());
sigsuspend(&new_mask);
printf("Child (%d) unsuspended\n", (int)getpid());
}
else
{
kill(child1, SIGUSR1);
sleep(1);
kill(child1, SIGTERM);
sleep(1);
kill(child1, SIGUSR1);
sleep(1);
kill(child1, SIGKILL);
int status;
int corpse = wait(&status);
printf("Child (%d) - corpse = %d (0x%.4X)\n", child1, corpse, status);
}
return 0;
}
Example output:
Signal 30
Child (42969) suspending
Signal 15
Child (42969) unsuspended
Child (42969) - corpse = 42969 (0x0000)
This time, the child dies normally, after reporting that it was unsuspended, because SIGTERM
and SIGUSR1
were not blocked.
You can ring the variations, adding SIGTERM
and/or SIGUSR1
to the signal mask passed to sigsuspend()
, maybe in response to different command line options.
I note that the man page for sigsuspend()
on macOS Sierra says:
The signal mask set is usually empty to indicate that all signals are to be unblocked for the duration of the call.