59
Slide: 1 Copyright © 2012 AdaCore José F. Ruiz EWiLi 2012, Lorient Senior Software Engineer AdaCore Safe and Reliable Embedded Linux Programming How to Get There 7 June 2012

Safe and Reliable Embedded Linux Programming: How to Get There

  • Upload
    adacore

  • View
    8.840

  • Download
    3

Embed Size (px)

DESCRIPTION

The talk provides an overview of techniques to design and implement reliable embedded applications. The goal is to achieve safe and analyzable behavior by construction, including handling parallel multiprocessor systems in an efficient and predictable way. The means to attain this objective is to statically configure the application to run on embedded linux platforms, and then to use run-time support to enforce constraints imposed to the system.

Citation preview

Page 1: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 1Copyright © 2012 AdaCore

José F. Ruiz

EWiLi 2012, Lorient

Senior Software EngineerAdaCore

Safe and Reliable Embedded Linux ProgrammingHow to Get There

7 June 2012

Page 2: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 2Copyright © 2012 AdaCore

Outline

• Safety issues– Language matters

• Concurrent execution on monoprocessors– Ada Ravenscar Profile

• Tasking on linux

• Parallel execution on multiprocessors– Processor affinities

Page 3: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 3Copyright © 2012 AdaCore

Safety

Page 4: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 4Copyright © 2012 AdaCore

Safety-Critical Software: Background

• What is “safety critical” software?– Failure can cause loss of human life or have other catastrophic consequences

• How does safety criticality affect software development?– Regulatory agencies require compliance with certification requirements

– Safety-related standards may apply to the finished product, to the development

process, or both

• Choice of programming language has large impact on the cost of

developing / certifying safety-critical systems– Dilemma: modern features that help in developing maintainable systems interfere with

safety certification

– Examples: Object-Oriented Programming, generics, exceptions, concurrency

– Traditional approach: select subset (profile) amenable to safety certification

– Chosen features depend on the analysis methods to be used (or dictate which

methods are feasible)

Page 5: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 5Copyright © 2012 AdaCore

Some language requirements (I)

• Reliability– Support for development of readable, correct code

– No “traps and pitfalls”

– Intuitive lexical and syntactic features

– Support for good software engineering practice

– Encapsulation

– Avoidance of concurrency bugs

– Race conditions

– Deadlock / livelock

– Early error detection

– Compile-time checking whenever possible

– Run-time checking, at least

• Predictability– Unambiguous language semantics

– No undefined / implementation-dependent behavior

– Ability to demonstrate statically that time and space constraints will be satisfied

– Deadlines will be met

– Stack and heap space will not be exhausted

Page 6: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 6Copyright © 2012 AdaCore

Some language requirements (II)

• Analyzability– Control and data flow analysis

– Detects / prevents references to uninitialized data

– Information flow analysis

– Identify dependencies between a routine’s inputs and outputs

– Formal code verification

– Range checking

– Traceability between program representations and different levels

– Dynamic analysis (testing)

• Expressiveness– General-purpose features consistent with the above requirements

– Support for specialized processing typical in embedded systems (safety)

– Interrupt handling

– Low-level programming

– Fixed-point arithmetic

Page 7: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 7Copyright © 2012 AdaCore

Modern programming languages

• Many features help– Encapsulation / namespace control

– Limit data coupling

– Strong typing, compile-time checking

– Early error detection

• Some features present difficulties– High expressive power vs. need to constrain feature

– Dynamic flexibility vs. need for static analysis

– High-level features vs. need to certify the code that actually runs

– Generated code may contain implicit loops, conditionals

– Run-time library, API need to be certifiable

– How, if at all, use features such as exceptions, concurrency, dynamic allocation

Page 8: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 8Copyright © 2012 AdaCore

Modern programming languages – basic issues

• No general-purpose language is appropriate for highest safety/security levels– Conflicts with reliability, predictability, analyzability

• Key issue: can the language be subsetted so that applications restricted to the

subset can be certified– Is/are the subset(s) precisely defined?

– Can compliance with the subset(s) be enforced by a tool?

– Who defines the subset(s)?

– Standards agency? Tool/compiler vendor? Application developer?

– Are there intrinsic problems with the language that cannot be solved by subsetting?

• Current approaches– C-based

– MISRA C / C++

– Ada-based

– Ada profiles

