Unix to Windows Porting Dictionary for HPC

RSS

Links

Function List

Signals


Background Information

Signals are one of the oldest communications methods in the *ix family of operating systems. They can be used to communicate asynchronous events to one or more processes. A signal can be generated by a hardware event (communications line hangup or SIGHUP), a keyboard input (Control-C or SIGINT), an error condition (segment violation or SIGSEGV) or a program action (raise()).

On the receiving side each process has the option of accepting the system default action for the signal (usually some form of termination), ignoring it, blocking it or handling it with a local signal handler.

There has been a significant change in the implementation of signaling over the years. The first implementation employed the now deprecated signal() call. This call manipulated a single signal mask of either 32 bits or 64 bits (depending on the underlying hardware). The bits in the mask either allowed the signal to be accepted or to be rejected. Pointers in the process data either directed the action of each signal to the system default routine or to the user written routine.

Recently the sigaction() call has replaced the signal() call. This routine manipulates a structure that contains the signal mask and the actions for the signals. The main difference is that the system can maintain a set of masks that can be swapped in and out without having to set or reset individual bits. Another significant difference is that you have the ability to manage a mask that takes effect while the signal handler is executing. In the past the signal handler inherited the signal mask of the process and had no other control over it.

Going back to usage, signals can be used to flag the occurrence of a programming error (SIGILL - illegal instruction, SIGSEGV - segment violation, SIGFPE - floating point error, etc). In the Windows model, these conditions can best be emulated by the use of a structured exception handler. The major differences between the two approaches is that the *ix approach is a "set it up once and forget it" while the Windows approach wraps each potential trouble spot with a __try/__catch structure. There are advantages to each (*ix is easier , but Windows is more flexible).

Signals can also flag the occurrence of an outside event (SIGINT - Control-C, SIGTSTP - Control-Z, SIGABRT - Control-\, etc). The Windows version of the signal() call can be used to emulate these with a good level of fidelity. This is the area that we will concentrate on for the signals portion of the conversion dictionary.

Caveats

Note that the sample code provided below has little to no error checking; for production code, developers should be sure to account for any possible errors as much as possible.

Additionally, each of the major branches of the Unix family does things a bit differently. At the command line they are all similar, but when you get down in the details, there is a divergence. Ultimately, developers will have to test code in the original environment and in the Windows environment to ensure the conversion preserved the functionality of the application. As with using any low level calls, testing needs to be thorough, since there is the possibility that conversion introduced race conditions or random lockups.

Finally, many implementations of signals are not thread-safe; developers should bear this in mind when dealing with ports that use signals.

Windows Implementation

Windows implements the two basic calls needed for signals – signal() and raise(). Signal() is used to set up the associations for the execution of any received signal. Raise() is used to programmatically generate a signal within an application.

When a process begins executing, any signal received will automatically be directed to the system maintained signal handler for that signal. As mentioned earlier, in most cases this will result in the termination of the application. It is possible to redirect the action of a signal to a user written signal handler by invoking the signal() routine. Here is a snippet of code to redirect the abort signal to a user written signal handler.

typedef void (*SignalHandlerPointer)(int);

    SignalHandlerPointer previousHandler;
    previousHandler = signal(SIGABRT, SignalHandler);

After this code has executed in the main body of a process, the next SIGABRT signal will cause the SignalHandler routine to be invoked.

Note: Upon entry to the user written signal handler, the redirection is cancelled and the system default signal handler for that signal is reinstated. If the user wants to use the user written handler to handle future signals, the signal routine must be invoked again BEFORE exiting the user handler.

If you need to handle multiple signals you have two choices. One, you can set up a separate handler for each signal. This would mean writing a handler for each signal and invoking signal() for each handler in a manner similar to the above snippet.

A second choice would be to point all of the signals to the same handler. Upon entry to the handler you can use the signum that is passed to the handler to determine which signal is invoking it. You then code all of the actions as a switch statement. This is the approach we are taking with the running example for the description of the signal family of routines.

*ix Signal Actions

The *ix implementation of signals allows the user several levels of action for each signal. First you can block or not block the signal. If you do not block a signal then it moves on to the action decision stage. The possible actions are ignore, default or user. If ignore is selected, the signal is just dropped and no further action takes place. If default is selected, the signal is passed on to the system defined default handler for that signal. As mentioned before, this will usually result in a termination of the process. If user is selected, the user written handler is invoked and the signal number is passed to it.

