Jump to content

OS/2 signal handling: Difference between revisions

From EDM2
Created page with "By Roger Orr OS/2 has a set of interprocess messages known generically as signals. They are the simplest form of inter-process communication under OS/2 and are invoked, for ..."
 
mNo edit summary
Line 1: Line 1:
By [[Roger Orr]]
By [[Roger Orr]]


OS/2 has a set of interprocess messages known generically as signals. They
OS/2 has a set of interprocess messages known generically as signals. They are the simplest form of inter-process communication under OS/2 and are invoked, for example, whenever you type Control+C to terminate a program.
are the simplest form of inter-process communication under OS/2 and are
invoked, for example, whenever you type Control+C to terminate a program.


OS/2 itself predefines some signals and their default signal handler, but
OS/2 itself predefines some signals and their default signal handler, but allows this default behaviour to be modified and also supports a few user-defined signal types.
allows this default behaviour to be modified and also supports a few
user-defined signal types.


The commonest example of an application modifying the default signal handler
The commonest example of an application modifying the default signal handler is to prevent Control+C aborting a program; and I am partly writing this article because I have recently met two programs (one IBM, one MicroSoft) which do NOT handle Control+C properly, so it seems likely that a brief overview would be of use to some OS/2 programmers!
is to prevent Control+C aborting a program; and I am partly writing this
article because I have recently met two programs (one IBM, one MicroSoft)
which do NOT handle Control+C properly, so it seems likely that a brief
overview would be of use to some OS/2 programmers!


==The basics==
==The basics==
There are 7 defined signals: four are system signals and three are application
There are 7 defined signals: four are system signals and three are application signals.
signals.


The system signals are:- SIG_BROKENPIPE, SIG_KILLPROCESS, SIG_CTRLC and
The system signals are:- SIG_BROKENPIPE, SIG_KILLPROCESS, SIG_CTRLC and SIG_CTRLBREAK.
SIG_CTRLBREAK.


The first signal, SIG_BROKENPIPE, is used to inform that a connection to a
The first signal, SIG_BROKENPIPE, is used to inform that a connection to a pipe was broken.  It will not be discussed further in this article.
pipe was broken.  It will not be discussed further in this article.


The second signal, SIG_KILLPROCESS, is sent when DosKillProcess() is invoked,
The second signal, SIG_KILLPROCESS, is sent when DosKillProcess() is invoked, and the default signal handler exits the program.
and the default signal handler exits the program.


The last two signals, SIG_CTRLC and SIG_CTRLBREAK, are sent by the keyboard
The last two signals, SIG_CTRLC and SIG_CTRLBREAK, are sent by the keyboard handler when Control+C or Control+BREAK are pressed.  The actual signal sent depends upon the keyboard mode - in ASCII mode (the default) SIG_CTRLC is sent by both Control+C and Control+BREAK, and in binary mode SIG_CTRLBREAK is sent by Control+BREAK.
handler when Control+C or Control+BREAK are pressed.  The actual signal sent
These signals can also be sent by using the DosSendSignal() function - note though that the target process must be a CHILD process of the signaller!  
depends upon the keyboard mode - in ASCII mode (the default) SIG_CTRLC is
sent by both Control+C and Control+BREAK, and in binary mode SIG_CTRLBREAK
is sent by Control+BREAK.
These signals can also be sent by using the DosSendSignal() function - note
though that the target process must be a CHILD process of the signaller!
As for SIG_KILLPROCESS the default signal handler exits the program.
As for SIG_KILLPROCESS the default signal handler exits the program.




The application signals are:- SIG_PFLG_A, SIG_PFLG_B and SIG_PFLG_C.
The application signals are:- SIG_PFLG_A, SIG_PFLG_B and SIG_PFLG_C.
These signals are sent by the DosFlagProcess() function, and an argument may
These signals are sent by the DosFlagProcess() function, and an argument may also be supplied.  The default signal handler ignores these signals.
also be supplied.  The default signal handler ignores these signals.




