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 Message_Processor.

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 Suspension_Object to 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.


About the Author

Pat Rogers has been a computing professional since 1975, primarily working on microprocessor-based real-time applications in Ada, C, C++ and other languages, including high-fidelity flight simulators and Supervisory Control and Data Acquisition (SCADA) systems controlling hazardous materials. Having first learned Ada in 1980, he was director of the Ada9X Laboratory for the U.S. Air Force’s Joint Advanced Strike Technology Program, Principle Investigator in distributed systems and fault tolerance research projects using Ada for the U.S. Air Force and Army, and Associate Director for Research at the NASA Software Engineering Research Center. He has B.S. and M.S. degrees in computer systems design and computer science from the University of Houston and a Ph.D. in computer science from the University of York, England. As a member of the Senior Technical Staff at AdaCore, he specializes in supporting real-time/embedded systems developers, creates and provides training courses, and is project leader and a developer of the GNATbench Eclipse plug-in for Ada. He also has a 3rd Dan black belt in Tae Kwon Do and is founder of the AdaCore club “The Wicked Uncles”.