If you block a signal, it is deferred. It will remain pending until it is unblocked. Since there is only one bit per signal in the system maintained blocked mask, you can only have a single signal "stacked up".

Windows Signal Actions

The main difference between the *ix and Windows signal implementation is the lack of a blocking mask in Windows. Essentially you move directly to the action decision stage where you can ignore it, pass it on to the system default handler or pass it on to the user written handler. To implement the effect of blocking we need to add some code to each user written routine or we can add an array of actions to a single handler that accepts all of the potential signals that could be generated.

Please note that in either case we are not performing exactly the same sequence that the original implementation did. Where the *ix implementation stopped the signal before it even entered either the default system supplied routine or the user written routine, we will have to actually invoke the user written routine and make some decisions. While that extra code is executing, a second signal could arrive and potentially get lost or confuse the handler. There are also possibilities for race conditions that could lead to some very difficult to debug problems.

First Implementation (without blocking)

The following program is a first attempt at a single signal handler which will deal with the 6 primary signals Windows implements (SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV and SIGTERM). This implementation assumes that the user is not blocking any signals.

// Exception2.cpp : main project file.
// compile with: /c
//
// Purpose: Use signal to attach a signal handler to the abort routine

#include "stdafx.h"

using namespace System;

// Definitions of constants
// SIG_DFL (default) is defined as 0 in signal.h
// SIG_IGN (ignore) is defined as 1 in signal.h
#define SIG_USR 5  // User supplied handler function

// Miscellaneous variables
int i;		// Counter

// Declare the signal mask used by the handler and main routine.  The main
// routine will initialize it appropriately before use.  Each entry can have
// one of three values.  0 = default, 1 = ignore and 5 = user.
int sigmask [32];

// The signal handler.  We will attach this handler to the 6 primary signals for
// the purposes of this example.  Once the signal handler has been invoked, it 
// will use the signal number to decide what course of action to take.

void SignalHandler(int signum)
{
    // Use the signal number passed to the handler to access the signal mask that
	// has been established by the main body of the process.
	// The entry in the signal mask array will determine whether to send control
	// to the default handler, send control to a user written routine or ignore
	// the signal altogether

	// Typedefs
	typedef void (*SignalHandlerPointer)(int);

	// Local variables
	int action;		//The action we retreive from the signal mask array
	SignalHandlerPointer locpreviousHandler;	//Local previous handler pointer

	// Get the current setting of the mask for the signal we are handling
	action = sigmask[signum];

	// Use the retrieved action
	switch(action) {

		// Default action.  Upon entry to our signal handler, the operating 
		// system has restored the default handler 
		// for this signal.  All we have to do is to raise the signal again to
		// activate it.
		case SIG_DFL:
			// Raise the signal again and stand back.
			raise(signum);
			break;
		
		// Ignore signal.  Nothing to do but reset the signal handler and return
		case SIG_IGN:
			// The operating system always resets the action of a signal handler
			// to the default upon entry to a user written signal handler.  We
			// need to set it back to ourselves for the next signal.
			locpreviousHandler = signal(signum, SignalHandler);

			// Since we are ignoring this signal all we do is return
			break;

		// Activate user written handler.  We will print out a message and return.
		case SIG_USR:
			// Now that we are supposed to activate a user written handler, we need
			// to decide which one based on the signal number passed to the handler
			// We need a case for each of the 6 we are working with, but we will only
			// fill in a message for the control-C signal (SIGINT).
			switch(signum) {

				// SIGINT
				case SIGINT:
					printf("Control-C detected\n");
					Sleep(5000);
					break;
				// SIGILL
				case SIGILL:
					break;
				// SIGFPE
				case SIGFPE:
					break;
				// SIGSEGV
				case SIGSEGV:
					break;
				// SIGTERM
				case SIGTERM:
					break;
				// SIGABRT
				case SIGABRT:
					break;
			}
			break;
	}

			// Return to the process that was interrupted
			return;
}	// End of user handler


// The main test routine for the example

