Guide for the use of the Ada Ravenscar Profile in high integrity systems

75  Download (0)

Testo completo

(1)

Guide for the use of the Ada Ravenscar Profile in high integrity systems

Alan Burns, Brian Dobbing and Tullio Vardanega

University of York Technical Report YCS-2017-348 June 2017

 2017 by the authors

(2)

Acknowledgements

This report is the result of input from a number of people. The authors wish to acknowledge the contributions of Peter Amey, Rod Chapman, Stephen Michell, Juan Antonio de la Puente, Phil Thornley, David Tombs, Jeff Cousins, Ed Schonberg, Joyce Tokar, and the permission by Aonix Inc (now PTC) to use sections of their cross-development guide for the ObjectAda/Raven®

product as the textual basis of the initial version of this report.

Revision

This is an updated version of the report first published in 2003. It reflects changes to the definition of Ada up to, and including, Ada 2012 TC1 [RM].

(3)

Contents

1  Introduction ... 1 

Structure of the Guide ... 2 

Readership ... 2 

Conventions ... 2 

2  Motivation for the Ravenscar Profile ... 3 

2.1  Scheduling Theory ... 3 

2.1.1  Tasks Characteristics ... 3 

2.1.2  Scheduling Model... 4 

2.2  Mapping Ada to the Scheduling Model ... 5 

2.3  Non-Preemptive Scheduling and Ravenscar ... 6 

2.4  Other Program Verification Techniques ... 7 

2.4.1  Static Analysis ... 7 

Control Flow ... 7 

Data Flow ... 7 

Information Flow ... 8 

Symbolic Execution ... 8 

Formal Code Verification ... 8 

2.4.2  Formal Analysis ... 8 

2.4.3  Formal Certification ... 9 

3  The Ravenscar Profile Definition ... 11 

3.1  Development History ... 11 

3.2  Definition ... 11 

3.2.1  Ravenscar Features ... 12 

3.3  Summary of Implications of pragma Profile(Ravenscar) ... 12 

4  Rationale ... 14 

4.1  Ravenscar Profile Restrictions ... 14 

4.1.1  Static Existence Model ... 14 

4.1.2  Static Synchronization and Communication Model ... 15 

4.1.3  Deterministic Memory Usage ... 16 

4.1.4  Deterministic Execution Model ... 16 

4.1.5  Simple Run-time Behaviour ... 18 

4.1.6  Parallel Semantics ... 18 

4.1.7  Implicit Restrictions ... 19 

4.2  Ravenscar Profile Dynamic Semantics ... 19 

4.2.1  Task Dispatching Policy ... 19 

4.2.2  Locking Policy ... 19 

4.2.3  Queuing Policy ... 19 

4.2.4  Additional Run-Time Errors Defined by the Ravenscar Profile ... 19 

4.2.5  Potentially-Blocking Operations in Protected Actions ... 20 

4.2.6  Exceptions and the No_Exceptions Restriction ... 21 

4.2.7  Access to Shared Variables ... 21 

4.2.8  Elaboration Control ... 22 

5  Examples of use ... 23 

5.1  Cyclic Task ... 23 

5.2  Co-ordinated release of Cyclic Tasks ... 24 

5.3  Cyclic Tasks with Precedence Relations ... 25 

5.4  Event-Triggered Tasks ... 26 

(4)

5.5  Shared Resource Control using Protected Objects ... 27 

5.6  Task Synchronization Primitives ... 27 

5.7  Minimum Separation between Event-Triggered Tasks ... 28 

5.8  Interrupt Handlers ... 29 

5.9  Catering for Entries with Multiple Callers ... 30 

5.10  Catering for Protected Objects with more than one Entry ... 31 

5.11  Programming Timeouts ... 33 

5.12  Further Expansions to the Expressive Power of the Ravenscar Profile ... 34 

6  Verification of Ravenscar Programs... 36 

6.1  Static Analysis of Sequential Code ... 36 

6.2  Static Analysis of Concurrent Code ... 36 

6.2.1  Program-wide Information Flow Analysis ... 37 

6.2.2  Absence of Run-time Errors ... 38 

Elaboration Errors ... 38 

Execution Errors Causing Exceptions ... 39 

Max_Entry_Queue_Length and Suspension Object Check ... 39 

Priority Ceiling Violation Check ... 39 

Potentially Blocking Operations in a Protected Action ... 40 

Task Termination ... 40 

Use of Unprotected Shared Variables ... 41 

6.3  Scheduling Analysis ... 41 

6.3.1  Priority Assignment ... 41 

6.3.2  Rate Monotonic Utilization-based Analysis ... 42 

6.3.3  Response Time Analysis ... 43 

6.3.4  Documentation Requirement on Run-time Overhead Parameters ... 44 

6.4  Formal Analysis of Ravenscar Programs ... 45 

7  Extended Example ... 46 

7.1  A Ravenscar Application Example ... 46 

7.2  Code ... 49 

Cyclic Task ... 50 

Event-response (Sporadic) Tasks ... 50 

Shared Resource Control Protected Object ... 54 

Task Synchronization Primitives... 54 

Interrupt Handler ... 56 

7.3  Scheduling Analysis ... 58 

7.4  Auxiliary Code ... 60 

8  Definitions, Acronyms, and Abbreviations ... 65 

9  References ... 70 

10  Bibliography ... 71 

(5)

1 Introduction

There is increasing recognition that the software components of critical real-time applications must be provably predictable. This is particularly so for a hard real-time system, in which the failure of a component of the system to meet its timing deadline can result in an unacceptable failure of the whole system. The choice of a suitable design and development method, in conjunction with supporting tools that enable the real-time performance of a system to be analysed and simulated, can lead to a high level of confidence that the final system meets its real-time constraints.

Traditional methods used for the design and development of complex applications, which

concentrate primarily on functionality, are increasingly inadequate for hard real-time systems. This is because non-functional requirements such as dependability (e.g. safety and reliability),

timeliness, memory usage and dynamic change management are left until too late in the development cycle.

The traditional approach to formal verification and certification of critical real-time systems has been to dispense entirely with separate processes, each with their own independent thread of control, and to use a cyclic executive that calls a series of procedures in a fully deterministic manner. Such a system becomes easy to analyse, but is difficult to design for systems of more than moderate complexity, inflexible to change, and not well suited to applications where sporadic activity may occur and where error recovery is important. Moreover, it can lead to poor software engineering if small procedures have to be artificially constructed to fit the cyclic schedule.

The use of Ada has proven to be of great value within high integrity and real-time applications, albeit via language subsets of deterministic constructs, to ensure full analysability of the code.

Such subsets have been defined for Ada 83, but these have excluded tasking on the grounds of its non-determinism and inefficiency. Advances in the area of schedulability analysis currently allow hard deadlines to be checked, even in the presence of a run-time system that enforces preemptive task scheduling based on multiple priorities. This valuable research work has been mapped onto a number of new Ada constructs and rules that have been incorporated into the Real-Time Annex of the Ada language standard [RM D]. This has opened the way for these tasking constructs to be used in high integrity subsets whilst retaining the core elements of predictability and reliability.

