Pemrograman sistem real-time dengan Ada — praktik pengendalian prioritas, periode, dan waktu eksekusi

· Diperbarui pada: · · Ada, RealTime, Ravenscar, CeilingLocking, Tasking, Scheduling, PriorityInversion, ProgrammingLanguage, Real-time, Keandalan tinggi

1. Pendahuluan — hubungan erat Ada dan real-time

Artikel sebelumnya, «Pemrosesan konkuren yang aman di Ada», menjelaskan dasar pemrosesan konkuren yang aman dengan task dan protected object pada Ada. Kali ini kita melangkah ke wilayah yang lebih ketat pada kelanjutan itu — sistem real-time.

Dalam sistem real-time, “kebenaran” bukan hanya kebenaran hasil perhitungan secara logis, melainkan juga bahwa hasil itu diperoleh dalam tenggat waktu. Jawaban yang benar tetapi terlambat satu milidetik sama berbahayanya dengan jawaban yang salah.

Untuk persyaratan itu, Ada menyediakan kumpulan fitur real-time yang komprehensif, distandarkan sebagai Annex D (Real-Time Systems) dalam spesifikasi bahasa. Ini bukan “tambahan belakangan lewat pustaka”, melainkan jaminan real-time yang tertanam di runtime bahasa itu sendiri.

Fitur real-time Ada (Annex D):
- Prioritas task dan preemption (FIFO_Within_Priorities)
- Protokol Ceiling_Locking (pencegahan inversi prioritas)
- Eksekusi periodik pada waktu absolut dengan delay until
- Profil Ravenscar (subset yang kritis bagi keselamatan)
- Timing event (bangun pada waktu tertentu tanpa polling)
- Pemantauan waktu eksekusi (Ada.Execution_Time)
- Penjadwalan multi-periode

Artikel ini menjelaskan semuanya secara bertahap melalui delapan contoh kode praktis. Setiap cuplikan dapat diperlakukan sebagai contoh mandiri, tetapi contoh 04/05 yang berisi beberapa unit kompilasi dipecah dulu dengan gnatchop lalu dibangun dengan gnatmake.

Pembaca sasaran dan prasyarat: Artikel ini mengandaikan Anda sudah mengenal dasar task, rendezvous, dan protected object yang dibahas di artikel sebelumnya. Sasaran utamanya adalah pengembang yang tertarik pada perangkat lunak kendali untuk peralatan embedded dan sistem keandalan tinggi; pengantar tata bahasa Ada itu sendiri tidak dibahas.

Lingkungan verifikasi: Kedelapan contoh dalam artikel ini telah dikonfirmasi dapat dibangun dengan GNAT 13.3.0 (Ubuntu 24.04, x86-64). Keluaran contoh yang dicantumkan di teks (bab 3 dan bab 5) juga diambil di lingkungan yang sama. Bagaimana prioritas dan preemption benar-benar berlaku bergantung pada OS dan runtime GNAT (bab 12), sehingga rincian keluaran dapat berbeda antar lingkungan.

Cuplikan kode yang muncul di artikel ini juga dipublikasikan di GitHub sebagai kumpulan kode rujukan yang diorganisasikan per bab ke dalam berkas.

ada-real-time-systems - komurasoft-blog-samples (GitHub)

Peta pengetahuan artikel ini

Annex D pada spesifikasi bahasa Ada menanamkan ke dalam runtime bahasa itu sendiri FIFO_Within_Priorities berdasarkan prioritas task, protokol Ceiling_Locking yang secara otomatis menetapkan ceiling priority pada protected object, eksekusi periodik dengan delay until yang mencegah drift kumulatif, profil Ravenscar yang memudahkan analisis statis, timing event yang tidak memerlukan polling, hingga pengukuran waktu eksekusi per task. Ceiling_Locking mencegah inversi prioritas yang benar-benar terjadi pada Mars Pathfinder tahun 1997 dan membuat wahana berulang kali di-reset, dengan secara otomatis menaikkan task yang masuk ke protected object ke ceiling priority. Profil Ravenscar membatasi task Ada menjadi subset yang mudah dianalisis timing-nya secara statis dengan melarang delay relatif, pernyataan select, dan sejenisnya, dan pada GNAT diaktifkan dengan menulis pragma ke berkas gnat.adc. Namun pemetaan prioritas yang sebenarnya bergantung pada OS dan runtime GNAT.

Peta pengetahuan pemrograman sistem real-time AdaDiagram yang menunjukkan bagaimana Annex D Ada menanamkan penjadwalan berbasis prioritas, Ceiling_Locking, delay until, profil Ravenscar, timing event, dan pengukuran waktu eksekusi di atas task dan protected object, serta bagaimana Ceiling_Locking mencegah inversi prioritas seperti yang terjadi pada Mars Pathfindermenggunakanmenggunakanmenggunakanmenggunakanmenggunakanmenggunakanmensyaratkanmensyaratkanmensyaratkanmencegahdapat menyebabkanmensyaratkanmensyaratkanmensyaratkandikonfigurasi dengandapat menyebabkanmencegahtidak disarankanmenggunakanmenggunakanmensyaratkanAnnex D (standar sistem real-time Ada)protokol Ceiling_LockingFIFO_Within_Prioritiesdelay until (penundaan waktu absolut)profil Ravenscartiming event (Ada.Real_Time.Timing_Events)Ada.Execution_Time (pengukuran waktu eksekusi)task Ada (pemrosesan concurrent)objek terlindung (protected object)GNATpembalikan prioritas (priority inversion)insiden priority inversion pada Mars Pathfinderdelay relatif (pernyataan delay)drift kumulatif eksekusi periodik

