برمجة أنظمة الوقت الفعليّ بلغة 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، على مستوى اللغة، نموذج تنفيذ قابلاً للتنبّؤ يسهِّل إجراء ذلك التحليل.

متطلّبات الوقت الفعليّفوات الموعد النهائيّ = فشل قاتليُسمَح بتجاوزات نادرةالموعد النهائيّوقت الاكتمال المطلقالدوريّةفاصل التكرارWCETأسوأ وقت تنفيذالاهتزازتذبذب الدوريّةالوقت الفعليّ الصارمالتحكّم في الطيرانالوسائد الهوائيّةمنظِّمات ضربات القلبالوقت الفعليّ المرنبثّ الفيديوالألعابواجهة المستخدمالملحق D في Adaآليّات تدعم قابليّة التنبّؤFIFO_Within_PrioritiesCeiling_Lockingdelay untilتحليل قابليّة الجدولةالشرط الضروريّ: WCET &lt;= الموعد النهائيّيُتحقَّق من الكفاية عبر تحليل وقت الاستجابة

من أخطر الظواهر في أنظمة الوقت الفعليّ انعكاس الأولويّة (priority inversion). حدثت هذه المشكلة فعليّاً في مسبار Mars Pathfinder عام 1997، وكانت سبباً في تكرار إعادة تشغيل المسبار (reset).

مورد مشتركمهمّة متوسّطة الأولويّةمهمّة عالية الأولويّةمهمّة منخفضة الأولويّةالمجدوِلمورد مشتركمهمّة متوسّطة الأولويّةمهمّة عالية الأولويّةمهمّة منخفضة الأولويّةالمجدوِلتنفيذ المنطقة الحرجةتستيقظ H، والمجدوِل يقتطع Lمحجوبة بانتظار القفل (الذي تحتفظ به L)!H محجوبة، لذا تستأنف Lتواصل نحو تحرير القفل...تستيقظ M، والمجدوِل يقتطع LL لا يمكنها تحرير القفلM تواصل التنفيذ (لا يمكن لِـH ولا لِـL المتابعة)[انعكاس الأولويّة] مهمّة عالية الأولويّة محجوبة إلى ما لا نهايةالحصول على القفلمحاولة الحصول على القفل

تُقتطَع المهمّة منخفضة الأولويّة أثناء احتفاظها بالقفل من قِبَل مهمّة متوسّطة الأولويّة، فتُحجَب إلى ما لا نهاية المهمّة عالية الأولويّة. المعالجة الفعليّة التي طُبِّقت في 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.
مهمّة منخفضة الأولويّة(الأولويّة=First)مهمّة عالية الأولويّة(الأولويّة=Last)المهمّة الرئيسيّةالمجدوِلمهمّة منخفضة الأولويّة(الأولويّة=First)مهمّة عالية الأولويّة(الأولويّة=Last)المهمّة الرئيسيّةالمجدوِلT=0ms، كلتا المهمّتين قابلة للتشغيلطباعة سجلّ البدءطباعة سجلّ البدءT=100ms، تستيقظ HPطباعة سجلّ الاكتمالT=500ms، تستيقظ LPطباعة سجلّ الاكتمال(T=800ms) تخرج المهمّة الرئيسيّةإنشاء المهمّةإنشاء المهمّةاختيار HP الأعلى أولويّةتُحجَب عند delay until T+100msتشغيل LP بعد ذلكتُحجَب عند delay until T+500msتشغيل HPتشغيل LP
مدى الأولويّة في 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:

  1. تُضبَط الأولويّة السقفيّة (ceiling priority) للكائن المحميّ عبر pragma Priority (Ceiling).
  2. أيّاً كانت المهمّة التي تدخل الكائن المحميّ، تُرقّى تلقائيّاً إلى الأولويّة السقفيّة عند الدخول.
  3. بهذا، لا يمكن للمهامّ متوسّطة الأولويّة اقتطاع المهمّة التي تستخدم الكائن المحميّ.
  4. عند الخروج من الكائن المحميّ، تعود المهمّة إلى أولويّتها الأصليّة.

الرسم أدناه ليس تتبّعاً زمنيّاً دقيقاً للشيفرة النموذجيّة السابقة، بل رسم مفاهيميّ يُظهِر كيف يُقيَّد نمط انعكاس الأولويّة في الرسم 2 عبر Ceiling_Locking.

