Pemrograman sistem real-time dengan Ada — praktik pengendalian prioritas, periode, dan waktu eksekusi
· Diperbarui pada: · Go Komura · 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.
flowchart LR
accTitle: Peta pengetahuan pemrograman sistem real-time Ada
accDescr: Diagram 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 Pathfinder
ada_annex_d["Annex D (standar sistem real-time Ada)"]
ceiling_locking["protokol Ceiling_Locking"]
fifo_within_priorities["FIFO_Within_Priorities"]
ada_delay_until["delay until (penundaan waktu absolut)"]
ravenscar_profile["profil Ravenscar"]
ada_timing_events["timing event (Ada.Real_Time.Timing_Events)"]
ada_execution_time["Ada.Execution_Time (pengukuran waktu eksekusi)"]
ada_task["task Ada (pemrosesan concurrent)"]
protected_object["objek terlindung (protected object)"]
gnat["GNAT"]
priority_inversion["pembalikan prioritas (priority inversion)"]
mars_pathfinder_priority_inversion["insiden priority inversion pada Mars Pathfinder"]
ada_relative_delay["delay relatif (pernyataan delay)"]
cumulative_drift["drift kumulatif eksekusi periodik"]
ada_annex_d -->|"menggunakan"| fifo_within_priorities
ada_annex_d -->|"menggunakan"| ceiling_locking
ada_annex_d -->|"menggunakan"| ada_delay_until
ada_annex_d -->|"menggunakan"| ravenscar_profile
ada_annex_d -->|"menggunakan"| ada_timing_events
ada_annex_d -->|"menggunakan"| ada_execution_time
ada_annex_d -->|"mensyaratkan"| ada_task
ada_annex_d -.->|"mensyaratkan"| protected_object
ada_annex_d -.->|"mensyaratkan"| gnat
ceiling_locking -->|"mencegah"| priority_inversion
priority_inversion -->|"dapat menyebabkan"| mars_pathfinder_priority_inversion
ravenscar_profile -->|"mensyaratkan"| ada_task
ravenscar_profile -->|"mensyaratkan"| fifo_within_priorities
ravenscar_profile -->|"mensyaratkan"| ceiling_locking
ravenscar_profile -.->|"dikonfigurasi dengan"| gnat
ada_relative_delay -->|"dapat menyebabkan"| cumulative_drift
ada_delay_until -->|"mencegah"| cumulative_drift
ada_relative_delay -->|"tidak disarankan"| ravenscar_profile
ada_timing_events -->|"menggunakan"| ceiling_locking
ada_timing_events -->|"menggunakan"| protected_object
ada_execution_time -->|"mensyaratkan"| ada_task
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.
flowchart LR
accTitle: Konsep sistem real-time dan mekanisme prediktabilitas Ada
accDescr: Diagram yang menghubungkan hard real-time, soft real-time, deadline, periode, WCET, dan jitter dengan analisis schedulability, serta mekanisme Annex D yang menopang prediktabilitas.
HRT[Hard real-time] -->|Tidak tercapai dalam tenggat = kegagalan fatal| Examples[Kendali penerbangan<br/>Airbag<br/>Pacemaker]
SRT[Soft real-time] -->|Kelebihan yang jarang masih ditoleransi| Examples2[Streaming video<br/>Game<br/>UI]
Ada[Ada Annex D<br/>Mekanisme penopang prediktabilitas] --> Mechanism[FIFO_Within_Priorities<br/>Ceiling_Locking<br/>delay until]
subgraph Requirements[Persyaratan real-time]
D[Deadline<br/>Waktu absolut untuk selesai]
P[Periode<br/>Interval pengulangan]
W[WCET<br/>Waktu eksekusi terburuk]
J[Jitter<br/>Variasi periode]
end
D --> Analysis[Analisis schedulability]
P --> Analysis
W --> Analysis
J --> Analysis
HRT --> Analysis
SRT --> Analysis
Mechanism --> Analysis
Analysis --> Constraint[Syarat perlu: WCET <= deadline<br/>Kecukupan 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.
sequenceDiagram
accTitle: Terjadinya inversi prioritas
accDescr: Task prioritas rendah memegang lock, task prioritas tinggi menunggu lock itu, lalu task prioritas menengah mem-preempt task rendah sehingga task tinggi terblokir tanpa batas.
participant S as Scheduler
participant L as Task prioritas rendah
participant H as Task prioritas tinggi
participant M as Task prioritas menengah
participant R as Sumber daya bersama
L->>R: Memperoleh lock
activate L
Note over L: Sedang menjalankan critical section
Note over S,L: H bangun, scheduler menangguhkan L
deactivate L
activate H
H->>R: Mencoba memperoleh lock
Note over H: Terblokir menunggu lock! (masih dipegang L)
deactivate H
Note over S,L: H menunggu lock, jadi L dilanjutkan
activate L
Note over L: Melanjutkan menuju pelepasan lock...
Note over S,L: M bangun, scheduler menangguhkan L
deactivate L
activate M
Note over L: L tidak dapat melepaskan lock
Note over M: M terus berjalan (H dan L tidak bisa bergerak)
Note over H: [Inversi prioritas] Prioritas tinggi terblokir tanpa batas
deactivate M
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 Prioritymemberi setiap task prioritas statis.Priority'Lasttertinggi,Priority'Firstterendah.- 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.
sequenceDiagram
accTitle: Penjadwalan FIFO_Within_Priorities
accDescr: Ketika dua task runnable, scheduler memilih task prioritas tertinggi dulu, lalu task prioritas rendah, dan keduanya bangun pada waktu delay until yang ditentukan.
participant S as Scheduler
participant Main as Task utama
participant HP as Task prioritas tinggi<br/>(Priority=Last)
participant LP as Task prioritas rendah<br/>(Priority=First)
Main->>HP: Pembuatan task
Main->>LP: Pembuatan task
Note over HP,LP: T=0ms: Kedua task runnable
S->>HP: Memilih HP, prioritas tertinggi
activate HP
Note over HP: Menulis log mulai
HP->>S: Blok dengan delay until T+100ms
deactivate HP
S->>LP: Menjalankan LP berikutnya
activate LP
Note over LP: Menulis log mulai
LP->>S: Blok dengan delay until T+500ms
deactivate LP
Note over S: T=100ms: HP bangun
S->>HP: Menjalankan HP
activate HP
Note over HP: Menulis log selesai
deactivate HP
Note over S: T=500ms: LP bangun
S->>LP: Menjalankan LP
activate LP
Note over LP: Menulis log selesai
deactivate LP
Note over Main: (T=800ms) Main selesai
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:
- Tetapkan ceiling priority pada protected object dengan
pragma Priority (Ceiling). - Task mana pun yang masuk ke protected object, pada saat masuk secara otomatis dinaikkan ke ceiling priority.
- Dengan demikian, task yang sedang memakai protected object tidak dapat di-preempt oleh task prioritas menengah.
- 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.
sequenceDiagram
accTitle: Ceiling_Locking mencegah inversi prioritas
accDescr: Task 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.
participant S as Scheduler
participant L as Task prioritas rendah<br/>(prioritas=10)
participant M as Task prioritas menengah<br/>(prioritas=20)
participant H as Task prioritas tinggi<br/>(prioritas=30)
participant PO as Protected object<br/>(ceiling priority=30)
Note over PO: Pemanggil dengan prioritas aktif > ceiling memunculkan Program_Error<br/>H(30) dalam diagram sama dengan ceiling(30) sehingga boleh masuk
L->>PO: Masuk operasi terproteksi
activate L
Note over L,PO: Prioritas eksekusi naik ke 30
Note over S: M bangun
Note over S,L: L berjalan pada ceiling priority 30<br/>M(20) tidak dapat mem-preempt
Note over S: H bangun
Note over S,H: H(30) lolos pemeriksaan ceiling<br/>tetapi menunggu karena L sedang memakai PO
L->>PO: Menjalankan operasi
L->>PO: Keluar dari operasi terproteksi
deactivate L
Note over L: Prioritas kembali ke 10
Note over S,H: Setelah PO dilepas, jalankan H
activate H
H->>PO: Masuk operasi terproteksi
Note over H,PO: H(30) = ceiling(30) sehingga dapat masuk setelah contention selesai
H->>PO: Keluar dari operasi terproteksi
deactivate H
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.
flowchart TB
accTitle: Perbandingan delay dan delay until
accDescr: delay relatif mengakumulasi waktu pemrosesan menjadi drift, delay until berpatokan pada waktu absolut, dan overrun periode harus ditangani sebagai deadline miss.
subgraph Bad["delay Period - drift kumulatif"]
B1[T=0ms: komputasi 15ms] --> B2[delay 100ms → bangun pada 115ms]
B2 --> B3[komputasi 10ms → 125ms]
B3 --> B4[delay 100ms → bangun pada 225ms]
B4 --> B5[Interval aktual: 115ms, 110ms...]
end
subgraph Good["delay until - berpatokan waktu absolut"]
G1[Berikutnya = T+100ms] --> G2[komputasi 15ms]
G2 --> G3[delay until T+100ms → bangun pada 100ms]
G3 --> G4[komputasi 10ms]
G4 --> G5[Berikutnya = T+200ms → bangun pada 200ms]
G5 --> G6[Interval aktual: 100ms, 100ms...]
end
subgraph Overrun["Melebihi periode - deadline miss"]
O1[Berikutnya = T+100ms] --> O2[komputasi 130ms]
O2 --> O3[delay until T+100ms kembali segera]
O3 --> O4[Deteksi keterlambatan dan perlakukan sebagai overload]
end
Bad --> Drift[Galat terakumulasi seiring waktu]
Good --> Stable[Mencegah drift kumulatif]
Good --> Overrun
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.
flowchart TB
accTitle: Pembatasan profil Ravenscar dan manfaat analisis
accDescr: Profil 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.
Full[Fitur task Ada lengkap] --> Profile[Profil Ravenscar]
Profile --> Restrict[Pembatasan]
Profile --> Policy[Kebijakan wajib]
Restrict --> R1[Larangan pembuatan task dinamis]
Restrict --> R2[Larangan pernyataan select]
Restrict --> R3[Larangan pernyataan abort]
Restrict --> R4[Larangan Task_Attributes]
Restrict --> R5[Dibatasi 1 entry per protected object]
Restrict --> R6[Larangan pernyataan requeue]
Restrict --> R7[Larangan delay relatif<br/>Gunakan delay until]
Restrict --> R8[Larangan perubahan prioritas dinamis]
Restrict --> R9[Larangan terminasi task<br/>Semua task non-terminating]
Policy --> P1[FIFO_Within_Priorities]
Policy --> P2[Ceiling_Locking]
Restrict --> Benefit[Yang menjadi lebih mudah:<br/>Analisis timing statis]
Policy --> Benefit
Benefit --> DO178[DO-178C<br/>Perangkat lunak pesawat]
Benefit --> ISO26262[ISO 26262<br/>Keselamatan fungsional otomotif]
Benefit --> IEC62304[IEC 62304<br/>Perangkat 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.
stateDiagram-v2
accTitle: Keadaan bounded buffer pada protected object
accDescr: Buffer berpindah antara kosong, terisi sebagian, dan penuh; Get diblokir saat kosong dan Put diblokir saat penuh, sesuai barrier Count.
Empty: Kosong / Count=0
Partial: Terisi sebagian / Count=1..Buffer_Size-1
Full: Penuh / Count=Buffer_Size
[*] --> Empty: Keadaan awal
Empty --> Partial: Put (menambah 1 elemen)
Partial --> Partial: Put / Get
Partial --> Empty: Get (mengambil elemen terakhir)
Partial --> Full: Put (mengisi slot kosong terakhir)
Full --> Partial: Get (ada slot kosong)
Empty --> Empty: Get diblokir (barrier Count=0)
Full --> Full: Put diblokir (barrier 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.
flowchart LR
accTitle: Perbedaan waktu wall-clock dan waktu CPU
accDescr: Waktu wall-clock mencakup tunggu, blok, dan preemption, sedangkan waktu CPU hanya menghitung komputasi aktual dan menjadi bantuan verifikasi WCET, bukan jaminan.
subgraph Wall[Waktu wall-clock]
W1[Total waktu berlalu: 500ms] --> W2[Rincian: komputasi + tunggu + blok + preemption]
end
subgraph CPU[Waktu CPU]
C1[Total waktu CPU: 120ms] --> C2[Rincian: hanya komputasi aktual]
end
Wall --> Diff[Selisih = waktu tunggu, blok, dan preemption]
CPU --> Diff
Diff --> Insight[Waktu CPU mengamati biaya komputasi aktual<br/>Membantu verifikasi dan pemantauan WCET<br/>Mengecualikan waktu tunggu, blok, dan preemption]
Insight --> Caveat[Perhatian<br/>Pengukuran tidak menjamin WCET sejati<br/>Analisis 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.
flowchart TB
accTitle: Contoh jadwal sistem real-time multi-periode
accDescr: Sensor cepat 100ms dapat mem-preempt pengendali lambat 400ms; setiap siklus pengendali lambat menunjukkan satu preemption representatif.
Assumption["Asumsi untuk penjelasan<br/>Sensor cepat: pemrosesan 10ms<br/>Pengendali lambat: pemrosesan 80ms"]
subgraph Cycle1["Pengendali lambat siklus 1 (release=150ms)"]
direction LR
C1F1["100-110ms<br/>Sensor cepat #1"] --> C1S1["150-200ms<br/>Pengendali lambat #1 bagian awal"]
C1S1 --> C1F2["200-210ms<br/>Sensor cepat #2<br/>P+3 sehingga terjadi preemption"]
C1F2 --> C1S2["210-240ms<br/>Pengendali lambat #1 bagian akhir"]
end
subgraph Cycle2["Pengendali lambat siklus 2 (release=550ms)"]
direction LR
C2S1["550-600ms<br/>Pengendali lambat #2 bagian awal"] --> C2F6["600-610ms<br/>Sensor cepat #6<br/>P+3 sehingga terjadi preemption"]
C2F6 --> C2S2["610-640ms<br/>Pengendali lambat #2 bagian akhir"]
end
subgraph Cycle3["Pengendali lambat siklus 3 (release=950ms)"]
direction LR
C3S1["950-1000ms<br/>Pengendali lambat #3 bagian awal"] --> C3F10["1000-1010ms<br/>Sensor cepat #10<br/>P+3 sehingga terjadi preemption"]
C3F10 --> C3S2["1010-1040ms<br/>Pengendali lambat #3 bagian akhir"]
end
Assumption --> C1F1
C1S2 --> C2S1
C2S2 --> C3S1
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.
flowchart TB
accTitle: Bidang penerapan fitur real-time Ada
accDescr: Annex D bernilai di kedirgantaraan, perkeretaapian, otomotif, alat kesehatan, kendali industri, dan pertahanan; otomotif digambarkan sebagai penerapan selektif.
Ada[Ada Annex D<br/>Fitur real-time] --> Aero[Kedirgantaraan<br/>DO-178C]
Ada --> Rail[Perkeretaapian<br/>Keluarga EN 50128]
Ada --> Auto[Otomotif<br/>ISO 26262]
Ada --> Medical[Alat kesehatan<br/>IEC 62304]
Ada --> Industrial[Kendali industri<br/>Keluarga IEC 61508]
Ada --> Defense[Pertahanan dan sistem keandalan tinggi]
Aero --> A1[Kendali penerbangan<br/>Domain penerapan yang sudah mapan]
Aero --> A2[Kendali satelit dan wahana antariksa]
Rail --> R1[Sistem persinyalan]
Rail --> R2[Kendali kereta otomatis]
Auto --> Au1[Kandidat ECU terkait keselamatan]
Auto --> Au2[Penerapan terbatas dan selektif<br/>di domain yang masih didominasi C / MISRA-C]
Medical --> M1[Pacemaker]
Medical --> M2[Pompa infus]
Industrial --> I1[Kendali robot]
Industrial --> I2[Mesin perkakas NC]
Defense --> D1[Komputer misi]
Defense --> D2[Sistem 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 Prioritybergantung pada lingkungan eksekusi (OS + runtime GNAT). Di Linux dipetakan keSCHED_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_Timeadalah 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
- Ada Reference Manual - Annex D: Real-Time Systems
- Ravenscar Profile Definition (ISO/IEC TR 24718:2005)
- GNAT Real-Time Topics (AdaCore)
- The Ravenscar Profile for High-Integrity Systems (AdaCore)
- Rate Monotonic Analysis (Liu & Layland, 1973)
- Alire - Ada Package Manager
- Kumpulan kode contoh Ada (GitHub)
Artikel terkait
Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.
Pemrosesan konkuren yang aman di Ada ── panduan praktis task dan protected object
Artikel pengantar tentang pemrosesan konkuren yang tertanam di bahasa Ada: task dan protected object. Rendezvous (entry/accept), accept s...
Pemrograman generik di Ada ── menulis kontrak dengan tipe, dan mewujudkan reuse tanpa biaya runtime
Penjelasan sistematis pemrograman generik di Ada: subprogram generik, paket generik, subprogram formal, kategori tipe, hingga pedoman des...
Kedalaman virtualisasi Windows (Bagian 3) — Mesin virtual yang boot dalam hitungan detik: mengapa WSL2, Windows Sandbox, dan kontainer begitu ringan
Mengapa WSL2 dan Windows Sandbox start dalam hitungan detik dan terasa begitu ringan? Artikel ini menjelaskan mekanismenya, dari dynamic ...
Kedalaman virtualisasi Windows (Bagian 2) — Memori yang bahkan kernel pun tidak bisa lihat: cara kerja VBS, HVCI, dan Credential Guard
Pada instalasi bersih ke perangkat keras yang kompatibel, VBS diaktifkan secara bawaan dan memakai hypervisor serta SLAT untuk membuat is...
Kedalaman virtualisasi Windows (Bagian 1) — Di mana Windows Anda sebenarnya berjalan? Hypervisor dan partisi
Ketika Anda mengaktifkan Hyper-V, Windows host sendiri berjalan di atas hypervisor sebagai root partition. Artikel ini menjelaskan fondas...
Topik terkait
Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.
Topik teknis Windows
Portal tentang pengembangan Windows, investigasi bug, dan pemanfaatan aset yang ada.
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.