next up previous contents index
Next: GNAT Implementation Up: Protected Objects Previous: Protected Objects   Contents   Index

Subsections

The Ada 95 Protected Object

A protected object in Ada encapsulates data items and provides access to them only via protected subprograms on protected entries. The language guarantees that these subprograms and entries will be executed in a manner that ensures that the data is updated under mutual exclusion [BW98, chapter 7.1].

A protected unit may be declared as a type or as a single instance. In this latter case it is said that the corresponding type is anonymous. A protected unit has a specification and a body. The specification has the following syntax [AAR95, section 9.4].

   protected_type_declaration ::=
      protected type defining_identifier [known_discriminant_part] is
      protected_definition;

   single_protected_declaration ::=
      protected defining_identifier is protected_definition;

   protected_definition ::=
      { protected_operation_declaration }
   [ private
      { protected_element_declaration } ]
   end [protected_identifier]

   protected_operation_declaration ::=
        subprogram_declaration
      | entry_declaration
      | aspect_clause

   protected_element_declaration ::=
        protected_operation_declaration
      | component_declaration

Thus a protected type has an interface that can contain functions, procedures and entries. As with tasks and records, the discriminants can only be a discrete or access type [BW98, chapter 7.1]. The body is declared using the following syntax:

   protected_body ::=
     protected body defining_identifier is
        { protected_operation_item }
     end [protected_identifier];

   protected_operation_item ::=
       subprogram_declaration
     | subprogram_body
     | entry_body
     | aspect_clause

A protected type is a ``limited type'', and therefore there are no predefined assignment or comparison operators (the same is true for task types).

A protected procedure provides mutually exclusive read/write access to the data encapsulated. Protected functions provide concurrent read-only access the encapsulated data. This means that many function calls can be executed simultaneously. However, calls to a protected function are still executed mutually exclusive with calls to a protected procedure. The core language does not define the order in which tasks waiting to execute protected functions and protected procedures are executed. If, however, the Real-Time Systems Annex [AAR95, Annex D] is being supported, certain assumptions can be made about the order of execution [BW98, chapter 7.2].

A protected entry is similar to a protected procedure in that it is guaranteed to execute in mutual exclusion and has the read/write access the encapsulated data. However, a protected entry is guarded by a boolean expression (called a Barrier) inside the body of the protected object; if this barrier evaluates to false when the entry call is made, the calling task is suspended until the barrier evaluates to true and no other task are currently active inside the protected object. Hence protected entry calls can be used to implement condition synchronization [BW98, chapter 7.3].

       protected type Signal_Object is
          entry Wait;
          procedure Signal;
          function Is_Open return Boolean;
       private
          Open : Boolean := False;
       end Signal_Object;

       protected body Signal_Object is

          entry Wait when Open is
          begin
             Open := False;
          end Wait;

          procedure Signal is
          begin
             Open := True;
          end Signal;

          function Is_Open return Boolean is
          begin
             return Open;
          end Is_Open;

       end Signal_Object;

The state of the object must be put in the private part of the specification. The reason is that the protected object interface must provide all the information required by the compiler to allocate the required memory in an efficient manner.

Clearly, it is possible for more than one task to be queued on a particular protected entry. As with task queues, a protected entry is, by default, ordered in a first-in-first-out fashion; however, if the Real-Time Systems Annex is being supported, other queuing disciplines are allowed [BW98, chapter 7.3].


Entry Calls and Barriers

To issue a call to a protected object, a task simply names the object and the required subprogram or entry. As with task entry calls, the caller can use the select statement to issue a timed or conditional entry call [BW98, chapter 7.4] (timed conditional entry call will be analyzed in a separate chapter).

   Object_Name.Entry_Name (Parameters)


   select
      Object_Name.Entry_Name (Parameters);
      <<Statements>>;
   else
      <<Statements>>;
   end select;

When a call on a protected procedure or protected entry is executed, the barrier is evaluated; if the barrier is closed (evaluates to False), the call is queued. When the execution of a protected procedure or entry is completed, all the barriers are re-evaluated and, potentially, entry bodies are executed. The evaluation of the entry barrier and the queuing of the entry call are also protected operations on the associated object. Barrier evaluation, protected entry queuing operations and protected subprogram execution are collectively referred to as protected actions.