The Ravenscar Profile is a subset of the tasking model, restricted to meet the real-time community requirements for determinism, schedulability analysis and memory-boundedness, as well as being suitable for mapping to a small and efficient run-time system that supports task synchronization and communication, and which could be certifiable to the highest integrity levels. The concurrency model promoted by the Ravenscar Profile is consistent with the use of tools that allow the static properties of programs to be verified. Potential verification techniques include information flow analysis, schedulability analysis, execution-order analysis and model checking. These techniques allow analysis of a system to be performed throughout its development life cycle, thus avoiding the common problem of finding only during system integration and testing that the design fails to meet its non-functional requirements.

It is important to note that the Ravenscar Profile is silent on the non-tasking (i.e. sequential) aspects of the language. For example it does not dictate how exceptions should, or should not, be used. For any particular application, it is likely that constraints on the sequential part of the language will be required. These may be due to other forms of static analysis to be applied to the code, or to enable worst-case execution time information to be derived for the sequential code. The reader is referred to the ISO Technical Report, Guide for the Use of Ada Programming Language

(6)

in High Integrity Systems [GA] for a detailed discussion on all aspects of static analysis of sequential Ada.

The Ravenscar Profile has been designed such that the restricted form of tasking that it defines can be used even for software that needs to be verified to the very highest integrity levels. The

Ravenscar Profile has already been included in the ISO technical report [GA] referenced above.

The aim of this guide is to give a complete description of the motivations behind the Ravenscar Profile, to show how conformant programs can be analysed and to give examples of usage.

Structure of the Guide

The report is organized as follows. The motivation for the development of the Ravenscar Profile is given in the next chapter. Chapter 3 includes the definition of the profile as specified by the Ada Standard; the definition is included here for convenience, but this report is not the definitive statement of the profile. In Chapter 4, the rationale for each aspect of the profile is described.

Examples of usage are then provided in Chapter 5. The need for verification is an important design goal for the Ravenscar Profile: Chapter 6 reviews the verification approach appropriate to

Ravenscar programs. Finally, in Chapter 7 an extended example is given. Definitions and references are included at the end of the report.

Readership

This report is aimed at a broad audience, including application programmers, implementers of run- time systems, those responsible for defining company/project guidelines, and academics.

Familiarity with the Ada language is assumed.

Conventions

This report uses the italics face to flag the first occurrence of terms that have a defining entry in Chapter 8. For all Ada-related terms, the report follows the language reference manual [RM] style:

it uses the Arial font where there is a reference to defined syntax entities (e.g.

delay_relative_statement). For all other names (e.g. Ada.Calendar) it uses normal text font, as do language keywords in the text except that they are in bold face.

(7)

2 Motivation for the Ravenscar Profile

Before describing the Ravenscar Profile in detail, in this chapter we explain some of the reasoning behind its features. These primarily come from the need to be able to verify concurrent real-time programs, and to have these programs implemented reliably and efficiently.

In this chapter, we look mainly at scheduling theory, as this is the main driver for the definition of the restrictions of the Ravenscar Profile. In addition, there is a section that summarizes other program verification techniques that can be used with the profile.

2.1 Scheduling Theory

Recent research in scheduling theory has found that accurate analysis of real-time behaviour is possible given a careful choice of scheduling/dispatching method together with suitable restrictions on the interactions allowed between tasks. An example of a scheduling method is preemptive fixed priority scheduling. Example analysis schemes are Rate Monotonic Analysis (RMA) [1] and Response Time Analysis (RTA) [2].

Priority-based preemptive scheduling is normally used with a Priority Ceiling Protocol (PCP) to avoid unbounded priority inversion and deadlocks. It provides a model suitable for the analysis of concurrent real-time systems. The approach supports cyclic and sporadic activities, the idea of hard, soft, firm, and non-critical components, and controlled inter-process communication and synchronization. It is also scalable to programs for distributed systems.

Tool support exists for RMA and RTA, and for the static simulation of concurrent real-time programs. The primary aim of analysing the real-time behaviour of a system is to determine whether it can be scheduled in such a way that it is guaranteed to meet its timing constraints.

Whether the timing constraints are appropriate for meeting the requirements of the application is not an issue for scheduling analysis. Such verification requires a more formal model of the program and the application of techniques such as model checking – see Section 2.4.

2.1.1 Tasks Characteristics

The various tasks in an application will each have timing constraints. For critical tasks, these are normally defined in terms of deadlines. The deadline is the maximum time within which a task must complete its operation in response to an event.

Each task is classified into one of the following four basic levels of criticality according to the importance of meeting its deadline:

 Hard

A hard deadline task is one that must meet its deadline. The failure of such a task to meet its deadline may result in an unacceptable failure at the system level.

 Firm

A firm deadline task is one that must meet its deadline under “average” or “normal”

conditions. An occasional missed deadline can be tolerated without causing system failure (but may result in degraded system performance). There is no value, and thus there is a system-level degradation of service, in completing a firm task after its deadline.

(8)

 Soft

A soft deadline task is also one that must meet its deadline under “average” or “normal”

conditions. An occasional missed deadline can be tolerated without causing system failure (but may result in degraded system performance). There is value in completing a soft task even if it has missed its deadline.

 Non-critical

A non-critical task has no strict deadline. Such a task is typically a background task that performs activities such as system logging. Failure of a non-critical task does not endanger the performance of the system.

2.1.2 Scheduling Model

At any moment in time, some tasks may be ready to run (meaning that they are able to execute instructions if processor time is made available). Others are suspended (meaning that they cannot execute until some event occurs) or blocked (meaning that they await access to a shared resource that is currently exclusively owned by another task). Suspended tasks may become ready

synchronously (as a result of an action taken by a currently running task) or asynchronously (as a result of an external event, such as an interrupt or timeout, that is not directly stimulated by the current task).

With priority-based preemptive scheduling on a mono-processor, a priority is assigned to each task and the scheduler ensures that the highest priority ready task is always executing. If a task with a priority higher than the currently running task becomes ready, the scheduler performs a context switch, as soon as it can, to enable the higher-priority task to resume execution. The term

“preemptive” indicates that this can occur because of an asynchronous event (i.e. one that is not caused by the running task).

Tasks will normally be required to interact as a result of contention for shared resources, exchange of data, and the need to synchronize their activities. Uncontrolled use of such interactions can lead to a number of problems:

 Unbounded Priority Inversion / Blocking

where a high-priority task is blocked awaiting a resource in use by a low-priority task; as a result, ready tasks of intermediate priority may hold up the high priority task for an

unbounded amount of time since they will run in preference to the low priority task that has locked the resource.

 Deadlock

where a group of tasks (possibly the whole system) block each other permanently due to circularities in the ownership of and the contention for shared resources.

 Livelock

where several tasks (possibly the whole system) remain ready to run, and do indeed execute, but fail to make progress due to circular data dependencies between the tasks that can never be broken.

 Missed Deadline

