برمجة أنظمة الوقت الفعليّ بلغة Ada ── التطبيق العمليّ للتحكّم في الأولويّة والدوريّة ووقت التنفيذ
· آخر تحديث: · غو كومورا · Ada, الوقت الفعليّ, Ravenscar, Ceiling Locking, المهامّ, الجدولة, انعكاس الأولويّة, لغة برمجة, أنظمة الوقت الفعليّ, الموثوقية العالية
1. مقدّمة ── العلاقة العميقة بين Ada والوقت الفعليّ
في المقال السابق «المعالجة المتوازية الآمنة في Ada»، شرحنا أساسيّات المعالجة المتوازية الآمنة عبر المهامّ والكائنات المحميّة في Ada. في هذا المقال، ننتقل إلى امتداد لذلك، وهو مجال أشدّ تقييداً ── أنظمة الوقت الفعليّ.
في أنظمة الوقت الفعليّ، لا تعني «الصحّة» فقط صحّة نتيجة الحساب المنطقيّة، بل تشمل أيضاً الحصول على تلك النتيجة ضمن الموعد النهائيّ. الإجابة الصحيحة المتأخّرة مللي ثانية واحدة قد تكون خطيرة بقدر خطورة الإجابة الخاطئة.
توفِّر Ada لهذا المتطلّب مجموعة شاملة من ميّزات الوقت الفعليّ، موحَّدة معياريّاً كـالملحق D (Real-Time Systems) من مواصفة اللغة. ليست هذه «إضافة لاحقة عبر مكتبة»، بل ضمان وقت فعليّ مُدمَج في وقت تشغيل اللغة نفسه.
ميّزات الوقت الفعليّ في Ada (الملحق D):
- أولويّة المهامّ والاقتطاع الاستباقيّ (FIFO_Within_Priorities)
- بروتوكول Ceiling_Locking (منع انعكاس الأولويّة)
- التنفيذ الدوريّ بالوقت المطلق عبر delay until
- ملف تعريف Ravenscar (مجموعة فرعيّة حرجة للسلامة)
- أحداث التوقيت (استيقاظ بالوقت دون استقصاء polling)
- مراقبة وقت التنفيذ (Ada.Execution_Time)
- الجدولة متعدِّدة الدوريّات
في هذا المقال، نشرح هذه الميّزات تدريجيّاً عبر ثمانية أمثلة برمجيّة تطبيقيّة. يمكن التعامل مع كلّ مقتطف كمثال مستقلّ، لكنّ الأمثلة 04/05 التي تحتوي على عدّة وحدات ترجمة تُقسَّم عبر gnatchop ثمّ تُبنى عبر gnatmake.
كما تجدر الإشارة إلى أنّ مقاطع الشيفرة الواردة في هذا المقال منشورة على GitHub كمجموعة شيفرات مرجعيّة مُرتَّبة في ملفّات حسب الفصل.
ada-real-time-systems - komurasoft-blog-samples (GitHub)
2. ما هو نظام الوقت الفعليّ
لنبدأ أوّلاً بترتيب المصطلحات.
| المفهوم | الشرح |
|---|---|
| الوقت الفعليّ الصارم (Hard Real-Time) | تجاوز الموعد النهائيّ يعني فشلاً قاتلاً للنظام (التحكّم في الطيران، الوسائد الهوائيّة، منظِّمات ضربات القلب) |
| الوقت الفعليّ المرن (Soft Real-Time) | تجاوز الموعد النهائيّ غير مرغوب فيه، لكن يُسمَح بتجاوز نادر (بثّ الفيديو، الألعاب) |
| الموعد النهائيّ (deadline) | الوقت المطلق الذي يجب أن تكتمل عنده المهمّة |
| الدوريّة (period) | الفاصل الزمنيّ الذي تُشغَّل فيه المهمّة بشكل متكرِّر |
| WCET (أسوأ وقت تنفيذ) | أسوأ وقت تنفيذ ممكن للمهمّة |
| الاهتزاز (jitter) | تذبذب التنفيذ الدوريّ |
في تصميم أنظمة الوقت الفعليّ، يُعدّ تحقّق «WCET <= الموعد النهائيّ» لكلّ مهمّة شرطاً ضروريّاً مهمّاً. لكن هذا وحده لا يضمن تحقيق النظام بأكمله للموعد النهائيّ. يلزم بشكل منفصل تحليل وقت الاستجابة (response-time analysis) الذي يشمل وقت الحجب (blocking time)، وتوزيع الأولويّات، والاهتزاز، والمقاطعات، وسلوك وقت التشغيل ونظام التشغيل. عمليّاً، نستهدف WCET < الموعد النهائيّ لترك هامش أمان. توفِّر ميّزات الوقت الفعليّ في Ada، على مستوى اللغة، نموذج تنفيذ قابلاً للتنبّؤ يسهِّل إجراء ذلك التحليل.
flowchart LR
HRT[الوقت الفعليّ الصارم] -->|فوات الموعد النهائيّ = فشل قاتل| Examples[التحكّم في الطيران<br/>الوسائد الهوائيّة<br/>منظِّمات ضربات القلب]
SRT[الوقت الفعليّ المرن] -->|يُسمَح بتجاوزات نادرة| Examples2[بثّ الفيديو<br/>الألعاب<br/>واجهة المستخدم]
Ada[الملحق D في Ada<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/>يُتحقَّق من الكفاية عبر تحليل وقت الاستجابة]
من أخطر الظواهر في أنظمة الوقت الفعليّ انعكاس الأولويّة (priority inversion). حدثت هذه المشكلة فعليّاً في مسبار Mars Pathfinder عام 1997، وكانت سبباً في تكرار إعادة تشغيل المسبار (reset).
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 سياسة توزيع (dispatching policy) قياسيّة قائمة على الأولويّة يمكن تحديدها في الملحق D من Ada. السلوك الافتراضيّ في حال عدم تحديد السياسة يُترَك لتعريف التنفيذ (implementation-defined)، لكن في GNAT تُستخدَم في كثير من المنصّات الهدف سياسة من هذا النوع. تُنفَّذ المهامّ داخل الأولويّة نفسها بنظام FIFO (الأوّل دخولاً، الأوّل خروجاً)، وتقوم المهامّ الأعلى أولويّة باقتطاع (preempt) المهامّ الأدنى أولويّة.
-- 01_task_priority.ada
-- الصيغة الأساسيّة لأولويّة المهامّ وFIFO_Within_Priorities
-- توضَع براغمات التهيئة قبل عبارات السياق (context clauses)
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/>(الأولويّة=Last)
participant LP as مهمّة منخفضة الأولويّة<br/>(الأولويّة=First)
Main->>HP: إنشاء المهمّة
Main->>LP: إنشاء المهمّة
Note over HP,LP: T=0ms، كلتا المهمّتين قابلة للتشغيل
S->>HP: اختيار HP الأعلى أولويّة
activate HP
Note over HP: طباعة سجلّ البدء
HP->>S: تُحجَب عند delay until T+100ms
deactivate HP
S->>LP: تشغيل LP بعد ذلك
activate LP
Note over LP: طباعة سجلّ البدء
LP->>S: تُحجَب عند delay until T+500ms
deactivate LP
Note over S: T=100ms، تستيقظ HP
S->>HP: تشغيل HP
activate HP
Note over HP: طباعة سجلّ الاكتمال
deactivate HP
Note over S: T=500ms، تستيقظ LP
S->>LP: تشغيل LP
activate LP
Note over LP: طباعة سجلّ الاكتمال
deactivate LP
Note over Main: (T=800ms) تخرج المهمّة الرئيسيّة
مدى الأولويّة في Ada (الافتراضيّ في GNAT):
Priority'First = 0 (الأدنى)
Priority'Last = 30 (الأعلى، يعتمد على نظام التشغيل)
4. Ceiling_Locking ── منع انعكاس الأولويّة عبر اللغة نفسها
من أكثر المشكلات إزعاجاً في أنظمة الوقت الفعليّ انعكاس الأولويّة (priority inversion). ظاهرة تنتظر فيها مهمّة عالية الأولويّة قفلاً تحتفظ به مهمّة منخفضة الأولويّة، وتُقتطَع تلك المهمّة منخفضة الأولويّة من قِبَل مهمّة متوسّطة الأولويّة، فتُحجَب المهمّة عالية الأولويّة إلى ما لا نهاية.
توفِّر Ada لهذه المشكلة بروتوكول Ceiling_Locking مدمَجاً مباشرةً في الكائنات المحميّة.
-- 02_ceiling_locking.ada
-- منع انعكاس الأولويّة عبر بروتوكول Ceiling_Locking
-- توضَع براغمات التهيئة قبل عبارات السياق (context clauses)
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:
- تُضبَط الأولويّة السقفيّة (ceiling priority) للكائن المحميّ عبر
pragma Priority (Ceiling). - أيّاً كانت المهمّة التي تدخل الكائن المحميّ، تُرقّى تلقائيّاً إلى الأولويّة السقفيّة عند الدخول.
- بهذا، لا يمكن للمهامّ متوسّطة الأولويّة اقتطاع المهمّة التي تستخدم الكائن المحميّ.
- عند الخروج من الكائن المحميّ، تعود المهمّة إلى أولويّتها الأصليّة.
الرسم أدناه ليس تتبّعاً زمنيّاً دقيقاً للشيفرة النموذجيّة السابقة، بل رسم مفاهيميّ يُظهِر كيف يُقيَّد نمط انعكاس الأولويّة في الرسم 2 عبر Ceiling_Locking.
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/>لكنّها تنتظر لأنّ L تستخدم PO
L->>PO: تنفيذ العمليّة
L->>PO: الخروج من العمليّة المحميّة
deactivate L
Note over L: تعود الأولويّة إلى 10
Note over S,H: تعمل H بمجرّد تحرّر PO
activate H
H->>PO: الدخول إلى العمليّة المحميّة
Note over H,PO: H(30) = السقف(30)، لذا الدخول مسموح بمجرّد زوال التعارض
H->>PO: الخروج من العمليّة المحميّة
deactivate H
مبدأ تصميم: يجب ضبط الأولويّة السقفيّة للكائن المحميّ لتكون أعلى من أو تساوي أعلى أولويّة بين جميع المهامّ التي تستخدم ذلك الكائن المحميّ. وإذا خُولف ذلك، واستدعت مهمّة ذات أولويّة نشطة أعلى من الأولويّة السقفيّة عمليّة محميّة، يمكن لِـ Ada كشف خطأ التصميم عبر
Program_Error.
لتحقيق أمر مماثل باستخدام أقفال pthread mutex في لغة C، يجب ضبط خاصّيّة PTHREAD_PRIO_PROTECT صراحةً، لكنّه في Ada ميّزة قياسيّة في اللغة نفسها.
5. delay until ── تنفيذ المهامّ الدوريّة دون انحراف
النمط الأساسيّ في أنظمة الوقت الفعليّ هو المهمّة الدوريّة. من المهمّ للغاية في المهامّ التي تُنفَّذ بشكل متكرِّر على فواصل زمنيّة ثابتة، منع الخطأ التراكميّ في التوقيت (الانحراف/drift).
يحلّ 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=0ms → معالجة(15ms) → delay 100ms → T=115ms → معالجة(10ms) → ...
الفاصل الفعليّ: 115ms، 110ms، ... (تراكم وقت المعالجة)
حالة delay until:
Next_Release: 100ms، 200ms، 300ms، ... (وقت مطلق)
T=0ms → معالجة(15ms) → delay until 100ms → T=100ms → معالجة(10ms) → delay until 200ms
الفاصل الفعليّ: 100ms، 100ms، ... (لا يتأثّر بوقت المعالجة)
سنستخدم نمط delay until هذا في جميع المهامّ الدوريّة اللاحقة.
flowchart TB
subgraph Bad["delay Period - انحراف تراكميّ"]
B1[T=0ms: معالجة 15ms] --> B2[delay 100ms -> يستيقظ عند 115ms]
B2 --> B3[معالجة 10ms -> 125ms]
B3 --> B4[delay 100ms -> يستيقظ عند 225ms]
B4 --> B5[الفاصل الفعليّ: 115ms، 110ms...]
end
subgraph Good["delay until - قاعدة الوقت المطلق"]
G1[Next = T+100ms] --> G2[معالجة 15ms]
G2 --> G3[delay until T+100ms -> يستيقظ عند 100ms]
G3 --> G4[معالجة 10ms]
G4 --> G5[Next = T+200ms -> يستيقظ عند 200ms]
G5 --> G6[الفاصل الفعليّ: 100ms، 100ms...]
end
subgraph Overrun["تجاوز الدوريّة - فوات الموعد النهائيّ"]
O1[Next = T+100ms] --> O2[معالجة 130ms]
O2 --> O3[delay until T+100ms يعود فوراً]
O3 --> O4[اكتشاف التأخّر ومعاملته كحمل زائد]
end
Bad --> Drift[يتراكم الخطأ مع الوقت]
Good --> Stable[يمنع الانحراف التراكميّ]
Good --> Overrun
6. ملف تعريف Ravenscar ── مجموعة فرعيّة للوقت الفعليّ قابلة للتحقّق
ميّزات المهامّ في Ada قويّة، لكن في الأنظمة التي تكون فيها السلامة بالغة الأهمّيّة، تصبح «القوّة المفرطة» مشكلة. إنشاء المهامّ الديناميكيّ، وجملة select، وجملة abort وغيرها، تجعل التحليل الساكن لأسوأ وقت تنفيذ صعباً.
ملف تعريف Ravenscar هو الإجابة التي تقدِّمها Ada لهذه المشكلات. يقيِّد ميّزات المهامّ إلى مجموعة فرعيّة قابلة للتحليل الساكن وحتميّة (deterministic).
-- 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 بالوقت المطلق |
| عدّة مداخل (entries) لكلّ كائن محميّ | يزداد شرط الحجب وموضوع التحليل |
| إنهاء المهمّة | تُعامَل جميع المهامّ في 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[مدخل واحد لكلّ كائن محميّ]
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. أحداث التوقيت ── استيقاظ مدفوع بالوقت دون استقصاء
في كثير من أنظمة الوقت الفعليّ، يتكرَّر متطلّب من نوع «استيقظ مهمّة عالية الأولويّة عند وصول وقت محدَّد». في التنفيذ الساذج، يصبح ذلك استقصاءً (polling) للمؤقّت، لكنّ 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+100ms, S.Fire'Access) ── تسجيل مُعالِج بوقت مطلق
2. مرور T+100ms ── يستدعي وقت التشغيل S.Fire **بالأولويّة السقفيّة**
3. يضبط Fire العلَم Fired إلى True ── ينفتح الحاجز (barrier)
4. تستيقظ مهمّة Reactor من Wait_For_Event
المهمّ هو أنّ هذا المثال يُصرِّح صراحةً بـCeiling_Locking، وبما أنّ مُعالِج Fire إجراء لكائن محميّ، فإنّه يُنفَّذ بالأولويّة السقفيّة. الإجراء المحميّ المُستخدَم كمُعالِج لحدث توقيت يُوضَع في كائن محميّ ذي أولويّة سقفيّة على مستوى المقاطعة، وهنا System.Interrupt_Priority'Last. بهذا، لا يحدث انعكاس أولويّة أثناء معالجة حدث التوقيت.
8. طابور وقت فعليّ عبر الكائنات المحميّة
من الأنماط الشائعة في أنظمة الوقت الفعليّ نمط المنتِج-المستهلِك (producer-consumer). يُنشئ مستشعِر بيانات، وتستهلكها مهمّة تحكّم ── هنا يلزم إجراء التحكّم الحصريّ للمخزن المؤقّت والحجب بكفاءة.
باستخدام الكائن المحميّ وحواجز المدخل (entry barriers) في Ada، يمكن تنفيذ ذلك كتزامن قائم على الحواجز. داخليّاً، يدير وقت التشغيل الاستبعاد المتبادل، لذا لا حاجة لكتابة mutex أو متغيّر شرط مباشرةً في شيفرة التطبيق.
-- 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)، ويُعاد تقييمه تلقائيّاً عند تحرير القفل.
لا تظهر في هذه الشيفرة أيّ mutex أو semaphore أو متغيّر شرط من جانب التطبيق. يُعبَّر عن الانتظار اللازم عبر حواجز مدخل الكائن المحميّ.
stateDiagram-v2
Empty: فارغ / Count=0
Partial: ممتلئ جزئيّاً / Count=1..Buffer_Size-1
Full: ممتلئ / Count=Buffer_Size
[*] --> Empty: الحالة الأوّليّة
Empty --> Partial: Put (يضيف عنصراً واحداً)
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 وقت استهلاك المعالج (CPU) لكلّ مهمّة.
-- 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;
الوقت الفعليّ (wall clock) مقابل وقت المعالج (CPU):
الوقت الفعليّ (Wall Clock): Ada.Real_Time.Clock
→ الوقت المنقضي فعليّاً. يشمل الحجب والاقتطاع.
وقت التنفيذ (Execution Time): Ada.Execution_Time.Clock
→ الوقت الذي نُفِّذت فيه المهمّة فعليّاً على المعالج فقط.
→ لا يُحتسَب أثناء الحجب أو الاقتطاع.
هذا التمييز نقطة بداية لمراقبة وقت التنفيذ والتحقّق من أسوأ وقت تنفيذ (WCET). أثناء انتظار Busy_Worker عبر delay until، لا يزداد وقت المعالج، ويزداد فقط أثناء المعالجة الحسابيّة الفعليّة. وأثناء delay until Clock + Milliseconds(500) للمهمّة الرئيسيّة أيضاً، يُفترَض أن يكون وقت المعالج شبه معدوم. مع ذلك، فإنّ القياس الفعليّ لوقت المعالج لا يضمن الـ WCET الحقيقيّ. الـ WCET الذي يشمل التخزين المؤقّت (cache)، وخطّ الأنابيب (pipeline)، وتنازع الذاكرة وغيرها، يحتاج إلى تحليل ساكن وتحقّق منفصل في بيئة الهدف.
flowchart LR
subgraph Wall["الوقت الفعليّ (Wall Clock)"]
W1[الوقت الإجماليّ المنقضي: 500ms] --> W2[التفصيل: معالجة + انتظار + حجب + اقتطاع]
end
subgraph CPU["وقت المعالج (CPU)"]
C1[إجماليّ وقت المعالج: 120ms] --> C2[التفصيل: المعالجة الفعليّة فقط]
end
Wall --> Diff[الفرق = وقت الانتظار/الحجب/الاقتطاع]
CPU --> Diff
Diff --> Insight[وقت المعالج يعكس التكلفة الحسابيّة الحقيقيّة<br/>يدعم التحقّق من WCET ومراقبته<br/>يستثني وقت الانتظار/الحجب/الاقتطاع]
Insight --> Caveat[تنبيه<br/>القياس لا يضمن الـ WCET الحقيقيّ<br/>يلزم تحليل ساكن وتحقّق في بيئة الهدف]
10. عرض توضيحيّ شامل ── نظام وقت فعليّ متعدِّد الدوريّات
هنا نجمع كلّ ما تعلّمناه حتّى الآن ── الأولويّة، وCeiling_Locking، وdelay until، والكائن المحميّ ── لبناء نظام وقت فعليّ متعدِّد الدوريّات نموذجيّ.
-- 08_multiperiodic.ada
-- عرض توضيحيّ متكامل لنظام وقت فعليّ متعدِّد الدوريّات
-- مهمّة قراءة مستشعِر بدوريّة سريعة (100ms)
-- مهمّة تحكّم بدوريّة بطيئة (400ms)
-- مشاركة البيانات عبر 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) في الشيفرة النموذجيّة (المستشعِر السريع بدوريّة 100ms، والتحكّم البطيء بدوريّة 400ms مع إزاحة 150ms)، وليس رسماً بيانيّاً لقياس فعليّ. لا تحتوي الشيفرة نفسها على حساب تحكّم مدّته 80ms. عندما تكون أولويّة المستشعِر السريع أعلى، إذا جاء إطلاق المستشعِر السريع أثناء تنفيذ التحكّم البطيء، يُقاطَع التحكّم البطيء مؤقّتاً. لتسهيل الرؤية، يُرسَم في الرسم مقاطعة تمثيليّة واحدة فقط لكلّ دوريّة تحكّم بطيء، لكن في الواقع يُطلَق المستشعِر السريع عند كلّ حدّ من حدود 100ms.
flowchart TB
Assumption["افتراض توضيحيّ<br/>المستشعِر السريع: 10ms من العمل<br/>التحكّم البطيء: 80ms من العمل"]
subgraph Cycle1["دوريّة التحكّم البطيء 1 (الإطلاق=150ms)"]
direction LR
C1F1["100-110ms<br/>المستشعِر السريع #1"] --> C1S1["150-200ms<br/>التحكّم البطيء #1، النصف الأوّل"]
C1S1 --> C1F2["200-210ms<br/>المستشعِر السريع #2<br/>P+3 تقتطعه"]
C1F2 --> C1S2["210-240ms<br/>التحكّم البطيء #1، النصف الثاني"]
end
subgraph Cycle2["دوريّة التحكّم البطيء 2 (الإطلاق=550ms)"]
direction LR
C2S1["550-600ms<br/>التحكّم البطيء #2، النصف الأوّل"] --> C2F6["600-610ms<br/>المستشعِر السريع #6<br/>P+3 تقتطعه"]
C2F6 --> C2S2["610-640ms<br/>التحكّم البطيء #2، النصف الثاني"]
end
subgraph Cycle3["دوريّة التحكّم البطيء 3 (الإطلاق=950ms)"]
direction LR
C3S1["950-1000ms<br/>التحكّم البطيء #3، النصف الأوّل"] --> C3F10["1000-1010ms<br/>المستشعِر السريع #10<br/>P+3 تقتطعه"]
C3F10 --> C3S2["1010-1040ms<br/>التحكّم البطيء #3، النصف الثاني"]
end
Assumption --> C1F1
C1S2 --> C2S1
C2S2 --> C3S1
هذا النمط بنية نموذجيّة شائعة في أنظمة التحكّم الصناعيّ والتحكّم الروبوتيّ، تجمع «جمع بيانات مستشعِر سريع + حلقة تحكّم بطيئة».
11. المجالات التي تتألّق فيها ميّزات الوقت الفعليّ في Ada
تظهر قيمة ميّزات الوقت الفعليّ في Ada بشكل خاصّ في المجالات التالية.
flowchart TB
Ada[الملحق D في Ada<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[مرشَّح لوحدات التحكّم الإلكترونيّة المتعلِّقة بالسلامة]
Auto --> Au2[استخدام محدود وانتقائيّ في مجال<br/>تهيمن عليه C / MISRA-C]
Medical --> M1[منظِّمات ضربات القلب]
Medical --> M2[مضخّات التسريب]
Industrial --> I1[التحكّم في الروبوتات]
Industrial --> I2[آلات CNC]
Defense --> D1[حواسيب المهامّ]
Defense --> D2[أنظمة طويلة العمر]
12. نقاط الحذر والحدود
ميّزات الوقت الفعليّ في Ada قويّة، لكنّها ليست قادرة على كلّ شيء.
1. الاعتماد على المنصّة:
- يعتمد الترابط الفعليّ لـ
pragma Priorityعلى بيئة التشغيل (نظام التشغيل + وقت تشغيل GNAT). على Linux يُربَط بـSCHED_FIFO، لكن على Windows قد لا يُضمَن الاقتطاع الكامل.
2. قيود Ravenscar:
- بما أنّ إنشاء المهامّ الديناميكيّ محظور، يجب الإعلان عن جميع المهامّ سكونيّاً عند بدء تشغيل النظام. هذا يقيِّد حرّيّة التصميم.
3. حدود قياس WCET:
Ada.Execution_Timeهو قياس وليس ضماناً. الـ WCET الحقيقيّ الذي يشمل إخفاقات التخزين المؤقّت (cache misses) ومخاطر خطّ الأنابيب (pipeline hazards) يحتاج إلى تحقّق منفصل بأدوات تحليل ساكن.
4. النفقات العامّة (overhead):
- يُنفَّذ تقييم حاجز الكائن المحميّ تلقائيّاً عند إتمام المدخل أو إلغائه، وعند الخروج من الكائن المحميّ. في الكائنات المحميّة التي تُستدعى بتردّد عالٍ، يجب مراعاة هذه النفقات العامّة.
5. عقبة سلسلة الأدوات:
- للاستفادة الكاملة من ميّزات الوقت الفعليّ في Ada، يلزم مترجم متقاطع (cross compiler) ووقت تشغيل مناسبين. خصوصاً في الأهداف المدمجة، يصبح الاعتماد على وقت التشغيل المُقدَّم من المورِّد أمراً لازماً.
13. الخلاصة
استعرضنا في هذا المقال ميّزات الوقت الفعليّ التي يوفِّرها الملحق D في Ada عبر ثمانية أمثلة برمجيّة، تدريجيّاً.
| الميّزة | القيمة المُقدَّمة |
|---|---|
| أولويّة المهامّ | جدولة استباقيّة قائمة على الأولويّة |
| Ceiling_Locking | منع انعكاس الأولويّة مدمَج في اللغة |
delay until |
تنفيذ دوريّ يمنع الانحراف التراكميّ |
| ملف تعريف Ravenscar | مجموعة فرعيّة من المهامّ يسهل تحليلها ساكنيّاً |
| أحداث التوقيت | استيقاظ مدفوع بالوقت دون استقصاء |
| طابور محميّ | تزامن قائم على حواجز الكائن المحميّ |
| قياس وقت التنفيذ | مراقبة وقت المعالج لكلّ مهمّة |
| التكامل متعدِّد الدوريّات | تصميم يُبقي مهامّ ذات دوريّات مختلفة تتعايش بأمان |
جوهر ميّزات الوقت الفعليّ في Ada هو أنّها «ليست إضافة لاحقة». قواعد القفل التي تكبح انعكاس الأولويّة، وتحديد الوقت للتنفيذ الدوريّ، ومراقبة وقت التنفيذ وغيرها، مُقدَّمة كـجزء من مواصفة اللغة نفسها. بالطبع، يُتحقَّق من تحقيق الموعد النهائيّ نفسه عبر التصميم والتحليل، لكنّ كون وقت تشغيل اللغة يهيّئ المتطلّبات الأساسيّة لذلك التحليل يمثِّل قوّة كبيرة.
mindmap
root((الملحق D في Ada<br/>أنظمة الوقت الفعليّ))
الجدولة
FIFO_Within_Priorities
أولويّة المهامّ
الاقتطاع
منع انعكاس الأولويّة
بروتوكول Ceiling_Locking
ترقية تلقائيّة للأولويّة
قاعدة الأولويّة السقفيّة
التنفيذ الدوريّ
delay until
يمنع الانحراف التراكميّ
قاعدة الوقت المطلق
ملف تعريف Ravenscar
مجموعة مهامّ ساكنة
تحليل حتميّ
لا delay نسبيّ
DO-178C / ISO 26262
أحداث التوقيت
استيقاظ دون استقصاء
تسجيل مُعالِج محميّ
المُعالِج يعمل بالأولويّة السقفيّة
الكائنات/الطوابير المحميّة
حواجز المدخل
إدارة حالات فارغ/جزئيّ/ممتلئ
تزامن قائم على الحواجز
مراقبة وقت التنفيذ
وقت المعالج لكلّ مهمّة
الوقت الفعليّ مقابل وقت المعالج
يدعم التحقّق من 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 (GitHub)
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
التزامن الآمن في Ada ── دليل عملي للمهامّ (Tasks) والكائنات المحمية (Protected Objects)
مقال تمهيدي عن التزامن المدمج في لغة Ada عبر المهامّ (tasks) والكائنات المحمية (protected objects). نستعرض الرندفو (entry/accept)، والقبو...
البرمجة العامّة (Generics) في Ada ── كتابة العقود بالأنواع، وتحقيق إعادة الاستخدام دون تكلفة إضافيّة
شرح شامل للبرمجة العامّة (generic programming) في Ada، من الإجراءات العامّة، والحزم العامّة، والمعطيات الفرعيّة (formal subprograms)، وفئ...
مدخل إلى التحقق الشكلي باستخدام SPARK ── من عقود Ada إلى البرهان الرياضي
مقال تطبيقيّ يقدّم مدخلاً إلى التحقق الشكلي باستخدام SPARK، مجموعة الأداة الفرعيّة للغة Ada. يتناول المقال الانتقال من العقود (Pre/Post) ...
سحر لغة Ada ── لغة تُعبِّر عن التصميم بالأنواع، وتدعم برمجيّات تعمل لعقود
نقدّم سحر لغة Ada. الأنواع القويّة، وقيود المدى، والفصل بين المواصفة والتنفيذ عبر الحزم، والتصميم بالعقد، والمهامّ المدمجة في اللغة، والت...
معالجة الأخطاء وتصميم إعادة المحاولة في Power Automate ── منع «توقّف تدفّق كان يعمل دون أن يلاحظ أحد»
مجموعة أنماط تصميميّة لمنع تدفّقات Power Automate من «التوقّف دون أن يلاحظ أحد». نستعرض، استناداً إلى مواصفات Microsoft Learn، القيم الاف...
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما هو الملحق D في Ada؟
- مجموعة ميّزات موجَّهة لأنظمة الوقت الفعليّ، موحَّدة معياريّاً كجزء من مواصفة لغة Ada. تشمل الجدولة الاستباقيّة (preemptive) المبنيّة على الأولويّة عبر FIFO_Within_Priorities، وبروتوكول Ceiling_Locking الذي يمنع انعكاس الأولويّة، والتنفيذ الدوريّ بالوقت المطلق عبر delay until، وملف تعريف Ravenscar، وأحداث التوقيت (timing events)، وقياس وقت التنفيذ لكلّ مهمّة عبر Ada.Execution_Time. السمة المميّزة هي أنّها ليست إضافة لاحقة عبر مكتبة، بل مُدمَجة في وقت تشغيل اللغة نفسه.
- ما هو انعكاس الأولويّة؟ وكيف تمنعه Ada؟
- ظاهرة تُقتطَع فيها مهمّة منخفضة الأولويّة أثناء احتفاظها بقفل، من قِبَل مهمّة متوسّطة الأولويّة، فتُحجَب إلى ما لا نهاية مهمّة عالية الأولويّة تنتظر ذلك القفل. حدثت فعليّاً في مسبار Mars Pathfinder عام 1997، وكانت سبباً في تكرار إعادة تشغيل المسبار (reset). توفِّر Ada بروتوكول Ceiling_Locking كميّزة لغويّة، حيث تُرقّى تلقائيّاً المهمّة التي تدخل كائناً محميّاً إلى الأولويّة السقفيّة (ceiling priority)، ما يمنع اقتطاع المهامّ متوسّطة الأولويّة لها.
- ما هو ملف تعريف Ravenscar؟
- ملف تعريف يقيِّد ميّزات المهامّ في Ada إلى مجموعة فرعيّة قابلة للتحليل الساكن وحتميّة (deterministic)، من أجل الأنظمة التي تكون فيها السلامة أمراً بالغ الأهمّيّة. يُحظَر فيها إنشاء المهامّ الديناميكيّ، وجملة select، وجملة abort، وdelay النسبيّ، وجملة requeue وغيرها. هذا التقييد يسهِّل إجراء تحليل التوقيت الساكن، ويسهِّل تلبية الخصائص المطلوبة في معايير السلامة مثل DO-178C (برمجيّات الطائرات) وISO 26262 (السلامة الوظيفيّة للسيّارات). يُفعَّل في GNAT عبر كتابة pragma Profile (Ravenscar) في ملفّ gnat.adc.
- لماذا يُستخدَم delay until بدلاً من delay في المهامّ الدوريّة؟
- لأنّ delay ذي التحديد النسبيّ للزمن يتراكم فيه وقت معالجة كلّ تكرار، فيحدث انحراف تراكميّ (drift) يُزيح الدوريّة تدريجيّاً. أمّا delay until فيحدِّد وقت التشغيل التالي استناداً إلى وقت مطلق، لذا حتّى لو تأخّرت معالجة واحدة، يبقى وقت التشغيل في المرّات اللاحقة صحيحاً. مع ذلك، إذا تجاوزت المعالجة وقت الاستيقاظ التالي، يعود delay until شبه فوراً، لذا يلزم تصميم منفصل يكتشف ذلك كفوات موعد نهائيّ (deadline miss) ويُعامَله كحمل زائد.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.
روابط عامة