Программирование систем реального времени на Ada — приоритеты, периодичность и контроль времени выполнения на практике
· Го Комура · Ada, Реальное время, Ravenscar, CeilingLocking, Таскинг, Планирование, Инверсия приоритета, Язык программирования, Реальное время, Высокая надёжность
1. Введение — глубокая связь Ada и реального времени
В предыдущей статье «Безопасная конкурентность в Ada» мы разобрали основы безопасной конкурентности в Ada на базе задач (task) и защищённых объектов (protected object). В этой статье мы двигаемся дальше — в более жёстко ограниченную область, системы реального времени.
В системах реального времени «правильность» означает не только логическую корректность результата вычислений, но и то, что результат получен в срок. Правильный ответ, полученный на миллисекунду позже дедлайна, так же опасен, как и неправильный.
Ada отвечает на это требование обширным набором функций реального времени, стандартизированным как Annex D (Real-Time Systems) спецификации языка. Это не «библиотека, добавленная поверх», а гарантии реального времени, встроенные непосредственно в среду выполнения языка.
Функции реального времени в Ada (Annex D):
- Приоритеты задач и вытеснение (FIFO_Within_Priorities)
- Протокол Ceiling_Locking (предотвращение инверсии приоритета)
- Периодическое выполнение по абсолютному времени через delay until
- Профиль Ravenscar (подмножество для систем с критичной безопасностью)
- Тайминговые события (пробуждение по времени без опроса)
- Мониторинг времени выполнения (Ada.Execution_Time)
- Мультипериодическое планирование
В этой статье мы последовательно разберём эти возможности на 8 практических примерах кода. Каждый фрагмент можно рассматривать как самостоятельный пример, но примеры 04 и 05, содержащие несколько единиц компиляции, нужно сначала разделить с помощью gnatchop, а затем собрать через gnatmake.
Все фрагменты кода из этой статьи опубликованы на GitHub в виде справочного набора, организованного по файлам — по одному на главу.
ada-real-time-systems - komurasoft-blog-samples (GitHub)
2. Что такое система реального времени
Начнём с уточнения терминологии.
| Понятие | Описание |
|---|---|
| Жёсткое реальное время (hard real-time) | Превышение дедлайна означает критический отказ системы (управление полётом, подушки безопасности, кардиостимуляторы) |
| Мягкое реальное время (soft real-time) | Превышение дедлайна нежелательно, но редкие случаи допустимы (потоковое видео, игры) |
| Дедлайн (deadline) | Абсолютное время, к которому задача должна завершиться |
| Период (period) | Интервал времени, с которым задача запускается повторно |
| WCET (Worst-Case Execution Time) | Наихудшее время выполнения задачи |
| Джиттер (jitter) | Разброс (нестабильность) периодического выполнения |
При проектировании систем реального времени важным необходимым условием является выполнение неравенства «WCET <= дедлайн» для каждой задачи. Однако само по себе это не гарантирует соблюдение сроков системой в целом — отдельно требуется анализ времени отклика, учитывающий время блокировки, назначение приоритетов, джиттер, прерывания, а также поведение среды выполнения и ОС. На практике, чтобы сохранить запас, обычно стремятся к WCET < дедлайн. Функции реального времени Ada предоставляют на уровне языка предсказуемую модель выполнения, которая облегчает такой анализ.
flowchart LR
HRT[Жёсткое реальное время] -->|Превышение срока = критический отказ| Examples[Управление полётом<br/>Подушка безопасности<br/>Кардиостимулятор]
SRT[Мягкое реальное время] -->|Редкие превышения допустимы| Examples2[Видеопотоки<br/>Игры<br/>UI]
Ada[Ada Annex D<br/>Механизмы предсказуемости] --> Mechanism[FIFO_Within_Priorities<br/>Ceiling_Locking<br/>delay until]
subgraph Requirements[Требования реального времени]
D[Дедлайн<br/>Абсолютное время завершения]
P[Период<br/>Интервал повторения]
W[WCET<br/>Наихудшее время выполнения]
J[Джиттер<br/>Разброс периода]
end
D --> Analysis[Анализ планируемости]
P --> Analysis
W --> Analysis
J --> Analysis
HRT --> Analysis
SRT --> Analysis
Mechanism --> Analysis
Analysis --> Constraint[Необходимое условие: WCET <= дедлайн<br/>Достаточность проверяется анализом времени отклика]
Одно из самых опасных явлений в системах реального времени — инверсия приоритета. Эта проблема реально возникла в 1997 году на аппарате Mars Pathfinder и стала причиной повторяющихся перезагрузок.
sequenceDiagram
participant S as Планировщик
participant L as Низкоприоритетная задача
participant H as Высокоприоритетная задача
participant M as Среднеприоритетная задача
participant R as Разделяемый ресурс
L->>R: Захватывает блокировку
activate L
Note over L: Выполняется критическая секция
Note over S,L: Пробуждается H, планировщик приостанавливает L
deactivate L
activate H
H->>R: Пытается захватить блокировку
Note over H: Блокировка! (удерживается L)
deactivate H
Note over S,L: H ждёт блокировку, поэтому L возобновляется
activate L
Note over L: Продолжает выполнение к освобождению блокировки...
Note over S,L: Пробуждается M, планировщик приостанавливает L
deactivate L
activate M
Note over L: L не может освободить блокировку
Note over M: M продолжает выполнение (и H, и L заблокированы)
Note over H: [ИНВЕРСИЯ ПРИОРИТЕТА] Высокий приоритет заблокирован на неопределённый срок
deactivate M
Низкоприоритетная задача, удерживающая блокировку, вытесняется среднеприоритетной задачей, и высокоприоритетная задача оказывается заблокированной на неопределённый срок. Реальным решением, применённым на Mars Pathfinder, было включение механизма priority inheritance в VxWorks, но Ada решает проблему того же рода другим способом — предоставляя Ceiling_Locking как встроенную возможность языка.
3. Основы приоритетов задач — FIFO_Within_Priorities
FIFO_Within_Priorities — это стандартная приоритетная политика диспетчеризации, которую можно задать в Ada Annex D. Если политика явно не указана, поведение по умолчанию определяется реализацией, но во многих целевых платформах GNAT используется политика именно этого семейства. В рамках одного уровня приоритета задачи выполняются по принципу FIFO (первым пришёл — первым обслужен), а задача с более высоким приоритетом вытесняет (прерывает) задачу с более низким приоритетом.
-- 01_task_priority.ada
-- Основы приоритетов задач и FIFO_Within_Priorities
-- Конфигурационная прагма должна стоять перед контекстными предложениями (with)
pragma Task_Dispatching_Policy (FIFO_Within_Priorities);
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
procedure Task_Priority_Demo is
task High_Priority_Task is
pragma Priority (Priority'Last);
pragma Storage_Size (4 * 1024);
end High_Priority_Task;
task Low_Priority_Task is
pragma Priority (Priority'First);
pragma Storage_Size (4 * 1024);
end Low_Priority_Task;
task body High_Priority_Task is
begin
Put_Line ("[T=0.0s] High priority task started");
delay until Clock + Milliseconds (100);
Put_Line ("[T=0.1s] High priority task completed");
end High_Priority_Task;
task body Low_Priority_Task is
begin
Put_Line ("[T=0.0s] Low priority task started");
delay until Clock + Milliseconds (500);
Put_Line ("[T=0.5s] Low priority task completed");
end Low_Priority_Task;
begin
Put_Line ("=== Task Priority Demo (FIFO_Within_Priorities) ===");
Put_Line ("Main: waiting for tasks to complete...");
delay until Clock + Milliseconds (800);
Put_Line ("Main: done");
end Task_Priority_Demo;
Ключевые моменты:
pragma Priorityприсваивает каждой задаче статический приоритет.Priority'Last— наивысший,Priority'First— наинизший.- Тела задач в этом примере не выполняют тяжёлых вычислений, а просто ожидают до указанного момента через
delay until. Здесь важно показать, что когда обе задачи одновременно готовы к выполнению, первой получает возможность выполниться задача с более высоким приоритетом. - Эта диаграмма демонстрирует именно сторону вытеснения между разными уровнями приоритета в рамках
FIFO_Within_Priorities. Чтобы показать порядок FIFO внутри одного уровня приоритета, нужен отдельный пример с несколькими задачами одинакового приоритета. - На практике приоритеты обычно проектируют относительно
System.Default_Priority.
sequenceDiagram
participant S as Планировщик
participant Main as Главная задача
participant HP as Высокоприоритетная задача<br/>(Priority=Last)
participant LP as Низкоприоритетная задача<br/>(Priority=First)
Main->>HP: Создаёт задачу
Main->>LP: Создаёт задачу
Note over HP,LP: T=0мс: обе задачи готовы к выполнению
S->>HP: Выбирает HP как наивысший приоритет
activate HP
Note over HP: Выводит стартовое сообщение
HP->>S: Блокируется до T+100мс
deactivate HP
S->>LP: Далее выполняет LP
activate LP
Note over LP: Выводит стартовое сообщение
LP->>S: Блокируется до T+500мс
deactivate LP
Note over S: T=100мс: пробуждается HP
S->>HP: Выполняет HP
activate HP
Note over HP: Выводит сообщение о завершении
deactivate HP
Note over S: T=500мс: пробуждается LP
S->>LP: Выполняет LP
activate LP
Note over LP: Выводит сообщение о завершении
deactivate LP
Note over Main: (T=800мс) главная задача завершена
Диапазон приоритетов Ada (по умолчанию в GNAT):
Priority'First = 0 (наинизший)
Priority'Last = 30 (наивысший, зависит от ОС)
4. Ceiling_Locking — предотвращение инверсии приоритета средствами языка
Одна из самых неприятных проблем в системах реального времени — инверсия приоритета (priority inversion). Высокоприоритетная задача ожидает блокировку, удерживаемую низкоприоритетной задачей, а та в это время вытесняется среднеприоритетной задачей — в результате высокоприоритетная задача оказывается заблокированной на неопределённый срок.
Ada решает эту проблему, встраивая протокол Ceiling_Locking непосредственно в защищённые объекты.
-- 02_ceiling_locking.ada
-- Предотвращение инверсии приоритета протоколом Ceiling_Locking
-- Конфигурационная прагма должна стоять перед контекстными предложениями (with)
pragma Locking_Policy (Ceiling_Locking);
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
procedure Ceiling_Locking_Demo is
Ceiling : constant System.Any_Priority := System.Any_Priority'Last;
protected Shared_Data is
pragma Priority (Ceiling);
procedure Write (V : Integer);
function Read return Integer;
private
Value : Integer := 0;
end Shared_Data;
protected body Shared_Data is
procedure Write (V : Integer) is
begin
Value := V;
end Write;
function Read return Integer is
begin
return Value;
end Read;
end Shared_Data;
task Producer is
pragma Priority (Priority'Last);
pragma Storage_Size (4 * 1024);
end Producer;
task Consumer is
pragma Priority (Priority'First);
pragma Storage_Size (4 * 1024);
end Consumer;
task body Producer is
begin
Put_Line ("[T=0.0s] Producer (high prio): about to write");
Shared_Data.Write (42);
Put_Line ("[T=0.0s] Producer (high prio): write done");
delay until Clock + Milliseconds (100);
end Producer;
task body Consumer is
begin
delay until Clock + Milliseconds (10);
Put_Line ("[T=0.01s] Consumer (low prio): about to read");
declare
V : Integer;
begin
V := Shared_Data.Read;
Put_Line ("[T=0.01s] Consumer (low prio): read done, got" &
Integer'Image (V));
end;
delay until Clock + Milliseconds (100);
end Consumer;
begin
Put_Line ("=== Ceiling_Locking Demo ===");
Put_Line ("Main: producer priority = Last, consumer priority = First");
Put_Line ("Ceiling = Any_Priority'Last, locking = Ceiling_Locking");
delay until Clock + Milliseconds (300);
Put_Line ("Main: done");
end Ceiling_Locking_Demo;
Как работает Ceiling_Locking:
- Для защищённого объекта через
pragma Priority (Ceiling)устанавливается потолочный приоритет (ceiling priority). - При входе в защищённый объект любая задача автоматически повышается до потолочного приоритета.
- Благодаря этому задачу, использующую защищённый объект, не может вытеснить среднеприоритетная задача.
- При выходе из защищённого объекта приоритет возвращается к исходному значению.
Диаграмма ниже — не точная временная трасса приведённого выше примера кода, а концептуальная схема того, как Ceiling_Locking подавляет паттерн инверсии приоритета из диаграммы в разделе 2.
sequenceDiagram
participant S as Планировщик
participant L as Низкоприоритетная задача<br/>(приоритет=10)
participant M as Среднеприоритетная задача<br/>(приоритет=20)
participant H as Высокоприоритетная задача<br/>(приоритет=30)
participant PO as Защищённый объект<br/>(потолок=30)
Note over PO: Вызывающий с активным приоритетом > потолка получает Program_Error<br/>H(30) на схеме равен потолку (30), поэтому вход разрешён
L->>PO: Входит в защищённую операцию
activate L
Note over L,PO: Активный приоритет повышается до 30
Note over S: Пробуждается M
Note over S,L: L выполняется с потолочным приоритетом 30<br/>M (20) не может его вытеснить
Note over S: Пробуждается H
Note over S,H: H (30) проходит проверку потолка<br/>но ждёт, так как PO занят L
L->>PO: Выполняет операцию
L->>PO: Выходит из защищённой операции
deactivate L
Note over L: Приоритет возвращается к 10
Note over S,H: После освобождения PO выполняется H
activate H
H->>PO: Входит в защищённую операцию
Note over H,PO: H (30) = потолок (30), вход возможен после снятия конкуренции
H->>PO: Выходит из защищённой операции
deactivate H
Правило проектирования: потолочный приоритет защищённого объекта должен быть не ниже наивысшего приоритета среди всех задач, использующих этот объект. Если это правило нарушено и защищённую операцию вызывает задача с активным приоритетом выше потолочного, Ada может обнаружить эту ошибку проектирования через исключение
Program_Error.
Чтобы добиться того же эффекта с мьютексами pthread в C, нужно явно задать атрибут PTHREAD_PRIO_PROTECT, тогда как в Ada это стандартная возможность языка.
5. delay until — выполнение периодических задач без дрейфа
Базовый паттерн систем реального времени — периодическая задача, которая запускается через фиксированный интервал. Крайне важно предотвращать накопление временной ошибки (дрейфа) при таком повторяющемся выполнении.
delay until в Ada элегантно решает эту проблему.
-- 03_periodic_task.ada
-- Периодическая задача через delay until — предотвращение накопительного дрейфа
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
procedure Periodic_Task_Demo is
Period_MS : constant Time_Span := Milliseconds (200);
Cycles : constant Positive := 5;
task Sensor_Reader is
pragma Priority (Priority'Last - 2);
pragma Storage_Size (4 * 1024);
end Sensor_Reader;
task body Sensor_Reader is
Start_Time : constant Time := Clock;
Next_Release : Time := Start_Time + Period_MS;
Cycle_Count : Natural := 0;
begin
Put_Line ("[Sensor] Periodic task starts, period=" &
To_Duration (Period_MS)'Image & "s, cycles=" &
Natural'Image (Cycles));
for I in 1 .. Cycles loop
delay until Next_Release;
Cycle_Count := Cycle_Count + 1;
Put_Line ("[Sensor] Cycle" & Natural'Image (Cycle_Count) &
" at" & Duration'Image (To_Duration (Clock - Start_Time)) & "s");
Next_Release := Next_Release + Period_MS;
end loop;
Put_Line ("[Sensor] Periodic task finished. Actual elapsed:" &
Duration'Image (To_Duration (Clock - Start_Time)) & "s");
end Sensor_Reader;
begin
Put_Line ("=== Periodic Task Demo (delay until) ===");
Put_Line ("Main: waiting for" & Natural'Image (Cycles) & " cycles...");
delay until Clock + Milliseconds (1500);
Put_Line ("Main: done");
end Periodic_Task_Demo;
Почему именно delay until:
| Подход | Проблема |
|---|---|
delay Period; |
Время обработки каждой итерации накапливается, период постепенно уплывает (кумулятивный дрейф) |
delay until Next_Release; Next_Release := Next_Release + Period; |
Опирается на абсолютное время, поэтому даже при задержке одной итерации время следующего запуска остаётся корректным |
Однако delay until автоматически не гарантирует, что время обработки укладывается в период. Если обработка превысила время следующего пробуждения, соответствующий вызов delay until возвращает управление практически мгновенно, и система оказывается в состоянии, которое следует трактовать как пропуск дедлайна (deadline miss).
Со случаем delay:
T=0мс → обработка(15мс) → delay 100мс → T=115мс → обработка(10мс) → ...
Фактические интервалы: 115мс, 110мс, ... (время обработки накапливается)
Со случаем delay until:
Next_Release: 100мс, 200мс, 300мс, ... (абсолютное время)
T=0мс → обработка(15мс) → delay until 100мс → T=100мс → обработка(10мс) → delay until 200мс
Фактические интервалы: 100мс, 100мс, ... (не зависят от времени обработки)
Этот паттерн delay until используется во всех периодических задачах, встречающихся далее в статье.
flowchart TB
subgraph Bad["delay Period - кумулятивный дрейф"]
B1[T=0мс: вычисления 15мс] --> B2[delay 100мс → пробуждение на 115мс]
B2 --> B3[вычисления 10мс → 125мс]
B3 --> B4[delay 100мс → пробуждение на 225мс]
B4 --> B5[Фактические интервалы: 115мс, 110мс...]
end
subgraph Good["delay until - на основе абсолютного времени"]
G1[Следующий = T+100мс] --> G2[вычисления 15мс]
G2 --> G3[delay until T+100мс → пробуждение на 100мс]
G3 --> G4[вычисления 10мс]
G4 --> G5[Следующий = T+200мс → пробуждение на 200мс]
G5 --> G6[Фактические интервалы: 100мс, 100мс...]
end
subgraph Overrun["Превышение периода - deadline miss"]
O1[Следующий = T+100мс] --> O2[вычисления 130мс]
O2 --> O3[delay until T+100мс возвращается мгновенно]
O3 --> O4[Обнаружение задержки и обработка как перегрузки]
end
Bad --> Drift[Ошибка накапливается со временем]
Good --> Stable[Предотвращает кумулятивный дрейф]
Good --> Overrun
6. Профиль Ravenscar — верифицируемое подмножество для реального времени
Возможности таскинга Ada мощны, но в системах, где критична безопасность, эта мощь сама становится проблемой: динамическое создание задач, операторы select и abort затрудняют статический анализ наихудшего времени выполнения.
Профиль Ravenscar — ответ Ada на эту проблему. Он ограничивает возможности таскинга статически анализируемым, детерминированным подмножеством.
-- 04_ravenscar_profile.ada
-- Основы профиля Ravenscar
-- Указывается при компиляции через pragma Profile (Ravenscar); в файле gnat.adc
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
package Ravenscar_State is
protected Signal is
pragma Priority (System.Default_Priority + 5);
entry Wait_For_Release;
procedure Release;
private
Released : Boolean := False;
end Signal;
task Periodic_Worker is
pragma Priority (System.Default_Priority + 1);
pragma Storage_Size (4 * 1024);
end Periodic_Worker;
task Monitor is
pragma Priority (System.Default_Priority);
pragma Storage_Size (4 * 1024);
end Monitor;
end Ravenscar_State;
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
package body Ravenscar_State is
protected body Signal is
entry Wait_For_Release when Released is
begin
Released := False;
end Wait_For_Release;
procedure Release is
begin
Released := True;
end Release;
end Signal;
task body Periodic_Worker is
Start_Time : constant Time := Clock;
Next_Release : Time := Start_Time + Milliseconds (100);
Period : constant Time_Span := Milliseconds (100);
Cycle_Count : Natural := 0;
begin
Put_Line ("[Worker] Ravenscar periodic task starts");
for I in 1 .. 4 loop
delay until Next_Release;
Cycle_Count := Cycle_Count + 1;
Put_Line ("[Worker] Cycle" & Natural'Image (Cycle_Count) &
" at" & Duration'Image (To_Duration (Clock - Start_Time)) & "s");
Signal.Release;
Next_Release := Next_Release + Period;
end loop;
Put_Line ("[Worker] Finished demo, waiting (Ravenscar: No_Task_Termination)");
loop
delay until Clock + Seconds (1);
end loop;
end Periodic_Worker;
task body Monitor is
begin
Put_Line ("[Monitor] Waiting for signals...");
for I in 1 .. 4 loop
Signal.Wait_For_Release;
Put_Line ("[Monitor] Received signal" & Natural'Image (I));
end loop;
Put_Line ("[Monitor] All signals received, waiting (Ravenscar: No_Task_Termination)");
loop
delay until Clock + Seconds (1);
end loop;
end Monitor;
end Ravenscar_State;
with Ravenscar_State; use Ravenscar_State;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Real_Time; use Ada.Real_Time;
procedure Ravenscar_Demo is
begin
Put_Line ("=== Ravenscar Profile Demo ===");
Put_Line ("(compile with: gnatmake -gnatec=gnat.adc ravenscar_demo)");
Put_Line ("Main: waiting for Ravenscar tasks...");
delay until Clock + Milliseconds (800);
Put_Line ("Main: demo window elapsed; waiting forever (Ravenscar: No_Task_Termination)");
loop
delay until Clock + Seconds (1);
end loop;
end Ravenscar_Demo;
Ограничения профиля Ravenscar:
| Запрещённая возможность | Причина |
|---|---|
Динамическое создание задач (new и типы доступа) |
Выделение памяти во время выполнения недетерминировано |
Оператор select |
Сложность анализа управляющего потока создаёт не только выбор альтернативы, но и вся конструкция select в целом |
Оператор abort |
Асинхронное прерывание делает состояние непредсказуемым |
Ada.Task_Attributes |
Динамическое поведение во время выполнения |
| Динамическое изменение приоритета | Предположения анализа планирования меняются во время выполнения |
Относительный delay |
Легко порождает кумулятивный дрейф — используйте абсолютный delay until |
| Несколько входов (entry) на один защищённый объект | Увеличивает число условий блокировки и объём анализа |
| Завершение задач | В Ravenscar все задачи считаются незавершающимися |
Оператор requeue |
Усложняет отслеживание управляющего потока |
Благодаря этим ограничениям программа, соответствующая Ravenscar, становится гораздо удобнее для статического анализа временных характеристик. Это свойство требуется стандартами безопасности, такими как DO-178C (авиационное ПО) и ISO 26262 (функциональная безопасность автомобилей). Список выше — это выдержка из основных ограничений; реальный профиль включает дополнительные правила, связанные со средой выполнения и анализируемостью, например No_Task_Hierarchy и Detect_Blocking.
flowchart TB
Full[Полные возможности таскинга Ada] --> Profile[Профиль Ravenscar]
Profile --> Restrict[Ограничения]
Profile --> Policy[Обязательные политики]
Restrict --> R1[Запрет динамического создания задач]
Restrict --> R2[Запрет оператора select]
Restrict --> R3[Запрет оператора abort]
Restrict --> R4[Запрет Task_Attributes]
Restrict --> R5[Ограничение: 1 вход на защищённый объект]
Restrict --> R6[Запрет оператора requeue]
Restrict --> R7[Запрет относительного delay<br/>используется delay until]
Restrict --> R8[Запрет динамического изменения приоритетов]
Restrict --> R9[Запрет завершения задач<br/>все задачи незавершающиеся]
Policy --> P1[FIFO_Within_Priorities]
Policy --> P2[Ceiling_Locking]
Restrict --> Benefit[Облегчает:<br/>статический анализ временных характеристик]
Policy --> Benefit
Benefit --> DO178[DO-178C<br/>Авиационное ПО]
Benefit --> ISO26262[ISO 26262<br/>Функциональная безопасность автомобилей]
Benefit --> IEC62304[IEC 62304<br/>ПО медицинских приборов]
Чтобы включить профиль Ravenscar, добавьте следующую строку в файл gnat.adc:
pragma Profile (Ravenscar);
7. Тайминговые события — пробуждение по времени без опроса
Во многих системах реального времени часто встречается требование «разбудить высокоприоритетную задачу в определённый момент времени». Наивная реализация опрашивала бы таймер, но Ada предлагает более элегантный механизм — тайминговые события (timing events).
-- 05_timing_events.ada
-- Тайминговые события (Ada.Real_Time.Timing_Events)
-- Механизм пробуждения высокоприоритетной задачи без опроса
pragma Locking_Policy (Ceiling_Locking);
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
with Ada.Real_Time.Timing_Events; use Ada.Real_Time.Timing_Events;
package Signal_Pkg is
protected type Signal_Type is
pragma Priority (System.Interrupt_Priority'Last);
entry Wait_For_Event;
procedure Fire (Event : in out Timing_Event);
private
Fired : Boolean := False;
end Signal_Type;
S : Signal_Type;
end Signal_Pkg;
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
with Ada.Real_Time.Timing_Events; use Ada.Real_Time.Timing_Events;
package body Signal_Pkg is
protected body Signal_Type is
entry Wait_For_Event when Fired is
begin
Fired := False;
end Wait_For_Event;
procedure Fire (Event : in out Timing_Event) is
begin
Fired := True;
end Fire;
end Signal_Type;
end Signal_Pkg;
with Signal_Pkg; use Signal_Pkg;
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
with Ada.Real_Time.Timing_Events; use Ada.Real_Time.Timing_Events;
procedure Timing_Events_Demo is
pragma Priority (29);
Timer_1 : Timing_Event;
Timer_2 : Timing_Event;
task Reactor is
pragma Priority (System.Default_Priority + 5);
pragma Storage_Size (4 * 1024);
end Reactor;
task body Reactor is
begin
Put_Line ("[Reactor] Waiting for timing events...");
S.Wait_For_Event;
Put_Line ("[Reactor] Got event #1");
S.Wait_For_Event;
Put_Line ("[Reactor] Got event #2");
Put_Line ("[Reactor] Done");
end Reactor;
begin
Put_Line ("=== Timing Events Demo ===");
Put_Line ("Scheduling two timers at +100ms and +250ms...");
Set_Handler (Timer_1, Clock + Milliseconds (100), S.Fire'Access);
Set_Handler (Timer_2, Clock + Milliseconds (250), S.Fire'Access);
delay until Clock + Milliseconds (500);
Put_Line ("Main: done");
end Timing_Events_Demo;
Как работают тайминговые события:
1. Set_Handler(Timer_1, T+100мс, S.Fire'Access) — регистрирует обработчик на абсолютное время
2. Проходит T+100мс — runtime вызывает S.Fire **на потолочном приоритете**
3. Fire устанавливает флаг Fired в True — барьер открывается
4. Задача Reactor пробуждается из Wait_For_Event
Важно, что в этом примере явно указан Ceiling_Locking, и поскольку обработчик Fire — это защищённая процедура, она выполняется на потолочном приоритете. Защищённую процедуру, используемую как обработчик тайминговых событий, следует помещать в защищённый объект с потолочным приоритетом уровня прерывания — в данном случае System.Interrupt_Priority'Last. Благодаря этому во время обработки тайминговых событий инверсия приоритета не возникает.
8. Очередь реального времени на основе защищённых объектов
Часто встречающийся в системах реального времени паттерн — производитель-потребитель (producer-consumer): датчик генерирует данные, а управляющая задача их потребляет. Здесь важно эффективно организовать взаимное исключение доступа к буферу и блокировку.
Защищённые объекты и барьеры входа (entry barrier) Ada позволяют реализовать это как синхронизацию на основе барьеров. Взаимное исключение внутри управляется средой выполнения, поэтому прикладному коду не нужно напрямую использовать мьютексы или условные переменные.
-- 06_protected_queue.ada
-- Совместное использование данных реального времени через защищённый объект
-- Конвейер: Producer -> Bounded_Buffer -> Consumer
-- Указывается при компиляции через pragma Locking_Policy (Ceiling_Locking); в файле gnat.adc
pragma Locking_Policy (Ceiling_Locking);
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
procedure Protected_Queue_Demo is
Buffer_Size : constant := 4;
type Buf_Array is array (1 .. Buffer_Size) of Integer;
protected Bounded_Buffer is
pragma Priority (System.Any_Priority'Last);
entry Put (Item : Integer);
entry Get (Item : out Integer);
private
Buf : Buf_Array;
Count : Natural := 0;
Head : Positive := 1;
Tail : Positive := 1;
end Bounded_Buffer;
protected body Bounded_Buffer is
entry Put (Item : Integer) when Count < Buffer_Size is
begin
Buf (Tail) := Item;
Tail := (Tail mod Buffer_Size) + 1;
Count := Count + 1;
end Put;
entry Get (Item : out Integer) when Count > 0 is
begin
Item := Buf (Head);
Head := (Head mod Buffer_Size) + 1;
Count := Count - 1;
end Get;
end Bounded_Buffer;
task Producer is
pragma Priority (System.Default_Priority + 2);
pragma Storage_Size (4 * 1024);
end Producer;
task Consumer is
pragma Priority (System.Default_Priority + 1);
pragma Storage_Size (4 * 1024);
end Consumer;
task body Producer is
Next_Release : Time := Clock + Milliseconds (50);
Period : constant Time_Span := Milliseconds (50);
begin
for I in 1 .. 6 loop
Bounded_Buffer.Put (I);
Put_Line ("[Producer] Put" & Integer'Image (I));
delay until Next_Release;
Next_Release := Next_Release + Period;
end loop;
Put_Line ("[Producer] Done");
end Producer;
task body Consumer is
Item : Integer;
Next_Release : Time := Clock + Milliseconds (80);
Period : constant Time_Span := Milliseconds (80);
begin
delay until Clock + Milliseconds (30);
for I in 1 .. 6 loop
Bounded_Buffer.Get (Item);
Put_Line ("[Consumer] Got" & Integer'Image (Item));
delay until Next_Release;
Next_Release := Next_Release + Period;
end loop;
Put_Line ("[Consumer] Done");
end Consumer;
begin
Put_Line ("=== Protected Queue Demo (Ceiling_Locking) ===");
Put_Line ("Buffer size = 4; Producer every 50ms, Consumer every 80ms");
delay until Clock + Milliseconds (800);
Put_Line ("Main: done");
end Protected_Queue_Demo;
Ключевые моменты проектирования:
entry Put when Count < Buffer_Size— когда буфер заполнен, Producer автоматически блокируется.entry Get when Count > 0— когда буфер пуст, Consumer автоматически блокируется.pragma Priority (System.Any_Priority'Last)— благодаря Ceiling_Locking между Producer и Consumer не возникает инверсии приоритета.- Условия барьера определяются внутренним состоянием защищённого объекта (
Count) и автоматически переоцениваются при освобождении блокировки.
В этом коде нет прикладных мьютексов, семафоров или условных переменных. Всё необходимое ожидание выражается через барьеры входа защищённого объекта.
stateDiagram-v2
Empty: Пусто / Count=0
Partial: Частично заполнен / Count=1..Buffer_Size-1
Full: Заполнен / Count=Buffer_Size
[*] --> Empty: Начальное состояние
Empty --> Partial: Put (добавлен 1 элемент)
Partial --> Partial: Put / Get
Partial --> Empty: Get (извлечён последний элемент)
Partial --> Full: Put (заполнено последнее место)
Full --> Partial: Get (освобождается место)
Empty --> Empty: Get блокируется (барьер Count=0)
Full --> Full: Put блокируется (барьер Count=Buffer_Size)
После успешного Put переоцениваются барьеры ожидающих Get, а после успешного Get — барьеры ожидающих Put. Эта переоценка происходит при завершении защищённой операции независимо от того, в каком состоянии на диаграмме находится объект.
9. Измерение времени выполнения — первый шаг к мониторингу
Чтобы оценить планируемость системы реального времени, нужно точно знать время выполнения (процессорное время) каждой задачи. Пакет Ada.Execution_Time в Ada предоставляет учёт потреблённого процессорного времени в разрезе задач.
-- 07_execution_time.ada
-- Контроль времени выполнения (Execution_Time)
-- Измеряет потребление процессорного времени по каждой задаче
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
with Ada.Execution_Time;
use type Ada.Execution_Time.CPU_Time;
procedure Execution_Time_Demo is
package ET renames Ada.Execution_Time;
task Busy_Worker is
pragma Priority (System.Default_Priority + 1);
pragma Storage_Size (4 * 1024);
end Busy_Worker;
task body Busy_Worker is
Wall_Start : Time;
Cpu_Start : ET.CPU_Time;
Dummy : Integer := 0;
pragma Volatile (Dummy);
begin
Wall_Start := Clock;
Cpu_Start := ET.Clock;
Put_Line ("[Worker] Starting compute-bound work...");
for I in 1 .. 20_000_000 loop
Dummy := Dummy + 1;
end loop;
Put_Line ("[Worker] Dummy =" & Integer'Image (Dummy));
declare
Wall_Elapsed : constant Duration :=
To_Duration (Clock - Wall_Start);
Cpu_Span : constant Time_Span :=
ET.Clock - Cpu_Start;
begin
Put_Line ("[Worker] Done, wall time:" &
Duration'Image (Wall_Elapsed) & "s");
Put_Line ("[Worker] CPU time consumed:" &
Duration'Image (To_Duration (Cpu_Span)) & "s");
end;
end Busy_Worker;
Cpu_Start_Main : constant ET.CPU_Time := ET.Clock;
begin
Put_Line ("=== Execution Time Demo ===");
delay until Clock + Milliseconds (500);
declare
Cpu_Span : constant Time_Span := ET.Clock - Cpu_Start_Main;
begin
Put_Line ("Main: CPU time consumed after 500ms:" &
Duration'Image (To_Duration (Cpu_Span)) & "s");
end;
Put_Line ("Main: done");
end Execution_Time_Demo;
Астрономическое время vs процессорное время:
Астрономическое время (Wall Clock): Ada.Real_Time.Clock
→ Фактически прошедшее время. Включает время блокировки и вытеснения.
Процессорное время (Execution Time): Ada.Execution_Time.Clock
→ Только время, которое задача реально выполнялась на CPU.
→ Время блокировки и вытеснения не учитывается.
Это различие — отправная точка для мониторинга времени выполнения и верификации WCET. Пока Busy_Worker ожидает в delay until, процессорное время не увеличивается — оно растёт только во время реальных вычислений. Аналогично, пока главная задача ждёт в delay until Clock + Milliseconds(500), её процессорное время должно оставаться практически нулевым. Однако фактическое измерение процессорного времени не гарантирует истинный WCET: он зависит от кэша, конвейера, конкуренции за память и требует отдельной верификации через статический анализ или тестирование на целевой платформе.
flowchart LR
subgraph Wall[Астрономическое время]
W1[Суммарное прошедшее время: 500мс] --> W2[Включает: вычисления + ожидание + блокировку + вытеснение]
end
subgraph CPU[Процессорное время]
C1[Суммарное CPU-время: 120мс] --> C2[Включает только: реальные вычисления]
end
Wall --> Diff[Разница = время ожидания, блокировки, вытеснения]
CPU --> Diff
Diff --> Insight[Процессорное время отражает реальную стоимость вычислений<br/>Помогает при верификации и мониторинге WCET<br/>Исключает время ожидания, блокировки и вытеснения]
Insight --> Caveat[Внимание<br/>Измерение не гарантирует истинный WCET<br/>Требуется статический анализ или проверка на целевой платформе]
10. Комплексная демонстрация — мультипериодическая система реального времени
Теперь объединим всё, что мы изучили — приоритеты, Ceiling_Locking, delay until и защищённые объекты — и построим типичную мультипериодическую систему реального времени.
-- 08_multiperiodic.ada
-- Комплексная демонстрация мультипериодической системы реального времени
-- Задача чтения датчика с быстрым периодом (100мс)
-- Управляющая задача с медленным периодом (400мс)
-- Совместный доступ к данным через Ceiling_Locking
pragma Locking_Policy (Ceiling_Locking);
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;
procedure Multiperiodic_Demo is
package Int_IO is new Ada.Text_IO.Integer_IO (Integer);
protected Shared_Sensor is
pragma Priority (System.Any_Priority'Last);
procedure Write (V : Integer);
function Read return Integer;
private
Value : Integer := 0;
end Shared_Sensor;
protected body Shared_Sensor is
procedure Write (V : Integer) is
begin
Value := V;
end Write;
function Read return Integer is
begin
return Value;
end Read;
end Shared_Sensor;
task Fast_Sensor is
pragma Priority (System.Default_Priority + 3);
pragma Storage_Size (4 * 1024);
end Fast_Sensor;
task body Fast_Sensor is
Next_Release : Time := Clock + Milliseconds (100);
Period : constant Time_Span := Milliseconds (100);
Cycle : Natural := 0;
begin
Put_Line ("[Fast] Sensor reader starts (100ms period)");
for I in 1 .. 12 loop
delay until Next_Release;
Cycle := Cycle + 1;
Shared_Sensor.Write (Cycle * 10);
Next_Release := Next_Release + Period;
end loop;
Put_Line ("[Fast] Done");
end Fast_Sensor;
task Slow_Controller is
pragma Priority (System.Default_Priority + 2);
pragma Storage_Size (4 * 1024);
end Slow_Controller;
task body Slow_Controller is
Next_Release : Time := Clock + Milliseconds (150);
Period : constant Time_Span := Milliseconds (400);
Cycle : Natural := 0;
Raw : Integer;
begin
Put_Line ("[Slow] Controller starts (400ms period)");
for I in 1 .. 3 loop
delay until Next_Release;
Cycle := Cycle + 1;
Raw := Shared_Sensor.Read;
Put_Line ("[Slow] Cycle" & Natural'Image (Cycle) &
" reads sensor =" & Integer'Image (Raw));
Next_Release := Next_Release + Period;
end loop;
Put_Line ("[Slow] Done");
end Slow_Controller;
begin
Put_Line ("=== Multiperiodic Real-Time System Demo ===");
Put_Line ("Fast sensor (100ms) x 12 + Slow controller (400ms) x 3");
Put_Line ("Ceiling_Locking prevents priority inversion on shared data");
delay until Clock + Milliseconds (2000);
Put_Line ("Main: done");
end Multiperiodic_Demo;
Архитектура системы:
Диаграмма ниже строится на реальных моментах release из примера кода (быстрый датчик — период 100мс, медленное управление — период 400мс со смещением 150мс), но использует иллюстративные значения времени выполнения. Это не результат реального измерения: в самом коде нет вычисления управления длительностью 80мс. Если у быстрого датчика более высокий приоритет, то release датчика во время выполнения медленного управления приводит к его временному прерыванию. Для наглядности на диаграмме показано только по одному характерному прерыванию на каждый цикл медленного управления, хотя на практике быстрый датчик получает release на каждой границе в 100мс.
flowchart TB
Assumption["Иллюстративное допущение<br/>Быстрый датчик: обработка 10мс<br/>Медленное управление: обработка 80мс"]
subgraph Cycle1["Медленное управление, цикл 1 (release=150мс)"]
direction LR
C1F1["100-110мс<br/>Быстрый датчик #1"] --> C1S1["150-200мс<br/>Медленное управление #1, часть A"]
C1S1 --> C1F2["200-210мс<br/>Быстрый датчик #2<br/>прерывает как P+3"]
C1F2 --> C1S2["210-240мс<br/>Медленное управление #1, часть B"]
end
subgraph Cycle2["Медленное управление, цикл 2 (release=550мс)"]
direction LR
C2S1["550-600мс<br/>Медленное управление #2, часть A"] --> C2F6["600-610мс<br/>Быстрый датчик #6<br/>прерывает как P+3"]
C2F6 --> C2S2["610-640мс<br/>Медленное управление #2, часть B"]
end
subgraph Cycle3["Медленное управление, цикл 3 (release=950мс)"]
direction LR
C3S1["950-1000мс<br/>Медленное управление #3, часть A"] --> C3F10["1000-1010мс<br/>Быстрый датчик #10<br/>прерывает как P+3"]
C3F10 --> C3S2["1010-1040мс<br/>Медленное управление #3, часть B"]
end
Assumption --> C1F1
C1S2 --> C2S1
C2S2 --> C3S1
Этот паттерн — «быстрый сбор данных с датчиков + медленный контур управления» — типичная структура, часто встречающаяся в промышленных системах управления и робототехнике.
11. Где особенно раскрываются функции реального времени Ada
Функции реального времени Ada особенно ценны в следующих областях.
flowchart TB
Ada[Ada Annex D<br/>Функции реального времени] --> Aero[Авиакосмическая отрасль<br/>DO-178C]
Ada --> Rail[Железнодорожный транспорт<br/>серия EN 50128]
Ada --> Auto[Автомобилестроение<br/>ISO 26262]
Ada --> Medical[Медицинские приборы<br/>IEC 62304]
Ada --> Industrial[Промышленное управление<br/>серия IEC 61508]
Ada --> Defense[Оборона и высоконадёжные системы]
Aero --> A1[Управление полётом<br/>область с богатым опытом применения]
Aero --> A2[Управление спутниками и космическими аппаратами]
Rail --> R1[Сигнальные системы]
Rail --> R2[Автоматическое управление поездами]
Auto --> Au1[Кандидат для ECU, связанных с безопасностью]
Auto --> Au2[Ограниченное, выборочное применение<br/>в области, где доминируют C / MISRA-C]
Medical --> M1[Кардиостимуляторы]
Medical --> M2[Инфузионные насосы]
Industrial --> I1[Управление роботами]
Industrial --> I2[Станки с ЧПУ]
Defense --> D1[Бортовые вычислители миссий]
Defense --> D2[Системы долгосрочной эксплуатации]
12. Ограничения и нюансы
Функции реального времени Ada мощны, но не являются панацеей.
1. Зависимость от платформы:
- Фактическое отображение
pragma Priorityзависит от среды выполнения (ОС + среда выполнения GNAT). В Linux оно отображается наSCHED_FIFO, а в Windows полное вытеснение может быть не гарантировано.
2. Ограничения Ravenscar:
- Поскольку динамическое создание задач запрещено, все задачи должны быть статически объявлены при запуске системы. Это ограничивает свободу проектирования.
3. Ограничения измерения WCET:
Ada.Execution_Timeдаёт измерение, а не гарантию. Истинный WCET, учитывающий промахи кэша и конфликты конвейера, нужно отдельно верифицировать инструментами статического анализа.
4. Накладные расходы:
- Переоценка барьеров защищённого объекта выполняется автоматически при завершении или отмене входа, а также при выходе из защищённого объекта. Для часто вызываемых защищённых объектов эти накладные расходы нужно учитывать.
5. Барьер инструментария:
- Чтобы полноценно использовать функции реального времени Ada, требуются подходящие кросс-компилятор и среда выполнения. Особенно для встраиваемых целевых платформ это означает зависимость от поставляемой производителем среды выполнения.
13. Заключение
В этой статье мы последовательно рассмотрели функции реального времени, которые предоставляет Annex D в Ada, на 8 примерах кода.
| Функция | Ценность |
|---|---|
| Приоритеты задач | Вытесняющее приоритетное планирование |
| Ceiling_Locking | Предотвращение инверсии приоритета, встроенное в язык |
delay until |
Периодическое выполнение без кумулятивного дрейфа |
| Профиль Ravenscar | Подмножество таскинга, удобное для статического анализа |
| Тайминговые события | Пробуждение по времени без опроса |
| Защищённая очередь | Синхронизация на основе барьеров защищённого объекта |
| Измерение времени выполнения | Мониторинг процессорного времени по задачам |
| Мультипериодическая интеграция | Проектирование безопасного сосуществования задач с разными периодами |
Суть функций реального времени Ada в том, что они «не добавлены задним числом». Правила блокировки, подавляющие инверсию приоритета, указание абсолютного времени для периодического выполнения, мониторинг времени выполнения — всё это предоставляется как часть спецификации языка. Соблюдение дедлайнов, разумеется, всё равно нужно подтверждать проектированием и анализом, но огромное преимущество в том, что необходимые для этого предпосылки уже обеспечивает сама среда выполнения языка.
mindmap
root((Ada Annex D<br/>Системы реального времени))
Планирование
FIFO_Within_Priorities
Приоритеты задач
Вытеснение
Предотвращение инверсии приоритета
Протокол Ceiling_Locking
Автоматическое повышение приоритета
Правило потолочного приоритета
Периодическое выполнение
delay until
Предотвращает кумулятивный дрейф
На основе абсолютного времени
Профиль Ravenscar
Статический набор задач
Детерминированный анализ
Запрет относительного delay
DO-178C / ISO 26262
Тайминговые события
Пробуждение без опроса
Регистрация защищённого обработчика
Обработчик выполняется на потолочном приоритете
Защищённые объекты / очереди
Барьеры входа
Управление состояниями пусто/частично/полно
Синхронизация на основе барьеров
Мониторинг времени выполнения
Процессорное время по задачам
Астрономическое время vs процессорное
Помощь в верификации и мониторинге WCET
Комплексное проектирование
Мультипериодическое проектирование
Защита через барьеры
Безопасность на уровне языка
В качестве следующего шага, чтобы попробовать разработку систем реального времени на Ada самостоятельно, установите набор инструментов GNAT через Alire и соберите примеры кода из этой статьи с помощью gnatchop + gnatmake.
Основы конкурентности в Ada (задачи, рандеву, защищённые объекты) рассмотрены в предыдущей статье «Безопасная конкурентность в Ada».
14. Справочные материалы
- Ada Reference Manual - Annex D: Real-Time Systems
- Ravenscar Profile Definition (ISO/IEC TR 24718:2005)
- GNAT Real-Time Topics (AdaCore)
- The Ravenscar Profile for High-Integrity Systems (AdaCore)
- Rate Monotonic Analysis (Liu & Layland, 1973)
- Alire - Ada Package Manager
- Ada Sample Code Collection (GitHub)
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Безопасная конкурентность в Ada — практическое руководство по задачам и защищённым объектам
Вводная статья о встроенной в язык Ada конкурентности — задачах и защищённых объектах. Рассматриваем рандеву (entry/accept), выборочный a...
Дженерики в Ada ── контракты через типы и повторное использование без накладных расходов
Систематическое руководство по обобщённому программированию в Ada: обобщённые подпрограммы и пакеты, формальные параметры-подпрограммы, к...
Введение в формальную верификацию с SPARK ── От контрактов Ada к математическому доказательству
Практическое введение в формальную верификацию с помощью SPARK — подмножества языка Ada. В статье рассматривается переход от контрактов (...
Притягательность языка Ada — типы как язык проектирования и опора для ПО, работающего десятилетиями
Рассказываем о притягательности языка Ada: строгая типизация, ограничения диапазона, разделение спецификации и реализации через пакеты, к...
Настройка планирования процессора в Windows — фоновые службы и P/E-ядра
Разбираемся, что на самом деле меняет настройка «Фоновые службы» в Windows — через призму кванта времени, приоритета foreground-процессов...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Что такое Annex D в Ada?
- Это набор функций для систем реального времени, стандартизированный как часть спецификации языка Ada. Он включает приоритетное вытесняющее планирование на основе FIFO_Within_Priorities, протокол Ceiling_Locking для предотвращения инверсии приоритета, периодическое выполнение по абсолютному времени через delay until, профиль Ravenscar, тайминговые события и измерение времени выполнения по задачам средствами Ada.Execution_Time. Отличительная особенность в том, что эти возможности встроены не в виде библиотеки поверх языка, а непосредственно в языковую среду выполнения (runtime).
- Что такое инверсия приоритета и как Ada её предотвращает?
- Это ситуация, когда низкоприоритетная задача, удерживающая блокировку, вытесняется среднеприоритетной задачей, из-за чего высокоприоритетная задача, ожидающая ту же блокировку, оказывается заблокированной на неопределённый срок. Именно это произошло в 1997 году на аппарате Mars Pathfinder и стало причиной повторяющихся перезагрузок. В Ada протокол Ceiling_Locking предоставляется как встроенная возможность языка: задача, входящая в защищённый объект, автоматически повышается до потолочного (ceiling) приоритета, что делает невозможным её вытеснение среднеприоритетными задачами.
- Что такое профиль Ravenscar?
- Профиль, который ограничивает возможности таскинга Ada статически анализируемым, детерминированным подмножеством — для систем, где критична безопасность. Он запрещает динамическое создание задач, операторы select и abort, относительный delay, оператор requeue и ряд других конструкций. Благодаря этим ограничениям становится значительно проще проводить статический анализ временных характеристик, что облегчает соответствие требованиям стандартов безопасности, таких как DO-178C (авиационное ПО) и ISO 26262 (функциональная безопасность автомобилей). В GNAT профиль включается директивой pragma Profile (Ravenscar) в файле gnat.adc.
- Почему для периодических задач используют delay until, а не delay?
- Потому что при использовании относительного delay время обработки каждой итерации накапливается, и период постепенно «уплывает» — возникает кумулятивный дрейф. delay until определяет момент следующего запуска относительно абсолютного времени, поэтому даже если одна итерация выполняется дольше обычного, время следующего запуска остаётся корректным. Однако если обработка превышает время следующего пробуждения, delay until возвращает управление почти мгновенно, поэтому отдельно нужно предусмотреть обнаружение такой ситуации как пропуска дедлайна (deadline miss) и обработку перегрузки.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.
Публичные ссылки