– SPARK Ada

– Java-based

– Real-Time / Safety-Critical Java Technology

Page 9: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 9Copyright © 2012 AdaCore

MISRA C

• Intended for embedded automotive systems– Around 170 rules

• Prevent or restrict usage of C constructs with unpredictable behavior

• Encourage static checking tools that enforce compliance with the subset

Strengths– “Best practices” for C programming

– General good style

– Avoids C features intrinsically non-

portable / ill-defined

– Simplicity of C compared with other

languages– Smaller run-time library

– Easier certification

– Large population of candidate users

– And tools, service providers, etc.

– May be appropriate for small systems on

specialized hardware lacking compilers

for alternative languages– MISRA-C code may be produced as

output of source-to-source translator

Weaknesses– C was not designed to meet the

needs of high-integrity systems– Error-prone

– Lacks features such as

encapsulation, namespaces

– C (and thus MISRA C) does not

“scale up” to large systems

– C standard has ambiguities and

thus so does MISRA C

– Some rules are not enforceable by

static tools

– Usage of MISRA C requires license

from MISRA

Page 10: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 10Copyright © 2012 AdaCore

Ada – introduction

• What is Ada?– General-purpose methodology-neutral strongly-typed ISO standard language

– Intended for large, long-lived systems

– Fits reliability-critical embedded real-time applications

• Ada in a nutshell– Pascal-style foundation (syntax, basic data types)

– Exception handling

– Packages / encapsulation

– Generic templates

– Concurrency support (“tasking”)

– Object-Oriented Programming

– Support for “contracts”

– Since Ada 2012

– Specialized support for particular domains, including:

– Systems programming

– Real-time systems

– High-Integrity applications

Page 11: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 11Copyright © 2012 AdaCore

Ada for safety-critical systems (I)

• Full Ada not appropriate– Run-time library too large / complex

– High level features interfere with traceability and analyzability

• Why consider Ada for safety/security-critical software– General design philosophy promotes sound software engineering

– Successful history from Ada 83

• Standard support for safety/security-critical systems– High-Integrity Systems Annex

– Contracts

– Profiles

– Such as the Ravenscar profile

– User-defined tailoring, specifying features that are not used (pragma Restrictions)

– Compiler rejects programs that use these features

– Executable program excludes run-time libraries for these features

• Guidelines documents – Guide for Ada in High-Integrity Systems (an ISO Technical Report)

– Guide for Ada Ravenscar Profile in High-Integrity Systems

Page 12: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 12Copyright © 2012 AdaCore

Ada for safety-critical systems (II)

• Reliability– Very few features have surprising effects

– Prevention of dangling references to declared objects, subprograms

– Specification of intent on operation inheritance

– Contracts

– No garbage collection

– Programmer responsible for memory management

• Predictability– Language semantics are generally well-defined, in an ISO standard

– But there are features whose effects are implementation defined, implementation

dependent, unspecified, or “bounded errors”

– Implementation decisions can affect time or space predictability

– Functions returning unconstrained arrays

– Solutions in practice

– Analyze source program (e.g. no read of uninitialized object)

– Adhere to subset (no functions returning unconstrained arrays)

– Analyze object code so that implementation decision is known

Page 13: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 13Copyright © 2012 AdaCore

Ada for safety-critical systems (III)

• Analyzability– Some features help analyzability

– Child units may be used for testing packages with encapsulated state

– But full language is too complex; need to subset

– Key features are pragma Restrictions and pragma Profile

– Ada is in effect a family of profiles, where user can select features à la carte

– No such thing as the safety-critical Ada profile

– Troublesome OOP features are easily avoided

– But no standard annotation facility

• Expressibility– Support for low-level and real-time programming

– Ravenscar Profile for certifiable concurrent applications

– Good inter-language interfacing facilities, to incorporate certifiable libraries from other

languages

– Some weaknesses

– Limited support for distribution / networking

Page 14: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 14Copyright © 2012 AdaCore

Ada examples

package Arith is procedure Double (X : in out Integer) with Pre => X >= Integer'First / 2 and then X <= Integer'Last / 2, Post => X = 2 * X'Old;end Arith;

Spec (design by contract)

pragma Profile (Ravenscar);-- Only Ravenscar tasking allowed

pragma Restrictions (No_Allocators);-- Avoid dynamic memory

