next up previous contents index
Next: Summary Up: Interrupts Previous: Ada Model of Interrupts   Contents   Index


GNAT Implementation

To foster a simple, efficient and multi-platform implementation, GNAT reuses the POSIX support for signals and adds the minimum set of run-time subprograms required to achieve the Ada semantics. This work is simplified because POSIX signals are delivered to individual threads in a multi-threaded process using much of the same semantics as for delivery to a single-threaded process [GB92, Section 5.1].

POSIX Signals

A POSIX signal6.1 is a form of software interrupt which can be generated in several ways. A signal may be generated:

Each POSIX thread has a signal mask: when a signal is generated for a thread and the thread has the signal masked, the signal remains pending until the thread unmasks it; the interface for manipulating the thread signal mask is pthread_sigmask. Only one pending instance of a masked signal is required to be retained; that is, if a signal is generated N times while it is masked the number of signal instances that are delivered to the thread when it finally unmasks the signal may be any number between 1 and N.

Each POSIX signal is associated with some action. The action may be to ignore the signal, terminate the process, continue the process, or execute a call to user-defined handler function (asynchronously and preemptively with respect to normal execution of the process). POSIX.1 specifies a default action for each signal. For most signals the application may override the default action by calling the function sigaction. The use of asynchronous handler procedures for signals is not recommended for POSIX threads, because the POSIX thread synchronization operations are not safe to be called within an asynchronous signal handler; instead, POSIX.1c recommends use of the pthread_sigwait function, which ``accepts'' one of a specified set of masked signals.

Reserved Signals

The definitions of ``reserved'' differs slightly between the ARM and POSIX. ARM specifies [AAR95, section C.3(1)]:

The set of reserved interrupts is implementation defined. A reserved interrupt is either an interrupt for which user-defined handlers are not supported, or one which already has an attached handler by some other implementation-defined means. Program unit can be connected to non-reserved interrupts.

POSIX.5b/.5c specifies further [s-intman.adb]:

Signals which the application cannot accept, and for which the application cannot modify the signal action or masking, because the signals are reserved for use by the Ada language implementation. The reserved signals defined by this standard are: If the implementation supports any signals besides those defined by this standard, the implementation may also reserve some of those.

The signals defined by POSIX.5b/5c that are not specified as being reserved are SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2, SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGIO, SIGURG and all the real-time signals.

The GNAT FSU Linux implementation handles 32 signals. In this case the reserved signals are:

 Number   Name      REASON                 Description
 ------ ----------  ------    --------------------------------------------
   2    * SIGINT      GNAT      Abort (used for CTRL-C)
   4    * SIGILL     POSIX (HW) Illegal Instruction
   5    * SIGTRAP     GNAT      Trace trap
   6    * SIGABRT     GNAT      Tasks abortion
   7    * SIGBUS     POSIX (HW) Bus error
   8    * SIGFPE     POSIX (HW) Floating Point Exception
   9      SIGKILL    POSIX      Abort (kill) 
  11    * SIGSEGV    POSIX (HW) Segmentation Violation
  14      SIGALRM    POSIX      Alarm Clock
  19      SIGSTOP               Stop
  20    * SIGTSTP     GNAT      User stop requested from tty
  21    * SIGTTIN     GNAT      Background tty read attempted
  22    * SIGTTOU     GNAT      Background tty write attempted
  26      SIGVTALRM             Virtual timer expired
  27    * SIGPROF     GNAT      Profiling timer expired
  31      SIGUNUSED             Unused signal

Signals marked with * are not allowed to be masked by the GNAT Run-Time. SIGINT can not be masked because it is used to terminate the Ada program when the CTRL-C sequence is pressed in the terminal that is controlling the process6.2. SIGILL, SIGFPE and SIGSEV can not be masked because they are used by the CPU to notify errors to the run-time. SIGTRAP is used by GNAT to enable debugging on multi-threaded applications. SIGABRT can not be masked because it is used by GNAT to implement the tasks abortion (described in chapter [*]). SIGTTIN, SIGTTOU and SIGTSTP are not allowed to be masked so that background processes and IO behaves as normal C applications. Finally, SIGPROF can not be masked to avoid confusing the profiler.