Pada diagram, garis utuh menunjukkan relasi yang selalu berlaku dan garis putus-putus menunjukkan relasi bersyarat (syaratnya ada pada penjelasan masing-masing relasi di halaman rincian). Daftar lengkap relasi (total 21, beserta bukti dan tingkat kepastian) serta definisi konsep utama dikumpulkan di halaman rincian peta pengetahuan (dalam bahasa Jepang). Data: JSON-LD / Turtle

2. Apa itu sistem real-time

Mari mulai dengan merapikan istilah.

Konsep Penjelasan
Hard real-time Melebihi deadline berarti kegagalan fatal bagi sistem (kendali penerbangan, airbag, pacemaker)
Soft real-time Melebihi deadline tidak diinginkan, tetapi kelebihan yang jarang masih ditoleransi (streaming video, game)
Deadline (deadline) Waktu absolut ketika task harus selesai
Periode (period) Interval waktu ketika task diaktifkan berulang kali
WCET (Worst-Case Execution Time) Waktu eksekusi terburuk suatu task
Jitter (jitter) Variasi pada eksekusi periodik
Schedulability (schedulability) Sifat “apakah himpunan task itu dapat dijalankan dengan menepati semua tenggat”. Pekerjaan memastikan hal ini di atas kertas adalah analisis schedulability (analisis waktu respons, dan sebagainya)
Profil Ravenscar (Ravenscar) Ketentuan yang membatasi fitur task Ada ke subset yang lebih mudah dianalisis secara statis. Namanya berasal dari desa Ravenscar di Inggris, tempat rapat perumusannya diadakan (bab 6)

Dalam perancangan sistem real-time, terpenuhinya “WCET <= deadline” untuk setiap task menjadi syarat perlu yang penting. Namun itu saja tidak menjamin seluruh sistem menepati tenggat. Analisis waktu respons yang mencakup waktu blocking, penetapan prioritas, jitter, interrupt, serta perilaku runtime dan OS tetap diperlukan secara terpisah. Dalam praktik, sasarannya adalah WCET < deadline agar ada margin. Fitur real-time Ada menyediakan, di tingkat bahasa, model eksekusi yang dapat diprediksi yang memudahkan analisis itu.

Konsep sistem real-time dan mekanisme prediktabilitas AdaDiagram yang menghubungkan hard real-time, soft real-time, deadline, periode, WCET, dan jitter dengan analisis schedulability, serta mekanisme Annex D yang menopang prediktabilitas.Persyaratan real-timeTidak tercapai dalam tenggat = kegagalan fatalKelebihan yang jarang masih ditoleransiDeadlineWaktu absolut untuk selesaiPeriodeInterval pengulanganWCETWaktu eksekusi terburukJitterVariasi periodeHard real-timeKendali penerbanganAirbagPacemakerSoft real-timeStreaming videoGameUIAda Annex DMekanisme penopang prediktabilitasFIFO_Within_PrioritiesCeiling_Lockingdelay untilAnalisis schedulabilitySyarat perlu: WCET &lt;= deadlineKecukupan dicek dengan analisis waktu respons

Salah satu fenomena paling berbahaya dalam sistem real-time adalah inversi prioritas. Masalah ini benar-benar terjadi pada Mars Pathfinder tahun 1997 dan menjadi penyebab wahana berulang kali di-reset.

Terjadinya inversi prioritasTask prioritas rendah memegang lock, task prioritas tinggi menunggu lock itu, lalu task prioritas menengah mem-preempt task rendah sehingga task tinggi terblokir tanpa batas.Sumber daya bersamaTask prioritas menengahTask prioritas tinggiTask prioritas rendahSchedulerSumber daya bersamaTask prioritas menengahTask prioritas tinggiTask prioritas rendahSchedulerSedang menjalankan critical sectionH bangun, scheduler menangguhkan LTerblokir menunggu lock! (masih dipegang L)H menunggu lock, jadi L dilanjutkanMelanjutkan menuju pelepasan lock...M bangun, scheduler menangguhkan LL tidak dapat melepaskan lockM terus berjalan (H dan L tidak bisa bergerak)[Inversi prioritas] Prioritas tinggi terblokir tanpa batasMemperoleh lockMencoba memperoleh lock

Task prioritas rendah yang masih memegang lock di-preempt oleh task prioritas menengah, sehingga task prioritas tinggi terblokir tanpa batas. Penanganan yang benar-benar diterapkan pada Mars Pathfinder adalah mengaktifkan priority inheritance di VxWorks, tetapi untuk masalah sejenis itu Ada menyediakan cara lain sebagai fitur bahasa, yaitu Ceiling_Locking.

3. Dasar prioritas task — FIFO_Within_Priorities

