Signals API

API Reference


Signals are software interrupts that can be sent to a running process or thread to indicate exceptional situations. The action taken when an event is received depends on the current settings for the signal and may be set to either:

  • Operating systems default action.
  • Ignore the signal.
  • A custom handler.

When a signal is received, unless it is ignored or blocked the action for the signal will preempt any code that is currently executing. Also, signals are asynchronous and may arrive at any time. See http://man7.org/linux/man-pages/man7/signal.7.html for more details.

The asynchronous and preemptive nature of signals can be difficult to deal with and is often a source of race conditions. Moreover asynchronous and preemptive signal handling is often unnecessary so code often looks something like this:

// A global volatile atomic flag.
static volatile int GotSignal;
 
void sigHandler(int sigNum)
{
// Must only use asynch-signal-safe functions in this handler, see signals(7) man page for
// more details.
 
// Set the flag.
GotSignal = 1;
}
 
int main (void)
{
while(1)
{
// Do something.
...
 
if (GotSignal = 1)
{
// Clear the flag.
GotSignal = 0;
 
// Process the signal.
...
}
}
}

In this code sample, the signal handler is only used to set a flag, while the main loop handles the actual signal processing. But handling signals this way requires the main loop to run continuously. This code is also prone to errors. For example, if the clearing of the flags was done after processing of the signal, any signals received during processing of the signals will be lost.

Signal Event Handlers

The Legato signals API provides a simpler alternative, called signal events. Signal events can be used to receive and handle signals synchronously without the need for a sit-and-wait loop or even a block-and-wait call.

To use signal events, the desired signals must first be blocked using le_sig_Block() (see Blocking signals). Then set a signal event handler for the desired signal using le_sig_SetEventHandler(). Once a signal to the thread is received, the signal event handler is called by the thread's Legato event loop (see Event Loop API for more details). The handler is called synchronously in the context of the thread that set the handler. Be aware that if the thread's event loop is not called or is blocked by some other code, the signal event handler will also be blocked.

Here is an example using signal events to handle the SIGCHLD signals:

// SIGCHILD event handler that will be called as a synchronous event.
static void SigChildEventHandler(int sigNum)
{
// Handle SIGCHLD event.
...
 
// There is no need to limit ourselves to async-signal-safe functions because we are now in
// a synchronous event handler.
}
 
 
{
// Block Signals that we are going to set event handlers for.
le_sig_Block(SIGCHLD);
 
// Setup the signal event handler.
le_sig_SetEventHandler(SIGCHILD, SigChildEventHandler);
}

Mixing Asynchronous Signal Handlers with Synchronous Signal Event Handlers

Signal events work well when dealing with signals synchronously, but when signals must be dealt with in an asynchronously, traditional signal handlers are still preferred. In fact, signal event handlers are not allowed for certain signals like program error signals (ie. SIGFPE, etc.) because they indicate a serious error in the program and all code outside of signal handlers are considered unreliable. This means that asynchronous signal handlers are the only option when dealing with program error signals.

Signal event handlers can be used in conjunction with asynchronous signal handlers but only if they do not deal with the same signals. In fact all signals that use signal events must be blocked for every thread in the process. The Legato framework takes care of this for you when you set the signals you want to use in the Legato build system.

If your code explicitly unblocks a signal where you currently have signal event handlers, the signal event handlers will no longer be called until the signal is blocked again.

Multi-Threading Support

In a multi-threaded system, signals can be sent to either the process or a specific thread. Signals directed at a specific thread will be received by that thread; signals directed at the process are received by one of the threads in the process that has a handler for the signal.

It is unspecified which thread will actually receive the signal so it's recommended to only have one signal event handler per signal.

Limitations and Warnings

A limitation of signals in general (not just with signal events) is called signal merging. Signals that are received but not yet handled are said to be pending. If another signal of the same type is received while the first signal is pending, then the two signals will merge into a single signal and there will be only one handler function call. Consequently, it is not possible to reliably know how many signals arrived.

Warning
Signals are difficult to deal with in general because of their asynchronous nature and although, Legato has simplified the situation with signal events certain limitations still exist. If possible, avoid using them.

Blocking signals

Signals that are to be used with a signal event handlers must be blocked for the entire process. To ensure this use le_sig_Block() to block signals in the process' first thread. All other threads will inherit the signal mask from the first thread.

The example below shows how to use a signal event in a separate thread.

// SIGCHILD event handler that will be called as a synchronous event in the context of the
// workThread.
static void SigChildEventHandler(int sigNum)
{
// Handle SIGCHILD event.
...
 
// There is no need to limit ourselves to async-signal-safe functions because we are now in
// a synchronous event handler.
}
 
 
// Work thread's main function.
static void* WorkThreadMain(void* context)
{
// Setup the signal event handler.
le_sig_SetEventHandler(SIGCHILD, SigChildEventHandler);
 
// Start this thread's event loop.
 
return NULL;
}
 
 
// Main thread code.
{
// Block Signals that we are going to set event handlers for in the main thread so that all
// subsequent threads will inherit the same signal mask.
le_sig_Block(SIGCHLD);
 
// Create and start a work thread that will actually handle the signal.
le_thread_Ref_t workThread = le_thread_Create("workThread", WorkThread, NULL);
 
le_thread_Start(workThread);
}