Figure [*] presents the overall architecture of the GNAT implementation. In the GNARL level two package hierarchies are used to handle interrupts: the hierarchy of the standard Ada package (left side of the figure) and the hierarchy of the GNARL System package (right side of the figure).

Figure 6.1: Architecture of the Implementation.

Package Ada.Interrupts is the standard Ada package described in section [*] (package used to attach and detach interrupt handlers in the non-nested style). Child package Ada.Interrupts.Names maps the high level Ada interrupts to the low level POSIX signals defined in package System.OS_Interface (35 Ada interrupts are mapped here to 32 POSIX signals).

Package System.Interrupts encapsulates the GNARL implementation of the signal handlers. It is a logical extension of the body of the standard package Ada.Interrupts. It is made a child of System to access various runtime system internal data and operations. Package System.Interrupt_Management associates a signal handler to the POSIX signals linked to Ada exceptions (SIGPFE and SIGILL signals raise Ada Constrained_Error exception and SIGSEGV signal raises Ada Storage_Error exception). Child package Operations is a low level package which issues calls to the GNULL level.

Let's see the type definitions associated with the high level Ada.Interrupt_ID data type. This simple example allows the reader to see the basic relations between these packages. Ada.Interrupt_ID type definition is based on the corresponding definition at System.Interrupts, which is based on the Interrupt_ID data type at System.Interrupt_Management; this data type is finally based on the corresponding SIGNAL type definition at the low-level package System.OS_Interface.

   type Ada.Interrupt_ID
      is new System.Interrupts.Ada_Interrupt_ID;

   type System.Interrupts.Ada_Interrupt_ID
      is new System.Interrupt_Management.Interrupt_ID;

   type System.Interrupt_Management.Interrupt_ID
      is new System.OS_Interface.Signal;

Basic Data Structures

No matter the association style used, GNARL always uses the following tables indexed by the Interrupt_ID to handle interrupts.

Figure 6.3: Table of User-Defined Interrupt-Handlers.

Figure [*] represents one protected procedure attached to signal SIGUSR1 in nested style (static style). The GNAT compiler associates two subprograms $P$ and $N$ to each protected subprogram (described in section [*]). As the reader can see, the run-time links the signal with the $P$ subprogram: the reference to the $P$ subprogram is stored in the corresponding field of the table, and the Static field is set to True to remember that it is a nested style association.

Attachment of Interrupt-Handling Protected Procedures

In the nested style the run-time must attach the UDIP to the signal when the protected object is elaborated. Thus the compiler adds one call to GNARL subprogram Install_Handlers6.6 to the elaboration code of the protected object. This subprogram saves the previous handlers in one additional field of the object (Previous_Handlers6.7) and installs the new handlers.

To avoid penalizing all protected objects with this additional field, GNARL uses one data type for handling protected objects with no interrupt handlers (Protection_Entries6.8, described in section [*]), one type extension for protected objects with nested style interrupt handlers (Static_Interrupt_Protection6.9), and another type extension for protected objects with non-nested style interrupt handlers (Dynamic_Interrupt_Protection6.10). (In this latter case the type extension is only defined for homogeneity because the run-time does not add any additional field to the basic data type). Let's see the compiler transformation of protected objects for these two latter cases:

Original Ada Code:

      protected type PO (Discriminants) is
         procedure Handler;
         function  ...
         entry     ...
         pragma Attach_Handler (Handler, <INT-Number>);
      end PO;

Code transformation done by the GNAT compiler:
   1: type poV (Discriminants) is new Limited_Controlled with record
   2:    <Private_Data_Fields>
   3:    _object : aliased GNARL.Static_Interrupt_Protection (<Num>);
   4: end record;
   6: procedure Finalize (O : poV) is
   7: begin
   8:    --  Raise Program_Error to the queued tasks.
   9:    ...
  10: end Finalize;