When a signal is issued thread 1 of the process is INTERRUPTED and the signal
When a signal is issued thread 1 of the process is INTERRUPTED and the signal handler is called.
handler is called.
Once the signal handler has returned (if it does) execution of the interrupted thread will resume from the point of interruption.
Once the signal handler has returned (if it does) execution of the interrupted
Note - thread 1 is the initial thread of the process, as opposed to any other threads created during execution with DosCreateThread.
thread will resume from the point of interruption.
Note - thread 1 is the initial thread of the process, as opposed to any other
threads created during execution with DosCreateThread.
==OS/2 signal handling functions==
==OS/2 signal handling functions==
Line 55: Line 34:




DosHoldSignal() is used to prevent signals while a critical section of code
DosHoldSignal() is used to prevent signals while a critical section of code is being processed.  This is typically used in conjuction with a semaphore to prevent concurrent access by other threads in the process.  DosHoldSignal is used as well to ensure that thread 1 does not get interrupted and thus destroy the consistency of the operation. NOTE: signals will override even DosEnterCritSec() unless this function is used.
is being processed.  This is typically used in conjuction with a semaphore
to prevent concurrent access by other threads in the process.  DosHoldSignal
is used as well to ensure that thread 1 does not get interrupted and thus
destroy the consistency of the operation. NOTE: signals will override even
DosEnterCritSec() unless this function is used.




Line 66: Line 40:
Various flavours are available:
Various flavours are available:


  o to set up an application signal handler
* to set up an application signal handler
  o to ignore a signal
* to ignore a signal
  o to return an error for a specific signal
* to return an error for a specific signal
  o to reset the signal handler to the default action
* to reset the signal handler to the default action


In addition, if you write a signal handler, it must call DosSetSigHandler
In addition, if you write a signal handler, it must call DosSetSigHandler when it is called to acknowledge the signal - this tells OS/2 that this signal can be sent again.
when it is called to acknowledge the signal - this tells OS/2 that this
signal can be sent again.




==Complications of signal handling==
==Complications of signal handling==
Thus far it seems easy - you just write a simple procedure to handle a specific
Thus far it seems easy - you just write a simple procedure to handle a specific signal, call DosSetSigHandler() and away you go.
signal, call DosSetSigHandler() and away you go.


Unfortunately there are various possible problems.
Unfortunately there are various possible problems.
Line 85: Line 56:
1) Deletion of thread 1
1) Deletion of thread 1


OS/2 will only use the first thread for signal handling.  If you terminate
OS/2 will only use the first thread for signal handling.  If you terminate this thread then signals won't work any longer.
this thread then signals won't work any longer.


2) Access to shared resources
2) Access to shared resources


The first problem, which I touched on above, is that signals can occur at
The first problem, which I touched on above, is that signals can occur at ANY time.  This means being extremely careful about such things as semaphores in order to prevent programs hanging.  For example, suppose a procedure printit() uses a semaphore to guarantee orderly access to the screen.  If the signal handler tries to use this function problems may well occur:
ANY time.  This means being extremely careful about such things as semaphores
in order to prevent programs hanging.  For example, suppose a procedure
printit() uses a semaphore to guarantee orderly access to the screen.  If
the signal handler tries to use this function problems may well occur:


         printit()
         printit()
Line 101: Line 66:
             .
             .
             .
             .
signal --->
signal --->
             printit()
             printit()
                 DosSemRequest(hsem, -1);
                 DosSemRequest(hsem, -1);


The semaphore request in procedure printit() will lock because the thread
The semaphore request in procedure printit() will lock because the thread has already locked it.
has already locked it.


This is a particular problem with the C runtime, especially the multithreaded
This is a particular problem with the C runtime, especially the multithreaded DLL, which uses semaphores to serialise access.
DLL, which uses semaphores to serialise access.


The safest rule is ... keep the signal handler REALLY SIMPLE.
The safest rule is ... keep the signal handler REALLY SIMPLE.