كائن محميّ(الأولويّة السقفيّة=30)مهمّة عالية الأولويّة(الأولويّة=30)مهمّة متوسّطة الأولويّة(الأولويّة=20)مهمّة منخفضة الأولويّة(الأولويّة=10)المجدوِلكائن محميّ(الأولويّة السقفيّة=30)مهمّة عالية الأولويّة(الأولويّة=30)مهمّة متوسّطة الأولويّة(الأولويّة=20)مهمّة منخفضة الأولويّة(الأولويّة=10)المجدوِلProgram_Error إذا تجاوزت الأولويّة النشطة للمستدعي الأولويّة السقفيّةH(30) في هذا الرسم تساوي السقف(30)، لذا الدخول مسموحتُرفَع أولويّة التنفيذ إلى 30تستيقظ ML تعمل بالأولويّة السقفيّة 30M(20) لا تستطيع اقتطاعهاتستيقظ HH(30) تجتاز فحص السقفلكنّها تنتظر لأنّ L تستخدم POتعود الأولويّة إلى 10تعمل H بمجرّد تحرّر POH(30) = السقف(30)، لذا الدخول مسموح بمجرّد زوال التعارضالدخول إلى العمليّة المحميّةتنفيذ العمليّةالخروج من العمليّة المحميّةالدخول إلى العمليّة المحميّةالخروج من العمليّة المحميّة

مبدأ تصميم: يجب ضبط الأولويّة السقفيّة للكائن المحميّ لتكون أعلى من أو تساوي أعلى أولويّة بين جميع المهامّ التي تستخدم ذلك الكائن المحميّ. وإذا خُولف ذلك، واستدعت مهمّة ذات أولويّة نشطة أعلى من الأولويّة السقفيّة عمليّة محميّة، يمكن لِـ 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 هذا في جميع المهامّ الدوريّة اللاحقة.

تجاوز الدوريّة - فوات الموعد النهائيّمعالجة 130msNext = T+100msdelay until T+100ms يعود فوراًاكتشاف التأخّر ومعاملته كحمل زائدdelay until - قاعدة الوقت المطلقمعالجة 15msNext = T+100msdelay until T+100ms -> يستيقظ عند 100msمعالجة 10msNext = T+200ms -> يستيقظ عند 200msالفاصل الفعليّ: 100ms، 100ms...delay Period - انحراف تراكميّdelay 100ms -> يستيقظ عند 115msT=0ms: معالجة 15msمعالجة 10ms -> 125msdelay 100ms -> يستيقظ عند 225msالفاصل الفعليّ: 115ms، 110ms...يتراكم الخطأ مع الوقتيمنع الانحراف التراكميّ

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.

ميّزات المهامّ الكاملة في Adaملف تعريف Ravenscarالقيودالسياسات الإلزاميّةلا إنشاء مهامّ ديناميكيّلا جملة selectلا جملة abortلا Task_Attributesمدخل واحد لكلّ كائن محميّلا جملة requeueلا delay نسبيّاستخدام delay untilلا تغيير أولويّة ديناميكيّلا إنهاء مهامّجميع المهامّ غير قابلة للإنهاءFIFO_Within_PrioritiesCeiling_Lockingالفائدة:تحليل التوقيت الساكن أسهلDO-178Cبرمجيّات الطيرانISO 26262السلامة الوظيفيّة للسيّاراتIEC 62304برمجيّات الأجهزة الطبّيّة

لتفعيل ملف تعريف 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 أو متغيّر شرط من جانب التطبيق. يُعبَّر عن الانتظار اللازم عبر حواجز مدخل الكائن المحميّ.

الحالة الأوّليّةPut (يضيف عنصراً واحداً)Put / GetGet (يزيل العنصر الأخير)Put (يملأ الخانة الأخيرة)Get (يحرِّر خانة)تُحجَب Get (الحاجز Count=0)تُحجَب Put (الحاجز Count=Buffer_Size)فارغ / Count=0ممتلئ جزئيّاً / Count=1..Buffer_Size-1ممتلئ / 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)، وتنازع الذاكرة وغيرها، يحتاج إلى تحليل ساكن وتحقّق منفصل في بيئة الهدف.

وقت المعالج (CPU)التفصيل: المعالجة الفعليّة فقطإجماليّ وقت المعالج: 120msالوقت الفعليّ (Wall Clock)التفصيل: معالجة + انتظار + حجب + اقتطاعالوقت الإجماليّ المنقضي: 500msالفرق = وقت الانتظار/الحجب/الاقتطاعوقت المعالج يعكس التكلفة الحسابيّة الحقيقيّةيدعم التحقّق من WCET ومراقبتهيستثني وقت الانتظار/الحجب/الاقتطاعتنبيهالقياس لا يضمن الـ WCET الحقيقيّيلزم تحليل ساكن وتحقّق في بيئة الهدف

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.

