0

I'm tyring to write a program that every 5 seconds checks all the signals received and varies its output depending on them. My main issue is making the code actually wait the 5 seconds, since the function sleep() gets interrupted as soon as a signal is processed. What I have tried to do is fork the program into two processes, and have only the child work with the signals, and communicate them by a pipe. The issue is that, being the pipe blocking, after the first 5 second, the parent will just try to read there, basically making the sleep useless.

In theory I should be able to solve such problem just using the libraries I included (or in alternative with pthread library,it's an assignment), otherwise I saw I could use other libraries to make the pipe not blocking, but at the moment I'm short of ideas on how to fix this (I also tried a failed attempt with thread).

I attach the code for reference, thanks in advance.

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int p[2];//pipe

void sigHandler (int sig) {
    char c;
    switch (sig) {
        case SIGUSR1:
            c = '1';
        break;
        case SIGUSR2:
            c = '2';
        break;
        case SIGALRM:
            c = '0';
        break;
        case SIGSTOP:
            exit (0);
        break;
    }
    write(p[1], &c, sizeof(char));
}


int main(int argc, char **argv) {
    int signals[3], sig, i;
    for (i=0; i< 3; i++) {
        signals[i] =0;
    }
    pid_t pid;
    char csig;
    pipe(p);

    /*fork code*/
    pid = fork();

    /*parent*/
    if (pid) { 
        close(p[1]);
        int proceed = 1;

        /*cycle keeps on indefinitely*/
        while (proceed) {
            sleep(15); //increased the time so I have time to send signals
            while (read(p[0], &csig, sizeof(char)) > 0) {
                sig=atoi(&csig);
                signals[sig]++;

                /*if alternating sigals, error*/
                if (signals[1] && signals[2]) {
                    printf("Error: alternating SIGUSR1 and SIGUSR2\n%d\t%d\n", signals[1], signals[2]);

                }
                /*if 2 consecutive signals, success*/
                if (signals[sig] == 2) {
                    printf("Success: two consecutive SIGUSR1 or SIGUSR2\n");
                }
                /*if three consecutive signals, exit*/
                if (signals[sig] == 3) {
                    kill(pid, SIGSTOP);
                    proceed = 0;
                    break;
                }
                /*make 0 any non repeating signal != sig*/
                for(i = 0; i< 3; i++) {
                    if (i != sig) {
                        signals[i] = 0;
                    }
                }
            }
        }
    /*child*/
    } else {
        close(p[0]);
        signal(SIGUSR1, sigHandler);
        signal(SIGUSR2, sigHandler);
        signal(SIGALRM, sigHandler);
        signal(SIGSTOP, sigHandler);
        while (1) {
            sleep(5); //no need to do anything else
        }
        exit(1);

    }
    return 0;
}
Aelar
  • 21
  • 2
  • Why do you need a separate child process? In the same process if you get a signal, just add it to a queue and resume the sleep for the remaining amount of time. You can easily check if your sleep was interrupted or it finished the time. Check the return value for [`sleep`](http://man7.org/linux/man-pages/man3/sleep.3.html) – Ajay Brahmakshatriya Dec 10 '18 at 18:35
  • Perhaps _[thread suspending and resuming](https://stackoverflow.com/questions/11468333/linux-threads-suspend-resume)_? – ryyker Dec 10 '18 at 18:37
  • Maybe call sigsetmask or sigprocmask before and after the sleep(5) if you only want signals to be processed every 5 seconds. – Mark Plotnick Dec 16 '18 at 03:19

3 Answers3

1
  1. Compute what time it will be in five seconds. Store this time.
  2. Compute how long it will be until the time you computed in step 1.
  3. Sleep for that long.
  4. Check the time.
  5. If it's at or past the time you stored in step 1, you are done.
  6. Go to step 2.
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Sleep also returns the amount left if it was interrupted. Don't need to calculate again. – Ajay Brahmakshatriya Dec 10 '18 at 18:37
  • @AjayBrahmakshatriya But that's rounded to the nearest second which might not be accurate enough for a five second sleep. Even if you only use second accuracy with the algorithm above, at least errors from multiple interruptions won't accumulate. But if interruptions are rare and accuracy isn't particularly important, that's definitely an option. – David Schwartz Dec 10 '18 at 18:39
  • @AjayBrahmakshatriya: it also does not include the time spent in the signal handler (or the time waking up the process to run the signal handler, or other overhead outside of sleep). So if a lot of signals are delivered you may end up waiting noticeably longer than the desired amount of time.,, – Chris Dodd Dec 10 '18 at 21:15
1

clock_nanosleep lets you sleep till an absolute time. You can repeatedly call this function till the desired time is reached.

struct timespec time_now;
clock_gettime(CLOCK_REALTIME, &time_now);
time_now.tv_sec += 5;
time_now.tv_nsec = 0;
while(clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &time_now, NULL) == EINTR);

This will make your thread to sleep repeatedly till it has completed a sleep of 5 seconds. In your signal handler, you keep adding the received signals to an array and handle it after the sleep is over.

Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • The time spent actually processing the signal that interrupted the nanosleep call is not included in (subtracted from) the time remaining returned, so if there are a lot of signals, this may be quite inaccurate. – Chris Dodd Dec 10 '18 at 21:17
  • @ChrisDodd I assumed that the overhead of that would be low because the signal handler would be short. Anyway, I changed the answer to use absolute time instead. – Ajay Brahmakshatriya Dec 10 '18 at 22:35
0

Why so complicated? Signals are a per-process (and optionally per-thread within same the process when POSIX threads are used), so it makes no sense to use multiple processes just to handle signals.

Consider the following program:

#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

static const char *signal_name(const int signum)
{
    switch (signum) {
    case SIGUSR1: return "SIGUSR1";
    case SIGUSR2: return "SIGUSR2";
    case SIGALRM: return "SIGALRM";
    case SIGSTOP: return "SIGSTOP";
    case SIGINT:  return "SIGINT";
    default:      return "unnamed";
    }
}

int main(void)
{
    siginfo_t  info;
    sigset_t   mask;
    int        signum;

    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    sigaddset(&mask, SIGUSR2);
    sigaddset(&mask, SIGALRM);
    sigaddset(&mask, SIGSTOP);
    sigaddset(&mask, SIGINT);

    /* Block the signals in mask. */
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
        fprintf(stderr, "Cannot block signals: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    printf("Process %d is ready for USR1 and USR2 signals.\n", (int)getpid());
    fflush(stdout);

    /* Ensure we receive a SIGALRM signal in five seconds or so. */
    alarm(5);

    while (1) {

        /* Receive one of the signals in mask. */ 
        signum = sigwaitinfo(&mask, &info);
        if (signum == -1) {
            const int  err = errno;

            /* EINTR should not happen, but if it did, it would be okay. */
            if (err == EINTR)
                continue;

            fprintf(stderr, "sigwaitinfo() failed: %s (%d).\n", strerror(err), err);
            exit(EXIT_FAILURE);
        }

        /* Describe the received signal. */
        if (info.si_pid)
            printf("Received %s (signal %d) from process %d.\n", signal_name(signum), signum, (int)info.si_pid);
        else
            printf("Received %s (signal %d).\n", signal_name(signum), signum);
        fflush(stdout);

        /* It is time to exit, if we receive INT or STOP, */
        if (signum == SIGINT || signum == SIGSTOP)
            break;

        /* or an ALRM timeout; don't be fooled by kill()/sigqueue() SIGALRM. */
        if (signum == SIGALRM && !info.si_pid)
            break;
    }

    printf("Done.\n");
    return EXIT_SUCCESS;
}

mask contains the set of signals blocked and only received via sigwaitinfo(). alarm(5) ensures a SIGALRM signal is delivered in five seconds or so. If info.si_pid is set, the signal was sent via kill() (or similar OS-specific syscalls).

This program just describes each signal it receives, until the alarm timeout triggers, or it receives a SIGSTOP or SIGINT signal. (When you run the program in a terminal, and press Ctrl+C, the terminal sends a SIGINT signal to the (foreground) process.)

You cannot fool the program by sending it a SIGALRM signal, because the kernel sets the .si_pid field of the siginfo_t structure to the sending process; it will be zero for all "genuine" alarms.

Remember that standard Unix/POSIX signals are not queued, and may have latency (like sleeps and timers, may be delivered later than intended).

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86