One solution is to create a SEPARATE thread, which waits for a semaphore to
One solution is to create a SEPARATE thread, which waits for a semaphore to be cleared by the signal handler.
be cleared by the signal handler.




3) Poor documentation of ERROR_INTERRUPT
3) Poor documentation of ERROR_INTERRUPT


The first signal handler I wrote was simple: it acknowledged the Ctrl+C signal,
The first signal handler I wrote was simple: it acknowledged the Ctrl+C signal, wrote out a string with VioWrtTTY and returned.  I tried testing it with a simple program which used getch(), and found that after pressing Ctrl+C and getting my message printed the program was exiting.  I traced this to getch() returning -1, which was treated as end of file and hence of the program!
wrote out a string with VioWrtTTY and returned.  I tried testing it with
a simple program which used getch(), and found that after pressing
Ctrl+C and getting my message printed the program was exiting.  I traced
this to getch() returning -1, which was treated as end of file and hence
of the program!


The reason for this behaviour is actually very simple: OS/2 was returning
The reason for this behaviour is actually very simple: OS/2 was returning ERROR_INTERRUPT to the C runtime which it treated as an error.
ERROR_INTERRUPT to the C runtime which it treated as an error.


Unfortunately as far as I can tell, ERROR_INTERRUPT is not documented - at least
Unfortunately as far as I can tell, ERROR_INTERRUPT is not documented - at least not in the manuals I have access to.  So here is a brief explanation of what this error return means, and why it is used.
not in the manuals I have access to.  So here is a brief explanation of
what this error return means, and why it is used.


Firstly, remember that thread 1 is interrupted by OS/2 in order to call
Firstly, remember that thread 1 is interrupted by OS/2 in order to call the signal handler.  If thread 1 is actually blocked inside OS/2 (waiting for a semaphore to clear, perhaps) then OS/2 has a potentially difficult job once the signal handler has completed in attempting to return the thread to its original state - perhaps the semaphore which was being waited on has now been cleared.  Rather than try and return to the same wait state internally, OS/2 returns a 'fake' error from the function - ERROR_INTERRUPT.
the signal handler.  If thread 1 is actually blocked inside OS/2 (waiting for
a semaphore to clear, perhaps) then OS/2 has a potentially difficult job
once the signal handler has completed in attempting to return the thread to
its original state - perhaps the semaphore which was being waited on has now
been cleared.  Rather than try and return to the same wait state internally,
OS/2 returns a 'fake' error from the function - ERROR_INTERRUPT.


This means that if your program has a signal handler then you must be prepared
This means that if your program has a signal handler then you must be prepared for ANY OS/2 calls in thread 1 to return ERROR_INTERRUPT and to process this correctly.  Again, be very careful if you call any of the C runtime functions since they may not handle the error code in the way you want.
for ANY OS/2 calls in thread 1 to return ERROR_INTERRUPT and to process this
correctly.  Again, be very careful if you call any of the C runtime functions
since they may not handle the error code in the way you want.


The safest course is usually to reserve thread 1 for signal handling alone.
The safest course is usually to reserve thread 1 for signal handling alone.


Another approach is to use this feature to work with you rather than against
Another approach is to use this feature to work with you rather than against you.  For example the OS/2 debugger interface DosPTrace() is a BLOCKING call and so on a GO command the return only occurs when the program being debugged next halts.  To force a halt when the user presses Ctrl+C in the debugger's screen group the debugger could install a minimal signal handler for Crtl+C and do the real work, such as issuing a STOP command, on the ERROR_INTERRUPT return.
you.  For example the OS/2 debugger interface DosPTrace() is a BLOCKING call
and so on a GO command the return only occurs when the program being debugged
next halts.  To force a halt when the user presses Ctrl+C in the debugger's
screen group the debugger could install a minimal signal handler for Crtl+C
and do the real work, such as issuing a STOP command, on the ERROR_INTERRUPT
return.