دوريّة التحكّم البطيء 3 (الإطلاق=950ms)دوريّة التحكّم البطيء 2 (الإطلاق=550ms)دوريّة التحكّم البطيء 1 (الإطلاق=150ms)1000-1010msالمستشعِر السريع #10P+3 تقتطعه950-1000msالتحكّم البطيء #3، النصف الأوّل1010-1040msالتحكّم البطيء #3، النصف الثاني600-610msالمستشعِر السريع #6P+3 تقتطعه550-600msالتحكّم البطيء #2، النصف الأوّل610-640msالتحكّم البطيء #2، النصف الثاني150-200msالتحكّم البطيء #1، النصف الأوّل100-110msالمستشعِر السريع #1200-210msالمستشعِر السريع #2P+3 تقتطعه210-240msالتحكّم البطيء #1، النصف الثانيافتراض توضيحيّالمستشعِر السريع: 10ms من العملالتحكّم البطيء: 80ms من العمل

هذا النمط بنية نموذجيّة شائعة في أنظمة التحكّم الصناعيّ والتحكّم الروبوتيّ، تجمع «جمع بيانات مستشعِر سريع + حلقة تحكّم بطيئة».

11. المجالات التي تتألّق فيها ميّزات الوقت الفعليّ في Ada

تظهر قيمة ميّزات الوقت الفعليّ في Ada بشكل خاصّ في المجالات التالية.

الملحق D في Adaميّزات الوقت الفعليّالطيران والفضاءDO-178Cالسكك الحديديّةعائلة EN 50128صناعة السيّاراتISO 26262الأجهزة الطبّيّةIEC 62304التحكّم الصناعيّعائلة IEC 61508الدفاع / الأنظمة عالية السلامةالتحكّم في الطيرانمجال مُثبَت الجدوى على نطاق واسعالتحكّم في الأقمار الصناعيّة / المركبات الفضائيّةأنظمة الإشاراتالتحكّم الآليّ في القطاراتمرشَّح لوحدات التحكّم الإلكترونيّة المتعلِّقة بالسلامةاستخدام محدود وانتقائيّ في مجالتهيمن عليه C / MISRA-Cمنظِّمات ضربات القلبمضخّات التسريبالتحكّم في الروبوتاتآلات CNCحواسيب المهامّأنظمة طويلة العمر

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 هو أنّها «ليست إضافة لاحقة». قواعد القفل التي تكبح انعكاس الأولويّة، وتحديد الوقت للتنفيذ الدوريّ، ومراقبة وقت التنفيذ وغيرها، مُقدَّمة كـجزء من مواصفة اللغة نفسها. بالطبع، يُتحقَّق من تحقيق الموعد النهائيّ نفسه عبر التصميم والتحليل، لكنّ كون وقت تشغيل اللغة يهيّئ المتطلّبات الأساسيّة لذلك التحليل يمثِّل قوّة كبيرة.

الملحق D في Adaأنظمة الوقت الفعليّالجدولةFIFO_Within_Prioritiesأولويّة المهامّالاقتطاعمنع انعكاس الأولويّةبروتوكول Ceiling_Lockingترقية تلقائيّة للأولويّةقاعدة الأولويّة السقفيّةالتنفيذ الدوريّdelay untilيمنع الانحراف التراكميّقاعدة الوقت المطلقملف تعريف Ravenscarمجموعة مهامّ ساكنةتحليل حتميّلا delay نسبيّDO-178C / ISO 26262أحداث التوقيتاستيقاظ دون استقصاءتسجيل مُعالِج محميّالمُعالِج يعمل بالأولويّة السقفيّةالكائنات/الطوابير المحميّةحواجز المدخلإدارة حالات فارغ/جزئيّ/ممتلئتزامن قائم على الحواجزمراقبة وقت التنفيذوقت المعالج لكلّ مهمّةالوقت الفعليّ مقابل وقتالمعالجيدعم التحقّق من WCETومراقبتهالتصميم المتكاملتصميم متعدِّد الدوريّاتالحماية عبر الحواجزالسلامة على مستوى اللغة

كخطوة تالية، لتجربة تطوير أنظمة الوقت الفعليّ باستخدام Ada فعليّاً، ثبِّت سلسلة أدوات GNAT عبر Alire، وابنِ الشيفرة النموذجيّة الواردة في هذا المقال عبر gnatchop وَgnatmake.

بخصوص أساسيّات المعالجة المتوازية في Ada (المهامّ، واللقاء، والكائنات المحميّة)، راجع المقال السابق «المعالجة المتوازية الآمنة في Ada».

14. مراجع

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

ما هو الملحق 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) ويُعامَله كحمل زائد.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

روابط عامة

العودة إلى المدونة