where a task fails to complete its response before its deadline has expired due to factors such as system overload, excessive preemption, excessive blocking, deadlocks, livelocks or CPU overrun.

The restricted scheduling model that is defined by the Ravenscar Profile is designed to minimize the upper bound on blocking time, to prevent deadlocks, and (via tool support) to verify that there is sufficient processing power available to ensure that all critical tasks meet their deadlines.

(9)

In this model, tasks do not interact directly, but instead interact via shared resources known as protected objects. Each protected object typically provides either a resource access control function (including a repository for the private data to manage and implement the resource), or a synchronization function, or a combination of both.

A protected object that is used for resource access control requires a mutual exclusion facility, commonly known as a monitor or critical region, where at most one task at a time can have access to the object. During the period that a task has access to the object, it must not perform any operation that could result in it becoming suspended. Ada directly supports protected objects and disallows internal suspension within these objects.

A protected object that is used for synchronization provides a signalling facility, whereby tasks can signal and/or wait on events. In the Ravenscar Profile definition, the use of protected objects for synchronization by the critical tasks is constrained so that at most one task can wait on each protected object. A simplified version of wait/signal is also provided in the Ravenscar Profile via the Ada Real-Time Annex functionality known as suspension objects [RM D.10]. These can be used in preference to the protected object approach for simple resumption of a suspended task, whereas the protected object approach should be used when more complex resumption semantics are required, for example including deterministic (race-condition-free) exchange of data between signaller and waiter tasks.

The Ravenscar Profile definition assures absence of deadlocks by requiring use of an appropriate locking policy. This policy requires a ceiling priority to be assigned to each protected object that is no lower than the highest priority of all its calling tasks, and results in the raising of the priority of the task that is using the protected object to this ceiling priority value. In addition to absence of deadlocks, this policy also allows an almost optimal time bound on the worst case blocking time to be computed for use within the schedulability analysis, thereby eliminating the unbounded priority inversion problem. This time bound is calculated as the maximum time that the object is in use by lower-priority tasks. Therefore, the smaller the worst-case time bound for this blocking period, the greater the likelihood that the task set will be schedulable.

The use of priority-based preemptive dispatching defines a mechanism for scheduling. The scheduling policy is defined by the mapping of tasks to priority values. Many different schemes exist for different temporal characteristics of the tasks and other factors such as criticality. What most of these schemes require is an adequate range of distinct priority values. Ada and the Ravenscar Profile ensure this.

2.2 Mapping Ada to the Scheduling Model

The analysis of an Ada application that makes unrestricted use of Ada run-time features including tasking rendezvous, select statements and abort is not currently feasible. In addition, the non- deterministic and potentially unbounded behaviour of many tasking and other run-time calls may make it impossible to provide the upper bounds on execution time that are required for

schedulability analysis and simulation. Thus, Ada coding style rules and subset restrictions must be followed to ensure that all code within critical tasks is statically time-bounded, and that the

execution of the tasks can be defined in terms of response times, deadlines, cycle times, and blocking times due to contention for shared resources.

The application must be decomposed into a number of separate tasks, each with a single thread of control, with all interaction between these tasks identified. Each task has a single primary invocation event. The tasks are categorized as time-triggered (meaning that they execute in

(10)

response to a time event), or event-triggered (meaning that they execute in response to a stimulus or event external to the task). If a time-triggered task receives a regular invocation time event with a statically-assigned rate, the task is termed periodic or cyclic.

Protected objects must be introduced to provide mutually-exclusive access to shared resources (e.g. for concurrent access to writable global data) and to implement task synchronization (e.g. via some event signalling mechanism). This decomposition is normally the result of applying a design methodology suitable to describe real-time systems.

In order to be suitable for schedulability analysis, the task set to be analysed must be static in composition and have all its dependencies between tasks via protected objects. Tasks nested inside other Ada structures incur unwanted visibility dependencies and termination dependencies.

Therefore, this model only permits tasks to be created at the library level, at system initialization time.

Hence, in the Ravenscar Profile, all tasks in the program are created at the library level.

Another consequence of requiring a static task set for schedulability analysis purposes is that the Ravenscar Profile must prohibit the dynamic creation of tasks and protected objects via allocators.

This implies that the memory requirements for the execution of the task set (e.g. the task stacks) are resolved prior to, or during, elaboration of the program. In addition, the Ravenscar Profile prohibits the implementation from implicitly acquiring dynamic memory from the standard storage pool [RM 13.11(17)]. The data structures that are required by the run-time system should either be declared globally, so that the memory requirements can be determined at link time, or in such a way as to cause the storage to be allocated on the stack (of the environment task) during elaboration of the run-time system.

The Ravenscar Profile places no restrictions on the declaration of large or dynamic-sized Ada objects in the application other than prohibiting the implementation from implicitly using the standard storage pool to acquire the storage for these objects. It is acceptable for the memory for such objects to be allocated on the task stack.

2.3 Non-Preemptive Scheduling and Ravenscar

The definition of the Ravenscar Profile requires preemptive scheduling of tasks. However, a similar profile could be defined that specified non-preemptive execution. Much of the material and guidelines contained in this report would also apply to the non-preemptive case. Non-preemptive implementation for a mono-processor is in between the cyclic executive approach and the preemptive tasking approach with regard to ease of timing analysis, flexibility with regard to change, and responsiveness to asynchronous events. In common with the cyclic executive approach, there is no contention for shared resources, and there is no need to analyse the impact from asynchronous events. There is still, however, the need to break up long code sequences using voluntary suspension points (e.g. a delay_until_statement with a wakeup time argument that denotes a time in the past) to obtain reasonable responsiveness to asynchronous events.

(11)

2.4 Other Program Verification Techniques

In addition to the provision of support for schedulability analysis, the rationale behind the Ravenscar Profile definition is also to support other static program verification techniques, and to simplify the formal certification process. These other techniques are discussed briefly in this section.

2.4.1 Static Analysis

Static analysis is recognized as a valuable mechanism for verifying software. For example, it is mandated for safety critical applications that are certified to the UK Defence Standard 00-55 [DS].

Industrial experience shows that the use of static analysis during development eliminates classes of errors that can be hard to find during testing. Moreover, these errors can be eliminated by the developer before the code has been compiled or entered into the configuration management system, saving the cost of repeated code review and testing which results from faults that are discovered during testing.

Static analysis as a technology has a fundamental advantage over dynamic testing. If a program property is shown to hold using static analysis, then the property is guaranteed for all scenarios.

Testing, on the other hand, may demonstrate the presence of an error, but the correct execution of a test only indicates that the program behaves correctly for the specific set of inputs provided by the test, and within the specific context that the test harness sets up. For all but the simplest systems, exhaustive testing of all possible combinations of input values and program contexts is infeasible.

Typically, test cases are devised to represent broad classes of inputs, so that tests can be created that use a representative value from each possible input class. However, complex program state contexts are usually only creatable during integration and system testing, when it may be very difficult to simulate all possible operational states. Further, the impact of correcting errors that are found only at this stage of the lifecycle is generally large in comparison to errors found during development.