A third approach with SINGLE threaded C programs is to use setjmp() and
A third approach with SINGLE threaded C programs is to use setjmp() and longjmp().  The program sets a signal handler up and calls setjmp() to save the current execution state.  If a signal occurs longjmp() can be called by the signal handling procedure to return the thread to the location of the setjmp() call.  This approach works very well when the program is doing a lot of calculations, etc. as may then be hard to decide where in the code to add checks for some global 'I have been interrupted' flag.  Used like this it is nearly as good as the non-existent DosKillThread API which everyone wants so much, except of course only for ONE thread...
longjmp().  The program sets a signal handler up and calls setjmp() to save
the current execution state.  If a signal occurs longjmp() can be called by
the signal handling procedure to return the thread to the location of the
setjmp() call.  This approach works very well when the program is doing
a lot of calculations, etc. as may then be hard to decide where in the code
to add checks for some global 'I have been interrupted' flag.  Used like
this it is nearly as good as the non-existent DosKillThread API which
everyone wants so much, except of course only for ONE thread...




4) DosDevIOCtl holding signals
4) DosDevIOCtl holding signals


If thread 1 is held by a DosDevIOCtl call, then signals are held until the
If thread 1 is held by a DosDevIOCtl call, then signals are held until the call completes.  This is a problem!  Be especially careful with 'hidden' calls to DosDevIOCtl - I was caught out in a network server program which used thread 1 to listen for incoming connections and spawned a thread to deal with each one.  If Ctrl+C was pressed then the program would exit the next time a client tried to connect but not before.
call completes.  This is a problem!  Be especially careful with 'hidden'
calls to DosDevIOCtl - I was caught out in a network server program which used
thread 1 to listen for incoming connections and spawned a thread to deal with
each one.  If Ctrl+C was pressed then the program would exit the next time
a client tried to connect but not before.


This can be solved by creating another thread, or by using a non-blocking call
This can be solved by creating another thread, or by using a non-blocking call and waiting with DosSemWait.
and waiting with DosSemWait.




5) Signal handling and DLLs
5) Signal handling and DLLs


Each signal has at most one active handler.  For this reason it is in general
Each signal has at most one active handler.  For this reason it is in general NOT good practice to use signal handlers within DLLs since this involves co-operation with any signal handling which the main program may be performing.
NOT good practice to use signal handlers within DLLs since this involves
co-operation with any signal handling which the main program may be performing.




==Uses for signals==
==Uses for signals==
If you manage to avoid the traps described above then there are a few useful
If you manage to avoid the traps described above then there are a few useful things signals can be used for, espcially during development.
things signals can be used for, espcially during development.


This first, and obvious, use is to stop users killing programs in an
This first, and obvious, use is to stop users killing programs in an uncontrolled manner.  You can either stop them completely, add a prompt for confirmation or do some of your own tidying up first.
uncontrolled manner.  You can either stop them completely, add a prompt
for confirmation or do some of your own tidying up first.
Here is a very simple example:
Here is a very simple example:


 
#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_DOS
#include        <os2.h>
#define INCL_DOSERRORS
#include        <os2.h>
#include        <stdio.h>
 
#include        <conio.h>
#include        <stdio.h>
#include        <conio.h>
 
/*****************************************************************************/
 
/* sigint_handler: 'do nothing' gracefully with Ctrl+C and Ctrl+Break        */
/*****************************************************************************/
/* DosSemWait will return ERROR_INTERRUPT                                    */
/* sigint_handler: 'do nothing' gracefully with Ctrl+C and Ctrl+Break        */
/*****************************************************************************/
/* DosSemWait will return ERROR_INTERRUPT                                    */
/*****************************************************************************/
VOID FAR pascal _loadds sigint_handler (USHORT sig_arg, USHORT sig_num)
 
VOID FAR pascal _loadds sigint_handler (USHORT sig_arg, USHORT sig_num)
   {
   {
   /* keep compiler happy about unreferenced formal parameters */
   /* keep compiler happy about unreferenced formal parameters */
   sig_arg = sig_arg;
   sig_arg = sig_arg;
   sig_num = sig_num;
   sig_num = sig_num;
 
   /* acknowledge signal and resume the interrupted thread */
   /* acknowledge signal and resume the interrupted thread */
   DosSetSigHandler( NULL, NULL, NULL,
   DosSetSigHandler( NULL, NULL, NULL,
Line 223: Line 140:
                     SIG_CTRLC);
                     SIG_CTRLC);
   }
   }
 
 