Let us now consider the compiler transformation of protected objects with non-nested style interrupt handlers.

Original Ada Code:

      protected type PO (Discriminants) is
         procedure Handler;
         function  ...
         entry     ...
         pragma Interrupt_Handler (Handler);
      end PO;

Code transformation done by the GNAT compiler:
   1: type poV (Discriminants) is new Limited_Controlled with record
   2:    <Private_Data_Fields>
   3:    _object : aliased GNARL.Dynamic_Interrupt_Protection (<Num>);
   4: end record;
   6: procedure Finalize (O : poV) is
   7: begin
   8:    --  Raise Program_Error to the queued tasks.
   9:    ...
  10: end Finalize;

If we compare both translations, in line 3 we find the only difference: the data type used to define the _object field.

In case of nested style association, during the finalization of the protected object the run-time needs to restore the previous handlers ( Install_Handlers does this work). In the non-nested style, nothing special needs to be done since the default handlers will be restored as part of task completion which is done just before global finalization.

In order to verify that all the non-nested style interrupt procedures have been annotated with pragma Interrupt_Handler ([AAR95, section C.3.2] requirement) the compiler adds calls to the GNARL subprogram Register_Interrupt_Handler6.11 to register these interrupt procedures in a GNARL single-linked list. The Head and Tail of this list are stored in two GNARL variables6.12(cf. Figure [*]). Every node keeps the address of one protected procedure associated with an interrupt in non-nested style. For simplicity, a single access to a protected procedure has been represented; however, each node has the access to its corresponding $P$ subprogram. Before the attachment of one non-nested style interrupt handler to one signal, GNARL traverses this list to verify that the protected procedure is registered in the list; otherwise it raises the exception Program_Error.

Figure 6.4: List of Interrupt Handlers in Non-Nested Style.

Interrupts Manager: Basic Approach

The GNAT run-time uses one Interrupts Manager6.13 task to serialize the execution of subprograms involved in the management of signals: attachment, detachment, replacement, etc. Figure [*] presents a simplified version of the automaton implemented by the Interrupt Manager. For simplicity we have considered only two basic operations: Binding and Unbinding User-Defined Interrrupt Procedures (UDIP) to interrupts.

Figure 6.5: Basic Automaton Implemented by the Interrupts Manager.

First the automaton calls GNARL subprogram Make_Independent6.14 to do the Interrupt Manager Task independent of its masters. GNARL Independent tasks are associated with master 0, and their ATCBs are not registered in All Tasks List (described in section [*]); thus they last until the end of the program. After the signal mask is set, the automaton goes to one state in which it waits for the next signal management operation.

Server Tasks: Basic Approach

The Ada run-time must provide a thread to execute the UDIP. There is a choice between dedicating one server task for all signals and providing a server task for each signal. The former approach looks attractive, since it saves run-time space, but it blocks other signals during the protected procedure call. This may result in delayed or lost signals. For this reason, GNARL provides a separate Server Task6.15 for each signal [DIBM96].

Instead of create/abort Server Tasks when the user-defined interrupt handlers are attached/detached, GNARL keeps them alive until the program terminates. Thus they are reused by all UDIPs associated with the same interrupt during the life of the program. The run-time has a Server_ID Table6.16 which saves Server Tasks references (cf. Figure [*]).

Figure 6.6: Server Tasks Signal Handling.

Figure [*] presents a simplified version of the Server Tasks Automaton.

Figure 6.7: Basic Automaton Implemented by the Server Tasks.

Interrupt-Manager and Server-Tasks Integration

