Copyright (c) Hyperion Entertainment and contributors.

Exec Signals

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Exec Signals

Tasks often need to coordinate with other concurrent system activities (like other tasks and interrupts). This coordination is handled by Exec through the synchronized exchange of specific event indicators called signals.

This is the primary mechanism responsible for all inter-task communication and synchronization on the Amiga. This signal mechanism operates at a low level and is designed for high performance. Signals are used extensively by the Exec message system as a way to indicate the arrival of an inter-task message. The message system is described in more detail in Exec Messages and Ports.

Not for Beginners.
This section concentrates on details about signals that most applications do not need to understand for general Amiga programming. For a general overview of signals, see Introduction to Exec.

The Signal System

The signal system is designed to support independent simultaneous events, so several signals can occur at the same time. Each task has 32 independent signals, 16 of which are pre-allocated for use by the operating system. The signals in use by a particular task are represented as bits in a 32-bit field in its Task structure (<exec/tasks.h>). Two other 32-bit fields in the Task structure indicate which signals the task is waiting for, and which signals have been received.

Signals are task relative. A task can only allocate its own signals, and may only wait on its own signals. In addition, a task may assign its own significance to a particular signal. Signals are not broadcast to all tasks; they are directed only to individual tasks. A signal has meaning to the task that defined it and to those tasks that have been informed of its meaning.

For example, signal bit 12 may indicate a timeout event to one task, but to another task it may indicate a message arrival event. You can never wait on a signal that you did not directly or indirectly allocate yourself, and any other task that wishes to signal you must use a signal that you allocated.

Signal Allocation

As mentioned above, a task assigns its own meaning to a particular signal. Because certain system libraries may occasionally require the use of a signal, there is a convention for signal allocation. It is unwise ever to make assumptions about which signals are actually in use.

Before a signal can be used, it must be allocated with the AllocSignal() function. When a signal is no longer needed, it should be freed for reuse with FreeSignal().

BYTE AllocSignal( LONG signalNum );
VOID FreeSignal( LONG signalNum );

AllocSignal() marks a signal as being in use and prevents the accidental use of the same signal for more than one event. You may ask for either a specific signal number, or more commonly, you would pass -1 to request the next available signal. The state of the newly allocated signal is cleared (ready for use). Generally it is best to let the system assign you the next free signal. Of the 32 available signals, the lower 16 are reserved for system use. This leaves the upper 16 signals free for application programs to allocate. Other subsystems that you may call depend on AllocSignal().

The following example asks for the next free signal to be allocated for its use:

if (-1 == (signal = IExec->AllocSignal(-1)))
    IDOS->Printf("no signal bits available\n");
else
    {
    IDOS->Printf("allocated signal number %ld\n", signal);
    /* Other code could go here */
    IExec->FreeSignal(signal)
    }

The value returned by AllocSignal() is a signal bit number. This value cannot be used directly in calls to signal-related functions without first being converted to a mask:

mask = 1L << signal;

It is important to realize that signal bit allocation is relevant only to the running task. You cannot allocate a signal from another task. Note that functions which create a signal MsgPort will allocate a signal from the task that calls the function. Such functions include OpenWindow() and AllocSysObject(). For this reason, only the creating task may Wait() (directly or indirectly) on the MsgPort's signal. Functions which call Wait() include DoIO(), WaitIO() and WaitPort().

Waiting for a Signal

Signals are most often used to wake up a task upon the occurrence of some external event. Applications call the Exec Wait() function, directly or indirectly, in order to enter a wait state until some external event triggers a signal which awakens the task.

Though signals are usually not used to interrupt an executing task, they can be used this way. Task exceptions, described in Exec Interrupts, allow signals to act as a task-local interrupt.

The Wait() function specifies the set of signals that will wake up the task and then puts the task to sleep (into the waiting state).

ULONG Wait( ULONG signalSet );

Any one signal or any combination of signals from this set are sufficient to awaken the task. Wait() returns a mask indicating which signals satisfied the Wait() call. Note that when signals are used in conjunction with a message port, a set signal bit does not necessarily mean that there is a message at the message port.

See Exec Messages and Ports for details about proper handling of messages.

Because tasks (and interrupts) normally execute asynchronously, it is often possible to receive a particular signal before a task actually Wait()s for it. In such cases the Wait() will be immediately satisfied, and the task will not be put to sleep.

The Wait() function implicitly clears those signal bits that satisfied the wait condition. This effectively resets those signals for reuse. However, keep in mind that a task might get more signals while it is still processing the previous signal. If the same signal is received multiple times and the signal bit is not cleared between them, some signals will go unnoticed.

Be aware that using Wait() will break a Forbid() or Disable() state. Wait() cannot be used in supervisor mode or within interrupts.

