Chapter 6: Concurrency in ADA Programming Language

Concurrency in programming refers to the ability of a system to perform more than one task at the same time. Ada programming language shines when it comes to handling concurrency with its built-in support for multitasking. In the aerospace industry, the need for concurrency is inevitable. Systems often need to monitor multiple sensors, manage communications, and control various moving parts simultaneously.

Introduction to Concurrency

Concurrency is a key concept in many areas of software development, but it’s particularly important in the field of aerospace. As we know, in an aircraft, multiple systems need to operate simultaneously, such as navigation, weather monitoring, communications, and engine control, just to name a few. Ada’s powerful support for concurrent programming makes it an excellent choice for such scenarios.

The Ada programming language has built-in support for concurrent programming through the use of tasks. A task in Ada is similar to a thread in other programming languages, but with more sophisticated and safer facilities for synchronization and communication. Tasks are defined with the task keyword and can have their own declarations and executable parts.

In the context of aerospace software, tasks might be used to manage various subsystems of an aircraft such as navigation, communication, and monitoring, each running concurrently.

task Weather_Monitor is
   entry Start_Monitoring;
end Weather_Monitor;

task body Weather_Monitor is
begin
   loop
      accept Start_Monitoring do
         -- code to start monitoring weather
      end Start_Monitoring;
   end loop;
end Weather_Monitor;

In this example, we declare a task Weather_Monitor to illustrate a basic task structure in Ada. In a real aerospace application, the weather monitoring task could be responsible for interfacing with sensors, interpreting data, and raising alerts when necessary, all potentially in parallel with other tasks.

The beauty of Ada’s concurrency model is that it models real-world concurrency directly. This makes it particularly suitable for real-time and embedded systems, like those found in aerospace applications. Each task is an independent thread of control and tasks can be dynamically activated and run to completion.

Moreover, Ada’s concurrency support is carefully designed to be deterministic and analyzable, which are vital properties for high-integrity real-time systems often found in the aerospace domain. Tasks and protected objects, rendezvous, and entries are all part of Ada’s high-level, powerful facilities for managing concurrency and synchronization. These will be explored in more detail in the following sections.

Tasks and Protected Objects

A key element in managing concurrency in Ada is the concept of tasks and protected objects. They represent two of the primary tools used to manage the concurrent execution of code in Ada.

Tasks in Ada are units of execution that can run concurrently with other tasks. They can be thought of as similar to threads in some other programming languages, but with built-in features to simplify synchronization and communication. Each task in Ada is defined as a separate flow of control.

A task can be declared as follows:

task Flight_Control is
   entry Start_Control;
end Flight_Control;

task body Flight_Control is
begin
   loop
      accept Start_Control do
         -- Code to control flight
      end Start_Control;
   end loop;
end Flight_Control;

In the above code, Flight_Control is a task that can run concurrently with other tasks. The Start_Control entry allows another task to trigger the flight control functionality in a synchronized way.

While tasks help us to structure our program in concurrent flows, protected objects provide a way to safeguard shared data, ensuring that only one task can access them at a time, thereby avoiding race conditions.

Here is an example of a protected object:

protected Flight_Data is
   procedure Set_Altitude(New_Altitude : in Integer);
   function Get_Altitude return Integer;
private
   Altitude : Integer := 0;
end Flight_Data;

protected body Flight_Data is
   procedure Set_Altitude(New_Altitude : in Integer) is
   begin
      Altitude := New_Altitude;
   end Set_Altitude;

   function Get_Altitude return Integer is
   begin
      return Altitude;
   end Get_Altitude;
end Flight_Data;

In this example, Flight_Data is a protected object that encapsulates the Altitude data. The Set_Altitude and Get_Altitude subprograms are the only means to modify and access the Altitude data, ensuring safe concurrent access.

This combination of tasks and protected objects make Ada a powerful language for handling concurrency safely and effectively. The language’s design ensures that the complexities and pitfalls often associated with concurrent programming (such as race conditions and deadlocks) can be easily avoided, making it a perfect choice for the rigor and safety-critical demands of aerospace software.

Synchronization and Communication between Tasks

In a complex aerospace system, tasks often need to communicate with each other to coordinate their actions. They also need to synchronize their operations to ensure the correct order of execution. Ada provides several mechanisms for synchronization and communication between tasks.

A primary form of communication between tasks in Ada is through the concept of entries. An entry is a point of synchronization that allows one task to signal another that it has reached a specific point in its execution or that a certain condition has occurred.

For example, let’s say we have an Engine_Monitor task that checks the status of the aircraft engine and a Dashboard_Display task that updates the engine status on the cockpit dashboard.