There are many methods of static analysis. By using combinations of these methods, a variety of properties can be guaranteed for a program. The following list of forms of analysis is drawn from a study of a variety of standards that is presented in the ISO Technical Report [GA]. Section 6.2 discusses how these analyses may be applied in the context of a concurrent Ravenscar Profile program.

Control Flow

Control flow analysis ensures that code is well structured, and does not contain any syntactically or semantically unreachable code.

Data Flow

Data flow analysis ensures that there is no executable path through the program that would result in access to a variable that does not have a defined value. Data flow analysis is only feasible on code that has valid control flow properties.

(12)

Information Flow

Information flow analysis is concerned with the dependencies between inputs and outputs within the code. It checks the specified dependencies against the implemented dependencies to ensure consistency. To be effective, information flow analysis needs to be performed with knowledge of the system requirements. It can be a powerful tool for demonstrating properties such as non- interference between critical and non-critical data.

Symbolic Execution

Symbolic execution generates a model of the function of the software in terms of parallel

assignments of expressions to outputs for each possible path through the code. This can be used to verify the code without the need for a formal specification.

Formal Code Verification

Formal code verification is the process of proving the code is correct against a formal specification of its requirements. Each operation is specified in terms of the pre-conditions that need to be satisfied for the operation to be callable, and the post-conditions that hold following a successful call to the operation. The verification process demonstrates that, given the pre-conditions, execution of the operation always gives rise to the post-conditions. The level of proof depends on the information provided in the formal specification. This can vary depending on the aspects of the code that need to be verified; this can vary from the proof of a single invariant right up to full functional behaviour.

Proof of absence of run-time errors is a special form of formal code verification. This does not require the provision of a formal specification of the program. Instead, formal code verification techniques are used to demonstrate that, at every point in the code where a run-time error may occur, the pre-conditions on execution of that code and the current set of data values in the expression guarantee that the run-time error cannot occur. This is a very valuable property to be able to demonstrate, especially in systems where the occurrence of an unexpected run-time exception is generally unrecoverable, and the overhead of dynamic defensive mechanisms for preventing all such faults is unacceptable.

2.4.2 Formal Analysis

The formal analysis of concurrent programs has been a fruitful research topic for a number of years. Current standard techniques allow many important properties of programs to be statically checked.

Concurrent programs, whilst more expressive than their sequential counterparts, have a number of distinct error conditions that must be addressed during program development. The most common of these is deadlock, where all processes are blocked on a synchronization primitive with no processes left to undertake the necessary unblocking actions. In general, a concurrent program should possess two important properties:

1. Safety - the system of tasks should not get into an unsafe (undesirable) state (for example; deadlock, livelock).

2. Liveness - all desirable states of the task must be reached eventually (that is, useful progress should always be made).

(13)

In a real-time concurrent system, ‘liveness’ becomes ‘bounded liveness’ as desirable states must be reached by known deadlines.

Ada, like all other engineering languages, does not have its semantics defined in a formal mathematical way. Hence, it is necessary to link a model of the program with the program itself.

This link cannot be formal but can be precise. The use of standard patterns for Ada tasks helps this linkage. The formal model could be derived from the code or, more likely in an engineering process, the model is derived from requirements, and the code is obtained via a series of refinements from the model.

There are two general forms for these models and two methods of extracting properties (behaviours) from these descriptions. First, an algebraic form could be used in one of the concurrency languages that does have formally defined semantics; examples being CSP (Communicating Sequential Processes) and CCS (Calculus of Communicating Systems). The other, more common, approach is to view the program as a collection of state-transition systems.

Verification comes either from a proof theoretic approach or via model checking. An algebraic description can be proved to be deadlock-free, for example, by the use of a theorem prover.

Alternatively, a state-transition description (or an algebraic one) can be exercised by an exhaustive search of the set of states the program can enter. This 'checking of the model' can deduce that all safe states, and no unsafe states, can be reached.

The disadvantage of model checking is that an explosion of states can make it impossible to terminate the search. However, there have been considerable (and continuing) advances in the tools for model checking, and now sizeable systems can be verified in a respectably small number of hours of processing time. Theorem proving does not have this problem but it is a more skilled activity and theorem proving tools are not simple to use (i.e. the verification process is not automatic). A proof theoretic approach also has the advantage that it can show that a property is true 'for any number of tasks'; whereas model checking cannot generalize in this way – it will show it is true for six client tasks, say, but for seven the check must be made again. Combinations of proof and model checking are possible and are the subject of current research.

For real-time systems, it is possible to add time to the concurrency model and to then validate temporal aspects of program. Timed versions of formalisms such as CSP [CSP] exist and state- transition systems with clocks allow timing requirements to be expressed and subsequently verified by model checking. A common formalism for this type of state-transition system is called timed automata. Again, tool support for model checking sets of timed automata is well advanced.

One of the very useful features of model checking tools is that they all produce a well-defined counter example for any failed check.

2.4.3 Formal Certification

In order to achieve formal certification of a software architecture and of its Ada implementation, it is necessary to provide verification evidence of safety and reliability of the Ada run-time system as well as for the application-specific components. The run-time system that is needed to implement the dynamic semantics of the full Ada concurrency model is complex, and the number of states that may be represented by its dynamic data structures is large. As a result, it is very challenging for a commercial Ada vendor to produce certification evidence to the highest integrity levels for an entire Ada run-time system.

(14)

The Ravenscar Profile definition greatly reduces the size and complexity of the required run-time system, to simplify the process of providing evidence of its safety and reliability. Ada concurrency features that have major impact on the run-time system semantics, such as abort, asynchronous transfer of control, multiple entry queues each with a list of waiting tasks, requeue statements, task hierarchy and dependency, and finalization actions of local protected objects, are eliminated. As a result, it is possible to create not only a small and highly efficient run-time system implementation, but also one that is amenable to the forms of verification applicable to sequential code as described in [GA], which may then be used as evidence to support the formal certification of an entire software system to the highest integrity levels.

(15)

3 The Ravenscar Profile Definition

3.1 Development History

The 8th International Real-Time Ada Workshop (IRTAW) was held in April 1997 at the small Yorkshire village of Ravenscar. Two position papers [3][4] led to an extended discussion on tasking profiles. By the end of the workshop, the Ravenscar Profile had been defined [5] in a form that is almost identical to its current specification.

At the 9th IRTAW [6] (March 1999) the Ravenscar Profile was again discussed at length. The definition was reaffirmed and clarified. The most significant change was the incorporation of Suspension Objects. An Ada Letters paper [5] became the de facto defining statement of the Ravenscar Profile.

By the 10th IRTAW [7] (September 2000) many of the position papers were on aspects of the Ravenscar Profile and its use and implementation. No major changes were made, although an attempt to standardize on the Restriction identifiers was undertaken. Time was spent on a non- preemptive version of the profile. Following the 10th IRTAW, the participants decided to forward the Ravenscar Profile to the ARG – the ISO body in charge of the maintenance of the Ada

