I am writing a Unix program where a father process forks a child and later it has to send a SIGUSR1 signal to be handled by handler_sigusr1, and a SIGKILL signal to finish the program. My problem is that when I run the program, the child program should receive SIGUSR1 and then SIGKILL, but it just receives SIGKILL. Is there something wrong with my code? Thanks for your help!
void handler_sigusr1()
{
printf("Signal SIGUSR1\n");
}
int main(int argc, char **argv)
{
struct sigaction action;
sigset_t new_mask, oldmask;
int status;
action.sa_flags = 0;
sigaction(SIGTERM, &action, NULL);
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGTERM);
sigaction(SIGUSR1, &action, NULL);
action.sa_handler = handler_sigusr1;
sigaddset(&new_mask, SIGUSR1);
child1 = fork();
if(child1 == 0)
{
sigprocmask(SIG_BLOCK, &new_mask, &oldmask);
sigsuspend(&new_mask);
}
if(child1 != 0)
{
kill(child1, SIGUSR1);
kill(child1, SIGKILL);
}
wait(&status);
}
SIGUSR1 and SIGUSR2 both have the default action Term -- the process is terminated. dd registers a handler to intercept the signal and do something useful with it, but if you signal too quickly it hasn't had time to register that handler yet, so the default action happens instead Thanks for contributing an answer to Unix & Linux Stack Exchange!
In the handler function, the signal handler re register to SIG_DFL for default action of the signal. When user typed Ctrl+C for second time, the process is terminated which is the default action of SIGINT signal.
Sending dd the USR1 signal too soon after it has started (i.e. in a bash script, the line after you started it) will in fact terminate it Can anybody explain why? Show activity on this post. Each signal has a "default disposition" -- what a process does by default when it receives that signal.
If the specified action for the signal is ignored, then the signal is discarded immediately. The program can register a handler function using function such as signal or sigaction. This is called a handler catches the signal. If the signal has not been neither handled nor ignored, its default action takes place.
There are 4 issues:
The code setting up the signal handler is wrong.
These 2 lines are in the wrong order:
sigaction(SIGUSR1, &action, NULL);
action.sa_handler = handler_sigusr1;
They should be
action.sa_handler = handler_sigusr1;
sigaction(SIGUSR1, &action, NULL);
Your parent process sends SIGKILL to your child right after it sends SIGUSR1. SIGKILL will terminate your child process - and it cannot be blocked or ignored.
Unless you're lucky, your child's sigusr1 handler will not be able to run or finish before the parent sends SIGKILL - which terminates the child process immediately.
You can greatly increase the chance SIGUSR1 gets delivered and handled by inserting a delay like so:
kill(child1, SIGUSR1);
sleep(1);
kill(child1, SIGKILL);
You have SIGUSR1 blocked, so it will not be delivered to your process.
Remove the line
sigaddset(&new_mask, SIGUSR1);
Note that when the signal mask is being set with sigprocmask()
or sigsuspend()
, the signals set in the mask are the signals that are blocked.
You cannot safely use printf() in a signal handler.
printf() is not signal async safe, and cannot be used inside a signal handler. If you're unlucky it might not behave as it should behave when used in a signal handler. Use write() instead to write directly to stdout, e.g.
write(1, "Signal SIGUSR1\n", 15);
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With