FIFO_Within_Priorities adalah kebijakan dispatching berbasis prioritas yang standar dan dapat ditentukan di Ada Annex D. Perilaku bawaan jika kebijakan tidak dinyatakan bersifat implementation-defined, tetapi pada GNAT, kebijakan keluarga ini dipakai di banyak target. Di dalam prioritas yang sama, eksekusi mengikuti FIFO (first-in, first-out), dan task berprioritas lebih tinggi mem-preempt (mengambil alih) task berprioritas lebih rendah.

-- 01_task_priority.ada
-- Bentuk dasar prioritas task dan FIFO_Within_Priorities
-- Pragma konfigurasi diletakkan sebelum context clause

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;

Poin penting:

  • pragma Priority memberi setiap task prioritas statis. Priority'Last tertinggi, Priority'First terendah.
  • Badan task dalam demo ini bukan komputasi berat, melainkan menunggu sampai waktu yang ditentukan dengan delay until. Yang ingin dikonfirmasi di sini adalah bahwa ketika keduanya menjadi runnable pada saat yang sama, task prioritas tinggi mendapat kesempatan eksekusi lebih dulu.
  • Diagram ini menunjukkan sisi preemption antar prioritas yang berbeda dalam FIFO_Within_Priorities. Untuk mengonfirmasi urutan FIFO di dalam prioritas yang sama, diperlukan contoh terpisah yang menyejajarkan beberapa task berprioritas sama.
  • Dalam sistem nyata, prioritas umumnya dirancang secara relatif terhadap System.Default_Priority.
Penjadwalan FIFO_Within_PrioritiesKetika dua task runnable, scheduler memilih task prioritas tertinggi dulu, lalu task prioritas rendah, dan keduanya bangun pada waktu delay until yang ditentukan.Task prioritas rendah(Priority=First)Task prioritas tinggi(Priority=Last)Task utamaSchedulerTask prioritas rendah(Priority=First)Task prioritas tinggi(Priority=Last)Task utamaSchedulerT=0ms: Kedua task runnableMenulis log mulaiMenulis log mulaiT=100ms: HP bangunMenulis log selesaiT=500ms: LP bangunMenulis log selesai(T=800ms) Main selesaiPembuatan taskPembuatan taskMemilih HP, prioritas tertinggiBlok dengan delay until T+100msMenjalankan LP berikutnyaBlok dengan delay until T+500msMenjalankan HPMenjalankan LP

Contoh eksekusi (GNAT 13.3.0 / Ubuntu 24.04, x86-64):

$ gnatchop -w 01_task_priority.ada .     # → task_priority_demo.adb
$ gnatmake task_priority_demo.adb
$ ./task_priority_demo
[T=0.0s] High priority task started
[T=0.0s] Low priority task started
=== Task Priority Demo (FIFO_Within_Priorities) ===
Main: waiting for tasks to complete...
[T=0.1s] High priority task completed
[T=0.5s] Low priority task completed
Main: done

Perhatikan bahwa dua baris log mulai dari kedua task muncul sebelum baris judul milik main. Task yang dideklarasikan di bagian deklarasi diaktifkan sebelum badan subprogram yang mengelilinginya dimasuki, sehingga urutan seperti ini dapat terjadi. Selain itu, urutan dua baris log mulai itu dapat saling bertukar setiap kali dijalankan. Seperti dijelaskan di bab 12, bagaimana pragma Priority benar-benar tercermin pada penjadwalan bergantung pada OS dan runtime GNAT, dan di Linux serbaguna urutan mulai sesuai prioritas tidak dijamin. Yang dapat diamati secara stabil dalam contoh ini adalah urutan selesai pada 0,1 detik / 0,5 detik yang ditentukan dengan delay until.

Rentang prioritas Ada (default GNAT):
  Priority'First  = 0   (terendah)
  Priority'Last   = 30  (tertinggi, tetapi bergantung pada OS)

4. Ceiling_Locking — bahasa yang mencegah inversi prioritas

Salah satu masalah paling merepotkan dalam sistem real-time adalah inversi prioritas (priority inversion). Task prioritas tinggi menunggu lock yang dipegang task prioritas rendah, lalu task prioritas rendah itu di-preempt oleh task prioritas menengah, sehingga task prioritas tinggi terblokir tanpa batas.

Ada menangani masalah ini dengan protokol Ceiling_Locking, yang tertanam langsung pada protected object.

-- 02_ceiling_locking.ada
-- Pencegahan inversi prioritas dengan protokol Ceiling_Locking
-- Pragma konfigurasi diletakkan sebelum context clause

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;

Cara kerja Ceiling_Locking:

  1. Tetapkan ceiling priority pada protected object dengan pragma Priority (Ceiling).
  2. Task mana pun yang masuk ke protected object, pada saat masuk secara otomatis dinaikkan ke ceiling priority.
  3. Dengan demikian, task yang sedang memakai protected object tidak dapat di-preempt oleh task prioritas menengah.
  4. Setelah keluar dari protected object, prioritas kembali ke nilai semula.

Diagram di bawah bukan jejak waktu yang ketat dari kode sampel sebelumnya, melainkan diagram konsep yang menunjukkan bagaimana pola inversi prioritas pada Gambar 2 ditekan oleh Ceiling_Locking.