/*****************************************************************************/
/*****************************************************************************/
/* process: do the work                                                      */
/* process: do the work                                                      */
/*****************************************************************************/
/*****************************************************************************/
 
VOID process(void)
VOID process(void)
   {
   {
   static ULONG ramsem = 0L;
   static ULONG ramsem = 0L;
   USHORT rc;
   USHORT rc;
 
   DosSemSet(&ramsem);
   DosSemSet(&ramsem);
   for (; ; )
   for (; ; )
Line 249: Line 166:




/*****************************************************************************/
/*****************************************************************************/
/* M A I N  P R O G R A M                                                    */
/* M A I N  P R O G R A M                                                    */
/*****************************************************************************/
/*****************************************************************************/
 
int main(int argc, char **argv)
int main(int argc, char **argv)
   {
   {
   int rc;                        /* return code                            */
   int rc;                        /* return code                            */
   PFNSIGHANDLER old_handler ;    /* receives value from DosSetSigHandler    */
   PFNSIGHANDLER old_handler ;    /* receives value from DosSetSigHandler    */
   USHORT old_action ;            /* receives value from DosSetSigHandler    */
   USHORT old_action ;            /* receives value from DosSetSigHandler    */
 
 
   /* keep compiler happy */
   /* keep compiler happy */
   argc = argc;
   argc = argc;
   argv = argv;
   argv = argv;
 
   /* set up signal handler for ctrl-c and break */
   /* set up signal handler for ctrl-c and break */
   rc = DosSetSigHandler((PFNSIGHANDLER)sigint_handler,
   rc = DosSetSigHandler((PFNSIGHANDLER)sigint_handler,
Line 270: Line 187:
                         SIGA_ACCEPT,
                         SIGA_ACCEPT,
                         SIG_CTRLC);
                         SIG_CTRLC);
 
   if (rc == 0)
   if (rc == 0)
       process();
       process();
 
   return rc;
   return rc;
   }
   }


This can be compiled as:
This can be compiled as:
         cl /AL /W3 /Zp /G2s flag.c -link os2
         cl /AL /W3 /Zp /G2s flag.c -link os2
Line 283: Line 199:
(I'm using Microsoft C6.0 but C5.1 or IBM C2 should take this too)
(I'm using Microsoft C6.0 but C5.1 or IBM C2 should take this too)


NOTE that I have marked the signal handler as _loadds.  Since this is a very
NOTE that I have marked the signal handler as _loadds.  Since this is a very simple procedure in a very simple program it is not actually necessary but in general it is obligatory since you will inherit the data segment which was active at the time of the signal.  Without this keyword, which tells the compiler to reload the expected value into the data segment on function entry, the signal handler may well work sometimes but not always...
simple procedure in a very simple program it is not actually necessary but
in general it is obligatory since you will inherit the data segment which was
active at the time of the signal.  Without this keyword, which tells the
compiler to reload the expected value into the data segment on function entry,
the signal handler may well work sometimes but not always...


Another use for signals is to handle global debugging variables.  I have for example used the user signal A with parameters 0 (off), 1 (on) and 2 (analyse) for simple inline profiling.  Signals are ideal for this purpose because they interrupt the signalled program, which is just what you want when you are trying to find out why your program is looping!


The only problem with this is that you need to know the process ID for the DosFlagProcess call.  There are three approaches:


Another use for signals is to handle global debugging variables.  I have for
1) A 'for' loop - send to all processes. Not recommended but can be quite exciting while it lasts....
example used the user signal A with parameters 0 (off), 1 (on) and 2 (analyse)
for simple inline profiling.  Signals are ideal for this purpose because they
interrupt the signalled program, which is just what you want when you are
trying to find out why your program is looping!
 
The only problem with this is that you need to know the process ID for the
DosFlagProcess call.  There are three approaches:
 
1) A 'for' loop - send to all processes. Not recommended but can be quite
  exciting while it lasts....


2) Use a program like the user group's KBN (kill by name) to find the PID
2) Use a program like the user group's KBN (kill by name) to find the PID