language – so that its definition could move from a de facto to a real standard. The HRG – the ISO body in charge of the high integrity aspects of the Ada language – was also tasked with producing a Rationale for the Ravenscar Profile, whichresulted in the production of this guide.

At the 11th IRTAW [8] (April 2002), the formal definition of the profile as formulated by the ARG was agreed. It was confirmed that the Ravenscar Profile requires task dispatching policy

FIFO_Within_Priorities and locking policy Ceiling_Locking.

Since 2002, the Ravenscar Profile has been a formal part of the definition of Ada. Each time the language is upgraded, the profile is revisited to make sure that it continues to have the right set of restrictions. The series of IRTAW workshops continues to review the Ravenscar Profile’s definition. This last took place at the 18th IRTAW, in April 2016.

3.2 Definition

The definition of the Ravenscar Profile is now included in the Ada Standard. The definition is reported here for information only. The latest version of Ada defining the Ravenscar Profile is Ada 2012; ARG agreed changes for the next version of Ada are incorporated into the definition given here.

An application requests the use of the Ravenscar Profile by means of the configuration pragma Profile with the Ravenscar identifier:

pragma Profile(Ravenscar);

There are, in general, two distinct ways of defining the details of a profile: either by defining what is in it, or by declaring those parts of Ada that are not. The ‘official’ definition defines the

restrictions that are needed to reduce the full tasking model to Ravenscar. However, this gives a rather negative definition. Therefore, we shall first introduce the profile by focusing on the features it does contain.

(16)

3.2.1 Ravenscar Features

Following from the discussion on verification in the previous chapter, we are able to define an adequate set of tasking features. The Ravenscar Profile allows programs to contain:

 Task types and objects, defined at the library level.

 Protected types and objects, defined at the library level, with a maximum of one entry per object and with a maximum of one task queued at any time on that entry. The entry barrier must be a single Boolean variable (or a Boolean literal).

 Atomic and Volatile aspects.

delay_until_statements.

 Ceiling_Locking policy and FIFO_Within_Priorities dispatching policy.

 The E’Count attribute for protected entries except within entry barriers.

 The Ada.Task_Identification package plus task attributes T'Identity and E'Caller.

 Synchronous task control.

 Task type and protected type discriminants.

 The Ada.Real_Time package.

 Protected procedures as statically bound interrupt handlers.

 Static allocation of task to cores on a multicore (or multiprocessor) platform so that each core hosts a separate set of tasks, to which the Ravenscar Profile’s scheduling and locking policies apply locally.

Together, these form a coherent set of features that define an adequate language for expressing the programming needs of statically defined real-time systems.

3.3 Summary of Implications of pragma Profile(Ravenscar)

The following restrictions apply to the alternative mode of operation defined by the Ravenscar Profile. Some restrictions require language features to be omitted, others can be achieved by simply requiring that certain defined (standard) library packages are not incorporated into the program that is conforming to the Ravenscar Profile (i.e. there is no semantic dependency on the specified package).

The Ravenscar Profile is defined as follows [RM D.13]:

(17)

pragma Task_Dispatching_Policy(FIFO_Within_Priorities);

pragma Locking_Policy(Ceiling_Locking);

pragma Detect_Blocking;

pragma Restrictions(

No_Abort_Statements, No_Dynamic_Attachment, No_Dynamic_CPU_Assignment, No_Dynamic_Priorities,

No_Implicit_Heap_Allocations, No_Local_Protected_Objects, No_Local_Timing_Events, No_Protected_Type_Allocators, No_Relative_Delay,

No_Requeue_Statements, No_Select_Statements,

No_Specific_Termination_Handlers, No_Task_Allocators,

No_Task_Hierarchy, No_Task_Termination, Simple_Barriers,

Max_Entry_Queue_Length => 1, Max_Protected_Entries => 1, Max_Task_Entries => 0,

No_Dependence => Ada.Asynchronous_Task_Control, No_Dependence => Ada.Calendar,

No_Dependence => Ada.Execution_Time.Group_Budgets, No_Dependence => Ada.Execution_Time.Timers,

No_Dependence => Ada.Synchronous_Barriers, No_Dependence => Ada.Task_Attributes,

No_Dependence => System.Multiprocessors.Dispatching_Domains);

(18)

4 Rationale

This chapter provides a description of each restriction, a detailed rationale for the imposition of each restriction and some general discussion about how to work within the restrictions while still retaining flexibility in the design and coding processes.

4.1 Ravenscar Profile Restrictions

4.1.1 Static Existence Model

The restrictions listed below ensure that the set of tasks and interrupts to be analysed is fixed and has static properties (in particular, base priority) after program elaboration. If a variable task set were to exist, then it would be impractical to perform static timing analysis of the program because of the dynamic nature of the requirements for CPU time and the meeting of deadlines.

No_Task_Hierarchy

[RM D.7] No task depends on a master other than the library-level master.

The restriction No_Task_Hierarchy prevents the declaration of tasks local to procedures or to other tasks. Thus, tasks may only be created at the library level, i.e. within the declarative part of library level package specifications and bodies, including child packages and package subunits.

No_Task_Allocators

[RM D.7] There are no allocators for task types or types containing task subcomponents.

The restriction No_Task_Allocators prevents the dynamic creation of tasks via the execution of Ada allocators [RM 4.8].

No_Task_Termination

[RM D.7] All tasks are non-terminating. It is implementation-defined what happens if a task attempts to terminate. If there is a fall-back handler set for the partition it should be called when the first task attempts to terminate.

The restriction attempts to mitigate the hazard that may be caused by tasks terminating silently.

Real-time tasks normally have an infinite loop as their last outermost statement.

No_Specific_Termination_Handlers

[RM D.7] There is no use of a name denoting the Set_Specific_Handler and Specific_Handler subprograms in Task_Termination.

The restriction No_Specific_Termination_Handlers ensures that the only termination handler defined for the program is a fall-back handler [RM C.7.3].

No_Abort_Statements

[RM D.7] There are no abort_statements, and there is no use of a name denoting Task_Identification.Abort_Task.

The restriction No_Abort_Statements ensures that tasks cannot be aborted. The removal of abort statements (and select then abort) significantly reduces the size and complexity of the run- time system. It also reduces non-determinacy.

(19)

No_Dynamic_Attachment

[RM D.7] There is no use of a name denoting any of the operations defined in package Interrupts (Is_Reserved, Is_Attached, Current_Handler, Attach_Handler, Exchange_Handler, Detach_Handler, and Reference).

The restriction No_Dynamic_Attachment excludes use of the operations in predefined package Ada.Interrupts, which contains primitives to attach and detach handlers dynamically during program execution. In conjunction with restriction No_Local_Protected_Objects (see below) this implies that interrupt handlers can only be attached statically using Attach_Handler applying to protected procedures within library-level protected objects. Note the types and names defined in Ada.Interrupts can be used.

No_Dynamic_Priorities

[RM D.7] There are no semantic dependencies on the package Ada.Dynamic_Priorities, and no occurrences of the attribute Priority.