int main(array ^args)
{

	// At the beginning of the process all of the signals are handled by their
	// default signal handler (SIG_DFL).  We need to point all of the signals to
	// our handler.  We need to call the signal function for each routine.

	// Typedefs
	typedef void (*SignalHandlerPointer)(int);

    SignalHandlerPointer previousHandler;
	// Abnormal termination
    previousHandler = signal(SIGABRT, SignalHandler);
	// Floating point error
    previousHandler = signal(SIGFPE, SignalHandler);
	// Illegal instruction
    previousHandler = signal(SIGILL, SignalHandler);
	// Control-C signal
    previousHandler = signal(SIGINT, SignalHandler);
	// Illegal storage access
    previousHandler = signal(SIGSEGV, SignalHandler);
	// Termination request
    previousHandler = signal(SIGTERM, SignalHandler);

	// Now that all of the signals will be handled by our handler we need to set
	// up our version of a signal mask to tell the handler what to do for each 
	// signal that it will catch.

	// Initially we will set all of the signals to IGNORE.  This will cause our
	// handler to simply return once it catches the signal.
	for ( i = 0; i < 32; i++ ) {
		sigmask[ i ] = 1;  // Initialize each entry to IGNORE
	}

	// Now we can begin our testing.

	// The handler will catch all of the calls that are directed to it.  The action
	// that the handler takes will depend on the setting of the mask entry for the
	// signal that the handler catches.  To begin, the handler will ignore all signals.
	
	// Test 1 - Ignore signal
	printf("Waiting 5 seconds for Control-C entry\n");
	printf("It should be ignored\n");

	Sleep(5000);

	// Test 2 - User routine activation from signal
	// Set up signalmask to user state for all signals
	for ( i = 0; i < 32; i++ ) {
	sigmask[ i ] = 5;  // Initialize each entry to USER
	}

	// Now wait 5 seconds for the user to enter a control-C.  The handler should
	// print out a message and return to the process.
	printf("Waiting 5 seconds for Control-C entry\n");
	printf("You should get a message from the handler\n");

	Sleep(5000);

	// Test 3 - Default action from signal
	// All of the signals that we are catching have a default action of terminating 
	// the application.  If everything is working according to the documentation,
	// entering a control-C at this point will terminate the program in the usual fashion.
	// Set up the signal mask so that all signals are set to their default action
	for ( i = 0; i < 32; i++ ) {
	sigmask[ i ] = 0;  // Initialize each entry to DEFAULT
	}

	// Now wait 5 seconds for the user to enter a control-C.  The system should take the 
	// default action (termination)
	printf("Waiting 5 seconds for Control-C entry\n");
	printf("The system should take the default action\n");
	printf("which will restult in program termination\n");

	Sleep(5000);

	// If the user didn't enter a control-C by now, we will ternimate anyway
	printf("No Control-C was entered.  Program terminating anyway\n");
	printf("in 10 seconds\n");

	Sleep(10000);
    
    // Terminating test program    
    return 0;
}

The example starts off by declaring a sigmask(32) array that will be used to hold the actions we want the handler to perform upon the receipt of any signal. Each entry in the array represents a single potential signal. We are actually using only six of the entries for the six signals we will be handling.

The handler itself is described next. On entry the system passes the signal number to the handler. We will use that number as an index to the sigmask array to determine the action we want to perform for that signal. The first case statement then carries out the action for the selected signal.

The first case is the default handler. To get there we simply raise() the signal a second time. Since the operating system has reset the destination for this signal to its default handler, we will immediately execute the default. Since all of these defaults terminate the program, no further action is necessary.

The second case is to ignore the signal. We simply reset the handler destination back to the user written handler and return to the interrupted program.

The last case is to invoke a user written handler. This is accomplished by a single case statement based on the signum that was passed to the handler when it was invoked. For the example we have only implemented a scrap of code for the SIGINT signal (Control-C), but you can see where you would insert other user written handlers.

Note: Since handlers are by their nature asynchronous, it is imperative that they be kept short. It is also not good practice to invoke any system calls since the system call itself could be interrupted and potentially lead to a lockup condition. In the example we are breaking this rule by using printf but this is just an example.

The main program follows the handler procedure. The first action is to redirect the six signals to the common signal handler. Once that is done, we set up the sigmask so that all of the entries will be ignored. Now we can try a few test cases.