A task may Wait() for a combination of signal bits and will wake up when any of the signals occur. Wait() returns a signal mask specifying which signal or signals were received. Usually the program must check the returned mask for each signal it was waiting on and take the appropriate action for each that occurred. The order in which these bits are checked is often important.

Here is a hypothetical example of a process that is using the console and timer devices, and is waiting for a message from either device and a possible break character issued by the user:

uint32 consoleSignal = 1L << ConsolePort->mp_SigBit;
uint32 timerSignal   = 1L << TimerPort->mp_SigBit;
uint32 userSignal    = SIGBREAKF_CTRL_C;    /* Defined in <dos/dos.h> */
 
uint32 signals = IExec->Wait(consoleSignal | timerSignal | userSignal);
 
if (signals & consoleSignal)
    IDOS->Printf("new character\n");
 
if (signals & timeOutSignal)
    IDOS->Printf("timeout\n");
 
if (signals & userSignal)
    IDOS->Printf("User Ctrl-C Abort\n");

This code will put the task to sleep waiting for a new character, or the expiration of a time period, or a Ctrl-C break character issued by the user. Notice that this code checks for an incoming character signal before checking for a timeout. Although a program can check for the occurrence of a particular event by checking whether its signal has occurred, this may lead to busy wait polling. Such polling is wasteful of the processor and is usually harmful to the proper function of the Amiga system. However, if a program needs to do constant processing and also check signals (a compiler for example) SetSignal(0,0) can be used to get a copy of your task's current signals.

ULONG SetSignal( ULONG newSignals, ULONG signalSet );

SetSignal() can also be used to set or clear the state of the signals. Implementing this can be dangerous and should generally not be done. The following fragment illustrates a possible use of SetSignal().

uint32 signals = SetSignal(0,0);           /* Get current state of signals */
 
if (signals & SIGBREAKF_CTRL_C)            /* Check for Ctrl-C.           */
    {
    IDOS->Printf("Break\n");               /* Ctrl-C signal has been set. */
    IExec->SetSignal(0, SIGBREAKF_CTRL_C)  /* Clear Ctrl-C signal.        */
    }

Looking for Break Keys

One common usage of signals on the Amiga is for processing a user break. The OS reserves 16 of a tasks 32 signals for system use. Four of those 16 signals are used to tell a task about the Control-C, D, E, and F break keys. An application can process these signals. Usually, only CLI-based programs receive these signals because the Amiga's console handler is about the only user input source that sets these signals when it sees the Control-C, D, E, and F key presses.

The signal masks for each of these key presses are defined in <dos/dos.h>:

SIGBREAKF_CTRL_C
SIGBREAKF_CTRL_D
SIGBREAKF_CTRL_E
SIGBREAKF_CTRL_F

Note that these are bit masks and not bit numbers.

Generating a Signal

Signals may be generated from both tasks and system interrupts with the Signal() function.

VOID Signal( struct Task *task, ULONG signalSet );

For example Signal(tc,mask) would signal the task with the specified mask signals. More than one signal can be specified in the mask. The following example code illustrates Wait() and Signal().

// signals.c
 
#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
static CONST_STRPTR VersTag = "$VER: signals 53.1 (20.6.2012)";
 
void subtaskcode(void);    /* prototype for our subtask routine */
 
struct Task *maintask = NULL;
uint32 mainsig = 0;
 
