Gem #14: Interrupt Handling Idioms (Part 2)
by Pat Rogers —AdaCore
Let's get started…
Last week we introduced this topic and explored the first of the two designs. The gist of it is that we want to implement a multi-level response to interrupts, in which a protected procedure implements the first-level handler and a task implements the secondary level handling outside of the interrupt context. We use serial communications over a UART (Universal Asynchronous Receiver Transmitter) as the example. In both designs we encapsulate the two levels of the interrupt handling code inside the body of a package named
Second Design Idiom
The second idiom is similar to the first, in that a protected object encapsulates the interrupt handling procedure, but this second design uses a
Suspension_Object, instead of a protected entry, to signal the task. The type
Suspension_Object is declared within package
Ada.Synchronous_Task_Control and is essentially a boolean flag with synchronization. A single task can await a given
Suspension_Object becoming “true” and will suspend until another task sets that state. The resulting structure for the package body is as follows:
with UART; with System; with Ada.Interrupts; with Ada.Synchronous_Task_Control; package body Message_Processor is Port : UART.Device; package STC renames Ada.Synchronous_Task_Control; Message_Ready : STC.Suspension_Object; Buffer : Contents (Message_Size); Length : Natural := 1; protected Receiver is ... protected body Receiver is ... begin UART.Configure (Port, UART_Data_Arrival, UART_Priority); UART.Enable_Interrupts (Port); end Message_Processor;
The significant differences in structure include the new
Suspension_Object and the buffer containing the most recently received message, moved from within the protected object to the package body declarative part.
The protected object Receiver no longer declares an entry because it uses
Message_Ready to signal the task. No local data are required either. Only the interrupt handling procedure is required.
protected Receiver is pragma Interrupt_Priority (UART_Priority); private procedure Handle_Incoming_Data; pragma Attach_Handler (Handle_Incoming_Data, UART_Data_Arrival); end Receiver;
The body of the procedure is identical to that of the other design idiom except that it sets the
True instead of setting a local boolean variable used in an entry barrier.
protected body Receiver is procedure Handle_Incoming_Data is begin -- as before ... STC.Set_True (Message_Ready); end Handle_Incoming_Data; end Receiver;
Now the task does the work that the entry did in the other design. It first waits for
Message_Ready to be
True, suspending if necessary. Then it copies the buffer content to the local Message object and resets for the next arrival. Note that when
Suspend_Until_True executes, the specified
Suspension_Object is automatically set to
False on exit.
task body Process_Messages is Next_Message : aliased Message (Size => Message_Size'Last); begin -- any initialization code here... loop STC.Suspend_Until_True (Message_Ready); -- capture the new message in Buffer Next_Message.Value (1..Length) := Buffer (1..Length); Next_Message.Length := Length; -- reset for next arrival Length := 1; -- process Next_Message ... end loop; end Process_Messages;
Design Idiom Comparison
The first idiom, in which an entry is used to signal the second-level handler, is a better design from a software engineering point of view: functionality is grouped with the manipulated data (the buffer is local to the protected object), condition synchronization is directly expressed via the entry barrier and is not implemented by the calling task, and communication is accomplished via the entry parameters. All these characteristics result in a more maintainable, robust implementation.
However, the semantics of protected objects implies a run-time cost that, albeit relatively small, is greater than that of a
Suspension_Object. The second idiom is very likely to be faster than the first, assuming a decent implementation of the type
Suspension_Object. However, placing all the code within the package body limits the deleterious effects of the resulting structure.
The interrupt handling and management facilities provided by Ada are defined in the Systems Programming Annex, section C.3, of the Reference Manual. Support is extensive and is well worth studying.