3) Ensure the program to be flagged writes its PID somewhere - add a call
3) Ensure the program to be flagged writes its PID somewhere - add a call to DosGetPID() and a print statement somewhere near the beginning of the program.
  to DosGetPID() and a print statement somewhere near the beginning of the
  program.




==Conclusion==
==Conclusion==


Although signals are extemely simple in outline, and thus very nice to use for
Although signals are extemely simple in outline, and thus very nice to use for quick and simple interprocess communication, there are unfortunately various complications when it comes to actually using them in a real program. It is worth experimenting with signal handlers, and I hope that by taking note of these possible pitfalls your programs will properly deal with Ctrl+C
quick and simple interprocess communication, there are unfortunately various
 
complications when it comes to actually using them in a real program.
[[Category:Languages_Articles]]
It is worth experimenting with signal handlers, and I hope that by taking note
of these possible pitfalls your programs will properly deal with Ctrl+C

Revision as of 22:41, 13 January 2012

By Roger Orr

OS/2 has a set of interprocess messages known generically as signals. They are the simplest form of inter-process communication under OS/2 and are invoked, for example, whenever you type Control+C to terminate a program.

OS/2 itself predefines some signals and their default signal handler, but allows this default behaviour to be modified and also supports a few user-defined signal types.

The commonest example of an application modifying the default signal handler is to prevent Control+C aborting a program; and I am partly writing this article because I have recently met two programs (one IBM, one MicroSoft) which do NOT handle Control+C properly, so it seems likely that a brief overview would be of use to some OS/2 programmers!

The basics

There are 7 defined signals: four are system signals and three are application signals.

The system signals are:- SIG_BROKENPIPE, SIG_KILLPROCESS, SIG_CTRLC and SIG_CTRLBREAK.

The first signal, SIG_BROKENPIPE, is used to inform that a connection to a pipe was broken. It will not be discussed further in this article.

The second signal, SIG_KILLPROCESS, is sent when DosKillProcess() is invoked, and the default signal handler exits the program.

The last two signals, SIG_CTRLC and SIG_CTRLBREAK, are sent by the keyboard handler when Control+C or Control+BREAK are pressed. The actual signal sent depends upon the keyboard mode - in ASCII mode (the default) SIG_CTRLC is sent by both Control+C and Control+BREAK, and in binary mode SIG_CTRLBREAK is sent by Control+BREAK. These signals can also be sent by using the DosSendSignal() function - note though that the target process must be a CHILD process of the signaller! As for SIG_KILLPROCESS the default signal handler exits the program.


The application signals are:- SIG_PFLG_A, SIG_PFLG_B and SIG_PFLG_C. These signals are sent by the DosFlagProcess() function, and an argument may also be supplied. The default signal handler ignores these signals.


When a signal is issued thread 1 of the process is INTERRUPTED and the signal handler is called. Once the signal handler has returned (if it does) execution of the interrupted thread will resume from the point of interruption. Note - thread 1 is the initial thread of the process, as opposed to any other threads created during execution with DosCreateThread. �

OS/2 signal handling functions

OS/2 provides two functions for modifying the default behaviour of signals: DosHoldSignal and DosSetSigHandler.


DosHoldSignal() is used to prevent signals while a critical section of code is being processed. This is typically used in conjuction with a semaphore to prevent concurrent access by other threads in the process. DosHoldSignal is used as well to ensure that thread 1 does not get interrupted and thus destroy the consistency of the operation. NOTE: signals will override even DosEnterCritSec() unless this function is used.


DosSetSigHandler() is used to change the handling for individual signals. Various flavours are available:

  • to set up an application signal handler
  • to ignore a signal
  • to return an error for a specific signal
  • to reset the signal handler to the default action