Ceiling_Locking mencegah inversi prioritasTask yang masuk protected object dinaikkan ke ceiling priority sehingga task prioritas menengah tidak dapat mem-preempt, dan task prioritas tinggi menunggu sampai operasi terproteksi selesai.Protected object(ceiling priority=30)Task prioritas tinggi(prioritas=30)Task prioritas menengah(prioritas=20)Task prioritas rendah(prioritas=10)SchedulerProtected object(ceiling priority=30)Task prioritas tinggi(prioritas=30)Task prioritas menengah(prioritas=20)Task prioritas rendah(prioritas=10)SchedulerPemanggil dengan prioritas aktif > ceiling memunculkan Program_ErrorH(30) dalam diagram sama dengan ceiling(30) sehingga boleh masukPrioritas eksekusi naik ke 30M bangunL berjalan pada ceiling priority 30M(20) tidak dapat mem-preemptH bangunH(30) lolos pemeriksaan ceilingtetapi menunggu karena L sedang memakai POPrioritas kembali ke 10Setelah PO dilepas, jalankan HH(30) = ceiling(30) sehingga dapat masuk setelah contention selesaiMasuk operasi terproteksiMenjalankan operasiKeluar dari operasi terproteksiMasuk operasi terproteksiKeluar dari operasi terproteksi

Pedoman perancangan: Ceiling priority suatu protected object ditetapkan paling rendah sama dengan prioritas tertinggi di antara semua task yang memakai protected object itu. Jika dilanggar, dan task dengan prioritas aktif lebih tinggi daripada ceiling memanggil operasi terproteksi, Ada dapat mendeteksi kesalahan rancangan itu dengan Program_Error.

Untuk mencapai hal yang setara dengan mutex pthread di C, atribut PTHREAD_PRIO_PROTECT harus ditetapkan secara eksplisit, tetapi di Ada itu adalah fitur standar bahasa.

5. delay until — menjalankan task periodik tanpa drift

Pola dasar sistem real-time adalah task periodik. Pada task yang dijalankan berulang pada interval tetap, mencegah galat timing yang menumpuk (drift) sangat penting.

delay until pada Ada menyelesaikan masalah ini dengan elegan.

-- 03_periodic_task.ada
-- Task periodik dengan delay until — mencegah drift kumulatif

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;

Mengapa delay until:

Cara Masalah
delay Period; Waktu pemrosesan setiap iterasi dijumlahkan, sehingga periode bergeser secara bertahap (drift kumulatif)
delay until Next_Release; Next_Release := Next_Release + Period; Berpatokan pada waktu absolut, jadi meskipun satu kali pemrosesan terlambat, waktu rilis berikutnya tetap benar

Namun delay until tidak secara otomatis menjamin bahwa waktu pemrosesan muat di dalam periode. Jika pemrosesan sudah melewati waktu bangun berikutnya, delay until itu kembali hampir seketika, dan sistem berada pada keadaan yang seharusnya ditangani sebagai deadline miss.

Dengan delay:
  T=0ms → pemrosesan(15ms) → delay 100ms → T=115ms → pemrosesan(10ms) → ...
  Interval aktual: 115ms, 110ms, ... (waktu pemrosesan menumpuk)

Dengan delay until:
  Next_Release: 100ms, 200ms, 300ms, ... (waktu absolut)
  T=0ms → pemrosesan(15ms) → delay until 100ms → T=100ms → pemrosesan(10ms) → delay until 200ms
  Interval aktual: 100ms, 100ms, ... (tidak bergantung pada waktu pemrosesan)

Contoh eksekusi (GNAT 13.3.0 / Ubuntu 24.04, x86-64):

$ gnatchop -w 03_periodic_task.ada .     # → periodic_task_demo.adb
$ gnatmake periodic_task_demo.adb
$ ./periodic_task_demo
[Sensor] Periodic task starts, period= 0.200000000s, cycles= 5
=== Periodic Task Demo (delay until) ===
Main: waiting for 5 cycles...
[Sensor] Cycle 1 at 0.200326876s
[Sensor] Cycle 2 at 0.400159550s
[Sensor] Cycle 3 at 0.600183089s
[Sensor] Cycle 4 at 0.800327451s
[Sensor] Cycle 5 at 1.000260472s
[Sensor] Periodic task finished. Actual elapsed: 1.000297359s
Main: done

Setiap siklus menunjukkan keterlambatan bangun sekitar 0,2–0,3 milidetik, tetapi dapat dibaca bahwa keterlambatan itu tidak dibawa ke periode berikutnya. Pada siklus ke-5 pun penyimpangan dari waktu acuan masih kurang dari 1 milidetik. Jika ditulis dengan delay Period;, keterlambatan ini akan menumpuk setiap kali dan setelah 5 siklus menjadi selisih yang terlihat. Angka ini hanyalah satu contoh di Linux serbaguna, bukan nilai yang dijamin di lingkungan hard real-time.

Pola delay until ini dipakai pada semua task periodik selanjutnya.

