Lập trình hệ thống real-time bằng Ada — thực hành ưu tiên, chu kỳ và kiểm soát thời gian thực thi
· Cập nhật ngày: · Go Komura · Ada, RealTime, Ravenscar, CeilingLocking, Tasking, Scheduling, PriorityInversion, ProgrammingLanguage, Thời gian thực, Độ tin cậy cao
1. Giới thiệu — mối liên hệ sâu giữa Ada và real-time
Bài trước, “Xử lý đồng thời an toàn trong Ada”, đã trình bày nền tảng xử lý đồng thời an toàn bằng task và protected object của Ada. Lần này ta đi tiếp trên cùng đường đó, vào một vùng ràng buộc chặt hơn — hệ thống real-time.
Trong hệ thống real-time, “đúng” không chỉ là kết quả tính toán logic đúng, mà còn gồm kết quả đó phải có trong hạn. Một đáp án đúng trễ 1 millisecond cũng nguy hiểm gần như một đáp án sai.
Ada đáp ứng yêu cầu này bằng một bộ tính năng real-time toàn diện, được chuẩn hóa thành Annex D (Real-Time Systems) của đặc tả ngôn ngữ. Đây không phải “gắn thêm bằng thư viện”, mà là đảm bảo real-time được gắn sẵn trong chính runtime của ngôn ngữ.
Tính năng real-time của Ada (Annex D):
- Ưu tiên task và preemption (FIFO_Within_Priorities)
- Giao thức Ceiling_Locking (ngăn priority inversion)
- Thực thi theo chu kỳ neo thời điểm tuyệt đối bằng delay until
- Ravenscar profile (tập con tasking cho an toàn tới hạn)
- Timing event (thức dậy theo thời điểm, không cần polling)
- Giám sát thời gian thực thi (Ada.Execution_Time)
- Lập lịch đa chu kỳ
Bài viết này giải thích lần lượt các tính năng đó qua 8 ví dụ mã thực hành. Mỗi snippet có thể xem như ví dụ độc lập, nhưng các ví dụ 04/05 chứa nhiều đơn vị biên dịch nên hãy tách bằng gnatchop rồi mới gnatmake.
Đối tượng đọc và kiến thức nền: Bài viết giả định bạn đã biết nền tảng task, rendezvous và protected object ở bài trước. Nó dành cho lập trình viên quan tâm phần mềm điều khiển trên thiết bị nhúng và hệ thống độ tin cậy cao; không phải nhập môn cú pháp Ada.
Môi trường kiểm chứng: 8 ví dụ trong bài đã được xác nhận biên dịch được với GNAT 13.3.0 (Ubuntu 24.04, x86-64). Output các ví dụ có kết quả chạy trong bài (chương 3 và chương 5) cũng lấy từ cùng môi trường. Cách ưu tiên và preemption thực sự có hiệu lực phụ thuộc OS và runtime GNAT (chương 12), nên chi tiết output có thể đổi theo môi trường.
Các đoạn mã trong bài cũng được công bố trên GitHub như bộ mã tham chiếu, sắp theo từng chương.
ada-real-time-systems - komurasoft-blog-samples (GitHub)
Bản đồ tri thức của bài viết này
Annex D của đặc tả ngôn ngữ Ada gắn sẵn vào chính runtime của ngôn ngữ cả FIFO_Within_Priorities dựa trên ưu tiên task, giao thức Ceiling_Locking tự động gán ceiling priority cho protected object, thực thi theo chu kỳ bằng delay until để ngăn cumulative drift, Ravenscar profile dễ phân tích tĩnh, timing event không cần polling, và đo thời gian thực thi theo từng task. Ceiling_Locking ngăn priority inversion từng thực sự xảy ra trên Mars Pathfinder năm 1997 và khiến tàu thám hiểm reset lặp lại, bằng cách tự động nâng task vào protected object lên ceiling priority. Ravenscar profile cấm delay tương đối, câu select và một số cấu trúc khác để thu hẹp task Ada thành tập con dễ phân tích timing tĩnh; trên GNAT, bật bằng cách ghi pragma trong tệp gnat.adc. Tuy nhiên, ánh xạ ưu tiên thực tế phụ thuộc OS và runtime GNAT.
flowchart LR
accTitle: Bản đồ tri thức lập trình hệ thống real-time của Ada
accDescr: Sơ đồ cho thấy Annex D của Ada gắn dispatch theo ưu tiên, Ceiling_Locking, delay until, Ravenscar profile, timing event và đo thời gian thực thi lên task cùng protected object thế nào, và Ceiling_Locking ngăn priority inversion kiểu sự cố Mars Pathfinder ra sao
ada_annex_d["Annex D (chuẩn hệ thống thời gian thực của Ada)"]
ceiling_locking["Giao thức Ceiling_Locking"]
fifo_within_priorities["FIFO_Within_Priorities"]
ada_delay_until["delay until (trì hoãn theo thời điểm tuyệt đối)"]
ravenscar_profile["Hồ sơ Ravenscar"]
ada_timing_events["Timing event (Ada.Real_Time.Timing_Events)"]
ada_execution_time["Ada.Execution_Time (đo thời gian thực thi)"]
ada_task["Task của Ada (xử lý đồng thời)"]
protected_object["Đối tượng được bảo vệ (protected object)"]
gnat["GNAT"]
priority_inversion["Đảo ngược độ ưu tiên (priority inversion)"]
mars_pathfinder_priority_inversion["Sự cố đảo ngược độ ưu tiên của Mars Pathfinder"]
ada_relative_delay["delay tương đối (câu lệnh delay)"]
cumulative_drift["Drift tích lũy khi chạy chu kỳ"]
ada_annex_d -->|"sử dụng"| fifo_within_priorities
ada_annex_d -->|"sử dụng"| ceiling_locking
ada_annex_d -->|"sử dụng"| ada_delay_until
ada_annex_d -->|"sử dụng"| ravenscar_profile
ada_annex_d -->|"sử dụng"| ada_timing_events
ada_annex_d -->|"sử dụng"| ada_execution_time
ada_annex_d -->|"yêu cầu"| ada_task
ada_annex_d -.->|"yêu cầu"| protected_object
ada_annex_d -.->|"yêu cầu"| gnat
ceiling_locking -->|"ngăn chặn"| priority_inversion
priority_inversion -->|"có thể gây"| mars_pathfinder_priority_inversion
ravenscar_profile -->|"yêu cầu"| ada_task
ravenscar_profile -->|"yêu cầu"| fifo_within_priorities
ravenscar_profile -->|"yêu cầu"| ceiling_locking
ravenscar_profile -.->|"cấu hình bằng"| gnat
ada_relative_delay -->|"có thể gây"| cumulative_drift
ada_delay_until -->|"ngăn chặn"| cumulative_drift
ada_relative_delay -->|"không khuyến nghị"| ravenscar_profile
ada_timing_events -->|"sử dụng"| ceiling_locking
ada_timing_events -->|"sử dụng"| protected_object
ada_execution_time -->|"yêu cầu"| ada_task
Trong sơ đồ, đường liền nét biểu thị quan hệ luôn đúng và đường nét đứt biểu thị quan hệ có điều kiện (điều kiện nằm trong phần giải thích từng quan hệ trên trang chi tiết). Danh sách đầy đủ các quan hệ (tổng 21, kèm bằng chứng và mức chắc chắn) cùng định nghĩa các khái niệm chính được tập hợp tại trang chi tiết bản đồ tri thức (bằng tiếng Nhật). Dữ liệu: JSON-LD / Turtle
2. Hệ thống real-time là gì
Trước hết, hãy thống nhất thuật ngữ.
| Khái niệm | Mô tả |
|---|---|
| Hard real-time | Vượt deadline nghĩa là thất bại chí mạng của hệ thống (điều khiển bay, túi khí, máy tạo nhịp) |
| Soft real-time | Vượt deadline không mong muốn, nhưng vượt thỉnh thoảng vẫn chấp nhận được (streaming video, game) |
| Deadline | Thời điểm tuyệt đối mà task phải hoàn thành |
| Period (chu kỳ) | Khoảng thời gian task được kích hoạt lặp lại |
| WCET (Worst-Case Execution Time) | Thời gian thực thi xấu nhất của task |
| Jitter | Độ biến thiên của thực thi theo chu kỳ |
| Schedulability | Tính chất “tập task đó có chạy được mà mọi hạn đều được giữ”. Việc kiểm trên giấy là phân tích schedulability (phân tích thời gian đáp ứng, v.v.) |
| Ravenscar profile (Ravenscar) | Quy ước thu hẹp tasking Ada thành tập con dễ phân tích tĩnh. Tên lấy từ làng Ravenscar ở Anh, nơi hội nghị soạn thảo được tổ chức (chương 6) |
Khi thiết kế hệ thống real-time, điều kiện cần quan trọng với mỗi task là “WCET <= deadline”. Nhưng chỉ thế chưa bảo đảm cả hệ thống đạt hạn. Phân tích thời gian đáp ứng — gồm thời gian bị block, gán ưu tiên, jitter, ngắt, và hành vi của runtime cùng OS — vẫn cần thêm. Thực tế thường nhắm WCET < deadline để còn dư địa. Tính năng real-time của Ada cung cấp ở cấp ngôn ngữ một mô hình thực thi dự đoán được, giúp làm phân tích đó dễ hơn.
flowchart LR
accTitle: Phân loại hệ thống real-time và điều kiện schedulability
accDescr: Hard real-time, soft real-time cùng deadline, period, WCET, jitter và các cơ chế Annex D được đưa vào phân tích schedulability.
HRT[Hard real-time] -->|Không đạt hạn = thất bại chí mạng| Examples[Điều khiển bay<br/>Túi khí<br/>Máy tạo nhịp]
SRT[Soft real-time] -->|Vượt thỉnh thoảng chấp nhận được| Examples2[Phát video<br/>Game<br/>UI]
Ada[Ada Annex D<br/>Cơ chế bảo đảm tính dự đoán được] --> Mechanism[FIFO_Within_Priorities<br/>Ceiling_Locking<br/>delay until]
subgraph Requirements[Yêu cầu real-time]
D[Deadline<br/>Thời điểm tuyệt đối phải xong]
P[Period<br/>Khoảng lặp]
W[WCET<br/>Thời gian thực thi xấu nhất]
J[Jitter<br/>Độ biến thiên chu kỳ]
end
D --> Analysis[Phân tích schedulability]
P --> Analysis
W --> Analysis
J --> Analysis
HRT --> Analysis
SRT --> Analysis
Mechanism --> Analysis
Analysis --> Constraint[Điều kiện cần: WCET <= deadline<br/>Đủ hay không xác nhận bằng phân tích thời gian đáp ứng]
Một trong những hiện tượng nguy hiểm nhất trong hệ thống real-time là priority inversion. Vấn đề này thực sự xảy ra trên Mars Pathfinder năm 1997 và khiến tàu thám hiểm reset lặp lại.
sequenceDiagram
accTitle: Priority inversion
accDescr: Task ưu tiên thấp giữ lock bị preempt bởi task ưu tiên trung bình, khiến task ưu tiên cao bị block vô hạn.
participant S as Scheduler
participant L as Task ưu tiên thấp
participant H as Task ưu tiên cao
participant M as Task ưu tiên trung bình
participant R as Tài nguyên dùng chung
L->>R: Chiếm lock
activate L
Note over L: Đang chạy trong critical section
Note over S,L: H thức dậy nên scheduler tạm dừng L
deactivate L
activate H
H->>R: Thử chiếm lock
Note over H: Block vì chờ lock! (L đang giữ)
deactivate H
Note over S,L: H đang chờ lock nên L được chạy tiếp
activate L
Note over L: Tiếp tục hướng tới nhả lock...
Note over S,L: M thức dậy nên scheduler tạm dừng L
deactivate L
activate M
Note over L: L không nhả được lock
Note over M: M tiếp tục chạy (cả H lẫn L đều không tiến được)
Note over H: 【Priority inversion】Ưu tiên cao bị block vô hạn
deactivate M
Task ưu tiên thấp đang giữ lock bị preempt bởi task ưu tiên trung bình, và task ưu tiên cao bị block vô hạn. Biện pháp thực tế trên Mars Pathfinder là bật priority inheritance của VxWorks; Ada thì xử lý cùng lớp vấn đề bằng một cách khác, Ceiling_Locking, cung cấp như tính năng ngôn ngữ.
3. Cơ bản về ưu tiên task — FIFO_Within_Priorities
FIFO_Within_Priorities là chính sách dispatch theo ưu tiên chuẩn mà Ada Annex D cho phép chỉ định. Nếu không nêu chính sách, hành vi mặc định do implementation định nghĩa; trên GNAT, nhiều target dùng họ chính sách này. Trong cùng một mức ưu tiên, task chạy FIFO (vào trước ra trước), và task ưu tiên cao hơn preempt (cướp CPU của) task ưu tiên thấp hơn.
-- 01_task_priority.ada
-- Dạng cơ bản của ưu tiên task và FIFO_Within_Priorities
-- Configuration pragma phải đứng trước các 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;
Điểm cần nắm:
pragma Prioritygán ưu tiên tĩnh cho từng task.Priority'Lastlà cao nhất,Priority'Firstlà thấp nhất.- Thân task trong demo này không phải tính toán nặng, mà chờ tới thời điểm chỉ định bằng
delay until. Điều cần xác nhận là, khi cả hai cùng runnable, task ưu tiên cao được cơ hội chạy trước. - Hình này cho phía preemption giữa các mức ưu tiên khác nhau của
FIFO_Within_Priorities. Để xác nhận thứ tự FIFO trong cùng một mức, cần ví dụ khác với nhiều task cùng ưu tiên. - Trong hệ thống thực tế, cách phổ biến là thiết kế ưu tiên tương đối lấy
System.Default_Prioritylàm mốc.
sequenceDiagram
accTitle: FIFO_Within_Priorities giữa hai mức ưu tiên
accDescr: Scheduler chọn task ưu tiên cao trước; sau delay until, thứ tự hoàn thành theo thời điểm thức dậy.
participant S as Scheduler
participant Main as Task main
participant HP as Task ưu tiên cao<br/>(Priority=Last)
participant LP as Task ưu tiên thấp<br/>(Priority=First)
Main->>HP: Tạo task
Main->>LP: Tạo task
Note over HP,LP: T=0ms: Cả hai task đều runnable
S->>HP: Chọn HP, ưu tiên cao nhất
activate HP
Note over HP: In log bắt đầu
HP->>S: Block bằng delay until T+100ms
deactivate HP
S->>LP: Chạy LP tiếp
activate LP
Note over LP: In log bắt đầu
LP->>S: Block bằng delay until T+500ms
deactivate LP
Note over S: T=100ms: HP thức dậy
S->>HP: Chạy HP
activate HP
Note over HP: In log hoàn thành
deactivate HP
Note over S: T=500ms: LP thức dậy
S->>LP: Chạy LP
activate LP
Note over LP: In log hoàn thành
deactivate LP
Note over Main: (T=800ms) Main kết thúc
Ví dụ chạy (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
Chú ý hai dòng log bắt đầu của hai task xuất hiện trước dòng tiêu đề của main. Task khai báo ở declarative part được kích hoạt trước khi vào thân subprogram bao quanh, nên thứ tự kiểu này có thể xảy ra. Thêm nữa, thứ tự hai dòng log bắt đầu đó có thể đảo mỗi lần chạy. Như chương 12, pragma Priority phản ánh vào lập lịch thực tế thế nào tùy OS và runtime GNAT; trên Linux thông dụng, thứ tự khởi động đúng theo ưu tiên không được bảo đảm. Điều ví dụ này quan sát ổn định được là thứ tự hoàn thành 0,1 giây / 0,5 giây đã chỉ định bằng delay until.
Phạm vi ưu tiên Ada (mặc định GNAT):
Priority'First = 0 (thấp nhất)
Priority'Last = 30 (cao nhất, nhưng phụ thuộc OS)
4. Ceiling_Locking — ngôn ngữ ngăn priority inversion
Một trong những vấn đề khó chịu nhất trong hệ thống real-time là priority inversion. Task ưu tiên cao chờ lock mà task ưu tiên thấp đang giữ, rồi task ưu tiên thấp bị preempt bởi task ưu tiên trung bình, khiến task ưu tiên cao bị block vô hạn.
Ada gắn giao thức Ceiling_Locking trực tiếp vào protected object để xử lý vấn đề này.
-- 02_ceiling_locking.ada
-- Ngăn priority inversion bằng giao thức Ceiling_Locking
-- Configuration pragma phải đứng trước các 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;
Cách Ceiling_Locking hoạt động:
- Gán ceiling priority cho protected object bằng
pragma Priority (Ceiling). - Task nào vào protected object cũng tự động được nâng lên ceiling priority lúc vào.
- Nhờ đó, task ưu tiên trung bình không preempt được task đang dùng protected object.
- Khi ra khỏi protected object, ưu tiên trở về mức ban đầu.
Hình dưới không phải vết thời gian sát từng mili giây của mã mẫu ngay trên, mà là hình khái niệm cho thấy mẫu priority inversion ở hình 2 bị Ceiling_Locking kìm thế nào.
sequenceDiagram
accTitle: Ceiling_Locking ngăn priority inversion
accDescr: Task vào protected object được nâng lên ceiling priority nên task ưu tiên trung bình không preempt được.
participant S as Scheduler
participant L as Task ưu tiên thấp<br/>(ưu tiên=10)
participant M as Task ưu tiên trung bình<br/>(ưu tiên=20)
participant H as Task ưu tiên cao<br/>(ưu tiên=30)
participant PO as Protected object<br/>(ceiling priority=30)
Note over PO: Caller có active priority > ceiling priority thì Program_Error<br/>H(30) bằng ceiling(30) nên được vào
L->>PO: Vào protected operation
activate L
Note over L,PO: Ưu tiên thực thi được nâng lên 30
Note over S: M thức dậy
Note over S,L: L đang chạy ở ceiling priority 30<br/>M(20) không preempt được
Note over S: H thức dậy
Note over S,H: H(30) vượt qua kiểm tra ceiling<br/>nhưng chờ vì L đang dùng PO
L->>PO: Thực hiện operation
L->>PO: Ra khỏi protected operation
deactivate L
Note over L: Ưu tiên trở về 10
Note over S,H: Sau khi PO được nhả thì chạy H
activate H
H->>PO: Vào protected operation
Note over H,PO: H(30) = ceiling(30) nên vào được sau khi hết tranh chấp
H->>PO: Ra khỏi protected operation
deactivate H
Nguyên tắc thiết kế: Ceiling priority của protected object phải được đặt ít nhất bằng ưu tiên cao nhất trong mọi task dùng object đó. Nếu phá quy tắc này và một task có active priority cao hơn ceiling gọi protected operation, Ada phát hiện lỗi thiết kế bằng
Program_Error.
Để làm tương đương với mutex pthread của C, bạn phải chủ động đặt thuộc tính PTHREAD_PRIO_PROTECT. Trong Ada, đó là tính năng chuẩn của ngôn ngữ.
5. delay until — chạy task chu kỳ mà không drift
Mẫu cơ bản của hệ thống real-time là task chu kỳ. Với task chạy lặp lại theo khoảng cố định, ngăn sai số timing cộng dồn (drift) cực kỳ quan trọng.
delay until của Ada giải quyết vấn đề này gọn.
-- 03_periodic_task.ada
-- Task chu kỳ bằng delay until — ngăn cumulative drift
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;
Vì sao dùng delay until:
| Cách | Vấn đề |
|---|---|
delay Period; |
Thời gian xử lý mỗi vòng được cộng thêm, chu kỳ lệch dần (cumulative drift) |
delay until Next_Release; Next_Release := Next_Release + Period; |
Neo theo thời điểm tuyệt đối, nên dù một lần xử lý bị trễ thì thời điểm phát lần sau vẫn đúng |
Tuy nhiên, delay until không tự bảo đảm thời gian xử lý nằm trong chu kỳ. Nếu xử lý đã vượt thời điểm thức dậy kế tiếp, delay until đó trả về gần như ngay, và hệ thống nên coi đó là trạng thái deadline miss.
Với delay:
T=0ms → xử lý(15ms) → delay 100ms → T=115ms → xử lý(10ms) → ...
Khoảng thực tế: 115ms, 110ms, ... (thời gian xử lý cộng dồn)
Với delay until:
Next_Release: 100ms, 200ms, 300ms, ... (thời điểm tuyệt đối)
T=0ms → xử lý(15ms) → delay until 100ms → T=100ms → xử lý(10ms) → delay until 200ms
Khoảng thực tế: 100ms, 100ms, ... (không phụ thuộc thời gian xử lý)
Ví dụ chạy (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
Mỗi chu kỳ có trễ thức dậy khoảng 0,2–0,3 millisecond, nhưng trễ đó không được mang sang chu kỳ sau. Ở chu kỳ thứ 5, lệch so với mốc gốc vẫn dưới 1 millisecond. Nếu viết bằng delay Period;, trễ này cộng mỗi vòng và sau 5 chu kỳ sẽ thành chênh thấy được. Các số này chỉ là một ví dụ trên Linux thông dụng, không phải giá trị được bảo đảm trong môi trường hard real-time.
Mẫu delay until này được dùng cho mọi task chu kỳ từ đây trở đi.
flowchart TB
accTitle: delay Period so với delay until
accDescr: delay tương đối tích lũy drift; delay until neo theo thời điểm tuyệt đối; nếu vượt chu kỳ thì delay until trả về ngay.
subgraph Bad["delay Period - cumulative drift"]
B1[T=0ms: tính 15ms] --> B2[delay 100ms → thức dậy lúc 115ms]
B2 --> B3[tính 10ms → 125ms]
B3 --> B4[delay 100ms → thức dậy lúc 225ms]
B4 --> B5[Khoảng thực tế: 115ms, 110ms...]
end
subgraph Good["delay until - neo thời điểm tuyệt đối"]
G1[Lần sau = T+100ms] --> G2[tính 15ms]
G2 --> G3[delay until T+100ms → thức dậy lúc 100ms]
G3 --> G4[tính 10ms]
G4 --> G5[Lần sau = T+200ms → thức dậy lúc 200ms]
G5 --> G6[Khoảng thực tế: 100ms, 100ms...]
end
subgraph Overrun["Vượt chu kỳ - deadline miss"]
O1[Lần sau = T+100ms] --> O2[tính 130ms]
O2 --> O3[delay until T+100ms trả về ngay]
O3 --> O4[Phát hiện trễ và xử lý như quá tải]
end
Bad --> Drift[Sai số cộng dồn theo thời gian]
Good --> Stable[Ngăn cumulative drift]
Good --> Overrun
6. Ravenscar profile — tập con real-time có thể kiểm chứng
Tasking của Ada mạnh, nhưng trong hệ thống an toàn cực kỳ quan trọng, “mạnh quá” lại thành vấn đề. Tạo task động, câu select, câu abort làm phân tích tĩnh thời gian thực thi xấu nhất trở nên khó.
Ravenscar profile là câu trả lời của Ada: nó thu hẹp tasking thành tập con tất định, có thể phân tích tĩnh.
-- 04_ravenscar_profile.ada
-- Dạng cơ bản của Ravenscar profile
-- Lúc biên dịch, chỉ định pragma Profile (Ravenscar); trong gnat.adc
-- Build: gnatchop -w 04_ravenscar_profile.ada .
-- → tách thành ravenscar_state.ads / ravenscar_state.adb / ravenscar_demo.adb
-- chuẩn bị gnat.adc rồi 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;
Các hạn chế của Ravenscar profile:
| Tính năng bị cấm | Lý do |
|---|---|
Tạo task động (new hoặc access type) |
Cấp phát bộ nhớ lúc chạy không tất định |
Câu select |
Không chỉ nhiều nhánh thay thế; cả câu select làm phân tích luồng điều khiển khó hơn |
Câu abort |
Ngắt bất đồng bộ làm trạng thái khó dự đoán |
Ada.Task_Attributes |
Hành vi động lúc chạy |
| Đổi ưu tiên động | Tiền đề phân tích lập lịch thay đổi lúc chạy |
Delay tương đối (delay) |
Dễ sinh cumulative drift, nên dùng delay until theo thời điểm tuyệt đối |
| Nhiều entry trên một protected object | Tăng điều kiện block và số trường hợp phải phân tích |
| Kết thúc task | Ravenscar coi mọi task là không kết thúc |
Câu requeue |
Theo dõi luồng điều khiển phức tạp hơn |
Nhờ các hạn chế này, chương trình tuân Ravenscar dễ đưa vào dạng có thể phân tích timing tĩnh. Đó là đặc tính mà các tiêu chuẩn an toàn như DO-178C (phần mềm hàng không) hay ISO 26262 (an toàn chức năng ô tô) yêu cầu. Danh sách dưới là trích các hạn chế chính; profile thực tế còn gồm thêm quy tắc liên quan runtime và khả năng phân tích, chẳng hạn No_Task_Hierarchy và Detect_Blocking.
flowchart TB
accTitle: Các hạn chế của Ravenscar profile
accDescr: Ravenscar thu hẹp tasking Ada thành tập con phân tích được, bắt buộc FIFO_Within_Priorities và Ceiling_Locking, phục vụ các tiêu chuẩn an toàn.
Full[Toàn bộ tasking Ada] --> Profile[Ravenscar profile]
Profile --> Restrict[Hạn chế]
Profile --> Policy[Chính sách bắt buộc]
Restrict --> R1[Cấm tạo task động]
Restrict --> R2[Cấm câu select]
Restrict --> R3[Cấm câu abort]
Restrict --> R4[Cấm Task_Attributes]
Restrict --> R5[Giới hạn 1 entry mỗi protected object]
Restrict --> R6[Cấm câu requeue]
Restrict --> R7[Cấm delay tương đối<br/>dùng delay until]
Restrict --> R8[Cấm đổi ưu tiên động]
Restrict --> R9[Cấm kết thúc task<br/>mọi task không kết thúc]
Policy --> P1[FIFO_Within_Priorities]
Policy --> P2[Ceiling_Locking]
Restrict --> Benefit[Dễ làm hơn:<br/>phân tích timing tĩnh]
Policy --> Benefit
Benefit --> DO178[DO-178C<br/>Phần mềm hàng không]
Benefit --> ISO26262[ISO 26262<br/>An toàn chức năng ô tô]
Benefit --> IEC62304[IEC 62304<br/>Phần mềm thiết bị y tế]
Để bật Ravenscar profile, ghi nội dung sau vào tệp gnat.adc:
pragma Profile (Ravenscar);
7. Timing event — thức dậy theo thời điểm, không polling
Nhiều hệ thống real-time thường gặp yêu cầu “tới thời điểm chỉ định thì đánh thức task ưu tiên cao”. Cách làm mộc là polling timer, nhưng Ada cung cấp cơ chế tinh hơn — timing event.
-- 05_timing_events.ada
-- Timing event (Ada.Real_Time.Timing_Events)
-- Cơ chế đánh thức task ưu tiên cao mà không polling
-- Build: gnatchop -w 05_timing_events.ada .
-- → tách thành 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;
Cách timing event hoạt động:
1. Set_Handler(Timer_1, T+100ms, S.Fire'Access) — đăng ký handler tại thời điểm tuyệt đối
2. T+100ms trôi qua — runtime gọi S.Fire ở **ceiling priority**
3. Fire đặt cờ Fired thành True — barrier mở
4. Task Reactor thức dậy từ Wait_For_Event
Điểm quan trọng là ví dụ này nêu rõ Ceiling_Locking, và vì handler Fire là procedure của protected object nên nó chạy ở ceiling priority. Protected procedure dùng làm handler timing event được đặt trong protected object có ceiling priority cấp interrupt; ở đây là System.Interrupt_Priority'Last. Nhờ đó, priority inversion không xảy ra trong lúc xử lý timing event.
8. Hàng đợi real-time bằng protected object
Một mẫu thường gặp trong hệ thống real-time là producer–consumer. Cảm biến sinh dữ liệu, task điều khiển tiêu thụ — lúc đó cần loại trừ tương hỗ trên bộ đệm và block hiệu quả.
Dùng protected object và entry barrier của Ada, có thể triển khai như đồng bộ dựa trên barrier. Runtime quản lý loại trừ tương hỗ bên trong, nên mã ứng dụng không cần tự viết mutex hay condition variable.
-- 06_protected_queue.ada
-- Chia sẻ dữ liệu real-time bằng protected object
-- Pipeline: Producer -> Bounded_Buffer -> Consumer
-- Lúc biên dịch, chỉ định pragma Locking_Policy (Ceiling_Locking); trong 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;
Điểm thiết kế:
entry Put when Count < Buffer_Size— nếu bộ đệm đầy, Producer tự động block.entry Get when Count > 0— nếu bộ đệm rỗng, Consumer tự động block.pragma Priority (System.Any_Priority'Last)— nhờ ceiling locking, priority inversion không xảy ra giữa Producer và Consumer.- Điều kiện barrier được định nghĩa bằng trạng thái nội bộ của protected object (
Count) và được đánh giá lại tự động khi nhả lock.
Mã này không có mutex, semaphore hay condition variable phía ứng dụng. Chờ đợi cần thiết được diễn đạt bằng entry barrier của protected object.
stateDiagram-v2
accTitle: Trạng thái hàng đợi bounded buffer
accDescr: Protected object chuyển Empty, Partial, Full; Get block khi rỗng và Put block khi đầy.
Empty: Rỗng / Count=0
Partial: Có một phần / Count=1..Buffer_Size-1
Full: Đầy / Count=Buffer_Size
[*] --> Empty: Trạng thái ban đầu
Empty --> Partial: Put (thêm 1 phần tử)
Partial --> Partial: Put / Get
Partial --> Empty: Get (lấy phần tử cuối)
Partial --> Full: Put (lấp chỗ trống cuối)
Full --> Partial: Get (có chỗ trống)
Empty --> Empty: Get block (barrier Count=0)
Full --> Full: Put block (barrier Count=Buffer_Size)
Khi Put thành công, barrier của bên chờ Get được đánh giá lại; khi Get thành công, barrier của bên chờ Put được đánh giá lại. Việc đó xảy ra lúc protected operation hoàn tất, bất kể hình đang ở trạng thái nào.
9. Đo thời gian thực thi — bước đầu giám sát execution time
Để đánh giá schedulability của hệ thống real-time, cần biết chính xác thời gian thực thi (CPU time) của từng task. Package Ada.Execution_Time của Ada cung cấp thời gian CPU tiêu thụ theo từng task.
-- 07_execution_time.ada
-- Kiểm soát thời gian thực thi (Execution_Time)
-- Đo thời gian CPU tiêu thụ theo từng 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;
Wall-clock time so với CPU time:
Wall-clock time: Ada.Real_Time.Clock
→ Thời gian trôi thực tế. Gồm cả lúc block và lúc bị preempt.
CPU time (execution time): Ada.Execution_Time.Clock
→ Chỉ thời gian task thực sự chạy trên CPU.
→ Lúc block hay bị preempt không được đếm.
Sự phân biệt này là điểm xuất phát cho giám sát thời gian thực thi lúc chạy và cho kiểm chứng WCET. Trong lúc Busy_Worker chờ bằng delay until, CPU time không tăng; nó chỉ tăng khi đang tính toán thật. Trong delay until Clock + Milliseconds(500) của task main, CPU time cũng gần như phải bằng không. Tuy nhiên, đo CPU time không bảo đảm WCET thật. WCET gồm cache, pipeline, tranh chấp bộ nhớ, v.v. vẫn cần phân tích tĩnh riêng hoặc kiểm trên môi trường target.
flowchart LR
accTitle: Wall-clock time so với CPU time
accDescr: Wall clock gồm chờ và preempt; CPU time chỉ đếm lúc task thực sự chạy, hữu ích khi kiểm WCET nhưng không tự bảo đảm WCET.
subgraph Wall[Wall-clock time]
W1[Tổng thời gian trôi: 500ms] --> W2[Gồm: tính + chờ + block + preempt]
end
subgraph CPU[CPU time]
C1[Tổng CPU time: 120ms] --> C2[Gồm: chỉ tính toán thật]
end
Wall --> Diff[Hiệu = thời gian chờ, block, preempt]
CPU --> Diff
Diff --> Insight[CPU time quan sát chi phí tính toán thật<br/>Hỗ trợ kiểm chứng và giám sát WCET<br/>Loại thời gian chờ, block, preempt]
Insight --> Caveat[Lưu ý<br/>Đo không bảo đảm WCET thật<br/>Cần phân tích tĩnh hoặc kiểm trên target]
10. Demo tổng hợp — hệ thống real-time đa chu kỳ
Ghép mọi yếu tố đã học — ưu tiên, Ceiling_Locking, delay until, protected object — để dựng một hệ thống real-time đa chu kỳ điển hình.
-- 08_multiperiodic.ada
-- Demo tích hợp hệ thống real-time đa chu kỳ
-- Task đọc cảm biến chu kỳ nhanh (100ms)
-- Task điều khiển chu kỳ chậm (400ms)
-- Chia sẻ dữ liệu bằng 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;
Cấu trúc hệ thống:
Hình dưới là ví dụ lịch, lấy thời điểm release của mã mẫu (cảm biến nhanh chu kỳ 100ms; điều khiển chậm chu kỳ 400ms, lệch 150ms) rồi giả định thời gian thực thi để minh họa. Bản thân mã không chứa phép tính điều khiển 80ms, nên đây không phải hình đo được. Nếu cảm biến nhanh có ưu tiên cao hơn, khi release của cảm biến nhanh đến trong lúc điều khiển chậm đang chạy, điều khiển chậm bị tạm dừng. Để dễ đọc, hình chỉ vẽ một lần ngắt đại diện mỗi chu kỳ điều khiển chậm; thực tế cảm biến nhanh được release ở mỗi mốc 100ms.
flowchart TB
accTitle: Lịch đa chu kỳ cảm biến nhanh và điều khiển chậm
accDescr: Cảm biến 100 ms preempt vòng điều khiển 400 ms tại các mốc minh họa.
Assumption["Giả định minh họa<br/>Cảm biến nhanh: xử lý 10ms<br/>Điều khiển chậm: xử lý 80ms"]
subgraph Cycle1["Điều khiển chậm chu kỳ 1 (release=150ms)"]
direction LR
C1F1["100-110ms<br/>Cảm biến nhanh #1"] --> C1S1["150-200ms<br/>Điều khiển chậm #1 nửa đầu"]
C1S1 --> C1F2["200-210ms<br/>Cảm biến nhanh #2<br/>preempt vì P+3"]
C1F2 --> C1S2["210-240ms<br/>Điều khiển chậm #1 nửa sau"]
end
subgraph Cycle2["Điều khiển chậm chu kỳ 2 (release=550ms)"]
direction LR
C2S1["550-600ms<br/>Điều khiển chậm #2 nửa đầu"] --> C2F6["600-610ms<br/>Cảm biến nhanh #6<br/>preempt vì P+3"]
C2F6 --> C2S2["610-640ms<br/>Điều khiển chậm #2 nửa sau"]
end
subgraph Cycle3["Điều khiển chậm chu kỳ 3 (release=950ms)"]
direction LR
C3S1["950-1000ms<br/>Điều khiển chậm #3 nửa đầu"] --> C3F10["1000-1010ms<br/>Cảm biến nhanh #10<br/>preempt vì P+3"]
C3F10 --> C3S2["1010-1040ms<br/>Điều khiển chậm #3 nửa sau"]
end
Assumption --> C1F1
C1S2 --> C2S1
C2S2 --> C3S1
Mẫu này — “thu thập cảm biến nhanh + vòng điều khiển chậm” — là cấu trúc điển hình thường thấy trong hệ thống điều khiển công nghiệp và điều khiển robot.
11. Nơi tính năng real-time của Ada phát huy
Tính năng real-time của Ada đặc biệt có giá trị ở các lĩnh vực sau.
flowchart TB
accTitle: Lĩnh vực Ada Annex D phát huy
accDescr: Hàng không vũ trụ, đường sắt, ô tô, thiết bị y tế, điều khiển công nghiệp và quốc phòng.
Ada[Ada Annex D<br/>Tính năng real-time] --> Aero[Hàng không vũ trụ<br/>DO-178C]
Ada --> Rail[Đường sắt<br/>Họ EN 50128]
Ada --> Auto[Ô tô<br/>ISO 26262]
Ada --> Medical[Thiết bị y tế<br/>IEC 62304]
Ada --> Industrial[Điều khiển công nghiệp<br/>Họ IEC 61508]
Ada --> Defense[Quốc phòng và hệ thống độ tin cậy cao]
Aero --> A1[Điều khiển bay<br/>Lĩnh vực áp dụng nhiều thành tích]
Aero --> A2[Điều khiển vệ tinh và tàu vũ trụ]
Rail --> R1[Hệ thống tín hiệu]
Rail --> R2[Điều khiển tàu tự động]
Auto --> Au1[Ứng viên cho ECU liên quan an toàn]
Auto --> Au2[Áp dụng hạn chế, có chọn lọc<br/>trong lĩnh vực C / MISRA-C vẫn chủ đạo]
Medical --> M1[Máy tạo nhịp]
Medical --> M2[Bơm truyền dịch]
Industrial --> I1[Điều khiển robot]
Industrial --> I2[Máy công cụ NC]
Defense --> D1[Máy tính nhiệm vụ]
Defense --> D2[Hệ thống vận hành dài hạn]
Trong hình, chỉ ô tô được ghi “áp dụng hạn chế, có chọn lọc” chủ yếu vì quy mô hệ sinh thái sẵn có, hơn là vì ngôn ngữ có hợp hay không. Phần mềm trên xe đã chất chồng trên giả định C và MISRA-C — từ đặc tả API ngành như AUTOSAR, mã do nhà cung cấp giao, compiler và công cụ kiểm đã qua chứng nhận, cho tới số kỹ sư. Đổi ngôn ngữ, dù chỉ một component, nghĩa là phải dựng lại cả bộ công cụ xung quanh cùng quy trình mua sắm và kiểm chứng. Vì thế Ada dễ đứng ở vị trí được chọn lọc cho một số component cần bảo đảm đặc biệt cao, hoặc cho tổ chức đã có tài sản Ada và tổ chức phát triển sẵn, hơn là thành chuẩn của cả xe. Ngược lại, ở lĩnh vực như hàng không vũ trụ hay đường sắt, nơi cả hệ sinh thái đã nghiêng về độ tin cậy cao, rào cản đó vốn không có.
12. Lưu ý và giới hạn
Tính năng real-time của Ada mạnh, nhưng không phải vạn năng.
1. Phụ thuộc nền tảng:
- Ánh xạ thực tế của
pragma Priorityphụ thuộc môi trường chạy (OS + runtime GNAT). Trên Linux nó được map sangSCHED_FIFO, nhưng trên Windows preemption đầy đủ có thể không được bảo đảm.
2. Ràng buộc của Ravenscar:
- Tạo task động bị cấm, nên mọi task phải được khai báo tĩnh lúc hệ thống khởi động. Điều đó hạn chế tự do thiết kế.
3. Giới hạn đo WCET:
Ada.Execution_Timelà đo, không phải bảo đảm. WCET thật, gồm cache miss và pipeline hazard, cần kiểm riêng bằng công cụ phân tích tĩnh.
4. Overhead:
- Đánh giá barrier của protected object được chạy tự động khi entry hoàn tất hoặc bị hủy, và khi ra khỏi protected object. Với protected object được gọi rất thường xuyên, cần tính overhead này.
5. Rào cản toolchain:
- Để dùng hết tính năng real-time của Ada, cần cross-compiler và runtime phù hợp. Đặc biệt trên target nhúng, bạn sẽ phụ thuộc runtime do vendor cung cấp.
13. Tóm tắt
Bài viết đã lần lượt xem các tính năng real-time mà Annex D của Ada cung cấp, qua 8 ví dụ mã.
| Tính năng | Giá trị mang lại |
|---|---|
| Ưu tiên task | Lập lịch preempt theo ưu tiên |
| Ceiling_Locking | Ngăn priority inversion gắn sẵn trong ngôn ngữ |
delay until |
Thực thi theo chu kỳ, ngăn cumulative drift |
| Ravenscar profile | Tập con tasking dễ đưa vào phân tích tĩnh |
| Timing event | Thức dậy theo thời điểm, không cần polling |
| Hàng đợi protected | Đồng bộ dựa trên barrier của protected object |
| Đo thời gian thực thi | Giám sát CPU time theo từng task |
| Tích hợp đa chu kỳ | Thiết kế để các task khác chu kỳ cùng tồn tại an toàn |
Bản chất tính năng real-time của Ada là chúng không phải gắn thêm sau. Quy tắc lock kìm priority inversion, chỉ định thời điểm cho thực thi theo chu kỳ, giám sát thời gian thực thi, v.v. được cung cấp như một phần của đặc tả ngôn ngữ. Việc đạt deadline vẫn phải xác nhận bằng thiết kế và phân tích, nhưng runtime ngôn ngữ xếp sẵn các tiền đề cho công việc đó — đó là thế mạnh lớn.
Bước tiếp theo, để thử phát triển hệ thống real-time bằng Ada, hãy cài toolchain GNAT qua Alire rồi build mã mẫu trong bài bằng gnatchop + gnatmake.
Nền tảng xử lý đồng thời của Ada (task, rendezvous, protected object) được trình bày trong bài trước, “Xử lý đồng thời an toàn trong Ada”.
14. Tài liệu tham khảo
Bài viết liên quan
Các bài viết gần đây có cùng thẻ để tìm hiểu sâu hơn những chủ đề lân cận.
Xử lý đồng thời an toàn trong Ada — Hướng dẫn thực hành task và protected object
Bài nhập môn về xử lý đồng thời gắn sẵn trong ngôn ngữ Ada: task và protected object. Hệ thống hóa rendezvous (entry/accept), selective a...
Lập trình generic trong Ada — Viết hợp đồng bằng kiểu, tái sử dụng với chi phí zero
Bài viết hệ thống hóa lập trình generic trong Ada, từ generic subprogram, generic package, formal subprogram, type category đến hướng dẫn...
Bên trong ảo hóa Windows (Phần 3) — Máy ảo khởi động trong vài giây: Vì sao WSL2, Windows Sandbox và container lại nhẹ
Vì sao WSL2 và Windows Sandbox khởi động trong vài giây và cảm giác nhẹ đến vậy? Bài viết này giải thích các cơ chế, từ ảnh cơ sở động và...
Bên trong ảo hóa Windows (Phần 2) — Bộ nhớ ngay cả kernel cũng không thấy: VBS, HVCI và Credential Guard
Khi cài sạch trên phần cứng tương thích, VBS được bật mặc định và dùng hypervisor cùng SLAT để tạo cô lập mạnh hơn kernel. Bài viết này g...
Bên trong ảo hóa Windows (Phần 1) — Windows của bạn thực sự chạy ở đâu? Hypervisor và phân vùng
Khi bạn bật Hyper-V, chính Windows máy chủ chạy trên hypervisor như phân vùng gốc. Bài viết này giải thích nền tảng ảo hóa qua vai trò củ...
Chủ đề liên quan
Các trang này đặt chủ đề trong bối cảnh rộng hơn của dịch vụ và quyết định.
Chủ đề kỹ thuật Windows
Cổng vào phát triển Windows, điều tra lỗi và khai thác tài sản hiện có.
Câu hỏi thường gặp
Các câu hỏi thường gặp khi tư vấn về chủ đề của bài viết.
- Annex D của Ada là gì?
- Đó là nhóm tính năng hướng hệ thống real-time được chuẩn hóa như một phần của đặc tả ngôn ngữ Ada. Nó gồm lập lịch preempt theo ưu tiên bằng FIFO_Within_Priorities, giao thức Ceiling_Locking để ngăn đảo ưu tiên (priority inversion), thực thi theo chu kỳ neo thời điểm tuyệt đối bằng delay until, Ravenscar profile, timing event, và đo thời gian thực thi theo từng task bằng Ada.Execution_Time. Điểm đặc trưng là chúng không phải thư viện gắn thêm sau, mà được gắn sẵn trong chính runtime của ngôn ngữ.
- Priority inversion là gì? Ada ngăn nó thế nào?
- Đó là hiện tượng task ưu tiên thấp giữ lock rồi bị preempt bởi task ưu tiên trung bình, khiến task ưu tiên cao đang chờ lock đó bị block vô hạn. Sự cố thực sự xảy ra trên Mars Pathfinder năm 1997 và khiến tàu thám hiểm reset lặp lại. Ada cung cấp giao thức Ceiling_Locking như tính năng ngôn ngữ: task vào protected object được tự động nâng lên ceiling priority, nên task ưu tiên trung bình không preempt được.
- Ravenscar profile là gì?
- Đó là profile thu hẹp tasking Ada thành một tập con tất định, có thể phân tích tĩnh, dành cho hệ thống an toàn cực kỳ quan trọng. Nó cấm tạo task động, câu select, câu abort, delay tương đối, câu requeue, và một số cấu trúc khác. Nhờ hạn chế đó, phân tích timing tĩnh dễ làm hơn, và chương trình dễ đáp ứng các đặc tính mà tiêu chuẩn an toàn như DO-178C (phần mềm hàng không) hay ISO 26262 (an toàn chức năng ô tô) yêu cầu. Trên GNAT, bạn bật bằng cách ghi pragma Profile (Ravenscar) trong tệp gnat.adc.
- Vì sao task chu kỳ dùng delay until chứ không dùng delay?
- Với delay chỉ định thời gian tương đối, thời gian xử lý mỗi vòng được cộng thêm nên chu kỳ lệch dần — đó là cumulative drift. delay until lấy thời điểm tuyệt đối để quyết thời điểm phát lần sau, nên dù một lần xử lý bị trễ, các lần phát tiếp theo vẫn đúng. Tuy nhiên, nếu xử lý đã vượt thời điểm thức dậy kế tiếp, delay until trả về gần như ngay; bạn cần thiết kế riêng để phát hiện đó là deadline miss và xử lý như quá tải.