Rationale for Ada 2005: Introduction

RUSTOP
BACKNEXT

ENG

3.4 Tasking and real-time facilities

@ Unless mentioned otherwise all the changes in this section concern the Real-Time Systems annex.

@ First, the well-established Ravenscar profile is included in Ada 2005 as directed by WG9. A profile is a mode of operation and is specified by the pragma Profile which defines the particular profile to be used. Thus to ensure that a program conforms to the Ravenscar profile we write

  1        pragma Profile (Ravenscar);

@ The purpose of Ravenscar is to restrict the use of many of the tasking facilities so that the effect of the program is predictable. This is very important for real-time safety-critical systems. In the case of Ravenscar the pragma is equivalent to the joint effect of the following pragmas

  1        pragma Task_Dispatching_Policy (FIFO_Within_Priorities);
  2        pragma Locking_Policy (Ceiling_Locking);
  3        pragma Detect_Blocking;

@ plus a pragma Restrictions with a host of arguments such as No_Abort_Statements and No_Dynamic_Priorities.

@ The pragma Detect_Blocking plus many of the Restrictions identifiers are new to Ada 2005. Further details will be given in a later paper.

@ Ada 95 allows the priority of a task to be changed but does not permit the ceiling priority of a protected object to be changed. This is rectified in Ada 2005 by the introduction of an attribute Priority for protected objects and the ability to change it by a simple assignment such as

  1        My_PO'Priority := P;

@ inside a protected operation of the object My_PO. The change takes effect at the end of the protected operation.

@ The monitoring and control of execution time naturally are important for real-time programs. Ada 2005 includes packages for three different aspects of this Ada.Execution_Time - this is the root package and enables the monitoring of execution time of individual tasks.

@ Ada.Execution_Time.Timers - this provides facilities for defining and enabling timers and for establishing a handler which is called by the run time system when the execution time of the task reaches a given value.

@ Ada.Execution_Time.Group_Budgets - this allows several tasks to share a budget and provides means whereby action can be taken when the budget expires.

@ The execution time of a task or CPU time, as it is commonly called, is the time spent by the system executing the task and services on its behalf. CPU times are represented by the private type CPU_Time. The CPU time of a particular task is obtained by calling the following function Clock in the package Ada.Execution_Time

  1        function Clock (T : Task_Id := Current_Task) return CPU_Time;

@ A value of type CPU_Time can be converted to a Seconds_Count plus residual Time_Span by a procedure Split similar to that in the package Ada.Real_Time. Incidentally we are guaranteed that the granularity of CPU times is no greater than one millisecond and that the range is at least 50 years.

@ In order to find out when a task reaches a particular CPU time we use the facilities of the child package Ada.Execution_Time.Timers. This includes a discriminated type Timer and a type Handler thus

  1        type Timer (T : not null access constant Task_Id) is tagged limited private;
  2        type Handler is access protected procedure (TM : in out Timer);

@ Note how the access discriminant illustrates the use of both not null and constant.