Perbandingan delay dan delay untildelay relatif mengakumulasi waktu pemrosesan menjadi drift, delay until berpatokan pada waktu absolut, dan overrun periode harus ditangani sebagai deadline miss.Melebihi periode - deadline misskomputasi 130msBerikutnya = T+100msdelay until T+100ms kembali segeraDeteksi keterlambatan dan perlakukan sebagai overloaddelay until - berpatokan waktu absolutkomputasi 15msBerikutnya = T+100msdelay until T+100ms → bangun pada 100mskomputasi 10msBerikutnya = T+200ms → bangun pada 200msInterval aktual: 100ms, 100ms...delay Period - drift kumulatifdelay 100ms → bangun pada 115msT=0ms: komputasi 15mskomputasi 10ms → 125msdelay 100ms → bangun pada 225msInterval aktual: 115ms, 110ms...Galat terakumulasi seiring waktuMencegah drift kumulatif

6. Profil Ravenscar — subset real-time yang dapat diverifikasi

Fitur task Ada kuat, tetapi pada sistem yang keselamatannya sangat kritis, “terlalu kuat” justru menjadi masalah. Pembuatan task dinamis, pernyataan select, pernyataan abort, dan sejenisnya mempersulit analisis statis waktu eksekusi terburuk.

Profil Ravenscar adalah jawaban Ada untuk masalah itu. Fitur task dibatasi menjadi subset deterministik yang dapat dianalisis secara statis.

-- 04_ravenscar_profile.ada
-- Bentuk dasar profil Ravenscar
-- Saat kompilasi, tentukan pragma Profile (Ravenscar); di gnat.adc
-- Build: gnatchop -w 04_ravenscar_profile.ada .
--         → terpecah menjadi ravenscar_state.ads / ravenscar_state.adb / ravenscar_demo.adb
--         siapkan gnat.adc lalu gnatmake ravenscar_demo.adb

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;

Pembatasan profil Ravenscar:

Fitur yang dilarang Alasan
Pembuatan task dinamis (new atau access type) Alokasi memori saat runtime tidak deterministik
Pernyataan select Bukan hanya banyak alternatif; seluruh pernyataan select mempersulit analisis alur kendali
Pernyataan abort Penghentian asinkron menimbulkan ketakterdugaan keadaan
Ada.Task_Attributes Perilaku dinamis saat runtime
Perubahan prioritas dinamis Prasyarat analisis penjadwalan berubah saat runtime
Delay relatif (delay) Mudah menimbulkan drift kumulatif, sehingga dipakai delay until pada waktu absolut
Lebih dari satu entry per protected object Kondisi blocking dan objek analisis bertambah
Terminasi task Ravenscar memperlakukan semua task sebagai non-terminating
Pernyataan requeue Pelacakan alur kendali menjadi rumit

Dengan pembatasan ini, program yang mematuhi Ravenscar menjadi lebih mudah untuk dianalisis timing-nya secara statis. Ini adalah sifat yang dituntut standar keselamatan seperti DO-178C (perangkat lunak pesawat) dan ISO 26262 (keselamatan fungsional otomotif). Daftar di bawah adalah cuplikan pembatasan utama; profil yang sesungguhnya juga mencakup aturan tambahan terkait runtime dan kemudahan analisis, seperti No_Task_Hierarchy dan Detect_Blocking.

Pembatasan profil Ravenscar dan manfaat analisisProfil Ravenscar membatasi tasking Ada penuh menjadi subset deterministik dengan kebijakan FIFO_Within_Priorities dan Ceiling_Locking, sehingga analisis timing statis lebih mudah dan selaras dengan standar keselamatan.Fitur task Ada lengkapProfil RavenscarPembatasanKebijakan wajibLarangan pembuatan task dinamisLarangan pernyataan selectLarangan pernyataan abortLarangan Task_AttributesDibatasi 1 entry per protected objectLarangan pernyataan requeueLarangan delay relatifGunakan delay untilLarangan perubahan prioritas dinamisLarangan terminasi taskSemua task non-terminatingFIFO_Within_PrioritiesCeiling_LockingYang menjadi lebih mudah:Analisis timing statisDO-178CPerangkat lunak pesawatISO 26262Keselamatan fungsional otomotifIEC 62304Perangkat lunak alat kesehatan

Untuk mengaktifkan profil Ravenscar, tuliskan hal berikut di berkas gnat.adc:

pragma Profile (Ravenscar);

7. Timing event — bangun pada waktu tertentu tanpa polling

Banyak sistem real-time sering membutuhkan “bangunkan task prioritas tinggi ketika waktu yang ditentukan tiba”. Implementasi naif akan melakukan polling pada timer, tetapi Ada menyediakan mekanisme yang lebih rapi — timing event.

-- 05_timing_events.ada
-- Timing event (Ada.Real_Time.Timing_Events)
-- Mekanisme untuk membangunkan task prioritas tinggi tanpa polling
-- Build: gnatchop -w 05_timing_events.ada .
--         → terpecah menjadi signal_pkg.ads / signal_pkg.adb / timing_events_demo.adb
--         gnatmake timing_events_demo.adb

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;

Cara kerja timing event:

1. Set_Handler(Timer_1, T+100ms, S.Fire'Access)  — daftarkan handler pada waktu absolut
2. T+100ms berlalu — runtime memanggil S.Fire pada **ceiling priority**
3. Fire menetapkan flag Fired ke True — barrier terbuka
4. Task Reactor bangun dari Wait_For_Event

Yang penting, contoh ini menyatakan Ceiling_Locking secara eksplisit, dan karena handler Fire adalah prosedur protected object, ia dijalankan pada ceiling priority. Prosedur terproteksi yang dipakai sebagai handler timing event diletakkan pada protected object dengan ceiling priority setingkat interrupt; di sini System.Interrupt_Priority'Last. Dengan demikian, inversi prioritas tidak terjadi selama penanganan timing event.

8. Antrean real-time dengan protected object

Pola yang sering muncul di sistem real-time adalah produsen–konsumen. Sensor menghasilkan data, task kendali mengonsumsinya — pada saat itu, mutual exclusion pada buffer dan blocking perlu dilakukan secara efisien.

Dengan protected object dan entry barrier pada Ada, ini dapat diimplementasikan sebagai sinkronisasi berbasis barrier. Di dalamnya runtime yang mengelola mutual exclusion, sehingga kode aplikasi tidak perlu menulis mutex atau condition variable secara langsung.

-- 06_protected_queue.ada
-- Berbagi data real-time dengan protected object
-- Pipeline: Producer -> Bounded_Buffer -> Consumer
-- Saat kompilasi, tentukan pragma Locking_Policy (Ceiling_Locking); di 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;

Pokok rancangan:

  • entry Put when Count < Buffer_Size — jika buffer penuh, Producer secara otomatis diblokir.
  • entry Get when Count > 0 — jika buffer kosong, Consumer secara otomatis diblokir.
  • pragma Priority (System.Any_Priority'Last) — berkat ceiling locking, inversi prioritas tidak terjadi antara Producer dan Consumer.
  • Kondisi barrier didefinisikan dari keadaan internal protected object (Count), dan dievaluasi ulang secara otomatis saat lock dilepas.

Kode ini tidak menampilkan mutex, semaphore, atau condition variable di sisi aplikasi. Penantian yang diperlukan dinyatakan dengan entry barrier pada protected object.

Keadaan bounded buffer pada protected objectBuffer berpindah antara kosong, terisi sebagian, dan penuh; Get diblokir saat kosong dan Put diblokir saat penuh, sesuai barrier Count.Keadaan awalPut (menambah 1 elemen)Put / GetGet (mengambil elemen terakhir)Put (mengisi slot kosong terakhir)Get (ada slot kosong)Get diblokir (barrier Count=0)Put diblokir (barrier Count=Buffer_Size)Kosong / Count=0Terisi sebagian / Count=1..Buffer_Size-1Penuh / Count=Buffer_Size

Saat Put berhasil, barrier yang menunggu Get dievaluasi ulang; saat Get berhasil, barrier yang menunggu Put dievaluasi ulang. Evaluasi ulang itu dilakukan saat operasi terproteksi selesai, terlepas dari keadaan mana yang sedang ditunjukkan diagram.

9. Mengukur waktu eksekusi — langkah pertama pemantauan waktu eksekusi

Untuk menilai schedulability sistem real-time, waktu eksekusi (waktu CPU) setiap task perlu diketahui secara akurat. Paket Ada.Execution_Time pada Ada menyediakan waktu konsumsi CPU per task.

-- 07_execution_time.ada
-- Pengendalian waktu eksekusi (Execution_Time)
-- Mengukur konsumsi CPU per task

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;

Waktu wall-clock vs waktu CPU:

Waktu wall-clock (Wall Clock): Ada.Real_Time.Clock
  → Waktu yang benar-benar berlalu. Termasuk saat diblokir atau di-preempt.

Waktu CPU (Execution Time): Ada.Execution_Time.Clock
  → Hanya waktu ketika task itu benar-benar berjalan di CPU.
  → Saat diblokir atau di-preempt tidak dihitung.

Pembedaan ini menjadi titik awal pemantauan waktu eksekusi dan verifikasi WCET. Selama Busy_Worker menunggu dengan delay until, waktu CPU-nya tidak bertambah, dan hanya meningkat selama komputasi aktual. Selama delay until Clock + Milliseconds(500) pada task utama pun, waktu CPU seharusnya hampir nol. Namun pengukuran waktu CPU tidak menjamin WCET yang sejati. WCET yang mencakup cache, pipeline, dan contention memori masih memerlukan analisis statis terpisah atau verifikasi di lingkungan target.

Perbedaan waktu wall-clock dan waktu CPUWaktu wall-clock mencakup tunggu, blok, dan preemption, sedangkan waktu CPU hanya menghitung komputasi aktual dan menjadi bantuan verifikasi WCET, bukan jaminan.Waktu CPURincian: hanya komputasi aktualTotal waktu CPU: 120msWaktu wall-clockRincian: komputasi + tunggu + blok + preemptionTotal waktu berlalu: 500msSelisih = waktu tunggu, blok, dan preemptionWaktu CPU mengamati biaya komputasi aktualMembantu verifikasi dan pemantauan WCETMengecualikan waktu tunggu, blok, dan preemptionPerhatianPengukuran tidak menjamin WCET sejatiAnalisis statis atau verifikasi target tetap perlu

10. Demo terintegrasi — sistem real-time multi-periode

Kita mengintegrasikan semua unsur yang telah dipelajari — prioritas, Ceiling_Locking, delay until, protected object — dan membangun sistem real-time multi-periode yang khas.

-- 08_multiperiodic.ada
-- Demo terintegrasi sistem real-time multi-periode
-- Task pembaca sensor berperiode cepat (100ms)
-- Task pengendali berperiode lambat (400ms)
-- Berbagi data dengan 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;

Konfigurasi sistem:

Diagram di bawah adalah contoh jadwal yang memakai waktu release pada kode sampel (sensor cepat berperiode 100ms, pengendali lambat berperiode 400ms dengan offset 150ms) dan mengasumsikan waktu eksekusi untuk keperluan penjelasan. Kode itu sendiri tidak berisi komputasi kendali 80ms, jadi ini bukan diagram hasil pengukuran. Jika prioritas sensor cepat lebih tinggi, ketika release sensor cepat tiba saat pengendali lambat sedang berjalan, pengendali lambat ditangguhkan sementara. Demi keterbacaan, diagram hanya menggambar satu preemption representatif per siklus pengendali lambat, tetapi pada kenyataannya sensor cepat di-release pada setiap batas 100ms.

Contoh jadwal sistem real-time multi-periodeSensor cepat 100ms dapat mem-preempt pengendali lambat 400ms; setiap siklus pengendali lambat menunjukkan satu preemption representatif.Pengendali lambat siklus 3 (release=950ms)Pengendali lambat siklus 2 (release=550ms)Pengendali lambat siklus 1 (release=150ms)1000-1010msSensor cepat #10P+3 sehingga terjadi preemption950-1000msPengendali lambat #3 bagian awal1010-1040msPengendali lambat #3 bagian akhir600-610msSensor cepat #6P+3 sehingga terjadi preemption550-600msPengendali lambat #2 bagian awal610-640msPengendali lambat #2 bagian akhir150-200msPengendali lambat #1 bagian awal100-110msSensor cepat #1200-210msSensor cepat #2P+3 sehingga terjadi preemption210-240msPengendali lambat #1 bagian akhirAsumsi untuk penjelasanSensor cepat: pemrosesan 10msPengendali lambat: pemrosesan 80ms

Pola ini adalah struktur khas “pengumpulan sensor cepat + loop kendali lambat” yang sering terlihat pada sistem kendali industri dan kendali robot.

11. Situasi ketika fitur real-time Ada bersinar

Fitur real-time Ada khususnya menunjukkan nilainya di bidang-bidang berikut.

Bidang penerapan fitur real-time AdaAnnex D bernilai di kedirgantaraan, perkeretaapian, otomotif, alat kesehatan, kendali industri, dan pertahanan; otomotif digambarkan sebagai penerapan selektif.Ada Annex DFitur real-timeKedirgantaraanDO-178CPerkeretaapianKeluarga EN 50128OtomotifISO 26262Alat kesehatanIEC 62304Kendali industriKeluarga IEC 61508Pertahanan dan sistem keandalan tinggiKendali penerbanganDomain penerapan yang sudah mapanKendali satelit dan wahana antariksaSistem persinyalanKendali kereta otomatisKandidat ECU terkait keselamatanPenerapan terbatas dan selektifdi domain yang masih didominasi C / MISRA-CPacemakerPompa infusKendali robotMesin perkakas NCKomputer misiSistem operasional jangka panjang

Alasan hanya otomotif yang ditulis “penerapan terbatas dan selektif” pada diagram lebih banyak karena ukuran ekosistem yang sudah ada, bukan karena bahasa itu sendiri cocok atau tidak. Perangkat lunak otomotif telah menumpuk asumsi C dan MISRA-C, mulai dari spesifikasi API standar industri seperti AUTOSAR, kode yang dipasok pemasok, kompiler dan alat verifikasi yang sudah tersertifikasi, hingga populasi insinyurnya. Mengganti bahasa, bahkan hanya untuk satu komponen sasaran, berarti harus menyiapkan ulang perangkat di sekitarnya beserta proses pengadaan dan verifikasi. Karena itu Ada cenderung diposisikan bukan sebagai standar seluruh kendaraan, melainkan dipakai secara selektif pada sebagian komponen yang menuntut jaminan sangat tinggi, atau di organisasi yang sudah memiliki aset dan organisasi pengembangan Ada. Sebaliknya, di bidang seperti kedirgantaraan dan perkeretaapian yang ekosistemnya sudah condong ke sisi keandalan tinggi, hambatan ini pada dasarnya tidak ada.

12. Peringatan dan keterbatasan

Fitur real-time Ada kuat, tetapi bukan obat untuk segala situasi.

1. Ketergantungan platform:

  • Pemetaan aktual pragma Priority bergantung pada lingkungan eksekusi (OS + runtime GNAT). Di Linux dipetakan ke SCHED_FIFO, tetapi di Windows preemption penuh mungkin tidak dijamin.

2. Batasan Ravenscar:

  • Karena pembuatan task dinamis dilarang, semua task harus dideklarasikan secara statis pada saat startup sistem. Ini membatasi kebebasan perancangan.

3. Batas pengukuran WCET:

  • Ada.Execution_Time adalah pengukuran, bukan jaminan. WCET yang sejati, termasuk cache miss dan pipeline hazard, perlu diverifikasi secara terpisah dengan alat analisis statis.

4. Overhead:

  • Evaluasi barrier pada protected object dijalankan secara otomatis saat entry selesai atau dibatalkan, dan saat keluar dari protected object. Pada protected object yang dipanggil dengan frekuensi tinggi, overhead ini perlu diperhitungkan.

5. Hambatan toolchain:

  • Untuk memanfaatkan fitur real-time Ada secara penuh, diperlukan cross-compiler dan runtime yang sesuai. Khususnya pada target embedded, Anda akan bergantung pada runtime yang disediakan vendor.

13. Ringkasan

Artikel ini menelusuri secara bertahap fitur real-time yang disediakan Annex D pada Ada, melalui delapan contoh kode.

Fitur Nilai yang diberikan
Prioritas task Penjadwalan preemptive berbasis prioritas
Ceiling_Locking Pencegahan inversi prioritas yang tertanam di bahasa
delay until Eksekusi periodik yang mencegah drift kumulatif
Profil Ravenscar Subset task yang lebih mudah dianalisis secara statis
Timing event Bangun pada waktu tertentu tanpa polling
Antrean terproteksi Sinkronisasi berbasis barrier pada protected object
Pengukuran waktu eksekusi Pemantauan waktu CPU per task
Integrasi multi-periode Rancangan yang memungkinkan task berperiode berbeda hidup berdampingan dengan aman

Hakikat fitur real-time Ada adalah bahwa fitur itu bukan tambahan belakangan. Aturan lock yang menekan inversi prioritas, penentuan waktu untuk eksekusi periodik, pemantauan waktu eksekusi, dan sejenisnya disediakan sebagai bagian dari spesifikasi bahasa. Tentu saja tercapainya deadline itu sendiri dikonfirmasi lewat perancangan dan analisis, tetapi prasyarat untuk itu dilengkapi oleh runtime bahasa — dan itu kekuatan utamanya.

Langkah berikutnya, untuk mencoba sendiri pengembangan sistem real-time dengan Ada, instal toolchain GNAT lewat Alire dan bangun kode sampel artikel ini dengan gnatchop + gnatmake.

Dasar pemrosesan konkuren Ada (task, rendezvous, protected object) dibahas di artikel sebelumnya, «Pemrosesan konkuren yang aman di Ada».

14. Referensi

Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.

Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.

Pertanyaan yang sering diajukan

Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.

Apa itu Annex D pada Ada?
Kumpulan fitur untuk sistem real-time yang distandarkan sebagai bagian dari spesifikasi bahasa Ada. Termasuk di dalamnya penjadwalan preemptive berbasis prioritas dengan FIFO_Within_Priorities, protokol Ceiling_Locking yang mencegah inversi prioritas, eksekusi periodik pada waktu absolut dengan delay until, profil Ravenscar, timing event, dan pengukuran waktu eksekusi per task dengan Ada.Execution_Time. Ciri utamanya adalah fitur ini bukan tambahan belakangan lewat pustaka, melainkan tertanam di runtime bahasa itu sendiri.
Apa itu inversi prioritas? Bagaimana Ada mencegahnya?
Fenomena ketika task prioritas rendah yang masih memegang lock di-preempt oleh task prioritas menengah, sehingga task prioritas tinggi yang menunggu lock itu terblokir tanpa batas. Peristiwa ini benar-benar terjadi pada Mars Pathfinder tahun 1997 dan menjadi penyebab wahana berulang kali di-reset. Ada menyediakan protokol Ceiling_Locking sebagai fitur bahasa: task yang masuk ke protected object secara otomatis dinaikkan ke ceiling priority, sehingga preemption oleh task prioritas menengah dapat dicegah.
Apa itu profil Ravenscar?
Profil yang membatasi fitur task Ada menjadi subset deterministik yang dapat dianalisis secara statis, untuk sistem yang keselamatannya sangat kritis. Pembuatan task dinamis, pernyataan select, pernyataan abort, delay relatif, pernyataan requeue, dan sejenisnya dilarang. Pembatasan ini memudahkan analisis timing statis, dan memudahkan memenuhi sifat yang dituntut standar keselamatan seperti DO-178C (perangkat lunak pesawat) dan ISO 26262 (keselamatan fungsional otomotif). Pada GNAT, profil diaktifkan dengan menulis pragma Profile (Ravenscar) di berkas gnat.adc.
Mengapa task periodik memakai delay until, bukan delay?
Karena delay dengan waktu relatif menambahkan waktu pemrosesan setiap iterasi, sehingga periode bergeser secara bertahap (drift kumulatif). delay until menentukan waktu rilis berikutnya berdasarkan waktu absolut, jadi meskipun satu kali pemrosesan terlambat, waktu rilis pada siklus berikutnya tetap benar. Namun jika pemrosesan sudah melewati waktu bangun berikutnya, delay until kembali hampir seketika, sehingga diperlukan rancangan terpisah yang mendeteksi kondisi itu sebagai deadline miss dan memperlakukannya sebagai overload.

Profil penulis

Halaman perkenalan penulis artikel.

Go Komura

Direktur KomuraSoft LLC

Berspesialisasi dalam pengembangan perangkat lunak Windows, konsultasi teknis, dan investigasi bug, terutama pada proyek dengan sistem yang sudah ada dan bug yang sulit direproduksi.

Kembali ke blog