In addition, if you write a signal handler, it must call DosSetSigHandler when it is called to acknowledge the signal - this tells OS/2 that this signal can be sent again.


Complications of signal handling

Thus far it seems easy - you just write a simple procedure to handle a specific signal, call DosSetSigHandler() and away you go.

Unfortunately there are various possible problems.


1) Deletion of thread 1

OS/2 will only use the first thread for signal handling. If you terminate this thread then signals won't work any longer.

2) Access to shared resources

The first problem, which I touched on above, is that signals can occur at ANY time. This means being extremely careful about such things as semaphores in order to prevent programs hanging. For example, suppose a procedure printit() uses a semaphore to guarantee orderly access to the screen. If the signal handler tries to use this function problems may well occur:

       printit()
           DosSemRequest(hsem, -1);
           .
           .
signal --->
           printit()
               DosSemRequest(hsem, -1);

The semaphore request in procedure printit() will lock because the thread has already locked it.

This is a particular problem with the C runtime, especially the multithreaded DLL, which uses semaphores to serialise access.

The safest rule is ... keep the signal handler REALLY SIMPLE.

One solution is to create a SEPARATE thread, which waits for a semaphore to be cleared by the signal handler.


3) Poor documentation of ERROR_INTERRUPT

The first signal handler I wrote was simple: it acknowledged the Ctrl+C signal, wrote out a string with VioWrtTTY and returned. I tried testing it with a simple program which used getch(), and found that after pressing Ctrl+C and getting my message printed the program was exiting. I traced this to getch() returning -1, which was treated as end of file and hence of the program!

The reason for this behaviour is actually very simple: OS/2 was returning ERROR_INTERRUPT to the C runtime which it treated as an error.

Unfortunately as far as I can tell, ERROR_INTERRUPT is not documented - at least not in the manuals I have access to. So here is a brief explanation of what this error return means, and why it is used.

Firstly, remember that thread 1 is interrupted by OS/2 in order to call the signal handler. If thread 1 is actually blocked inside OS/2 (waiting for a semaphore to clear, perhaps) then OS/2 has a potentially difficult job once the signal handler has completed in attempting to return the thread to its original state - perhaps the semaphore which was being waited on has now been cleared. Rather than try and return to the same wait state internally, OS/2 returns a 'fake' error from the function - ERROR_INTERRUPT.

This means that if your program has a signal handler then you must be prepared for ANY OS/2 calls in thread 1 to return ERROR_INTERRUPT and to process this correctly. Again, be very careful if you call any of the C runtime functions since they may not handle the error code in the way you want.

The safest course is usually to reserve thread 1 for signal handling alone.

Another approach is to use this feature to work with you rather than against you. For example the OS/2 debugger interface DosPTrace() is a BLOCKING call and so on a GO command the return only occurs when the program being debugged next halts. To force a halt when the user presses Ctrl+C in the debugger's screen group the debugger could install a minimal signal handler for Crtl+C and do the real work, such as issuing a STOP command, on the ERROR_INTERRUPT return.

A third approach with SINGLE threaded C programs is to use setjmp() and longjmp(). The program sets a signal handler up and calls setjmp() to save the current execution state. If a signal occurs longjmp() can be called by the signal handling procedure to return the thread to the location of the setjmp() call. This approach works very well when the program is doing a lot of calculations, etc. as may then be hard to decide where in the code to add checks for some global 'I have been interrupted' flag. Used like this it is nearly as good as the non-existent DosKillThread API which everyone wants so much, except of course only for ONE thread...


4) DosDevIOCtl holding signals

If thread 1 is held by a DosDevIOCtl call, then signals are held until the call completes. This is a problem! Be especially careful with 'hidden' calls to DosDevIOCtl - I was caught out in a network server program which used thread 1 to listen for incoming connections and spawned a thread to deal with each one. If Ctrl+C was pressed then the program would exit the next time a client tried to connect but not before.