The restriction No_Dynamic_Priorities disallows the use of the predefined package Ada.Dynamic_Priorities, thereby ensuring that the priority assigned at task creation is unchanged during task execution, except when the task is executing a protected operation, during which time it inherits the ceiling priority. Protected objects also have unchanging ceiling priorities (as the Priority attribute [RM 4.1.4] cannot be used).

No_Local_Timing_Events

[RM D.7] Timing events are declared only at library level.

The restriction No_Local_Timing_Events prevents the declaration of timing events local to procedures or tasks. Thus, Timing_Events may only be created at the library level.

4.1.2 Static Synchronization and Communication Model

These restrictions are a natural consequence of the static execution model, since a locally declared protected object is meaningless for mutual exclusion and task synchronization purposes if it can only be accessed by one task. Furthermore, a static set of protected objects is required for schedulability analysis.

No_Local_Protected_Objects

[RM D.7] Protected objects are declared only at library-level.

The restriction No_Local_Protected_Objects prevents the declaration of protected objects local to subprograms, tasks, or other protected objects.

No_Protected_Type_Allocators

[RM D.7] There are no allocators for protected types or types containing protected type subcomponents.

The restriction No_Protected_Type_Allocators prevents the dynamic creation of protected objects via Ada allocators [RM 4.8].

No_Select_Statements

[RM D.7] There are no select_statements.

Max_Task_Entries => N

[RM D.7] Specifies the maximum number of entries per task.

(20)

For the Ravenscar Profile, the value of Max_Task_Entries is zero.

The restrictions Max_Task_Entries => 0 and No_Select_Statements prohibit the use of Ada rendezvous for task synchronization and communication. This ensures that these operations are achieved using only the two supported task synchronization primitives: protected object entries and suspension objects, both of which exhibit the time-deterministic execution properties needed for static timing analysis.

4.1.3 Deterministic Memory Usage

The Ravenscar Profile contains two restrictions that are designed to prevent implicit dynamic memory allocation by the implementation. The Ravenscar Profile does not prevent the use of the standard storage pool or a user-defined storage pool via explicit allocators. However, if there were no application-level visibility or control over how the storage in the standard storage pool was managed, the use of this pool would not be recommended.

No_Implicit_Heap_Allocations

[RM D.7] There are no operations that implicitly require heap storage allocation to be performed by the implementation. The operations that implicitly require heap storage allocation are implementation defined.

The restriction No_Implicit_Heap_Allocations prevents the implementation from allocating memory from the standard storage pool other than as part of the execution of an Ada allocator.

No dependence on Ada.Task_Attributes

[RM D.13] There are no semantic dependencies on the package Ada.Task_Attributes.

The restriction No_Task_Attributes_Package prevents use of the predefined package

Ada.Task_Attributes [RM C.7.2], which is used to dynamically create attributes of each task in the application. Attribute creation may cause implicit dynamic allocation of memory. Although an implementation is allowed to statically reserve space for such attributes and then to impose a restriction on usage, it is felt that support of this feature is not compatible with the static nature of Ravenscar programs.

4.1.4 Deterministic Execution Model

The following restrictions ensure deterministic execution:

Max_Protected_Entries => N

[RM D.7] Specifies the maximum number of entries per protected type. The bounds of every entry family of a protected unit shall be static, or shall be defined by a discriminant of a subtype whose corresponding bound is static.

For the Ravenscar Profile, the value of Max_Protected_Entries is 1.

Max_Entry_Queue_Length => N

[RM D.7] Defines the maximum number of calls that are queued on an entry. Violation of this restriction results in the raising of Program_Error exception at the point of the call.

For the Ravenscar Profile, the value of Max_Entry_Queue_Length is 1, and a call can only be queued on a protected entry, since Max_Task_Entries is 0.

(21)

The restrictions Max_Protected_Entries => 1 and Max_Entry_Queue_Length => 1 ensure that at most one task can be suspended waiting on a closed entry barrier for each protected object which is used as a task synchronization primitive. This avoids the possibility of queues of task calls forming on an entry, with the associated non-determinism of the length of the waiting time in the queue. It also avoids two or more barriers becoming open simultaneously as the result of a protected action, with the associated non-determinism of selecting which entry should be serviced first. The restriction also enables a tight time bound on the epilogue code to be determined.

The Max_Entry_Queue_Length restriction may only be checkable at run time, in which case violation would result in the raising of the Program_Error exception at the point of the entry call. This is consistent with the Ada rule that states that Program_Error exception is raised upon calling Suspend_Until_True if another task is waiting on that suspension object (when the Detect_Blocking pragma is enabled as it is in the Ravenscar Profile) [RM D.10]. An application could further restrict a Ravenscar program so that only one task is able to call one specific entry.

A static check could then be provided, but this goes beyond what the Ravenscar Profile defines.

When the restriction Max_Entry_Queue_Length => 1 is in force, Queuing_Policy ([RM D.4]) has no effect, since there are no queues.

Simple_Barriers

[RM D.7] The Boolean expression in an entry barrier shall be either a static expression or a name that statically denotes a component of the enclosing protected object.

The restriction Simple_Barriers, coupled with Max_Protected_Entries => 1, ensures a deterministic execution time and absence of side effects for the evaluation of entry barriers at the epilogue of protected actions within a protected object that is used for task synchronization.

There is also scope for additional optimization by the implementation since the barrier value is either static or can be read directly from one of the protected object components, without needing to be computed separately. If the application requires composite entry barrier

expressions, this can be achieved by declaring an additional Boolean in the protected data and assigning the composite expression to the Boolean whenever its evaluation result may change.

The Boolean variable must be declared within the protected object (or type).

No_Requeue_Statements

[RM D.7] There are no requeue_statements.

The restriction No_Requeue_Statements ensures deterministic task release from protected entry barriers used for task synchronization. The requeue_statement in Ada causes the current caller of a protected entry to be requeued to a different entry dynamically, thereby making it difficult to perform static analysis of task release.

No dependence on Ada.Asynchronous_Task_Control

[RM D.13] There are no semantic dependencies on the package Ada.Asynchronous_Task_Control.

The restriction No_Asynchronous_Control excludes the use of asynchronous suspension of execution. This ensures that task execution is temporally deterministic. See also the comments made on No_Abort_Statements.

No_Relative_Delay

[RM D.7] There are no delay_relative_statements, and there is no use of a name that denotes the Timing_Events.Set_Handler subprogram that has a Time_Span parameter.

(22)

The restriction No_Relative_Delay prohibits use of the delay_relative_statement based on type Duration. This statement exhibits non-determinism with respect to the absolute time at which the delay expires in the case when the delaying task is preempted after calculating the required relative delay, but before actual suspension occurs. In contrast, the delay_until_statement is deterministic and should be used for accurate release of time-triggered tasks.

No dependency on Ada.Calendar

[RM D.13] There are no semantic dependencies on the package Ada.Calendar.

The restriction No_Calendar ensures that all timing is performed using the high precision afforded by the time type in package Ada.Real_Time [RM D.8], or by an implementation- defined time type. The Ada.Real_Time time type has a precision of the same order of