Previous sections have been concerned with the basic functionality of the Interrupt Manager Task and the Server Tasks. However, the GNARL implementation is a little more complex because:

  1. Ada nested style of interrupts implies that UDIPs are dynamically attached and detached to signals in the elaboration and finalization of protected objects. Therefore:

    1. If no UDIP is registered GNARL must take the default POSIX action, and the simplified implementation of the Interrupt Manager did not consider POSIX default actions (cf. Figure [*]).

    2. When one UDIP is registered the signal is programmed to be handled by the UDIP. Following UDIPs registered to the same signal replace previous UDIPs.

    3. If all UDIPs are detached, GNARL must again take the default POSIX action. The previous implementation can not achieve this effect so long as the Server Task is sitting on the sigwait. Even if the POSIX sigaction command is used to set the asynchronous signal action to the default, that action will not be taken unless the signal is unmasked, and GNARL can not unmask the signal while the Server Task is blocked on sigwait because in POSIX.1c the effect is undefined. Therefore, GNARL must wake up the Server Task and cause it to wait on some operation instead for which it is safe to leave the signal unmasked, so that the default action can be taken [DIBM96].

  2. GNARL must protect data structures shared by the Interrupts Manager Task and the Server Tasks. Therefore, some locks must be added.

The second requirement (locks) is easy to solve by means of POSIX mutexes. However, the first requirement is more complex. So let's focus our attention on the GNARL solution of the first requirement.

In order to better understand the GNARL implementation, we need to simplify the Server Tasks Automaton to its main states:

In order to notify the automaton that it must jump from State 1 to State 2 GNARL uses one POSIX Condition Variable; in order to force the automaton to jump from State 2 (waiting in the POSIX sigwait operation) to State 1 the POSIX signal SIGABORT is used (this signal is used to kill the POSIX thread, and thus forces the Server Task to return from the POSIX sigwait operation). Figure [*] presents this automaton.

Figure 6.8: Simplified Server Tasks Automaton.

If we add these new transitions to our basic Task Server Automaton (cf. Figure [*]) we have the real automaton implemented in GNARL (cf. Figure [*]). In order to help the reading of the automaton all the states have been numbered. Inside dotted rectangles we find the states associated with the simplified states of the previous example (State_1 and State_2).

Figure 6.9: Server Tasks Automaton.

After the initializations (states numbered 1 to 3), the automaton verifies if any UDIP has been registered by the Interrupt Manager (state 4). Initially, because no UDIP has been registered, it takes the POSIX default action (state 9) and waits in the Condition Variable (cond_wait, state 10) until some UDIP is registered by the Interrupt_Manager.

When any UDIP is registered, the Interrupt_Manager signals the Condition Variable and the Server Task Automaton jumps to state 4, checks if some UDIP has been registered (now this evaluates to True) and jumps to state 5 to wait for the next signal occurrence. When the signal is received, it again checks if the UDIP is still registered (state 6), because it may have been removed by the Interrupt Manager while the automaton was waiting for the signal. Then it calls the UDIP (state 7) and again jumps to state 4.

While the Server Task is in state 5 waiting for the signal occurrence, it may happen that all UDIPs have been removed the Interrupt Manager. In this case the Interrupt Manager sends the SIGABRT signal to the Server Task to force it to jump to state 9. This signal wakes up the Server Task Automaton, which jumps to state 8 to reply to the Interrupt Manager with the same signal to inform it is not in state 5 (waiting for the signal). After this notification the automaton jumps to state 4 and, because no UDIP is found, it jumps to state 9.


... signal6.1
The contents of this section are a summary of [DIBM96, section 2].
... process6.2
By keeping SIGINT reserved, the programmer allows the user to do Ctrl-C but, in the same way, disable the ability of handling this signal in the Ada program. GNAT Pragma Unreserve_All_Interrupts [BG01] gives the programmer the ability to change this behavior.
... Signals6.3
... table6.4
In the GNARL sources it is declared as variable just to be able to initialize it in the package body to aid portability.
... Table6.5
... variables6.12
System.Interrupts.Registered_Handler_Head and System.Interrupts.Registered_Handler_Tail
... Manager6.13
... Task6.15
... Table6.16

next up previous contents index
Next: Summary Up: Interrupts Previous: Ada Model of Interrupts   Contents   Index
(c) Javier Miranda. Canary Islands (Spain), 2002. Version 1.0