My task is:
Write a program in which the parent process creates exactly 1 child. After creating the child, the behaviour of the parent process is as follows: it sends the signal SIGUSR1
to the child every 5 seconds. To implement this behaviour the parent process most use the following system calls: alarm()
and pause()
. After sending the signal SIGUSR1
three times, the fourth time the signal SIGUSR2
is sent to the child. After this, the parent waits for the child to finish.
The behaviour of the child is as follows: it waits until it is interrupted by any signal. If the signal received is SIGUSR1
it prints a message to the standard output. If the signal received is SIGUSR2
then it finishes. In addition, during the first 5 seconds of its execution, the signal SIGUSR2
should be blocked. Students are expected to check the behaviour of alarm()
and pause()
in the man pages.
My solution looks like this. I am trying to ignore the alarm()
in the parent and by the alarm in the child I set the flag on true and change unblock SIGUSR2
.
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/wait.h>
bool flag = false;
void alert_ignore(int signum) {
printf("catched alarm\n");
return;
}
void alert_setting_flag(int signum) {
flag = true;
return;
}
void sigusr1_handler(int signum) {
printf("Recieved SIGUSR1\n");
return;
}
void sigusr2_handler(int signum) {
printf("Recieved SIGUSR2\n");
exit(0);
}
int main(int argc, char *argv[]) {
sigset_t set;
if (sigemptyset(&set) == -1) {
perror("sigemptyset");
exit(1);
}
if (sigaddset(&set, SIGUSR2) == -1) {
perror("sigaddset");
exit(1);
}
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
perror("sigprocmask");
exit(1);
}
struct sigaction alert_action;
alert_action.sa_handler = alert_ignore;
alert_action.sa_mask = set;
if (sigaction(SIGALRM, &alert_action, NULL) == -1) {
perror("sigaction");
exit(1);
}
struct sigaction sigusr1_action;
sigusr1_action.sa_handler = sigusr1_handler;
sigusr1_action.sa_mask = set;
if (sigaction(SIGUSR1, &sigusr1_action, NULL) == -1) {
perror("sigaction");
exit(1);
}
struct sigaction sigusr2_action;
sigusr2_action.sa_handler = sigusr2_handler;
if (sigaction(SIGUSR2, &sigusr2_action, NULL) == -1) {
perror("sigaction");
exit(1);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
/* Child process duties */
/* Setting alert handler to turn flag form false to true */
alert_action.sa_handler = alert_setting_flag;
if (sigaction(SIGALRM, &alert_action, NULL) == -1) {
perror("sigaction");
exit(1);
}
alarm(5);
int count = 0;
while (true) {
pause();
if (flag == true && !count) {
if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1) {
perror("sigprocmask");
exit(1);
}
printf("SIGUSR2 unblocked\n");
count++;
}
}
}
/* Parent Process duties */
for (int i = 0; i < 3; ++i) {
alarm(5);
pause();
kill(pid, SIGUSR1);
}
kill(pid, SIGUSR2);
wait(NULL);
return 0;
}
This works, but only sometimes and I get much random behavior, like the following, and I don't know why.
Case(0) desired behavior
catched alarm // after 5 seconds
SIGUSR2 unblocked
Recieved SIGUSR1
catched alarm // after 10 seconds
Recieved SIGUSR1
catched alarm // after 15 seconds
Recieved SIGUSR1
Recieved SIGUSR2
Case 1 (a lot):
catched alarm // after 5 seconds
Recieved SIGUSR1
SIGUSR2 unblocked
Alarm clock // after 10 seconds
Case 2 (very rare) needed to terminate it:
catched alarm // after 5 seconds
catched alarm // after 10 seconds
catched alarm // after 15 seconds
^Z
Case 3 (double printing SIGUSR2 unblocked):
catched alarm // after 5 seconds
SIGUSR2 unblocked
SIGUSR2 unblocked
Recieved SIGUSR1
catched alarm // after 10 seconds
Recieved SIGUSR1
catched alarm // after 15 seconds
Recieved SIGUSR1
Recieved SIGUSR2
Case 4:
catched alarm // after 5 seconds
Alarm clock
What is the reason for this behavior? (Most important part for me is, why the SIGALRM is not ignored as desired, I know that there are problems that I am not setting the flag in an atomic way, but that shouldn't influence my parent process, no?)
You have:
struct sigaction alert_action;
alert_action.sa_handler = alert_ignore;
alert_action.sa_mask = set;
if (sigaction(SIGALRM, &alert_action, NULL) == -1) {
Here, alert_action.sa_flags
isn't initialized nor assigned to, so it will be a random integer. A number of bad things could happen. If the SA_RESETHAND
flag is on, then the signal action will be reset to the default after the first SIGALRM is received, and the next SIGALRM will kill the process. That's why you're sometimes seeing the shell print out Alarm clock
.
To fix this, either initialize the structure or have assignment statements for all of the necessary fields. For example:
struct sigaction alert_action = {
.sa_handler = alert_ignore,
.sa_mask = set,
.sa_flags = 0
};
You should do this for all your struct sigaction
variables.