magnitude as the real-time clock device on the underlying processor board. In contrast, the time type in package Calendar generally has much coarser precision than the real-time clock, due to the need to support a 200-year range, and so its use could result in less accuracy in task release times. In addition, only the clock available from Ada.Real_Time is required to be monotonic.

4.1.5 Simple Run-time Behaviour

To reduce the overheads required to support the full Ada model, some features are removed from the Ravenscar Profile: in particular, time-triggered tasks.

No dependency on Ada.Execution_Time.Group_Budgets

[RM D.13] There are no semantic dependencies on the package Ada.Execution_Time.Group_Budgets.

A Ravenscar runtime can monitor the execution time of tasks, but it does not support the sharing of a CPU budget within a group of tasks. Neither does it require a handler to be

executed if a task executes beyond a defined level of execution time (hence the next restriction).

This simplifies the runtime but makes it harder to construct programs that can recover from timing errors.

No dependency on Ada.Execution_Time.Timers

[RM D.13] There are no semantic dependencies on the package Ada.Execution_Time.Timers.

4.1.6 Parallel Semantics

More recent definitions of the Ada language have included features that provide more control over the execution of multi-tasking programs on parallel hardware. Such hardware includes multiprocessors (with various memory configurations), multi-core processor and various forms of heterogeneous architectures. The definition of the Ravenscar Profile has been extended to deal with these forms of truly parallel (rather than just concurrent) execution. The basic approach chosen for the Ravenscar Profile has been to support the static allocation of tasks to processors.

No_Dynamic_CPU_assignment

[RM D.13] All of the tasks in the partition will execute on the same CPU unless the programmer explicitly uses aspect CPU to specify the CPU assignments for tasks.

This results in tasks being statically assigned to processors.

(23)

No dependency on Ada.Synchronous_Barriers

[RM D.13] There are no semantic dependencies on the package Ada.Synchronous_Barriers.

Synchronous barriers [RM D.10.1] are used on some forms of parallel hardware. As they can be programmed by the user in a Ravenscar application, the use of the predefined package is not explicitly supported by the Ravenscar Profile.

No dependency on System.Multiprocessors.Dispatching_Domains [RM D.13] There are no semantic dependencies on the package System.Multiprocessors.Dispatching_Domains.

Dispatching domains allow more structured approaches to parallel execution to be supported.

Currently, this leads to programs that are deemed to be beyond what can be easily analysed;

they are therefore not included in the Ravenscar Profile

4.1.7 Implicit Restrictions

The set of restriction identifiers for Ada does not represent an orthogonal set of restrictions with the result that some restrictions are implied by others. For example, No_Select_Statements implies Max_Select_Alternatives must be zero.

4.2 Ravenscar Profile Dynamic Semantics

4.2.1 Task Dispatching Policy

The task dispatching policy that is required by pragma Profile(Ravenscar) is FIFO_Within_Priorities [RM D.2].

4.2.2 Locking Policy

The locking policy that is required by pragma Profile(Ravenscar) is Ceiling_Locking [RM D.3].

This policy provides one of the lowest worst case blocking times for contention for shared

resources, and so maximizes the schedulability of the task set when preemptive scheduling is used.

4.2.3 Queuing Policy

The queuing policy is not meaningful for pragma Profile(Ravenscar) since no entry queues can form. Thus queuing policy identifiers FIFO_Queuing and Priority_Queuing have no effect.

4.2.4 Additional Run-Time Errors Defined by the Ravenscar Profile

The Ada language standard defines a number of concurrency-related run-time checks that may lead to the raising of an exception. The Ravenscar Profile restrictions greatly reduce the quantity of these checks, and thus the number of exception cases that can occur. The two concurrency-related run-time checks that apply to Ravenscar programs are:

 detection of priority ceiling violation as defined by Ceiling_Locking policy;

 detection of violation of not more than one task waiting concurrently on a suspension object (via the Suspend_Until_True operation).

(24)

The Ravenscar Profile introduces some additional concurrency-related checks that are potentially detectable only at execution time:

 the maximum number of calls that are queued concurrently on an entry must not exceed one. Program_Error exception is raised if the error occurs (pragma

Restrictions(Max_Entry_Queue_Length => 1));

 all tasks are non-terminating (pragma Restrictions(No_Task_Termination)).

A conforming implementation must document the effect of a task that attempts to terminate.

Possible effects may include:

 allowing the task to terminate silently;

 holding the task in a permanent pre-terminated state;

 executing a task termination handler.

Whatever action is taken by the implementation, the application cannot assume that full task termination actions (including finalization) have been executed.

4.2.5 Potentially-Blocking Operations in Protected Actions

The Ravenscar Profile requires detection of the following bounded error in the Ada standard, with the consequential raising of Program_Error exception:

 execution of a potentially-blocking operation during a protected action (pragma Detect_Blocking).

The Ravenscar Profile definition does however significantly reduce the list of potentially-blocking operations that may occur during a protected action. In particular, the following potentially- blocking operations are eliminated by the Ravenscar Profile definition:

 a select_statement

 an accept_statement

 a task entry call

 a delay_relative_statement

 an abort_statement

 task creation or activation

 an external requeue_statement with the same target object as that of the protected action.

The Ravenscar Profile definition does not require detection, at compile time, of other potentially blocking operations defined by the language standard [RM 9.5.1 (16)]. In this case, it is allowed for the detection to occur at the point of execution of the potentially blocking operation within the called subprogram body.

The rationale for requiring detection of potentially-blocking operations in protected actions is to allow a highly efficient and temporally deterministic implementation of Ceiling_Locking policy on a mono-processor. In effect, the ceiling priority alone is sufficient to provide the required mutual exclusion without the need to use locks such as mutexes once it is guaranteed that the task cannot suspend co-operatively whilst inside the protected operation. This form of locking is also non-queuing on a mono-processor, with the associated benefit of removing the need to compute the worst-case duration that a task call may wait in the queue.

(25)

4.2.6 Exceptions and the No_Exceptions Restriction

The general concern within high integrity systems of the occurrence of unhandled exceptions is not addressed directly by the Ravenscar Profile since exceptions relate to the sequential, rather than the concurrent, part of the language. Consequently, whereas an unhandled exception will cause a sequential program to terminate, and hence offer an immediate opportunity for some program level control to invoke recovery actions, an unhandled exception during the execution phase of a concurrent program may not be detected. In particular, an unhandled exception can cause any of the following effects:

 silent abandonment of the execution of an interrupt handler;

 silent termination of a task;

 premature exit from a protected action.

The Ravenscar Profile statically avoids the possibility that an exception can be raised by an entry barrier via the restriction Simple_Barriers. In addition, the Ravenscar Profile imposes the

restriction No_Task_Termination that requires the implementation to document the effect of a task attempting to terminate. Nevertheless, this is inadequate for most high integrity applications that require static demonstration of absence of exceptions due to run-time check failure. Some