task Engine_Monitor is
   entry Report_Status(Status : out Engine_Status);
end Engine_Monitor;

task Dashboard_Display is
   entry Update_Display(Status : in Engine_Status);
end Dashboard_Display;

task body Engine_Monitor is
   Status : Engine_Status;
begin
   loop
      -- Code to check engine status
      -- ...
      accept Report_Status(Status : out Engine_Status) do
         -- Pass the engine status to the calling task
         Status := --engine status-- ;
      end Report_Status;
   end loop;
end Engine_Monitor;

task body Dashboard_Display is
   Status : Engine_Status;
begin
   loop
      -- Wait for Engine_Monitor to report status
      Engine_Monitor.Report_Status(Status);

      accept Update_Display(Status : in Engine_Status) do
         -- Code to update display with new engine status
      end Update_Display;
   end loop;
end Dashboard_Display;

In the above code, the Engine_Monitor task checks the engine status and passes it to the Dashboard_Display task through the Report_Status entry. The Dashboard_Display task then updates the display with the new engine status via the Update_Display entry.

These rendezvous points form the primary method of synchronization between tasks in Ada. The task calling the entry will block until the task owning the entry accepts the call. This way, tasks can synchronize their activities by waiting for each other at these rendezvous points.

This ability to safely manage synchronization and communication between concurrent tasks makes Ada a powerful tool for building complex, high-integrity real-time systems, such as those commonly found in the aerospace industry.

Synchronization Example

Here’s a more complex, yet illustrative, example that features an aerospace Flight Management System (FMS).

Consider two tasks: Sensor_Data_Collection and Navigation_Control. The Sensor_Data_Collection task is responsible for gathering altitude and speed data, while Navigation_Control uses this data to control the aircraft’s course.

-- Define data type for sensor data
type Sensor_Data is record
   Altitude : Integer;
   Speed : Integer;
end record;

-- Define tasks and protected objects
task Sensor_Data_Collection is
   entry Get_Data(Data : out Sensor_Data);
end Sensor_Data_Collection;

task Navigation_Control is
   entry Process_Data(Data : in Sensor_Data);
end Navigation_Control;

-- Implementation of Sensor_Data_Collection
task body Sensor_Data_Collection is
   Current_Data : Sensor_Data;
begin
   loop
      -- Simulate data collection from sensors
      Current_Data.Altitude := -- Some function or sensor reading --;
      Current_Data.Speed := -- Some function or sensor reading --;

      accept Get_Data(Data : out Sensor_Data) do
         Data := Current_Data;
      end Get_Data;
   end loop;
end Sensor_Data_Collection;

-- Implementation of Navigation_Control
task body Navigation_Control is
   Current_Data : Sensor_Data;
begin
   loop
      -- Get sensor data from Sensor_Data_Collection task
      Sensor_Data_Collection.Get_Data(Current_Data);

      accept Process_Data(Data : in Sensor_Data) do
         -- Process data and control the aircraft's course
         -- This can be as complex as required by the system,
         -- involving calculations based on altitude and speed,
         -- updates to the aircraft's control surfaces,
         -- generation of pilot alerts, etc.
      end Process_Data;
   end loop;
end Navigation_Control;

In this example, Sensor_Data_Collection task collects sensor data and Navigation_Control task uses this data to control the flight. The Get_Data entry in Sensor_Data_Collection and Process_Data entry in Navigation_Control allow for synchronization and communication between these two tasks.

The Sensor_Data_Collection task continually collects sensor data and makes it available via its Get_Data entry. The Navigation_Control task calls this entry to retrieve the latest sensor data and then processes this data to control the aircraft’s course.

This code provides a simplified example of how tasks and entries can be used for synchronization and communication in Ada, key features that make Ada a valuable language for developing concurrent and real-time systems like an aerospace Flight Management System (FMS).

Real-Time Programming in Ada

Ada is one of the few programming languages that has native support for real-time programming. Real-time systems are used where the correct functioning of the system doesn’t just depend on the logical correctness of the software, but also on the timing of events. These characteristics are crucial for many aerospace systems, where delays can have catastrophic consequences.

In Ada, real-time systems are built around the concept of tasks and their scheduling. Tasks in Ada can be assigned a priority, with higher-priority tasks being executed before lower-priority tasks. This priority-based scheduling is deterministic, which means the behavior of the system can be predicted and analyzed accurately.

For example, let’s say we have an Aircraft_Control_System with two tasks: Flight_Control and Ground_Communication. Flight_Control is crucial for safety and needs to be highly responsive, so it is given a high priority. Ground_Communication is less time-critical, so it is given a lower priority:

task type High_Priority_Task is
   entry Start_Task;
end High_Priority_Task;