int main()
{
  BOOL Done = FALSE;
  BOOL WaitingForSubtask = TRUE;
 
  /* We must allocate any special signals we want to receive. */
  int8 mainsignum = IExec->AllocSignal(-1);
  if (mainsignum == -1)
    IDOS->Printf("No signals available\n");
  else
  {
    mainsig = 1U << mainsignum;        /* subtask can access this global */
    maintask = IExec->FindTask(NULL);  /* subtask can access this global */
 
    IDOS->Printf("We alloc a signal, create a task, wait for signals\n");
 
    struct Task *subtask = IDOS->CreateTaskTags("subtask", 0, subtaskcode, 16000, TAG_END);
 
    if (subtask == NULL)
        IDOS->Printf("Can't create subtask\n");
    else
    {
      IDOS->Printf("After subtask signals, press CTRL-C or CTRL-D to exit\n");
 
      while (!Done || WaitingForSubtask)
      {
        /* Wait on the combined mask for all of the signals we are
         * interested in.  All processes have the CTRL_C thru CTRL_F
         * signals.  We're also Waiting on the mainsig we allocated
         * for our subtask to signal us with.  We could also Wait on
         * the signals of any ports/windows our main task created ... */
 
        uint32 wakeupsigs = IExec->Wait(mainsig | SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
 
        /* Deal with all signals that woke us up - may be more than one */
        if (wakeupsigs & mainsig)
        {
          IDOS->Printf("Signalled by subtask\n");
          WaitingForSubtask = FALSE;   /* OK to kill subtask now */
        }
 
        if (wakeupsigs & SIGBREAKF_CTRL_C)
        {
          IDOS->Printf("Got CTRL-C signal\n");
          Done = TRUE;
        }
 
        if(wakeupsigs & SIGBREAKF_CTRL_D)
        {
          IDOS->Printf("Got CTRL-D signal\n");
          Done = TRUE;
        }
      }
    }
 
    IExec->FreeSignal(mainsignum);
  }
}
 
void subtaskcode(void)
{
  IExec->Signal(maintask, mainsig);
  IExec->RemTask(0);  // Remove myself from the system. 
}

Reserved System Signals

There are 16 signal bits which are reserved for system use. Applications are never allowed to use these signal bits unless explicitly documented (e.g. SIGF_SINGLE).

SIGB_ABORT

Reserved for system use.

SIGB_CHILD

Reserved for system use.

SIGB_SINGLE (SIGB_BLIT)

When a system function needs a Task to stop and IExec->Wait() for a single signal it will use SIGB_SINGLE.

This signal used to be named SIGB_BLIT when it was used to wait on the classic hardware blitter.

SIGB_INTUITION

Reserved for system use.

SIGB_NET

Reserved for system use.

SIGB_DOS

SIGB_DOS is currently used as the wait signal bit for the embedded message port in the process structure. This message port is initialised by the IDOS->CreateNewProc() function. This message port is used by default for DosPacket transactions via IDOS->DoPkt() and IDOS->WaitPkt() and it is also used for sending the initial ACTION_STARTUP DosPacket for DOS handlers. This message port is also where the workbench.library sends the initial struct WBStartup message to every process it starts.

SIGBREAKB_CTRL_C

The SIGBREAKB_CTRL_C signal bit is used extensively as the general purpose "Break" signal. It is used by all shell handler commands and many applications to invoke a normal exit of the program. It is up to all applications to follow this recommendation.

Applications should never crash or generally misbehave when receiving a SIGBREAKB_CTRL_C signal bit from any source.

SIGBREAKB_CTRL_D

The SIGBREAKB_CTRL_D signal bit is used mostly by the shell handler to stop execution of a script file or non-interactive stream. It may also be used by applications.

Applications should never crash or generally misbehave when receiving a SIGBREAKB_CTRL_D signal bit from any source.

SIGBREAKB_CTRL_E

The SIGBREAKB_CTRL_E signal bit is not currently used by the shell handler but may be used by some other handlers, OS subsystems or multi-process applications for general undefined inter process signalling.

Applications should never crash or generally misbehave when receiving a SIGBREAKB_CTRL_E signal bit from any source.

SIGBREAKB_CTRL_F

The SIGBREAKB_CTRL_E signal bit is not currently used by the shell handler but may be used by some other handlers, OS subsystems or multi-process applications for general undefined inter process signalling.

Applications should never crash or generally misbehave when receiving a SIGBREAKB_CTRL_C signal bit from any source.

Signalling with SIGB_SINGLE

Many of a task's 32 signal bits are reserved for the operating system's private use, but, like any good rule, there is an exception. One of these bits, the SIGB_SINGLE bit, can be useful to some applications, if used correctly.

Many system functions need to put their task to sleep while waiting for a single event, which requires using one of the task's signals. Rather than forcing each of these system functions to allocate a signal, then Wait(), then deallocate the signal, the operating system has permanently allocated one signal, the SIGB_SINGLE, for this type of signalling. When a system function needs stop a task to Wait() for a single signal, it can use SIGB_SINGLE.

The only purpose a program can use SIGB_SINGLE for is Wait()ing because the task cannot call any system functions while it is using SIGB_SINGLE. A program that calls system functions while using SIGB_SINGLE can cause itself and the operating system serious problems because the system functions can use SIGB_SINGLE as well. If a program calls a system function while using SIGB_SINGLE, two bad things can happen:

1) The errant task's event takes place before the system function waits on SIGB_SINGLE (or while the system function is waiting on SIGB_SINGLE). In this case, the system function will think its event has taken place because its signal became set. The errant task will never find out that its event has taken place, as the system function will clear the SIGB_SINGLE bit after Wait()ing on it.

2) The errant task's event and the system function's event take place while the system function is waiting on SIGB_SINGLE. In this case, the system function will function normally, clear the SIGB_SINGLE bit, and exit. The errant task will never know that its event has taken place.

Before Wait()ing on SIGB_SINGLE, clear it using SetSignal():

