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].
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.
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:
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).
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;
No matter the association style used, GNARL always uses the following tables indexed by the Interrupt_ID to handle interrupts.
Figure represents one protected procedure attached to signal SIGUSR1 in nested style (static style). The GNAT compiler associates two subprograms and to each protected subprogram (described in section ). As the reader can see, the run-time links the signal with the subprogram: the reference to the 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.
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>); private <Private_Data_Fields> 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; 5: 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); private <Private_Data_Fields> 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; 5: 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 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.
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.
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.
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 presents a simplified version of the Server Tasks Automaton.
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:
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.
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).
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.