techniques are presented in Section 6.2 to address the topic of proof of absence of the concurrency- related run-time errors that may occur in a Ravenscar Profile program, using static analysis.

The Ada standard includes the identifier No_Exceptions as a valid argument for the Restrictions pragma. It should be noted that the inclusion of this pragma does not provide a static guarantee of exception freedom – it merely guarantees that the application code does not contain any explicit

raise_statement, nor code generation for language-defined checks, nor any exception handlers.

However, it is possible for an exception to be raised automatically by the underlying hardware, or by built-in code in the run-time system. There is a documentation requirement on the

implementation to define such cases [RM H.4 (25)].

In addition, the language standard defines execution of a program to become erroneous if a language-defined check is suppressed via the No_Exceptions restriction and the conditions arise that would have caused the check to fail [RM H.4 (26)]. This is consistent with the suppression of checks using pragma Suppress [RM 11.5 (26)]. Since erroneous execution results in the behaviour of a program becoming undefined, the recommendation for high integrity systems is that the No_Exceptions restriction should only be used in conjunction with verification and analysis techniques (see Chapter 1) that can statically prove that no exceptions due to run-time check failure can occur. In this case, the No_Exceptions restriction is providing the additional safeguard that exception raising via explicit raise_statements will be prohibited at compile time.

4.2.7 Access to Shared Variables

The Ravenscar Profile requires all synchronization and communication between tasks and interrupt handlers to use data that are guaranteed to have mutually-exclusive access. This prevents any erroneous execution that might arise if concurrent access (that includes a write operation) to the same unprotected shared variable is permitted. Such access control is provided in Ada using one of the following constructs:

 a protected object;

 a suspension object;

 an atomic object (to which the Atomic aspect applies).

(26)

This access control model applies to the operational phase of the application, after program initialization via elaboration of library-level packages is complete. For each class of object above, it is possible to ensure that its initialization is completed as part of program elaboration.

There is an issue however, in that the semantics of Ada define task activation and interrupt handler attachment to occur during library-level elaboration code for objects that are declared within library-level packages. Consequently, it is the case that tasks will execute their declarative part and may proceed into their sequence_of_statements, and that interrupt handlers may execute, prior to the elaboration code for program initialization being completed. This scenario could give rise to the following undesirable effects:

 a task body or interrupt handler may suffer an access-before-elaboration exception;

 a task body or interrupt handler may access uninitialized data;

 a task body or interrupt handler may access unprotected data concurrently that it shares only with the thread of control that is performing the data initialization.

It is possible to program each task such that it suspends itself at the start of its sequence of statements, but this is not possible for interrupt handlers (although an application may be able to inhibit interrupts if the device allows). Furthermore, the code executed as part of task activation (prior to the suspension point) may suffer the effects listed above. In order to address this issue, the Partition_Elaboration_Policy is defined in the Ada standard (see below).

4.2.8 Elaboration Control

The new pragma Partition_Elaboration_Policy [RM H.6] is not part of the Ravenscar Profile, but it is closely related to it. If given the argument Sequential, this defines an alternative elaboration behaviour in which all tasks declared at the library level proceed to their activation only after the environment task has completed all its elaborations and the main program is leaving its

declarative_part. It is only at that point that interrupt handlers are attached (so that no interrupt can be delivered earlier), and all tasks eventually start their concurrent execution. This pragma complements those that are defined by the Ravenscar Profile and helps achieve the goal that controlled access to global shared variables is met during program initialization.

(27)

5 Examples of use

This chapter illustrates some simple patterns of use of the Ravenscar Profile.

The Ravenscar Profile can be used with a variety of coding styles. However, if the user is required to perform program analysis, for example to check the schedulability of the tasks, then certain coding styles are recommended. Indeed, a small number of templates can cater for a large class of application needs. In the first eight sections of this chapter, we give examples that illustrate the straightforward use of the Ravenscar Profile. After that, in Sections 5.9 to 5.12, we show how the Ravenscar Profile can deal with requirements that would appear to lie outside of its scope.

With the 2012 version of the language specification, aspects should be used in place of most pragmas. Accordingly, we have replaced all occurrences of the obsolete pragmas with the corresponding aspect.

5.1 Cyclic Task

The task body for a cyclic (or periodic) task typically has, as its last statement, an outermost infinite loop containing one or more delay_until_statements. The basic form of a cyclic task has just a single delay statement either at the start or at the end of the statements within the loop. The Ravenscar Profile supports only one time type for use as the argument – Ada.Real_Time.Time, although a user-defined time type could be used.

Task termination is considered to be an error condition in Ravenscar-compliant code since there is no dynamic task creation (and hence the thread of control would be permanently lost). Hence, the loop that is presented in the template below is infinite.

A cyclic task will not usually contain any other form of voluntary-suspension statement in the infinite loop, since this would undermine the schedulability analysis.

The Ravenscar Profile supports the use of discriminants for task types and protected types. One use of a discriminant is to set differing priorities for task objects or protected objects that are of the same type by using it as the argument of the Priority aspect.

Discriminants can also be used to indicate the period of a cyclic task or other task parameters, including the assigned priority.

(28)

Example 1, Cyclic Template

task type Cyclic(Pri : System.Priority; Cycle_Time : Positive) with Priority => Pri;

task body Cyclic is

Next_Period : Ada.Real_Time.Time;

Period : constant Ada.Real_Time.Time_Span :=

Ada.Real_Time.Microseconds(Cycle_Time);

-- Other declarations as needed begin

-- Initialization code

Next_Period := Ada.Real_Time.Clock + Period;

loop

delay until Next_Period; -- Wait one whole period before executing -- Non-suspending periodic response code

-- May include calls to protected procedures Next_Period := Next_Period + Period;

end loop;

end Cyclic;

-- Now we declare two task objects of this type C1 : Cyclic(20,200);

C2 : Cyclic(15,100);

Cyclic tasks normally exchange data through protected operations. In this coding style, there are no protected entries since the only activation event is on delay until. Conformance with the Ravenscar Profile requires that all shared data be placed in protected objects to avoid corruption.

5.2 Co-ordinated release of Cyclic Tasks

The simple example illustrated above has a number of cyclic tasks that each read the clock and then suspend for time 'Period'. It can however by useful for all such tasks to co-ordinate their start times so that they share a common epoch. This can help to enforce precedence relations across tasks. To achieve this a protected object is used, which reads the clock on creation and then makes this clock value available to all cyclic tasks.

Example 2, Protected Object Implementing an Epoch

protected Epoch

with Priority => System.Priority'Last is

function Start_Time return Ada.Real_Time.Time;

private

Start : Ada.Real_Time.Time := Ada.Real_Time.Clock;

end Epoch;

protected body Epoch is

function Start_Time return Ada.Real_Time.Time is begin

return Start;

end Start_Time;

end Epoch;

A protected object is not strictly needed to this end, since a shared variable appropriately initialized will suffice. A more robust scheme and one that only reads the epoch time once a task actually needs it is as follows.

figura

Updating...

Riferimenti

Argomenti correlati :