Any exception raised during the evaluation of a barrier results in Program_Error being raised in all tasks currently waiting on the entry queues associated with the protected object containing the barrier [BW98, chapter 7.8]).


The Eggshell Model

A queued entry call has precedence over other operations on the protected object. This is often explained in terms of the eggshell model. The lock on a protected object is the eggshell.

Figure 4.1: Graphical Representation of the Protected Object.
\begin{figure}\centerline{\psfig{figure=figures/04-PO/01-object.eps,
width=7.0cm}}\end{figure}

Figure [*] presents a graphical representation of the protected objects. Tasks flow of control is represented by means of shadowed circles; the protected object levels are represented by means of a big circle (associated with the object lock) and a big rectangle (associated with the object state and operations--represented by small rectangles). Closed entries are represented by means of black rectangles; opened entries are represented by means of while rectangles. In this example the reader can see one single task executing one protected operation (in mutual exclusion), several tasks queued in the entry queues and several additional tasks queued in the lock.

To perform any protected operation, a task (represented by small shadowed circles) must enter the eggshell. One protected procedure or entry call or several protected function calls can be active in the eggshell at a time. Tasks attempting to enter an eggshell that is occupied by a procedure or entry call will be blocked.

An entry call must enter the eggshell to evaluate the associated barrier (represented by small rectangles). If the barrier is Open (white small rectangle), the entry body is executed inside the eggshell; otherwise (black small rectangle), the call is queued within the eggshell. Since queued entry calls are not active, other calls can enter the eggshell in which they are queued.

Queued entry calls become eligible to execute when their barriers become Open. This need only be checked when they become true as the result of a protected procedure or entry call on the same object [AAR95], essentially treating the barrier expression as though they depended only on the state of the protected object. Therefore before an operation that may have changed the state of a protected object exits the eggshell, any queued entry calls waiting on barriers that now evaluate to True must be executed. Only when the barriers of all entries with queued calls are False can the eggshell be exited. This assures that all entry calls made eligible by a state change are executed before any further operations are initiated.


Private Entries and Entry Families

As with tasks, protected objects may have private entries. These are not directly visible to users of the protected object. They may be used during requeue operations [BW98, chapter 7.5].

A protected type can also declare a family of entries by placing a discrete subtype definition in the specification of the entry declaration. Unlike task entry families, however, the programmer need not provide a separate entry body for each member of the family. The barrier associated with the entry can use the index of the family (usually to index into an array of booleans) [BW98, chapter 7.5] (see examples in  [BW98, chapter 7.5])


Restrictions on Protected Objects

In general, code executed inside a protected object should be as brief as possible. This is because whilst the code is being executed other tasks are delayed when they try to gain access to the protected object. The Ada language clearly cannot enforce a maximum length of execution time for a protected action. However, it does try to ensure that a task cannot be blocked indefinitely waiting to gain access to a protected procedure or a protected function. The ARM defines it to be a bounded error to call a potentially blocking operation from within a protected action. The following operations are defined as potentially blocking [BW98, chapter 7.6]:

  1. A select statement.
  2. An accept statement.
  3. An entry call statement.
  4. A delay statement.
  5. Task creation or activation.
  6. A call on a subprogram whose body contains a potentially blocking operation.

If a bounded error is detected, the predefined exception Program_Error is raised.


Elaboration and Finalization

A protected object is elaborated when it comes into scope in the usual way. However, a protected object cannot simply go out of scope if there are still tasks queued on its entries. Finalization of a protected object requires that any tasks left on entry queues have the exception Program_Error raised [BW98, chapter 7.8]:


The Count Attribute

Protected object entries, like task entries, have a Count attribute defined that provides the current number of tasks queued on the specified entry. It is important to note that even if a task is destined to end up on an entry queue (due to the barrier being closed) it requires the write-lock to be placed on the queue. Moreover, having been queued, the Count attribute will have changed (for that queue) and hence any barriers that have made reference to that attribute will need to be re-evaluated [BW98, chapter 7.4].


next up previous contents index
Next: GNAT Implementation Up: Protected Objects Previous: Protected Objects   Contents   Index
(c) Javier Miranda. Canary Islands (Spain), 2002. Version 1.0