INF: Writing a SIGFPE Signal Handler for Multithreaded Code

ID Number: Q71418

5.10 6.00 6.00a

OS/2

Summary:

In Microsoft C versions 5.1, 6.0, and 6.0a writing a floating-point

exception handler for a multithreaded OS/2 application is very similar

to writing one for a single-threaded application under MS-DOS or OS/2.

The main items to remember are that you must set the handler from

thread 1, and since you have multiple threads, you cannot use a single

set of global variables for each thread's instance data.

More Information:

After reading the documentation, it appears that the easiest thing to

do is to use the signal() run-time function with SIGFPE as the first

argument. However, if you then examine the MTDYNA.DOC file (from C

version 5.1) or the "Microsoft C Advanced Programming Techniques"

manual (from C version 6.0), you will note that both documents state

that using signal() does not work properly for processing signals in a

multithreaded program. In both cases, it is suggested that you instead

use DosSetSigHandler(). However, if you check the OS/2 documentation

for DosSetSigHandler(), you will notice that there is no support for

floating-point exceptions.

While there seems to be no resolution to this dilemma, the answer is

in the definition of a floating-point exception. A floating-point

error is not a true signal in the sense that the operating system

takes the exception and processes it through the usual signal handler

API. Instead, it is an exception that the run-time library traps.

Therefore, you can use signal() in a multithreaded program in the

specific case of SIGFPE processing.

The sample program below illustrates how to use signal() for SIGFPE

processing. This is essentially the same code as the SIGFP.C program,

which is included in the C versions 6.0 and 6.0a online help to

demonstrate the use of setjmp(), longjmp(), and signal(), but it has

been modified to support multiple threads.

The following are some notes about this procedure:

1. The signal() run-time function MUST be called from thread 1. If it

is not, the call will not fail but the handler will not be set and

the default error routine will be used.

2. setjmp() and longjmp() work on the thread they were called on.

Therefore, you must save the longjmp() address for each thread.

3. Along the same lines, the floating-point error number is thread

specific.

4. The run-time library provides a variable called _threadid, which

points to the current thread ID. This can be used to keep the thread

instance data separate.

In the sample program, an array indexed by the thread ID is used for

both the longjmp() address and the floating-point error number.

Depending on the requirements of the implementation, it may be more

appropriate to use a linked list or a larger array to handle this.

Sample Code

-----------

/* Compile options needed: /MT

*/

#define INCL_DOSSEMAPHORE

#include <os2.h>

#include <process.h>

#include <stdio.h>

#include <signal.h>

#include <setjmp.h>

#include <stdlib.h>

#include <float.h>

#include <math.h>

#include <string.h>

#define MAX_TID 10

extern int far *_threadid;

jmp_buf mark[MAX_TID]; // Long Jump address array

int fperr[MAX_TID]; // FP Error number array

void fphandler( int sig, int num );

void fpcheck( void );

void main(void);

void ThreadMain(void _far *);

char far *ThreadStackPtr;

ULONG hsemThreadSem = 0;

void main(void)

{

// Set up floating-point error handler.

if( signal( SIGFPE, fphandler ) == SIG_ERR )

{

fprintf( stderr, "\nCouldn't set SIGFPE\n" );

abort();

}

ThreadStackPtr = (void far *)malloc(2024);

DosSemSet(&hsemThreadSem);

_beginthread(ThreadMain, ThreadStackPtr, 2024, NULL);

DosSemWait(&hsemThreadSem, SEM_INDEFINITE_WAIT);

printf("\nFinished processing\n");

}

void ThreadMain(void _far * arg)

{

double n1, n2, r;

int jmpret;

// If an FP exception occurs, set the place to come back to.

jmpret = setjmp( mark[*_threadid] );

// If we had an error jmpret would be equal to -1. Otherwise

// it is equal to 0. Process as needed.

if( jmpret == 0 )

{

printf( "\nTest for invalid operation - enter two numbers: " );

scanf( "%lf %lf", &n1, &n2 );

r = n1 / n2;

printf( "\n\n%4.3g / %4.3g = %4.3g\n", n1, n2, r );

r = n1 * n2;

printf( "\n%4.3g * %4.3g = %4.3g\n", n1, n2, r );

}

else

fpcheck();

DosSemClear(&hsemThreadSem);

_endthread();

}

void fphandler( int sig, int num )

{

// Set the error number for the thread in question.

fperr[*_threadid] = num;

// Reset the floating-point library to indicate the error

// has been handled.

_fpreset();

// Pull off the stored address for this thread and

// resume processing.

longjmp( mark[*_threadid], -1 );

}

void fpcheck()

{

char fpstr[30];

// Find out what the error was for the thread that is executing

// this function.

switch( fperr[*_threadid] )

{

case FPE_INVALID:

strcpy( fpstr, "Invalid number" );

break;

case FPE_OVERFLOW:

strcpy( fpstr, "Overflow" );

break;

case FPE_UNDERFLOW:

strcpy( fpstr, "Underflow" );

break;

case FPE_ZERODIVIDE:

strcpy( fpstr, "Divide by zero" );

break;

default:

strcpy( fpstr, "Other floating point error" );

break;

}

printf("\nThread: %d\tError %d: %s\n", *_threadid,

fperr[*_threadid], fpstr);

}

Additional reference words: 5.10 6.00 6.00a