@ We can then set the timer to expire at some absolute time by

  1        Set_Handler (My_Timer, Time_Limit, My_Handler'Access);

@ and then when the CPU time of the task reaches Time_Limit (of type CPU_Time), the protected procedure My_Handler is executed. Note how the timer object incorporates the information regarding the task concerned using an access discriminant and that this is passed to the handler via its parameter. Another version of Set_Handler enables the timer to be triggered after a given interval (of type Time_Span).

@ In order to program various aperiodic servers it is necessary for tasks to share a CPU budget. This can be done using the child package Ada.Execution_Time.Group_Budgets. In this case we have

  1        type Group Budget is tagged limited private;
  2        type Handler is access protected procedure (GB : in out Group_Budget);

@ The type Group_Budget both identifies the group of tasks it belongs to and the size of the budget. Various subprograms enable tasks to be added to and removed from a group budget. Other procedures enable the budget to be set and replenished.

@ A procedure Set_Handler associates a particular handler with a budget.

  1        Set_Handler (GB => My_Group_Budget, Handler => My_Handler'Access);

@ When the group budget expires the associated protected procedure is executed.

@ A somewhat related topic is that of low level timing events. The facilities are provided by the package Ada.Real_Time.Timing_Events. In this case we have

  1        type Timing_Event is tagged limited private;
  2        type Timing_Event_Handler is access protected procedure (Event : in out Timing_Event);

@ The idea here is that a protected procedure can be nominated to be executed at some time in the future. Thus to ring a pinger when our egg is boiled after four minutes we might have a protected procedure

  1        protected body Egg is
  2                procedure Is_Done (Event : in out Timing_Event) is
  3                begin
  4                        Ring_The_Pinger;
  5                end Is_Done;
  6        end Egg;

@ and then

  1        Egg_Done : Timing_Event;
  2        Four_Min : Time_Span := Minutes (4);
  3        ...
  4        Put_Egg_In_Water;
  5        Set_Handler (Event => Egg_Done, In_Time => Four_Min, Handler => Egg.Is_Done'Access);
  6        -- now read newspaper whilst waiting for egg

@ This facility is of course very low level and does not involve Ada tasks at all. Note that we can set the event to occur at some absolute time as well as at a relative time as above. Incidentally, the function Minutes is a new function added to the parent package Ada.Real_Time. Otherwise we would have had to write something revolting such as 4*60*Milliseconds(1000). A similar function Seconds has also been added.

@ There is a minor flaw in the above example. If we are interrupted by the telephone between putting the egg in the water and setting the handler then our egg will be overdone. We will see how to cure this in a later paper.

@ Readers will recall the old problem of how tasks can have a silent death. If something in a task goes wrong in Ada 95 and an exception is raised which is not handled by the task, then it is propagated into thin air and just vanishes. It was always deemed impossible for the exception to be handled by the enclosing unit because of the inherent asynchronous nature of the event.

@ This is overcome in Ada 2005 by the package Ada.Task_Termination which provides facilities for associating a protected procedure with a task. The protected procedure is invoked when the task terminates with an indication of the reason. Thus we might declare a protected object Grim_Reaper

  1        protected Grim_Reaper is
  2        procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence);
  3        end Grim_Reaper;

@ We can then nominate Last_Gasp as the protected procedure to be called when task T dies by

  1        Set_Specific_Handler (T'Identity, Grim_Reaper.Last_Gasp'Access);

@ The body of the protected procedure Last_Gasp might then output various diagnostic messages

  1        procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence) is
  2        begin
  3                case C is
  4                        when Normal              => null;
  5                        when Abnormal            => Put ("Something nasty happened"); ...
  6                        when Unhandled_Exception => Put ("Unhandled exception occurred"); ...
  7                end case;
  8        end Last_Gasp;

@ There are three possible reasons for termination, it could be normal, abnormal, or caused by an unhandled exception. In the last case the parameter X gives details of the exception occurrence.

@ Another area of increased flexibility in Ada 2005 is that of task dispatching policies. In Ada 95, the only predefined policy is FIFO_Within_Priorities although other policies are permitted. Ada 2005 provides further pragmas, policies and packages which facilitate many different mechanisms such as non-preemption within priorities, the familiar Round Robin using timeslicing, and the more recently acclaimed Earliest Deadline First (EDF) policy. Moreover, it is possible to mix different policies according to priority level within a partition.

@ Various facilities are provided by the package Ada.Dispatching plus two child packages

@ Ada.Dispatching - this is the root package and simply declares an exception Dispatching_Policy_Error.

@ Ada.Dispatching.Round_Robin - this enables the setting of the time quanta for time slicing within one or more priority levels.

@ Ada.Dispatching.EDF - this enables the setting of the deadlines for various tasks.

@ A policy can be selected for a whole partition by one of

  1        pragma Task_Dispatching_Policy (Non_Preemptive_FIFO_Within_Priorities);
  2        pragma Task_Dispatching_Policy (Round_Robin_Within_Priorities);
  3        pragma Task_Dispatching_Policy (EDF_Across_Priorities);

@ In order to mix different policies across different priority levels we use the pragma

@ Priority_Specific_Dispatching with various policy identifiers thus

  1        pragma Priority_Specific_Dispatching (Round_Robin_Within_Priorities, 1, 1);
  2        pragma Priority_Specific_Dispatching (EDF_Across_Priorities, 2, 10);
  3        pragma Priority_Specific_Dispatching (FIFO_Within_Priorities, 11, 24);

@ This sets Round Robin at priority level 1, EDF at levels 2 to 10, and FIFO at levels 11 to 24.

@ The final topic in this section concerns the core language and not the Real-Time Systems annex.

@ Ada 2005 introduces a means whereby object oriented and real-time features can be closely linked together through inheritance.

@ Recall from Section 3.1 that we can declare an interface to be limited thus type LI is limited interface;

@ We can also declare an interface to be synchronized, task, or protected thus type SI is synchronized interface;

  1        type TI is task interface;
  2        type PI is protected interface;

@ A task interface or protected interface has to be implemented by a task type or protected type respectively. However, a synchronized interface can be implemented by either a task type or a protected type. These interfaces can also be composed with certain restrictions. Detailed examples will be given in a later paper.

Rationale for Ada 2005: Introduction

ENGRUSTOP
BACKNEXT

3.4 Управление задачами и средствами реального времени

@ Если не упомянуто иначе все изменения в этом разделе касаются приложения Систем реального времени.

@ Во-первых, известная конфигурация Ravenscar включена в Аду 2005 как заявлено WG9. Конфигурация определяется прагмой Profile, которая определяет специфическую конфигурацию, которая будет использоваться. Таким образом, чтобы гарантировать, что программа соответствует конфигурации Ravenscar, мы должны написать:

  1        pragma Profile (Ravenscar);

@ Назначение Ravenscar состоит в том, чтобы ограничить использование некоторых средств управления задачами так, чтобы эффект программы был предсказуем. Это очень важно для критических по отношению к безопасности систем реального времени. Эффект от прагмы Ravenscar эквивалентен совместному эффекту от следующих прагм:

  1        pragma Task_Dispatching_Policy (FIFO_Within_Priorities);
  2        pragma Locking_Policy (Ceiling_Locking);
  3        pragma Detect_Blocking;

@ плюс прагма Restriction с главными параметрами, такими как No_Abort_Statements и No_Dynamic_Priorities.

@ Прагма Detect_Blocking и многие другие из Restrictions идентификаторов являются новыми в Аде 2005. Дальнейшие подробности будут даны в более поздней публикации.

@ Ада 95 позволяет менять приоритет задачи, но не разрешает менять ceiling приоритет защищенного объекта. Это исправлено в Аде 2005 введением атрибута Priority для защищенных объектов и возможностью изменить его простым присваиванием:

  1        My_PO'Priority := P;

@ в защищенной операции объекта My_PO. Изменение вступает в силу в конце защищенной операции.

@ Контроль и управление временем выполнения, естественно, важны для программ в реальном времени. Ада 2005 включает пакеты для трех различных аспектов Аda.Execution_Time - корневого пакета и допускает контроль времени выполнения индивидуальных задач.

@ Ада.Execution_Time.Timers обеспечивает средства для определения таймеров и обработчиков, которые вызывает система времени выполнения, когда время выполнения задачи достигает заданного значения.

@ Ада.Execution_Time.Group_Budgets позволяет нескольким задачам совместно использовать бюджет и обеспечивает средства для того чтобы предпринять некое действие, когда бюджет истекает.

@ Время выполнения задачи или процессорное время, как его обычно называют, является временем, проведенным системой, выполняющей задачу или сервис от своего лица. Процессорное время представлено частным типом CPU_Time. Процессорное время специфической задачи получается вызовом функции Clock пакета Ada.Execution_Time.

  1        function Clock (T : Task_Id := Current_Task) return CPU_Time;

@ Значение типа CPU_Time может быть преобразовано к Seconds_Count плюс остаточный Time_Span процедурой Split, подобной как в пакете Ada.Real_Time. Случайно нам гарантируют это, степень детализации процессорного времени не больше чем одна миллисекунда и что диапазон составляет по крайней мере 50 лет.

@ Чтобы узнать, когда задача достигает специфического процессорного времени, мы используем средства дочернего пакета Ada.Execution_Time.Timers. Он включает дискриминантный тип Timer и тип Handler:

  1        type Timer (T : not null access constant Task_Id) is tagged limited private;
  2        type Handler is access protected procedure (TM : in out Timer);

@ Обратите внимание, как ссылочный дискриминант определяет использование и не пустого указателя и константы.

@ Тогда мы можем заставить таймер завершиться в некоторое абсолютное время:

  1        Set_Handler (My_Timer, Time_Limit, My_Handler'Access);

@ и затем, когда процессорное время задачи достигает Time_Limit (типа CPU_Time), запустится защищенная процедура My_Handler. Обратите внимание, как объект таймера включает информацию относительно задачи, заинтересованной, использует ссылочный дискриминант и что его передают на обработчик через его параметр. Другая версия Set_Handler дает возможность таймеру быть вызванным после истечения некоторого интервала (типа Time_Span).

@ Чтобы программировать различные апериодические серверы (а это необходимо для задач совместно использующих бюджет центрального процессора) мы можем использовать дочерний пакет Ada.Execution_Time.Group_Budgets. В этом случае мы имеем:

  1        type Group Budget is tagged limited private;
  2        type Handler is access protected procedure (GB : in out Group_Budget);

@ Тип Group_Budget идентифицирует группу задач бюджета и размер бюджета. Различные подпрограммы дают возможность задачам быть добавленными к и удаленными из бюджета группы. Другие процедуры дают возможность бюджету быть установленным и пополненным.

@ Процедура Set_Handler связывает специфический обработчик с бюджетом.

  1        Set_Handler (GB => My_Group_Budget, Handler => My_Handler'Access);

@ Когда бюджет группы истекает, связанная с ним защищенная процедура выполняются.

@ Несколько связанный раздел - связанный раздел низкого уровня, рассчитывающего события. Средства предоставляются пакетом Ada.Real_Time.Timing_Events. В этом случае мы имеем:

  1        type Timing_Event is tagged limited private;
  2        type Timing_Event_Handler is access protected procedure (Event : in out Timing_Event);

@ Идея здесь состоит в том, что защищенная процедура может быть назначена, чтобы быть выполненной в некоторое время в будущем. Таким образом, чтобы зазвонить в звонок, когда наше яйцо будет варится более четырех минут у нас могла бы быть защищенная процедура:

  1        protected body Egg is
  2                procedure Is_Done (Event : in out Timing_Event) is
  3                begin
  4                        Ring_The_Pinger;
  5                end Is_Done;
  6        end Egg;

@ и тогда:

  1        Egg_Done : Timing_Event;
  2        Four_Min : Time_Span := Minutes (4);
  3        ...
  4        Put_Egg_In_Water;
  5        Set_Handler (Event => Egg_Done, In_Time => Four_Min, Handler => Egg.Is_Done'Access);
  6        -- now read newspaper whilst waiting for egg

@ Это средство - конечно очень низкий уровень который не вовлекает задачи Ады вообще. Отметим, что мы можем заставить событие происходить в некоторое абсолютное время так же как в относительное время как выше. Случайно, функция Minutes - новая функция, добавленная к родительской пакетау Ada.Real_Time. Иначе мы должны были бы написать кое-что вызывающее отвращение: 4 * 60 * Milliseconds (1000). Подобная функция Seconds была также добавлена.

@ В вышеупомянутом примере есть небольшой недостаток. Если мы будем прерваны телефоном между помещением яйца в воду и установкой обработчика тогда наше яйцо будет переварено. Мы увидим, как это исправить в более поздней публикации.

@ Читатели напомнят старую проблему того, как у задач может быть тихая смерть. Если кое-что в задаче идет не так как надо и в Аде 95 возбуждается исключение, которое не обрабатывается задачей, то она только испускает дух и исчезает. Для исключений всегда считали невозможным быть обработанным модулем включения из-за врожденной асинхронной природы событий.

@ Это преодолено в Аде 2005 пакетом Ada.Task_Termination, который обеспечивает средства для того, что они связали защищенную процедуру с задачей. Защищенная процедура вызывается, когда задача заканчивается с индикацией относительно причины. Таким образом, мы могли бы объявить защищенный объект Grim_Reaper

  1        protected Grim_Reaper is
  2                procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence);
  3        end Grim_Reaper;

@ Мы можем тогда назначить Last_Gasp как защищенную процедуру, которую вызовут, когда задача T помрёт:

  1        Set_Specific_Handler (T'Identity, Grim_Reaper.Last_Gasp'Access);

@ Тело защищенной процедуры Last_Gasp могло бы тогда вывести различные диагностические сообщение:

  1        procedure Last_Gasp (C : Cause_Of_Termination; T : Task_Id; X : Exception_Occurrence) is
  2        begin
  3                case C is
  4                        when Normal              => null;
  5                        when Abnormal            => Put ("Something nasty happened"); ...
  6                        when Unhandled_Exception => Put ("Unhandled exception occurred"); ...
  7                end case;
  8        end Last_Gasp;

@ Есть три возможных причины для завершения: нормальное, аварийное, или вызванное необработанным исключением. В последнем случае параметр X сообщает подробности происхождения исключения.

@ Другая область увеличенной гибкости в Аде 2005 является политика диспетчеризации задач. В Аде 95, единственная предопределенная политика - FIFO_Within_Priorities, хотя и другая политика разрешена. Ада 2005 обеспечивает новые прагмы, политику и пакеты, которые обеспечивают много различных механизмов, таких как неприоритетное прерывание в пределах приоритетов, знакомый Циклический алгоритм, с использованием timeslicing, и позже приветствуемая политика Самый ранний Крайний срок Сначала (EDF). Кроме того, возможно, чтобы смешать различную политику согласно приоритетному уровню в пределах разделения.

@ Различные средства предоставлены пакетом Ada.Dispatching плюс два дочерних пакета:

@ Ada.Dispatching - это корневой пакет, он просто объявляет исключение Dispatching_Policy_Error.

@ Ada.Dispatching.Round_Robin - это допускает установку квантов времени для квантования времени в пределах одного или более приоритетных уровней.

@ Ada.Dispatching.EDF - это допускает установку пределов для различных задач.

@ Политика может быть выбрана для целого разделения одним из способов:

  1        pragma Task_Dispatching_Policy (Non_Preemptive_FIFO_Within_Priorities);
  2        pragma Task_Dispatching_Policy (Round_Robin_Within_Priorities);
  3        pragma Task_Dispatching_Policy (EDF_Across_Priorities);

@ Чтобы смешать различную политику через различные приоритетные уровни, мы используем прагму Priority_Specific_Dispatching с различными идентификаторами политики следующим образом:

  1        pragma Priority_Specific_Dispatching (Round_Robin_Within_Priorities, 1, 1);
  2        pragma Priority_Specific_Dispatching (EDF_Across_Priorities, 2, 10);
  3        pragma Priority_Specific_Dispatching (FIFO_Within_Priorities, 11, 24);

@ Это устанавливает Циклический алгоритм на приоритетном уровне 1, EDF на уровнях 2 - 10, и в порядке поступления на уровнях 11 - 24.

@ Последняя тема в этом разделе касается базового языка, а не приложения Систем реального времени.

@ Ада 2005 вводит средство, посредством которого объектно-ориентированные средсва и средства реального времени могут быть близко соединены через наследование.

@ Возвращаясь к разделу 3.1, мы можем объявить, что интерфейс ограничен таким образом тип LI, ограничен интерфейс;

@ Мы можем также объявить, что интерфейс синхронизирован, задача, или защищен таким образом тип, PI синхронизирован интерфейс;

  1        type TI is task interface;
  2        type PI is protected interface;

@ Интерфейс задачи или защищенный интерфейс должны быть осуществлены типом задачи или защищали тип соответственно. Однако, синхронизированный интерфейс может быть осуществлен или типом задачи или защищенным типом. Эти интерфейсы могут также быть составлены с определенными ограничениями. Детальные примеры будут даны в более поздней публикации.

ENG RUS

TOP BACK NEXT

2010-10-24 00:26:52

. .