pragma Restrictions (No_Access_Subprograms);-- No pointers to subprograms

Configuration

type Parity_Kind is (Even, Odd);

type Reserved_21 is mod 2**21;for Reserved_21'Size use 21;

Type UART_Data_Register is record Data : Character; Data_Ready : Boolean; Parity : Parity_Kind; Parity_Error : Boolean; Reserved : Reserved_21; end record;

for UART_Data_Register use record Data at 0 range 0 .. 7; Data_Ready at 0 range 8 .. 8; Parity at 0 range 9 .. 9; Parity_Error at 0 range 10 .. 10; Reserved at 0 range 11 .. 31; end record;for UART_Data_Register'Size use 32;

UART_A : UART_Data_Register;pragma Atomic (UART_A);for UART_A’Address use To_Address (16#8000_0100#);

System programming

Page 15: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 15Copyright © 2012 AdaCore

Ada evaluation

• Strengths– Sound base language for programming high-reliability systems

– Allows construction of tailored high-integrity subsets that are:

– Powerful enough to use for real systems

– Simple enough to be amenable to certification

– Scales up to large systems

– ISO Standard

– Concurrency support / Ravenscar Profile

– Design by contract

– Free and publicly available documents

– Ada Reference Manual, Rationale

– Guidelines documents

– Good track record in avionics, train control, other safety-critical domains

• Weaknesses– Whole language is too large, so must be subsetted

– Aside from the standard Ravenscar Profile, others are vendor specific

– Language has some features with implementation-dependent or unspecified behavior

– Such as referencing a variable before its initialization

– Smaller user / tool vendor community than other languages

Page 16: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 16Copyright © 2012 AdaCore

SPARK introduction (I)

• SPARK is– A language, plus

– Static analysis tools based on underlying principle of correctness by construction

• Facilitate rigorous, static demonstration that program does what it is supposed to do– And only that

• Guarantee bounded time and space requirements

• Allow simple run-time library amenable to certification

• Ada-based language– Ada plus contracts minus features that interfere with above principle

– Contracts are special comments handled by the SPARK tools

• Language restrictions (Ada features intentionally omitted)– Features that complicate analysis / formal proofs, e.g.:

– Exceptions, goto statement, generic templates, function side effects

– Features that interfere with bounded time / space requirement, e.g.:

– Recursion, pointers, dynamically-sized arrays

Page 17: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 17Copyright © 2012 AdaCore

SPARK introduction (II)

• Language features include:– Most of Ada’s “static semantic” features, including packages, private types,

unconstrained array types, “OOP Lite”

– Concurrency (Ravenscar tasking profile)

• Contracts– Data / information flow (use of global variables, formal parameters)

– Inter-module dependencies

– Specification of dynamic behavior (pre / post-conditions, assertions)

• Analysis performed by SPARK tools– Static semantic analysis – detect aliasing, function side effects, …

– Control flow analysis – detect dead code, banned constructs (goto, …)

– Data-flow analysis – detect unused or uninitialized variables, …

– Information-flow analysis – check input / output variable coupling

– Verification condition generation – generate theorems

– Theorem proving

Page 18: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 18Copyright © 2012 AdaCore

SPARK example

procedure Process (Output : out T; Input1, Input2 : in T);

--# global out Global_Output;--# in Global_Input;--# derives Output from Input1, Input2 &--# Global_Output from Global_Input, Input2;--# pre Input1 /= 0;--# post Output = Input2 / Input1;

Contracts and flow information

Page 19: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 19Copyright © 2012 AdaCore

SPARK for safety-critical systems

• Reliability– Inherits Ada’s general advantages (few syntactic surprises)

– Avoids some errors that may occur in full Ada

– Reference to uninitialized variable

• Predictability– Unambiguous specification with no implementation dependencies

– But not a formal standard

– Contracts allow statically checkable assertions about resource requirements

• Analyzability– SPARK Ada subset eliminates features that are complicated to analyze

• Expressibility– Missing some useful functionality (e.g. generics) but does include some tasking

features

Page 20: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 20Copyright © 2012 AdaCore

SPARK evaluation

• Strengths– Designed from the start to satisfy high-integrity requirements

– Language rules are unambiguous, implementation independent

– Insecurities (e.g., “aliasing” of formal parameter and global variable) and many kinds of hard-to-

detect bugs (e.g. use before initialization) are prevented

– Contracts show programmer’s intent, aid readability

– Support for concurrency (Ravenscar profile)

– Tedious aspects of constructing proofs are automated

– Positive experience across a range of high-integrity projects

– Lower certification costs and post-delivery bug rates

– Excellent reference material

• Weaknesses– Language lacks some useful features

– No “pointers”, but in many kinds of safety critical systems a restricted style for pointers is

permitted• Pointers to statically declared data• Dynamic allocation at system startup

– Some restrictions (e.g. no recursion) require stylistic workarounds

– Contracts require different development style than what most programmers are used to

– Relatively small user community

– Small number of suppliers for tools, services

Page 21: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 21Copyright © 2012 AdaCore

Concurrency

Page 22: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 22Copyright © 2012 AdaCore

Concurrency in Ada (short history)

• Concurrency a first-class citizen in Ada– Easy to use and analyze

– Since the beginning

– Well-developed tasking in Ada 83

– Ada 95, Ada 2005 , Ada 2012 improved and extended tasking

Ad

a

Decades of experience in using Ada on multiprocessors

Page 23: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 23Copyright © 2012 AdaCore

Why Ravenscar

• Use concurrency in embedded real-time systems– Verifiable

– Simple

– Implemented reliably and efficiently

• Scheduling theory for accurate analysis of real-time behavior– Preemptive fixed priority scheduling

– Rate Monotonic Analysis (RMA)

– Response Time Analysis (RTA)

– Priority Ceiling Protocol (PCP)

– Avoids unbounded priority inversion and deadlocks

• Tool support– RMA and RTA

– Static simulation of concurrent real-time programs

Page 24: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 24Copyright © 2012 AdaCore

What is the Ravenscar profile

• A subset of the Ada tasking model

• Defined to meet safety-critical real-time requirements– Determinism

– Schedulability analysis

– Memory-boundedness

– Execution efficiency and small footprint

– Suitability for certification

• State-of-the-art concurrency constructs– Adequate for most types of real-time software

Page 25: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 25Copyright © 2012 AdaCore

Tasks

• Fixed set of tasks– Only at library level

– No dynamic allocation

– No nested declaration of tasks

– Statically created

– Task descriptors, stacks, …

• Each task is infinite loop– Single “triggering” action (delay or event)

• Task creation and activation is very simple and deterministic– All tasks are created at initialization

– Then all activated and executed according to their priority

Page 26: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 26Copyright © 2012 AdaCore

How tasks look like in Ravenscar

task body Cyclic is Period : constant Time_Span := Milliseconds (10); Activation : Time := Clock;begin loop delay until Activation; -- Do something

Something; -- Compute next activation time

Activation := Activation + Period; end loop;end Cyclic;

Time-triggered task

task body Sporadic isbegin loop Monitor.Wait_Event; -- Protected entry -- Do something

Something; end loop;end Sporadic;

Event-triggered task

Page 27: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 27Copyright © 2012 AdaCore

Periodic activity in Ada and C

task body Periodic is Activation : Time := Clock;begin loop delay until Activation; -- Do something

Something; -- Compute next activation time

Activation := Activation + Milliseconds (100); end loop;end Periodic;

static void *periodic (void *arg) { struct timespec activation;

clock_gettime (CLOCK_REALTIME, &activation);

while (1) { clock_nanosleep (CLOCK_REALTIME, TIMER_ABSTIME, &activation, NULL);

/* Do something */

something ();

/* Compute next activation time */

if (1000000000 - activation.tv_nsec < 100000000) { activation.tv_sec += 1; activation.tv_nsec += 100000000 - 1000000000; } else { activation.tv_nsec += 100000000; } }}

int main(void) { pthread_t thread; pthread_attr_t attr;

pthread_attr_init (&attr); pthread_create (&thread, &attr, &periodic, NULL); …}

C Periodic

Freq = 10 Hz

Page 28: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 28Copyright © 2012 AdaCore

Synchronization in Ravenscar

Pr Buffer

Put

C Producer

S Consumer

Get

task body Consumer is Element : Item;begin loop Buffer.Get (Element);

Consume (Element); end loop;end Consumer;

task body Producer is Period : constant Time_Span := Milliseconds (10); Activation : Time := Clock; Element : Item;begin loop delay until Activation;

Element := Produce_It;

Buffer.Put (Element);

Activation := Activation + Period; end loop;end Producer;

protected Buffer is procedure Put (Element : Item); entry Get (Element : out Item);private Container : Item; Received : Boolean := False;end Buffer;

protected body Buffer is procedure Put (Element : Item) is begin Container := Element; Received := True; end Put;

entry Get (Element : out Item) when Received is begin Element := Container; Received := False; end Get;end Buffer;

Page 29: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 29Copyright © 2012 AdaCore

Interrupt support in Ravenscar

Pr Monitor

Handler

S Interrupt_Task

Wait

External Interrupt

task body Interrupt_Task isbegin loop Monitor.Wait;

Handle_Interrupt; end loop;end Interrupt_Task;

protected Monitor is pragma Priority (Interrupt_Priority’Last);

procedure Handler; pragma Attach_Handler (Handler, Interrupt_ID);

entry Wait;private Signaled : Boolean := False;end Monitor;

protected body Monitor is procedure Handler is begin Signaled := True; end Put;

entry Wait when Signaled is begin Signaled := False; end Wait;end Monitor;

Page 30: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 30Copyright © 2012 AdaCore

Concurrency on linux

Page 31: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 31Copyright © 2012 AdaCore

The model – Ada tasks

• Ada task runs on top of operating system thread– One-to-one correspondence

• Task dispatching policy– Can be selected with pragma Dispatching_Policy

– SCHED_OTHER by default

– Preemptive priority scheduling

– pragma Dispatching_Policy (FIFO_Within_Priorities)• pthread_setschedparam (Thread, SCHED_FIFO, Param’Access)

– Means run until blocked (or preempted), no time slicing

– Reduces non-determinism

– NOTE!!: Requires special (root) privileges

• Xenomai– Real-time behavior

– The Ada run-time library can call the Xenomai API to create tasks in kernel domain

Page 32: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 32Copyright © 2012 AdaCore

The model – Synchronization

• A protected type is a data object with locks– Data encapsulation, accessible through interface (locked access routines)

– functions (read the data with read lock)

– procedures (read/write the data with write lock)

– entries (wait until some condition is met, then read/write the data with write lock)

• Priority inheritance– Guard against priority inversion

– low priority task grabs resource X

– high priority task needs resource X, waits

– medium priority task preempts low priority task, and runs for a long time, holding up high priority

task

– Solution, while high priority task is waiting, lend high priority to low priority task

• Implementation

– Mutual exclusion– Mutex

– Waiting / Signaling operations– Conditional variable

Page 33: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 33Copyright © 2012 AdaCore

The model – System programming

• Clock and delay– Clock uses gettimeofday

– Delay operations use timed conditional variables– We can wakeup the task before expiration if needed

• Interrupt handling– Underlying signal mechanism

– A server task per interrupt served– With specific mask to serve the required signal

– Call to sigwait

Page 34: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 34Copyright © 2012 AdaCore

Multiprocessors

Page 35: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 35Copyright © 2012 AdaCore

Why addressing multiprocessors?

• The answer to increasing processing demands– We cannot increase the clock frequency forever

– We cannot increase the instruction-level parallelism forever

– Provides higher performance for less consumed energy

Courtesy IEEE Computer, January 2011, page 33.

Page 36: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 36Copyright © 2012 AdaCore

Periodic activity in Ada and C (multiprocessor version)

task Periodic with CPU => 1;

task body Periodic is Activation : Time := Clock;begin loop delay until Activation; -- Do something

Something; -- Compute next activation time

Activation := Activation + Milliseconds (100); end loop;end Periodic;

static void *periodic (void *arg) { struct timespec activation;

clock_gettime (CLOCK_REALTIME, &activation);

while (1) { clock_nanosleep (CLOCK_REALTIME, TIMER_ABSTIME, &activation, NULL); /* Do something */ something ();

/* Compute next activation time */

if (1000000000 - activation.tv_nsec < 100000000) { activation.tv_sec += 1; activation.tv_nsec += 100000000 - 1000000000; } else { activation.tv_nsec += 100000000; } }}

int main(void) { cpu_set_t cpuset; pthread_t thread; pthread_attr_t attr;

CPU_ZERO (&cpuset); CPU_SET (0, &cpuset);

pthread_attr_init (&attr); pthread_attr_setaffinity_np (&attr, sizeof (cpu_set_t), &cpuset);

pthread_create (&thread, &attr, &periodic, NULL); …}

C Periodic

Freq = 10 HzCPU affinity = 1

Page 37: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 37Copyright © 2012 AdaCore

Some advantages of Ada

• Better readability– No doubts, right?

• Semantics– The Ada run time enforces required dispatching policy, signal mask, master-dependent

tasks, …

• Portability

Platform Ada C

Linux

Solaris

Windows

VxWorks

Bare board

Page 38: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 38Copyright © 2012 AdaCore

Ada 2012

• Ada 2012 added explicit support for controlling processor allocation– Dispatching_Domain

• Ravenscar profile for multiprocessors

• Parallel task synchronization

• Memory barriers

Page 39: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 39Copyright © 2012 AdaCore

Concurrent / parallel execution in Ada

• Ada has always taken into account parallel architectures– Allow concurrent/parallel execution

– Multicomputers, multiprocessors, interleaved execution

– Even allow parallel execution of a single task

– … if its effect is as executed sequentially

– A task can be on the ready queues of more than one processor

– Many partitioning schemes allowed

– Via implementation-defined pragmas or non standard library packages

Page 40: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 40Copyright © 2012 AdaCore

Synchronization and communication of parallel activities

• Task synchronization– Protected objects

– No language-defined ordering or queuing presumed for tasks competing to start a

protected action• Tasks are intended to spin lock on multi-processors

– Shared variables

– Cache coherence

– Rendezvous

– The call to the task entry is blocking

Page 41: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 41Copyright © 2012 AdaCore

Symmetric Multi-Processor (SMP)

• Several similar processors– All processors can perform the same functions

• Centralized memory with uniformed access time– Problem of cache coherence

Main memory I/O System

One ormore cache

levels

Processor

One ormore cache

levels

Processor

One ormore cache

levels

Processor

One ormore cache

levels

Processor

Page 42: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 42Copyright © 2012 AdaCore

• Tasks assigned to a given processor

• How to schedule a group of tasks on

a processor is known– Rate Monotonic Scheduling (static

priorities)

– Earliest Deadline First (dynamic

priorities)

• But, dividing the tasks into groups is

NP-hard

• Task migration is permitted– Overhead of task migration

increases with the number of CPUs

– Reduced cache performance

Partitioned versus global scheduling

Partitioning Global Scheduling

None is better than the other in terms of guaranteed CPU utilization

Page 43: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 43Copyright © 2012 AdaCore

Typical OS support for multiprocessors

• Set CPU affinity– Allocate tasks to one CPU (or to a group of CPUs)

• Get CPU affinity

• Task migration– From one CPU to another

– Either user-requested or performed by the OS

• Spin locks– Tasks wait in a loop until lock is free (busy waiting)

– Multiprocessor synchronization

Page 44: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 44Copyright © 2012 AdaCore

Support for multiprocessors in Ada 83, 95, 05

• Ada has always allowed a program’s implementation to be on a

multiprocessor system– Real parallelism

– Inter-processor synchronization

• No direct support for affinities– The OS can decide the best allocation

– The developer

– Implementation-defined pragmas or non standard library packages

• Allows the full range of partitioning– But no user control defined in the standard

Page 45: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 45Copyright © 2012 AdaCore

Explicit support for multiprocessors in Ada 2012

• Notion of dispatching domain

• Safe multiprocessor tasking

• Parallel task synchronization

• Memory barriers

Page 46: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 46Copyright © 2012 AdaCore

Ada 2012 dispatching domains

• Focus on SMPs

• Handle mapping of tasks to processors– Support all schemes

– Partitioned• Tasks allocated to a subset of CPUs

– Global• Implicit task migration supported• Explicit task migration allowed

• Notion of processor dispatching domain– Group of processors across which global scheduling occurs

– Non-overlapping dispatching domains

– Tasks are assigned to an unique dispatching domain

– A task may be allocated to a given processor within the dispatching domain

– Or free to be in any of the domain

Page 47: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 47Copyright © 2012 AdaCore

Static allocation to processors

task type Allocated_Task (Affinity : CPU) with CPU => Affinity;

T1 : Allocated_Task (1);T2 : Allocated_Task (2);T3 : Allocated_Task (3);T4 : Allocated_Task (4);

GroupA : aliased Dispatching_Domain := Create (1, 2);GroupB : aliased Dispatching_Domain := Create (3, 4);GroupC : aliased Dispatching_Domain := Create (5, 6);GroupD : aliased Dispatching_Domain := Create (7, 8);

task type Grouped_Task (Group : access Dispatching_Domain) with Dispatching_Domain => Group.all; T1, T2, T3, T4 : Grouped_Task (GroupA’Access);T5, T6 : Grouped_Task (GroupB’Access);T7, T8, T9 : Grouped_Task (GroupC’Access);T10, T11 : Grouped_Task (GroupD’Access);

Page 48: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 48Copyright © 2012 AdaCore

Dynamic affinity handling

GroupA : Dispatching_Domain := Create (1, 2);GroupB : Dispatching_Domain := Create (3, 4);GroupC : Dispatching_Domain := Create (5, 6);GroupD : Dispatching_Domain := Create (7, 8);

task T_In_A with Dispatching_Domain => GroupA;task T_Non_Allocated;

task body Driver isbegin -- Allocate T_Non_Allocated to GroupB

Assign_Task (GroupB, 3, T_Non_Allocated’Identity); Do_Something;

-- Move it to a different processor

if Proc_3_Overloaded then Set_CPU (4, T_Non_Allocated’Identity); end if; Do_Something;

end Driver;

task body T_In_A is Current_CPU : CPU;begin -- In processor 1 or 2

Do_Something;

-- In processor 1 only

Set_CPU (1); Do_Something; -- In processor 2 only

Set_CPU (Get_Last_CPU (GroupA)); Do_Something;

-- Now again in processor 1 or 2

Set_CPU (Not_A_Specific_CPU); Do_Something;

-- Now I am lost. Where am I?

Current_CPU := Get_CPU; pragma Assert (Current_CPU = Not_A_Specific_CPU);end T_In_A;

Page 49: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 49Copyright © 2012 AdaCore

• What I want– Create a group of processors

– Define an specific scheduling policy for

the group

– Execute a set of tasks within the group

• What I have to do– Create a dispatching domain

– Define a non-overlapping priority band

– Allocate tasks to the dispatching

domain

– Use priorities in the priority band

Handle affinity and dispatching policy

pragma Priority_Specific_Dispatching (FIFO_Within_Priorities, 20, 25);

Group : Dispatching_Domain := Create (1, 2);

task T1 with Dispatching_Domain => Group, Priority => 22;

task T2 with Dispatching_Domain => Group, Priority => 23, CPU => 1;

task T3 with Dispatching_Domain => Group, Priority => 24;

Page 50: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 50Copyright © 2012 AdaCore

Synchronization on multiprocessors

• Protected objects– There is a lock-free optimization for monoprocessors (using priorities)

– No longer viable on multiprocessors

– Currently Ada advise that tasks should busy-wait (spin) at their active priority for the

lock

• Task entries– Requires internal synchronization primitives aware of multiprocessor

– Spin locks

Page 51: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 51Copyright © 2012 AdaCore

Multicores for real-time safety-critical embedded systems

• We need to address:– Reliability

– Predictability

– Analyzability

• The Ravenscar profile for monoprocessors is– Deterministic

– Time analyzable

– Simple to use and implement

• Extend the Ravenscar profile model from monoprocessor to multiprocessor– Fully partitioned model

– Fixed-priority scheduling

– Static model

• Allow for– Simple implementation

– Verifiable

– Schedulability analysis

Page 52: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 52Copyright © 2012 AdaCore

Static model

• Concurrent entities fixed and static– Tasks and shared memory defined before execution

• Static fixed priority scheduling algorithm– Preemptive fixed priority scheduling in each CPU

– Analyzable as in Ravenscar for monoprocessors

– Dynamic-priority scheduling algorithms could increase CPU utilization but:

– Higher complexity

– Higher run-time overhead

– Lower predictability, lower robustness in case of overload

• Partitioned– Each task allocated to an user-defined processor forever

– CPU utilization of partitioned scheduling is neither better nor worse than global

– It relies on very well known monoprocessor techniques for priority allocation and timing

analysis

– It is much simpler to implement

– No task migration

Page 53: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 53Copyright © 2012 AdaCore

• Tasks statically allocated to

processors– No task migration

• Preemptive fixed-priority

scheduling

• Single shared run time– Per-CPU ready queues

– Spin-locks to protect shared data

– Disabling interrupts is not

enough

• Operations on a different

processors– Triggering an special interrupt in the

target processor

Task scheduling

0

3

1

2

task Cyclic

with Priority => 100,

CPU => 3;

end Cyclic;

task Cyclic with Priority => 100, CPU => 3;end Cyclic;

Ravenscar system on monoprocessor

Page 54: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 54Copyright © 2012 AdaCore

Task synchronization

• Library-level protected objects– Shared data with mutual exclusion

– Both for inter- and intra-processor communication

• Simple and efficient mutual exclusion changing priority for intra-processor

communication– As in Ravenscar monoprocessor

– Could be statically detected

– Efficiency

– Simple timing analysis

• Spin-locking for inter-processor synchronization

• Awaking tasks from other processors– Inter-processor interrupt facility to modify the ready queues

Page 55: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 55Copyright © 2012 AdaCore

Parallel task synchronization

• Goal– Effective parallel task synchronization

– Set of tasks blocked and released at once

• Typical case– A group of tasks must wait until all of them reach a synchronization point

– And then be released together to work in parallel

• Mimic the POSIX barrier mechanism

Page 56: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 56Copyright © 2012 AdaCore

Parallel barrier example

package Ada.Synchronous_Barriers is pragma Preelaborate (Synchronous_Barriers); subtype Barrier_Limit is Positive range 1 .. <imp-def>;

type Synchronous_Barrier (Release_Threshold : Barrier_Limit) is limited private;

procedure Wait_For_Release (The_Barrier : in out Synchronous_Barrier; Notified : out Boolean);

private -- not specified by the languageend Ada.Synchronous_Barriers;

Number_Of_Tasks : constant := 8; Barrier : Synchronous_Barrier (Number_Of_Tasks);

task type Worker (Affinity : CPU) with CPU => Affinity;

task body Worker is Notified : Boolean;begin loop Wait_For_Release (Barrier, Notified); -- Do something in parallel at the same time

Something; if Notified then -- Only one task does this

Ask_For_More_Work; end if; end loop;end Worker;

Page 57: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 57Copyright © 2012 AdaCore

Memory barriers

• Goal– Have control over cache memories

• Typical case– Non-blocking algorithms to effectively exploit hardware parallelism

– lock-free and wait-free

• Problem to solve– How to ensure the correct order of loads and stores with multi-level caches

– Modern multicores do not guarantee this ordering between processors

– Optimizations that can result in out-of-order execution

– Unless special instructions are used

• How to do it with Volatile– Until Ada 2005

– They can never be in cache or registers

– The Ada 2012 (more realistic) approach

– Volatiles can be handled in cache memories, but

– Guarantee serial ordering• All tasks of the program (on all processors) that read or update volatile variables see the same

order of updates to the variables• May need the use of an appropriate memory barrier to flush the cache

Page 58: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 58Copyright © 2012 AdaCore

• In Ada 83, 95, 2005– Shared_Data and Barrier can never be in

cache

• In Ada 2012– Shared_Data and Barrier can be in cache

Example of memory barriers

Shared_Data : Integer;pragma Volatile (Shared_Data); Barrier : Boolean := False;pragma Volatile (Barrier);

task Producer with CPU => 1;

task Consumer with CPU => 2;

task body Producer isbegin … Produce (Shared_Data); Barrier := True; …end Producer; task body Consumer isbegin … while not Barrier loop null; end loop;

-- If we see that Barrier has been updated, -- we must see the produced value of -- Shared_Data.

Use(Shared_Data); …end Consumer;

Page 59: Safe and Reliable Embedded Linux Programming: How to Get There

Slide: 59Copyright © 2012 AdaCore

Conclusion

• There is support in programming environments for high-integrity

• Embedded programming can use high-level abstraction constructs

• Real-time support– Computational model amenable to timing analysis

• Ada has supported execution on parallel architectures since its inception– Ada 2012 dispatching domains

– Good flexibility and analyzability

– Implementable on top of typical operating systems and kernels

– Ravenscar for multiprocessors– Simple extension to Ravenscar on monoprocessors– Partitioning into a set of monoprocessor Ravenscar systems– Keep desired properties found in monoprocessor Ravenscar