This can be solved by creating another thread, or by using a non-blocking call and waiting with DosSemWait.


5) Signal handling and DLLs

Each signal has at most one active handler. For this reason it is in general NOT good practice to use signal handlers within DLLs since this involves co-operation with any signal handling which the main program may be performing.


Uses for signals

If you manage to avoid the traps described above then there are a few useful things signals can be used for, espcially during development.

This first, and obvious, use is to stop users killing programs in an uncontrolled manner. You can either stop them completely, add a prompt for confirmation or do some of your own tidying up first. Here is a very simple example:

#define INCL_DOS
#define INCL_DOSERRORS
#include        <os2.h>

#include        <stdio.h>
#include        <conio.h>


/*****************************************************************************/
/* sigint_handler: 'do nothing' gracefully with Ctrl+C and Ctrl+Break        */
/* DosSemWait will return ERROR_INTERRUPT                                    */
/*****************************************************************************/

VOID FAR pascal _loadds sigint_handler (USHORT sig_arg, USHORT sig_num)
  {
  /* keep compiler happy about unreferenced formal parameters */
  sig_arg = sig_arg;
  sig_num = sig_num;

  /* acknowledge signal and resume the interrupted thread */
  DosSetSigHandler( NULL, NULL, NULL,
                    SIGA_ACKNOWLEDGE,
                    SIG_CTRLC);
  }


/*****************************************************************************/
/* process: do the work                                                      */
/*****************************************************************************/

VOID process(void)
  {
  static ULONG ramsem = 0L;
  USHORT rc;

  DosSemSet(&ramsem);
  for (; ; )
     {
     rc = DosSemWait(&ramsem, -1);
     if (rc == ERROR_INTERRUPT)
        {
        printf("\nBREAK - do you want to exit ? ");
        if (getch() == 'y')
           break;
        printf("\n");
        }
     }
  }


/*****************************************************************************/
/* M A I N  P R O G R A M                                                    */
/*****************************************************************************/

int main(int argc, char **argv)
  {
  int rc;                        /* return code                             */
  PFNSIGHANDLER old_handler ;    /* receives value from DosSetSigHandler    */
  USHORT old_action ;            /* receives value from DosSetSigHandler    */


  /* keep compiler happy */
  argc = argc;
  argv = argv;

  /* set up signal handler for ctrl-c and break */
  rc = DosSetSigHandler((PFNSIGHANDLER)sigint_handler,
                        &old_handler,
                        &old_action,
                        SIGA_ACCEPT,
                        SIG_CTRLC);

  if (rc == 0)
     process();

  return rc;
  }

This can be compiled as:

       cl /AL /W3 /Zp /G2s flag.c -link os2

(I'm using Microsoft C6.0 but C5.1 or IBM C2 should take this too)

NOTE that I have marked the signal handler as _loadds. Since this is a very simple procedure in a very simple program it is not actually necessary but in general it is obligatory since you will inherit the data segment which was active at the time of the signal. Without this keyword, which tells the compiler to reload the expected value into the data segment on function entry, the signal handler may well work sometimes but not always...

Another use for signals is to handle global debugging variables. I have for example used the user signal A with parameters 0 (off), 1 (on) and 2 (analyse) for simple inline profiling. Signals are ideal for this purpose because they interrupt the signalled program, which is just what you want when you are trying to find out why your program is looping!

The only problem with this is that you need to know the process ID for the DosFlagProcess call. There are three approaches:

1) A 'for' loop - send to all processes. Not recommended but can be quite exciting while it lasts....

2) Use a program like the user group's KBN (kill by name) to find the PID

3) Ensure the program to be flagged writes its PID somewhere - add a call to DosGetPID() and a print statement somewhere near the beginning of the program.


Conclusion

Although signals are extemely simple in outline, and thus very nice to use for quick and simple interprocess communication, there are unfortunately various complications when it comes to actually using them in a real program. It is worth experimenting with signal handlers, and I hope that by taking note of these possible pitfalls your programs will properly deal with Ctrl+C