IExec->SetSignal(0, SIGF_SINGLE); // Note SIGF_SINGLE is the bit mask. SIGB_SINGLE is the signal bit.

This step is necessary because it is possible that the last system function that used the SIGB_SINGLE signal did not clear the SIGB_SINGLE bit.

Also, an application should not wait on other signals while it is waiting on SIGB_SINGLE. Waiting on other signals at the same time makes it possible for a program to wake up while the SIGB_SINGLE is still outstanding. If this happens, the program will still have to go back to sleep, which requires calling a system function.

SIGB_SINGLE Example

Below is a simple example of using the SIGB_SINGLE signal. It starts a child process and waits for that child process to signal the main process using the SIGB_SINGLE signal.

// This example program illustrates simple usage of the SIGB_SINGLE
// signal for "single shot" signalling. This signal is one of the
// system private signals, but applications can use it in certain
// cases, but only if used carefully. Specifically, applications
// should use it only to Wait() on, and using only that signal
// (applications cannot Wait() on other signals in the same
// Wait()). Not following these rules can cause serious system
// problems.
 
#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
#include <stdio.h>
 
void childprocesscode(void);      /* prototype for our childprocess routine. */
 
struct Process *mainprocess = NULL, *childprocess = NULL;
UBYTE childprocessname[] = "RKM_signal_childprocess";
 
BPTR output;
 
int main(int argc, char **argv)
{
    if (output = IDOS->Open("CONSOLE:", MODE_OLDFILE))  /* Open the console for the  */
                                                        /*            child process. */
    {
        mainprocess = (struct Process *)IExec->FindTask(NULL); /* childprocess can   */
                                                               /* access this global.*/
 
        if (childprocess = IDOS->CreateNewProcTags(
                    NP_Entry,       childprocesscode,  /* The child process  */
                    NP_Name,        childprocessname,
                    NP_Output,      output,
                    NP_FreeSeglist, FALSE,
                    NP_CloseOutput, TRUE,
                    NP_Child,       TRUE,
                    TAG_END))
        {
            IDOS->Printf("Main Process: Created a child process and waiting on SIGB_SINGLE.\n");
 
            IDOS->FFlush(IDOS->Output());  /* Make sure the Printf() above appears      */
                                           /* in the console window before the child    */
                                           /* process starts printing to the console. */
 
            IExec->SetSignal(0, SIGF_SINGLE);  /* Use SIGF_SINGLE only after */
                                               /* clearing it.               */
 
            /* Wake up the child. */
            IExec->Signal((struct Task *)childprocess, SIGBREAKF_CTRL_F);
 
            IExec->Wait(SIGF_SINGLE);  /* Only use SIGF_SINGLE for Wait()ing and */
                                       /* Wait on that signal alone!             */
 
            IDOS->Printf("Main Process: Received signal from child.\n");
        }
        else
            IDOS->Printf("Main Process: Can't create child process. Exiting.\n");
    }
    else
        IDOS->Printf("Main Process: Can't open CONSOLE:.  Exiting.\n");
}
 
int32 childprocesscode(void)     /* This function is what CreateNewProcTags() */
{                               /* loads as the child process.  This child   */
                                /* signals the parent using SIGF_SINGLE.     */
 
    /* Wait for a startup signal. This is to allow the parent process to
     * print its banner message and clear SIGF_SINGLE.
     */
    IExec->Wait(SIGBREAKF_CTRL_F);
 
    IDOS->Printf("Child Process: I'm alive and starting a 5 second TimeDelay()");
    IDOS->FFlush(IDOS->Output());
 
    for (uint32 x = 0; x < 5; x++)
    {
        IDOS->Delay(50);        /* Delay for 5 seconds, printing a */
        IDOS->Printf(" .");     /* dot during each second.         */
    }
 
    IDOS->Delay(50);
 
    IDOS->Printf(" Finished.\n");
    IDOS->Printf("Child Process: Signalling main process and exiting.  Bye.\n");
    IDOS->FFlush(IDOS->Output());
 
    IExec->Signal((struct Task *)mainprocess, SIGF_SINGLE);  /* Finished waiting, */
                                                             /* signal the main   */
                                                             /* process and exit  */
                                                             /* child process.    */
    return 0;
}

Function Reference

The following chart gives a brief description of the Exec functions that control task signalling. See the SDK for details about each call.

Exec Signal Function Description
AllocSignal() Allocate a signal bit.
FreeSignal() Free a signal bit allocated with AllocSignal().
SetSignal() Query or set the state of the signals for the current task.
Signal() Signal a task by setting signal bits in its Task structure.
Wait() Wait for one or more signals from other tasks or interrupts.