Unix Signals and Periodic Behavior

Working on an embedded C application at work, I get the chance daily to work with developers that have been working in C for decades. I learn so much from them that pushes me to further my understanding of C and *nix system programming as a whole. With the restrictions of the platform that we work on, I am unable to utilize a lot of the modern amenities of a fully featured server. Normally this is okay, but sometimes this poses interesting challenges to get things working.

Our problem was this, we needed to add a heartbeat protocol to the embedded application. The protocol couldn’t interfere with the application’s core functionality and would wake up periodically, send the heartbeat, then give up control waiting to do it again. My mentor decided on using Unix signals to accomplish this.

Unix Signals, a Primer

Signals are an asynchronous IPC (Inter-Process Communication) mechanism. They are essentially limited messages that some process wants to deliver to another process and have the other process deal with that message in some way. When a process receives a signal, it MUST explicitly deal with it somehow, because the kernel will interrupt the regular control flow of the process to invoke the process’s signal handler. As the application programmer, one is able to register what are called signal handlers, which execute desired behaviors when a signal is delivered. However, if no signal handler is registered, then the process will execute the default signal handler (often leading to termination).

The most common signal to programmers is SIGINT which is delivered to the process when we press Ctrl-C in our terminal to stop a runaway program. One way to handle a SIGINT would be to register a signal handler that cleaned up any memory allocated to the process before exit when a SIGINT is delivered.

A Signal “Whitelist”

However, one does not always need to subscribe to the default ways of registering signal handlers. *nix systems often have in their C standard library a function called sigwait. What sigwait does is most aptly described by the man pages.

SIGWAIT(3)                                                                  Linux Programmer's Manual                                                                  SIGWAIT(3)

NAME
       sigwait - wait for a signal

sigwait is available to create a blocking operation waiting on any signal to be delivered to the calling process. This is extremely useful, because now instead of having to think of our asynchronous behavior in disparate functions, we can have one loop with essentially a switch statement that performs any number of actions based on what signal is delivered.

Obviously this approach has its drawbacks, and is mostly useful only in child processes that depend on some other process when deciding what to do, but it’s powerful nonetheless! If your program only needs to react to signals in edge cases, then sigwait is definitely not for you.

How sigwait is Used

sigwait simply needs two things to act accordingly. It needs a sigset_t object to hold all possible signals you would like to whitelist, and an int to store the signal number in. Once both are set up, you use sigaddset to add signals that you care about to the sigset_t for sigwait.

#include <signal.h>

const sigset_t sigset;
int signo;
sigaddset(&sigset, SIGINT);

sigwait(&sigset, &signo);

Using sigwait For Periodic Behavior

With all this knowledge about sigwait, it is easy to utilize sigwait and a specific signal SIGALRM to implement periodic looping behavior. SIGALRM is a special signal that is delivered to the calling process through a *nix system call called alarm. When using alarm, you pass in just one argument which is the number of seconds you’d like to wait before a SIGALRM is delivered to your process. As you probably guessed, we simply need to add SIGALRM to our sigset_t, and call alarm at appropriate times (like the end of a loop).

#include <signal.h>
#include <unistd.h> // For the alarm system call.

const sigset_t sigset;
int signo;

sigaddset(&sigset, SIGALRM);
sigprocmask(SIG_BLOCK, &sigset, NULL); // Necessary to ensure that default signal handlers don't run.
alarm(30);

for (;;) {
    // Block here for a signal.
    sigwait(&sigset, &signo);

    // Check the signal number.
    if (signo == SIGALRM) {
        // do your thing.
        alarm(30); // Call alarm to set up a future signal delivery which sigwait will receive
    }
}

The code example above is in no way complete, good practice is to ensure that you handle some signal to allow for exiting this loop cleanly. Once you’ve done that though, then you have a neat way to loop and do things in your program with some specified waiting period!

 Share!

 
I run WindleWare! Feel free to reach out!

Subscribe for Exclusive Updates

* indicates required