First case - ignore the signal. The mask is already set so all we need to do is to wait for 5 seconds while the user types a Control-C. If everything is working correctly, it should be ignored.

Second case - user written handler invocation. Set the mask so that the Control-C signal will be directed to our user written code. Put out the message and wait 5 seconds. If the user enters a Control-C we should see the “Control-C detected” message from the handler.

Last case - default system handler. Set the mask so the Control-C signal will be directed to the system default handler. Put out the prompt message. If a Control-C is entered the program will abort in the normal Windows fashion.

This takes care of the absolute basics of signaling.

Sigignore Implementation

The *ix implementation has several other calls designed to make it easy to control the execution of signal handling. For instance there is the sigignore(signum) call. This routine will set the signum signal to the ignore action.

To simulate this routine all we have to do is to have it set the mask entry for signum to 1. A snippet like this should do the trick:

void sigignore(int signum);
    sigmask(signum) = 1;
    return;

Sighold Implementation

The implementation of the sighold() call adds the complication of blocking to the situation. As we have implemented the handler so far, we do not have the ability to block a signal more than simply ignoring it. But, blocking and ignoring are fundamentally different in that a blocked signal will eventually be executed when it is unblocked. The signal that is ignored is discarded and never acted upon.

To add blocking to the handler we need to add the equivalent of the *ix blocking mask. This would be an array which would store the state of each signal action(blocked or unblocked) as well as a history (have we accepted a blocked signal?). One potential implementation would have bit 0 storing the blocking state and bit 1 storing a pending signal. The possible states then become:

Bit 0 Bit 1
0 0 Not Blocked
1 0 Blocked, no pending signal
1 1 Blocked, pending signal

The pseudocode for the blocking decision would be:

  1. If signal is not blocked proceed to action determination
  2. If signal is blocked and if there is a pending signal then return
  3. If signal is blocked and if there is no pending signal then save signal and return

The sighold() signal will add the specified signal to the signal mask. This would mean that the code snippet would look like:

void sighold(int signum);
    sigblockmask(signum) = 1;
    return;

Sigrelse Implementation

The implementation of the sigrelse() call is a bit more complicated than simply clearing the blocked bit for the specified signal. The problem is that, if there is a pending signal for the signal to be released, we need to execute it. The pseudocode for the sigrelse() call would be:

  1. If there is no pending signal then clear the blocked bit and return
  2. If there is a pending signal, clear the blocked bit, raise the signal and return

This will cause the signal handler to be reentered so the pending signal can be executed. The code snippet for the sigrelse() would look like:

void sigrelse(int signum);
    if (sigblockmask(signum) & 10B = 0 then
	sigblockmask(signum) = 0;
    else
	{sigblockmask(signum) = 0;
	 Raise(signum);}
    return;

Sigset Implementation

The implementation of the sigset() routine is similar to the sighold() and sigrelse() routines with the addition of the ability to set the address of a user written handler. The pseudocode for the routine would be:

  1. If disp is SIG_HOLD then set the blocked bit for the signal in the blockmask and return
  2. If disp is SIG_DFL then set the value for the signal in the sigmask to 0, clear any pending bit in the blocked mask, unblock the signal in the blocked mask and return
  3. If disp is SIG_IGN then set the value for the signal in the sigmask to 1, clear any pending bit in the blocked mask, unblock the signal in the blocked mask and return
  4. If disp is the address of a user written signal handler set the value for the signal in the sigmask to 5, set the address of the signal handler to the address supplied, unblock the signal in the blocked mask and return

The resulting code could look like:

void sigset(int signum, sighandler_t disp);
    if ( disp = SIG_HOLD) {
	sigblockmask(signum) = 1;
	return;
	}
    else if (disp = SIG_DFL) {
	sigmask(signum) = 0;
	sigblockmask(signum) = sigblockmask(signum) & 01B;
	return;
    else if (disp = SIG_IGN) {
	sigmask(signum) = 1;
	sigblockmask(signum) = sigblockmask(signum) & 01B;
	sigblockmask(signum) = sigblockmask(signum) & 00B;
	return;
    else {
        previousHandler = signal(signum, sighandler);
	sigmask(signum) = 5;
	sigblockmask(signum) = sigblockmask(signum) & 00B;
	return;
    }
Valid HTML 4.01 Transitional Valid CSS!