task type Low_Priority_Task is
   entry Start_Task;
end Low_Priority_Task;

task body High_Priority_Task is
begin
   loop
      accept Start_Task do
         -- Code for flight control goes here
      end Start_Task;
   end loop;
end High_Priority_Task;

task body Low_Priority_Task is
begin
   loop
      accept Start_Task do
         -- Code for ground communication goes here
      end Start_Task;
   end loop;
end Low_Priority_Task;

Flight_Control : High_Priority_Task;
Ground_Communication : Low_Priority_Task;

In this example, High_Priority_Task is assigned a higher priority than Low_Priority_Task. This means that, if both tasks are ready to execute, High_Priority_Task will be chosen over Low_Priority_Task.

Moreover, Ada supports the Ravenscar and Jorvik tasking profiles. These are subsets of the full Ada tasking model that are designed for high-integrity and real-time systems. They provide deterministic behavior and are amenable to static analysis, properties that are very important in aerospace and other safety-critical systems.

In addition, Ada provides facilities for handling real-time clocks, timeouts, and delays, as well as for interrupt handling. These features provide fine-grained control over the system timing and responsiveness, making Ada a robust choice for real-time programming in aerospace systems.

Example

I can provide an example of a simple real-time Flight Management System (FMS) implemented in Ada. This system will have two tasks: Flight_Control and Ground_Communication, each running at different priorities. Let’s also add a Sensor_Data_Collection task, which gathers data from sensors at a fixed rate.

-- Import required packages
with Ada.Real_Time; use Ada.Real_Time;

-- Define data type for sensor data
type Sensor_Data is record
   Altitude : Integer;
   Speed : Integer;
end record;

-- Define the task types
task type Sensor_Data_Collection with
   Dispatching_Policy => Ada.Real_Time.Rate_Monotonic_Policy,
   Priority => 1 is
   entry Get_Data(Data : out Sensor_Data);
end Sensor_Data_Collection;

task type Flight_Control with
   Dispatching_Policy => Ada.Real_Time.Rate_Monotonic_Policy,
   Priority => 3 is
   entry Process_Data(Data : in Sensor_Data);
end Flight_Control;

task type Ground_Communication with
   Dispatching_Policy => Ada.Real_Time.Rate_Monotonic_Policy,
   Priority => 2 is
   entry Send_Data(Data : in Sensor_Data);
end Ground_Communication;

-- Implementation of Sensor_Data_Collection
task body Sensor_Data_Collection is
   Current_Data : Sensor_Data;
   Next_Time : Time := Clock;
begin
   loop
      -- Simulate data collection from sensors
      Current_Data.Altitude := -- Some function or sensor reading --;
      Current_Data.Speed := -- Some function or sensor reading --;

      accept Get_Data(Data : out Sensor_Data) do
         Data := Current_Data;
      end Get_Data;
      
      -- Ensure the task runs at a fixed rate
      Next_Time := Next_Time + Milliseconds(10);
      delay until Next_Time;
   end loop;
end Sensor_Data_Collection;

-- Implementation of Flight_Control
task body Flight_Control is
   Current_Data : Sensor_Data;
begin
   loop
      -- Get sensor data from Sensor_Data_Collection task
      Sensor_Data_Collection.Get_Data(Current_Data);

      accept Process_Data(Data : in Sensor_Data) do
         -- Process data and control the aircraft's course
         -- This can be as complex as required by the system
      end Process_Data;
   end loop;
end Flight_Control;

-- Implementation of Ground_Communication
task body Ground_Communication is
   Current_Data : Sensor_Data;
begin
   loop
      -- Get sensor data from Sensor_Data_Collection task
      Sensor_Data_Collection.Get_Data(Current_Data);

      accept Send_Data(Data : in Sensor_Data) do
         -- Code to send the data to ground control
      end Send_Data;
   end loop;
end Ground_Communication;

-- Initialize tasks
Sensor_Task : Sensor_Data_Collection;
Flight_Task : Flight_Control;
Ground_Task : Ground_Communication;

In this example, the Sensor_Data_Collection task collects sensor data at a fixed rate every 10 milliseconds and provides this data to the Flight_Control and Ground_Communication tasks.

These tasks run at different priorities, with Flight_Control having a higher priority than Ground_Communication. This ensures that flight control, being a more time-critical operation, has precedence over ground communication.

This code demonstrates real-time programming in Ada by ensuring tasks execute at certain priorities and the sensor data collection task runs at a fixed rate. These concepts are fundamental in real-time and safety-critical systems like a Flight Management System (FMS) in the aerospace industry.

Chapter 6: Concurrency in ADA Programming Language
Scroll to top
error: Content is protected !!