Ada দিয়ে রিয়েল-টাইম সিস্টেম প্রোগ্রামিং ── priority, period ও execution time নিয়ন্ত্রণ বাস্তবে

· হালনাগাদের তারিখ: · · Ada, RealTime, Ravenscar, CeilingLocking, Tasking, Scheduling, PriorityInversion, ProgrammingLanguage, রিয়েল-টাইম, উচ্চ নির্ভরযোগ্যতা

1. সূচনা ── Ada ও রিয়েল-টাইমের গভীর সম্পর্ক

আগের নিবন্ধ “Ada-তে নিরাপদ concurrency“-এ Ada-এর টাস্ক ও protected object দিয়ে নিরাপদ concurrency-এর ভিত্তি ব্যাখ্যা করেছি। এবার তারই ধারাবাহিকতায় আরও কঠোরভাবে সীমিত এলাকা—রিয়েল-টাইম সিস্টেম—নিয়ে এগোব।

রিয়েল-টাইম সিস্টেমে “সঠিকতা” মানে শুধু লজিক্যাল computation-এর ফল সঠিক হওয়া নয়, সেই ফল সময়সীমার মধ্যে পাওয়াও। এক মিলিসেকেন্ড দেরিতে আসা সঠিক উত্তর ভুল উত্তরের মতোই বিপজ্জনক।

এই চাহিদার জবাবে Ada ভাষার স্পেসিফিকেশনের Annex D (Real-Time Systems) হিসেবে মানক করা একটি পূর্ণাঙ্গ রিয়েল-টাইম ফিচারসেট দেয়। এটা “লাইব্রেরি দিয়ে পরে জুড়ে দেওয়া” নয়, ভাষার runtime-এই বসানো রিয়েল-টাইম গ্যারান্টি

Ada-এর রিয়েল-টাইম ফিচার (Annex D):
- টাস্ক priority ও preemption (FIFO_Within_Priorities)
- Ceiling_Locking protocol (priority inversion প্রতিরোধ)
- delay until দিয়ে absolute time-এর periodic execution
- Ravenscar profile (safety-critical subset)
- timing event (polling ছাড়া সময়মতো জাগানো)
- execution time monitoring (Ada.Execution_Time)
- multi-period scheduling

এই নিবন্ধে এগুলো আটটি ব্যবহারিক কোড উদাহরণ দিয়ে ধাপে ধাপে ব্যাখ্যা করব। প্রতিটি স্নিপেট আলাদা উদাহরণ হিসেবে ধরা যায়, তবে একাধিক compilation unit থাকা 04/05 উদাহরণ gnatchop দিয়ে ভাগ করে তারপর gnatmake করতে হয়।

উদ্দিষ্ট পাঠক ও পূর্বজ্ঞান: আগের নিবন্ধে আলোচনা করা টাস্ক, rendezvous ও protected object-এর ভিত্তি যারা জানেন, তাদের ধরে নেওয়া হয়েছে। Embedded ডিভাইস বা উচ্চ-নির্ভরযোগ্য সিস্টেমের কন্ট্রোল সফটওয়্যারে আগ্রহী ডেভেলপারদের জন্য; Ada ব্যাকরণের পরিচিতি এখানে নেই।

যাচাইয়ের পরিবেশ: এই নিবন্ধের আটটি উদাহরণ GNAT 13.3.0 (Ubuntu 24.04, x86-64)-এ বিল্ড হয় কি না দেখা হয়েছে। যেসব উদাহরণে রান আউটপুট আছে (অধ্যায় 3 ও 5), সেগুলোও একই পরিবেশে নেওয়া। Priority ও preemption বাস্তবে কীভাবে কাজ করে তা OS ও GNAT runtime-এর উপর নির্ভর করে (অধ্যায় 12), তাই আউটপুটের খুঁটিনাটি পরিবেশভেদে বদলাতে পারে।

এই নিবন্ধের কোড খণ্ড অধ্যায় অনুযায়ী ফাইলে সাজানো রেফারেন্স সংগ্রহ হিসেবে GitHub-এ প্রকাশিত।

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

এই নিবন্ধের জ্ঞান মানচিত্র

Ada-এর ভাষা স্পেসিফিকেশন Annex D task অগ্রাধিকারের উপর ভিত্তি করা FIFO_Within_Priorities, protected object-এ স্বয়ংক্রিয়ভাবে সিলিং অগ্রাধিকার বরাদ্দ করা Ceiling_Locking প্রোটোকল, cumulative drift আটকানো delay until দিয়ে পিরিয়ডিক চালনা, static analysis সহজ করা Ravenscar প্রোফাইল, পোলিং ছাড়া timing events, আর task অনুযায়ী execution time পরিমাপ পর্যন্ত ভাষার রানটাইমেই বসিয়েছে। Ceiling_Locking ১৯৯৭ সালে Mars Pathfinder-এ সত্যি ঘটে যাওয়া এবং মহাকাশযানকে বারবার রিসেট করানো priority inversion-কে, protected object-এ ঢোকা task-কে সিলিং অগ্রাধিকারে স্বয়ংক্রিয়ভাবে তুলে দিয়ে আটকায়। Ravenscar প্রোফাইল আপেক্ষিক delay বা select স্টেটমেন্ট ইত্যাদি নিষিদ্ধ করে Ada-এর task-কে static timing analysis সহজ এমন সাবসেটে সীমাবদ্ধ করে, আর GNAT-এ gnat.adc ফাইলে pragma লিখে সক্রিয় করা হয়। তবে অগ্রাধিকারের বাস্তব ম্যাপিং OS ও GNAT রানটাইমের উপর নির্ভর করে।

Ada রিয়েল-টাইম সিস্টেম প্রোগ্রামিংয়ের জ্ঞান মানচিত্রAda Annex D যেভাবে অগ্রাধিকারভিত্তিক ডিসপ্যাচ · Ceiling_Locking · delay until · Ravenscar প্রোফাইল · timing events · execution time পরিমাপকে task ও protected object-এর উপর বসায়, এবং Ceiling_Locking Mars Pathfinder-এ যে ধরনের priority inversion ঘটেছিল তা কীভাবে আটকায় তা দেখানো চিত্রব্যবহার করেব্যবহার করেব্যবহার করেব্যবহার করেব্যবহার করেব্যবহার করেপূর্বশর্তপূর্বশর্তপূর্বশর্তপ্রতিরোধ করেকারণ হতে পারেপূর্বশর্তপূর্বশর্তপূর্বশর্তদিয়ে কনফিগারকারণ হতে পারেপ্রতিরোধ করেব্যবহার নিরুৎসাহিতব্যবহার করেব্যবহার করেপূর্বশর্তAnnex D (Ada-এর রিয়েল-টাইম সিস্টেম স্ট্যান্ডার্ড)Ceiling_Locking প্রোটোকলFIFO_Within_Prioritiesdelay until (পরম সময়ের delay)Ravenscar প্রোফাইলtiming events (Ada.Real_Time.Timing_Events)Ada.Execution_Time (execution time পরিমাপ)Ada-এর task (concurrency)প্রোটেক্টেড অবজেক্ট (protected object)GNATপ্রায়োরিটি ইনভার্সন (priority inversion)Mars Pathfinder-এর প্রায়োরিটি ইনভার্সন দুর্ঘটনাআপেক্ষিক delay (delay স্টেটমেন্ট)পিরিয়ডিক চালনার cumulative drift

চিত্রে, টানা রেখা সবসময় সত্য এমন সম্পর্ককে এবং ভাঙা রেখা শর্তসাপেক্ষ সম্পর্ককে নির্দেশ করে (শর্তগুলো বিস্তারিত পাতায় প্রতিটি সম্পর্কের ব্যাখ্যায় আছে)। সম্পর্কের সম্পূর্ণ তালিকা (মোট 21, প্রমাণ ও নিশ্চয়তার মাত্রাসহ) এবং প্রধান ধারণাগুলোর সংজ্ঞা জ্ঞান মানচিত্রের বিস্তারিত পাতায় সংগ্রহ করা আছে (জাপানি ভাষায়)। তথ্য: JSON-LD / Turtle

2. রিয়েল-টাইম সিস্টেম কী

প্রথমে পরিভাষা গুছিয়ে নেই।

ধারণা ব্যাখ্যা
Hard real-time Deadline ছাড়িয়ে যাওয়া মানে সিস্টেমের মারাত্মক ব্যর্থতা (ফ্লাইট কন্ট্রোল, এয়ারব্যাগ, পেসমেকার)
Soft real-time Deadline ছাড়ানো অবাঞ্ছিত, তবে বিরল ছাড়িয়ে যাওয়া সহনীয় (ভিডিও স্ট্রিমিং, গেম)
Deadline যে absolute time-এ টাস্ক শেষ হতেই হবে
Period টাস্ক বারবার চালু হওয়ার সময়ের ব্যবধান
WCET (Worst-Case Execution Time) টাস্কের সবচেয়ে খারাপ execution time
Jitter Periodic চালানোর তারতম্য
Schedulability “সেই টাস্ক সেট সব deadline মেনে চালানো যায় কি না” সেই বৈশিষ্ট্য। কাগজে এটা যাচাই করার কাজই schedulability analysis (response-time analysis ইত্যাদি)
Ravenscar profile Ada-এর টাস্ক ফিচারকে static analysis-সহজ subset-এ সীমিত করার নিয়ম। নাম এসেছে যে ব্রিটিশ গ্রামে খসড়া বৈঠক হয়েছিল, সেই Ravenscar থেকে (অধ্যায় 6)

রিয়েল-টাইম সিস্টেম ডিজাইনে প্রতিটি টাস্কের জন্য “WCET <= deadline” একটি গুরুত্বপূর্ণ প্রয়োজনীয় শর্ত। তবে শুধু এতে পুরো সিস্টেমের deadline পূরণ নিশ্চিত হয় না। Blocking time, priority বণ্টন, jitter, interrupt, আর runtime ও OS-এর আচরণ ধরে response-time analysis আলাদাভাবে লাগে। কাজে সাধারণত মার্জিন রাখতে WCET < deadline লক্ষ্য করা হয়। Ada-এর রিয়েল-টাইম ফিচার সেই বিশ্লেষণ সহজ করে, ভাষার স্তরেই একটি predictable execution model দেয়।

রিয়েল-টাইম প্রয়োজনীয়তা ও schedulability বিশ্লেষণHard ও soft real-time, deadline, period, WCET, jitter এবং Ada Annex D-এর mechanism কীভাবে schedulability বিশ্লেষণে যুক্ত হয় তা দেখায়।রিয়েল-টাইম প্রয়োজনীয়তাসময়সীমার মধ্যে না পৌঁছানো = মারাত্মক ব্যর্থতাবিরল ছাড়িয়ে যাওয়া সহনীয়Deadlineশেষ হওয়ার absolute timePeriodপুনরাবৃত্তির ব্যবধানWCETসবচেয়ে খারাপ execution timeJitterperiod-এর তারতম্যহার্ড রিয়েল-টাইমফ্লাইট কন্ট্রোলএয়ারব্যাগপেসমেকারসফট রিয়েল-টাইমভিডিও স্ট্রিমিংগেমUIAda Annex Dpredictability সমর্থন করা mechanismFIFO_Within_PrioritiesCeiling_Lockingdelay untilSchedulability analysisপ্রয়োজনীয় শর্ত: WCET &lt;= deadlineপর্যাপ্ততা response-time analysis-এ যাচাই

রিয়েল-টাইম সিস্টেমের সবচেয়ে বিপজ্জনক ঘটনাগুলোর একটি priority inversion। এই সমস্যা 1997 সালে Mars Pathfinder-এ সত্যি ঘটেছিল, আর প্রোব বারবার reset হচ্ছিল।

Priority inversion কীভাবে ঘটেনিম্ন priority টাস্ক lock ধরে রাখার সময় মধ্যম priority টাস্ক তাকে preempt করলে উচ্চ priority টাস্ক অনির্দিষ্টকাল block হয়ে যায়।শেয়ারড রিসোর্সমধ্যম priority টাস্কউচ্চ priority টাস্কনিম্ন priority টাস্কSchedulerশেয়ারড রিসোর্সমধ্যম priority টাস্কউচ্চ priority টাস্কনিম্ন priority টাস্কSchedulerCritical section চলছেH জেগে ওঠায় scheduler L-কে থামায়Lock-এর অপেক্ষায় block! (L ধরে আছে)H lock-এর অপেক্ষায়, তাই L আবার চলেLock ছাড়ার দিকে এগোচ্ছে...M জেগে ওঠায় scheduler L-কে থামায়L lock ছাড়তে পারছে নাM চলতে থাকে (Hও Lও নড়তে পারে না)[priority inversion] উচ্চ priority অনির্দিষ্টকাল blockLock নেয়Lock নেওয়ার চেষ্টা করে

নিম্ন priority টাস্ক lock ধরে রাখার সময় মধ্যম priority টাস্ক তাকে preempt করে, আর উচ্চ priority টাস্ক অনির্দিষ্টকাল block হয়ে যায়। Mars Pathfinder-এ যা সত্যি করা হয়েছিল তা VxWorks-এর priority inheritance চালু করা; Ada একই ধরনের সমস্যায় অন্য পদ্ধতি Ceiling_Locking ভাষার ফিচার হিসেবে দেয়।

3. টাস্ক priority-এর ভিত্তি ── FIFO_Within_Priorities

FIFO_Within_Priorities হলো Ada Annex D-এ নির্দিষ্ট করা যায় এমন মানক priority-ভিত্তিক dispatching policy। Policy না লিখলে ডিফল্ট আচরণ implementation-defined, তবে GNAT অনেক টার্গেটে এই ধারার policy ব্যবহার করে। একই priority-র ভিতরে FIFO (first-in, first-out) দিয়ে চলে, আর উচ্চতর priority-র টাস্ক নিম্নতর priority-র টাস্ককে preempt করে।

-- 01_task_priority.ada
-- টাস্ক priority ও FIFO_Within_Priorities-এর মৌলিক রূপ
-- configuration pragma 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;

পয়েন্ট:

  • pragma Priority দিয়ে প্রতিটি টাস্কে static priority দেওয়া হয়। Priority'Last সর্বোচ্চ, Priority'First সর্বনিম্ন।
  • এই ডেমোর টাস্ক বডি ভারী computation নয়; delay until দিয়ে নির্দিষ্ট সময় পর্যন্ত অপেক্ষা করে। এখানে দেখতে চাই, একসঙ্গে runnable হলে উচ্চ priority টাস্ক আগে execution সুযোগ পায়।
  • এই চিত্র FIFO_Within_Priorities-এর মধ্যে ভিন্ন priority-র মধ্যকার preemption দিকটা দেখায়। একই priority-র ভিতরে FIFO ক্রম দেখতে হলে একই priority-র একাধিক টাস্ক সাজানো আলাদা উদাহরণ লাগে।
  • বাস্তব সিস্টেমে সাধারণত System.Default_Priority ধরে relative priority ডিজাইন করা হয়।
FIFO_Within_Priorities-এ উচ্চ priority টাস্কের preemptionএকসঙ্গে runnable হলে scheduler প্রথমে উচ্চ priority টাস্ক চালায়, delay until-এর পর নির্ধারিত সময়ে সেগুলো আবার জেগে ওঠে।নিম্ন priority টাস্ক(Priority=First)উচ্চ priority টাস্ক(Priority=Last)Main টাস্কSchedulerনিম্ন priority টাস্ক(Priority=First)উচ্চ priority টাস্ক(Priority=Last)Main টাস্কSchedulerT=0ms: দুই টাস্কই runnableশুরুর লগ লেখেশুরুর লগ লেখেT=100ms: HP জেগে ওঠেশেষের লগ লেখেT=500ms: LP জেগে ওঠেশেষের লগ লেখে(T=800ms) Main শেষটাস্ক তৈরিটাস্ক তৈরিসর্বোচ্চ priority-র HP বেছে নেয়delay until T+100ms-এ blockতারপর LP চালায়delay until T+500ms-এ blockHP চালায়LP চালায়

রান উদাহরণ (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

দুই টাস্কের শুরুর লগ মেইনের শিরোনাম সারির আগে বেরিয়েছে—এদিকে খেয়াল রাখুন। ডিক্লারেশন অংশের টাস্ক ঘিরে থাকা সাবপ্রোগ্রামের বডিতে ঢোকার আগেই activate হয়, তাই এমন ক্রম হতে পারে। আরও, শুরুর এই দুই লাইনের ক্রম রানভেদে উল্টে যেতে পারে। অধ্যায় 12-এ বলা হয়েছে, pragma Priority বাস্তব scheduling-এ কীভাবে পড়ে তা OS ও GNAT runtime-এর উপর; general-purpose Linux-এ priority অনুযায়ী স্টার্ট অর্ডার গ্যারান্টি নেই। এই উদাহরণে স্থিরভাবে দেখা যায় delay until-এ লেখা 0.1 সেকেন্ড / 0.5 সেকেন্ডের শেষ হওয়ার ক্রম।

Ada-এর priority সীমা (GNAT-এর ডিফল্ট):
  Priority'First  = 0   (সর্বনিম্ন)
  Priority'Last   = 30  (সর্বোচ্চ, তবে OS-এর উপর নির্ভর করে)

4. Ceiling_Locking ── ভাষাই priority inversion ঠেকায়

রিয়েল-টাইম সিস্টেমের সবচেয়ে জটিল সমস্যাগুলোর একটি priority inversion। উচ্চ priority টাস্ক নিম্ন priority টাস্কের ধরে রাখা lock-এর অপেক্ষা করে, আর সেই নিম্ন priority টাস্ককে মধ্যম priority টাস্ক preempt করলে উচ্চ priority টাস্ক অনির্দিষ্টকাল block হয়ে যায়।

Ada এই সমস্যায় Ceiling_Locking protocol সরাসরি protected object-এ বসিয়ে দিয়েছে।

-- 02_ceiling_locking.ada
-- Ceiling_Locking protocol দিয়ে priority inversion প্রতিরোধ
-- configuration pragma 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;

Ceiling_Locking কীভাবে কাজ করে:

  1. Protected object-এ pragma Priority (Ceiling) দিয়ে ceiling priority সেট করা।
  2. যে টাস্কই protected object-এ ঢুকুক, ঢোকার সময় স্বয়ংক্রিয়ভাবে ceiling priority-তে উঠে যায়
  3. ফলে protected object ব্যবহাররত টাস্ককে মধ্যম priority-র টাস্ক preempt করতে পারে না।
  4. Protected object থেকে বেরোলে আগের priority-তে ফেরে।

নিচের চিত্র আগের স্যাম্পল কোডের হুবহু সময় ট্রেস নয়; চিত্র 2-এর priority inversion প্যাটার্ন Ceiling_Locking-এ কীভাবে ঠেকানো হয়, সেই ধারণাচিত্র।

Ceiling_Locking কীভাবে priority inversion ঠেকায়Protected object-এ ঢোকার সময় টাস্ক ceiling priority-তে উঠে যায়, তাই মধ্যম priority টাস্ক তাকে preempt করতে পারে না।Protected object(ceiling priority=30)উচ্চ priority টাস্ক(priority=30)মধ্যম priority টাস্ক(priority=20)নিম্ন priority টাস্ক(priority=10)SchedulerProtected object(ceiling priority=30)উচ্চ priority টাস্ক(priority=30)মধ্যম priority টাস্ক(priority=20)নিম্ন priority টাস্ক(priority=10)SchedulerActive priority > ceiling priority এমন caller-এ Program_Errorচিত্রের H(30) ceiling(30)-এর সমান, তাই ঢুকতে পারেExecution priority 30-এ উঠে যায়M জেগে ওঠেL ceiling priority 30-এ চলছেM(20) preempt করতে পারে নাH জেগে ওঠেH(30) ceiling চেক পাস করেতবে L PO ব্যবহার করছে, তাই অপেক্ষা করেPriority 10-এ ফেরেPO ছাড়ার পর H চালায়H(30) = ceiling(30), তাই contention মিটলে ঢুকতে পারেProtected operation-এ ঢোকেOperation চালায়Protected operation থেকে বেরোয়Protected operation-এ ঢোকেProtected operation থেকে বেরোয়

ডিজাইন নির্দেশ: Protected object-এর ceiling priority সেই object ব্যবহার করা সব টাস্কের সর্বোচ্চ priority-র সমান বা তার বেশি হতে হবে। এটা ভেঙে ceiling-এর চেয়ে উচ্চ active priority-র টাস্ক protected operation ডাকলে Ada Program_Error দিয়ে ডিজাইন ভুল ধরতে পারে।

C-এর pthread mutex দিয়ে একই কাজ করতে PTHREAD_PRIO_PROTECT অ্যাট্রিবিউট স্পষ্ট করে সেট করতে হয়; Ada-তে এটা ভাষার মানক ফিচার।

5. delay until ── periodic টাস্ক drift ছাড়া চালানো

রিয়েল-টাইম সিস্টেমের মৌলিক প্যাটার্ন periodic টাস্ক। নির্দিষ্ট ব্যবধানে বারবার চলা টাস্কে cumulative timing error (drift) ঠেকানো অত্যন্ত জরুরি।

Ada-এর delay until এই সমস্যা পরিষ্কারভাবে সমাধান করে।

-- 03_periodic_task.ada
-- delay until দিয়ে periodic টাস্ক ── 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;

কেন delay until:

পদ্ধতি সমস্যা
delay Period; প্রতি iteration-এর processing time যোগ হয়ে period ধীরে ধীরে সরে যায় (cumulative drift)
delay until Next_Release; Next_Release := Next_Release + Period; absolute time ধরে চলে, তাই একবারের কাজ দেরি হলেও পরের release time ঠিক থাকে

তবে delay until processing time যেন period-এর ভিতরে থাকে, সেটা নিজে থেকে গ্যারান্টি দেয় না। কাজ যদি পরের wake time ছাড়িয়ে যায়, সেই delay until প্রায় সঙ্গে সঙ্গে ফিরে আসে, আর সিস্টেমকে deadline miss হিসেবে ধরতে হয়।

delay-এর ক্ষেত্রে:
  T=0ms → কাজ(15ms) → delay 100ms → T=115ms → কাজ(10ms) → ...
  আসল ব্যবধান: 115ms, 110ms, ... (processing time জমা হয়)

delay until-এর ক্ষেত্রে:
  Next_Release: 100ms, 200ms, 300ms, ... (absolute time)
  T=0ms → কাজ(15ms) → delay until 100ms → T=100ms → কাজ(10ms) → delay until 200ms
  আসল ব্যবধান: 100ms, 100ms, ... (processing time-এর উপর নির্ভর করে না)

রান উদাহরণ (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

প্রতি সাইকেলে প্রায় 0.2–0.3 মিলিসেকেন্ড wake delay দেখা যায়, কিন্তু সেই delay পরের period-এ গড়িয়ে যায়নি। 5 নম্বর সাইকেলেও বেস সময় থেকে সরে যাওয়া 1 মিলিসেকেন্ডের কম। delay Period; দিয়ে লিখলে এই delay প্রতিবার জমে 5 সাইকেল পরে চোখে পড়ত। এই সংখ্যা general-purpose Linux-এর একটি উদাহরণ; hard real-time পরিবেশে গ্যারান্টি করা মান নয়।

এই delay until প্যাটার্ন এখান থেকে সব periodic টাস্কে ব্যবহার করব।

delay Period বনাম delay untilRelative delay cumulative drift তৈরি করে; delay until absolute time ধরে চলে, কিন্তু period ছাড়িয়ে গেলে deadline miss ধরতে হয়।Period ছাড়িয়ে যাওয়া - deadline missprocessing 130msপরেরবার = T+100msdelay until T+100ms সঙ্গে সঙ্গে ফেরেদেরি ধরে overload হিসেবে সামলানোdelay until - absolute time-এর ভিত্তিprocessing 15msপরেরবার = T+100msdelay until T+100ms → 100ms-এ জাগেprocessing 10msপরেরবার = T+200ms → 200ms-এ জাগেআসল ব্যবধান: 100ms, 100ms...delay Period - cumulative driftdelay 100ms → 115ms-এ জাগেT=0ms: processing 15msprocessing 10ms → 125msdelay 100ms → 225ms-এ জাগেআসল ব্যবধান: 115ms, 110ms...সময়ের সঙ্গে ত্রুটি জমা হয়Cumulative drift ঠেকায়

6. Ravenscar profile ── যাচাইযোগ্য রিয়েল-টাইম subset

Ada-এর টাস্ক ফিচার শক্তিশালী, কিন্তু নিরাপত্তা অত্যন্ত গুরুত্বপূর্ণ সিস্টেমে “অতিরিক্ত শক্তিশালী” হয়ে সমস্যা হয়। Dynamic টাস্ক তৈরি, select statement, abort statement ইত্যাদি worst-case execution time-এর static analysis কঠিন করে তোলে।

Ravenscar profile এই সমস্যার Ada-এর জবাব। টাস্ক ফিচারকে statically analyzable ও deterministic subset-এ সীমিত করে।

-- 04_ravenscar_profile.ada
-- Ravenscar profile-এর মৌলিক রূপ
-- কম্পাইলের সময় gnat.adc-এ pragma Profile (Ravenscar); নির্দিষ্ট করতে হয়
-- বিল্ড: gnatchop -w 04_ravenscar_profile.ada .
--         → ravenscar_state.ads / ravenscar_state.adb / ravenscar_demo.adb-এ ভাগ হয়
--         gnat.adc তৈরি করে তারপর 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;

Ravenscar profile-এর সীমাবদ্ধতা:

নিষিদ্ধ ফিচার কারণ
Dynamic টাস্ক তৈরি (new বা access type) রানটাইম মেমরি বরাদ্দ non-deterministic
select statement শুধু একাধিক বিকল্প নয়, select statement পুরোটাই control-flow analysis কঠিন করে
abort statement Asynchronous abort অবস্থাকে অনির্দেশ্য করে
Ada.Task_Attributes রানটাইম dynamic আচরণ
Dynamic priority পরিবর্তন Scheduling analysis-এর অনুমান রানটাইমে বদলে যায়
Relative delay (delay) Cumulative drift তৈরি করে সহজে, তাই absolute time-এর delay until ব্যবহার করতে হয়
প্রতি protected object-এ একাধিক entry Blocking শর্ত ও বিশ্লেষণের বিষয় বেড়ে যায়
টাস্ক শেষ হওয়া Ravenscar-এ সব টাস্ককে non-terminating ধরা হয়
requeue statement Control-flow ট্র্যাক করা জটিল হয়

এই সীমাবদ্ধতায় Ravenscar-অনুসারী প্রোগ্রাম static timing analysis করা সহজ রূপ পায়। এটা DO-178C (বিমান সফটওয়্যার) বা ISO 26262 (স্বয়ংচালিত functional safety)-এর মতো নিরাপত্তা মানে চাওয়া বৈশিষ্ট্য। নিচের তালিকা প্রধান সীমাবদ্ধতার নির্বাচিত অংশ; আসল profile-এ No_Task_Hierarchy বা Detect_Blocking-এর মতো runtime ও বিশ্লেষণযোগ্যতা নিয়ে অতিরিক্ত নিয়মও থাকে।

Ravenscar profile-এর সীমাবদ্ধতা ও নীতিAda টাস্কিংকে static analysis-যোগ্য subset-এ সীমিত করে FIFO_Within_Priorities ও Ceiling_Locking বাধ্যতামূলক করে, যা নিরাপত্তা মান পূরণে সহায়ক।পূর্ণ Ada টাস্ক ফিচারRavenscar profileসীমাবদ্ধতাবাধ্যতামূলক policyDynamic টাস্ক তৈরি নিষিদ্ধselect statement নিষিদ্ধabort statement নিষিদ্ধTask_Attributes নিষিদ্ধপ্রতি protected object-এ 1 entry-তে সীমাrequeue statement নিষিদ্ধRelative delay নিষিদ্ধdelay until ব্যবহারDynamic priority পরিবর্তন নিষিদ্ধটাস্ক শেষ হওয়া নিষিদ্ধসব টাস্ক non-terminatingFIFO_Within_PrioritiesCeiling_Lockingযা সহজ হয়:static timing analysisDO-178Cবিমান সফটওয়্যারISO 26262স্বয়ংচালিত functional safetyIEC 62304চিকিৎসা যন্ত্রের সফটওয়্যার

Ravenscar profile চালু করতে gnat.adc ফাইলে নিচের লাইন লিখতে হয়:

pragma Profile (Ravenscar);

7. Timing event ── polling ছাড়া সময়চালিত জাগানো

অনেক রিয়েল-টাইম সিস্টেমে “নির্দিষ্ট সময়ে উচ্চ priority টাস্ক জাগাতে হবে” এমন চাহিদা বারবার আসে। সরল বাস্তবায়নে টাইমার poll করতে হয়, কিন্তু Ada আরও পরিশীলিত ব্যবস্থা দেয়—timing event

-- 05_timing_events.ada
-- Timing event (Ada.Real_Time.Timing_Events)
-- উচ্চ priority টাস্ককে polling ছাড়া জাগানোর ব্যবস্থা
-- বিল্ড: gnatchop -w 05_timing_events.ada .
--         → 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;

Timing event কীভাবে চলে:

1. Set_Handler(Timer_1, T+100ms, S.Fire'Access)  ── absolute time-এ handler register করা
2. T+100ms পার ── runtime S.Fire-কে **ceiling priority-তে** ডাকে
3. Fire Fired ফ্ল্যাগ True করে ── barrier খুলে যায়
4. Reactor টাস্ক Wait_For_Event থেকে জেগে ওঠে

গুরুত্বপূর্ণ বিষয়, এই উদাহরণে Ceiling_Locking স্পষ্ট করা আছে, আর Fire handler protected object-এর procedure বলে ceiling priority-তে চলে। Timing event-এর handler হিসেবে ব্যবহার করা protected procedure এমন protected object-এ রাখতে হয় যার ceiling priority interrupt-স্তরের—এখানে System.Interrupt_Priority'Last। এতে timing event সামলানোর সময় priority inversion হয় না।

8. Protected object দিয়ে রিয়েল-টাইম কিউ

রিয়েল-টাইম সিস্টেমে ঘন ঘন দেখা প্যাটার্ন producer–consumer। সেন্সর ডেটা তৈরি করে, কন্ট্রোল টাস্ক সেটা consume করে—এখানে বাফারের mutual exclusion ও blocking দক্ষভাবে সামলাতে হয়।

Ada-এর protected objectentry barrier দিয়ে এটা barrier-ভিত্তিক সিঙ্ক হিসেবে লেখা যায়। ভিতরে runtime mutual exclusion সামলায়, তাই অ্যাপ্লিকেশন কোডে mutex বা condition variable সরাসরি লিখতে হয় না।

-- 06_protected_queue.ada
-- Protected object দিয়ে রিয়েল-টাইম ডেটা শেয়ারিং
-- পাইপলাইন: Producer -> Bounded_Buffer -> Consumer
-- কম্পাইলের সময় gnat.adc-এ pragma Locking_Policy (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 Protected_Queue_Demo is

   Buffer_Size : constant := 4;

   type Buf_Array is array (1 .. Buffer_Size) of Integer;

   protected Bounded_Buffer is
      pragma Priority (System.Any_Priority'Last);
      entry Put (Item : Integer);
      entry Get (Item : out Integer);
   private
      Buf    : Buf_Array;
      Count  : Natural := 0;
      Head   : Positive := 1;
      Tail   : Positive := 1;
   end Bounded_Buffer;

   protected body Bounded_Buffer is
      entry Put (Item : Integer) when Count < Buffer_Size is
      begin
         Buf (Tail) := Item;
         Tail := (Tail mod Buffer_Size) + 1;
         Count := Count + 1;
      end Put;

      entry Get (Item : out Integer) when Count > 0 is
      begin
         Item := Buf (Head);
         Head := (Head mod Buffer_Size) + 1;
         Count := Count - 1;
      end Get;
   end Bounded_Buffer;

   task Producer is
      pragma Priority (System.Default_Priority + 2);
      pragma Storage_Size (4 * 1024);
   end Producer;

   task Consumer is
      pragma Priority (System.Default_Priority + 1);
      pragma Storage_Size (4 * 1024);
   end Consumer;

   task body Producer is
      Next_Release : Time := Clock + Milliseconds (50);
      Period       : constant Time_Span := Milliseconds (50);
   begin
      for I in 1 .. 6 loop
         Bounded_Buffer.Put (I);
         Put_Line ("[Producer] Put" & Integer'Image (I));
         delay until Next_Release;
         Next_Release := Next_Release + Period;
      end loop;
      Put_Line ("[Producer] Done");
   end Producer;

   task body Consumer is
      Item         : Integer;
      Next_Release : Time := Clock + Milliseconds (80);
      Period       : constant Time_Span := Milliseconds (80);
   begin
      delay until Clock + Milliseconds (30);
      for I in 1 .. 6 loop
         Bounded_Buffer.Get (Item);
         Put_Line ("[Consumer] Got" & Integer'Image (Item));
         delay until Next_Release;
         Next_Release := Next_Release + Period;
      end loop;
      Put_Line ("[Consumer] Done");
   end Consumer;

begin
   Put_Line ("=== Protected Queue Demo (Ceiling_Locking) ===");
   Put_Line ("Buffer size = 4; Producer every 50ms, Consumer every 80ms");
   delay until Clock + Milliseconds (800);
   Put_Line ("Main: done");
end Protected_Queue_Demo;

ডিজাইনের মূল কথা:

  • entry Put when Count < Buffer_Size ── বাফার ভর্তি থাকলে Producer স্বয়ংক্রিয়ভাবে block হয়।
  • entry Get when Count > 0 ── বাফার খালি থাকলে Consumer স্বয়ংক্রিয়ভাবে block হয়।
  • pragma Priority (System.Any_Priority'Last) ── ceiling locking-এর ফলে Producer ও Consumer-এর মধ্যে priority inversion হয় না
  • Barrier শর্ত protected object-এর অভ্যন্তরীণ অবস্থা (Count) দিয়ে লেখা, আর lock ছাড়ার সময় স্বয়ংক্রিয়ভাবে আবার মূল্যায়ন হয়।

এই কোডে অ্যাপ্লিকেশন-স্তরের mutex, semaphore বা condition variable নেই। দরকারি অপেক্ষা protected object-এর entry barrier দিয়েই প্রকাশ করা।

Bounded buffer-এর empty, partial, full অবস্থাPut ও Get barrier Count অনুযায়ী টাস্ককে block করে বা এগোতে দেয়।প্রাথমিক অবস্থাPut (1 উপাদান যোগ)Put / GetGet (শেষ উপাদান নেওয়া)Put (শেষ খালি ঘর ভরা)Get (একটি খালি হয়)Get block হয় (barrier Count=0)Put block হয় (barrier Count=Buffer_Size)খালি / Count=0কিছু আছে / Count=1..Buffer_Size-1ভর্তি / Count=Buffer_Size

Put সফল হলে Get-এর অপেক্ষা, Get সফল হলে Put-এর অপেক্ষা আবার মূল্যায়ন হয়। চিত্রে যে অবস্থায়ই থাকুক, protected operation শেষ হওয়ার সময় এটা হয়।

9. Execution time পরিমাপ ── monitoring-এর প্রথম ধাপ

রিয়েল-টাইম সিস্টেমের schedulability মূল্যায়ন করতে প্রতিটি টাস্কের execution time (CPU time) সঠিকভাবে জানা দরকার। Ada-এর Ada.Execution_Time প্যাকেজ টাস্কপ্রতি CPU খরচ দেয়।

-- 07_execution_time.ada
-- Execution time পরিমাপ (Execution_Time)
-- টাস্কপ্রতি CPU খরচ পরিমাপ

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 বনাম CPU time:

Wall-clock time: Ada.Real_Time.Clock
  → আসল অতিবাহিত সময়। Block বা preempt হওয়া সময়ও ধরে।

CPU time (execution time): Ada.Execution_Time.Clock
  → সেই টাস্ক CPU-তে সত্যি চলার সময়ই।
  → Block বা preempt হওয়া সময় গোনা হয় না।

এই পার্থক্য execution time monitoring ও WCET যাচাইয়ের সূচনা। Busy_Worker delay until-এ অপেক্ষা করলে CPU time বাড়ে না; আসল processing চলার সময়ই বাড়ে। Main টাস্কের delay until Clock + Milliseconds(500)-এর মধ্যেও CPU time প্রায় শূন্য হওয়া উচিত। তবে CPU time-এর মাপ সত্যিকারের WCET গ্যারান্টি করে না। Cache, pipeline, মেমরি contention সহ WCET-এর জন্য আলাদা করে static analysis বা টার্গেট পরিবেশে যাচাই লাগে।

Wall-clock time বনাম CPU timeWall clock অপেক্ষা ও preemption ধরে; CPU time শুধু আসল processing ধরে, তাই WCET যাচাইয়ের সহায়ক কিন্তু নিশ্চয়তা নয়।CPU timebreakdown: শুধু আসল processingমোট CPU time: 120msWall-clock timebreakdown: processing + wait + block + preemptমোট অতিবাহিত সময়: 500msফারাক = অপেক্ষা, block, preempt সময়CPU time আসল processing খরচ দেখেWCET যাচাই ও monitoring-এর সহায়কঅপেক্ষা, block, preempt সময় বাদ দেয়সতর্কতামাপ সত্যিকারের WCET গ্যারান্টি করে নাStatic analysis বা টার্গেট যাচাই দরকার

10. সমন্বিত ডেমো ── মাল্টি-পিরিয়ডিক রিয়েল-টাইম সিস্টেম

এখান পর্যন্ত শেখা সব উপাদান—priority, Ceiling_Locking, delay until, protected object—একত্র করে একটি সাধারণ মাল্টি-পিরিয়ডিক রিয়েল-টাইম সিস্টেম গড়ব।

-- 08_multiperiodic.ada
-- মাল্টি-পিরিয়ডিক রিয়েল-টাইম সিস্টেমের সমন্বিত ডেমো
-- দ্রুত period (100ms)-এর সেন্সর পড়ার টাস্ক
-- ধীর period (400ms)-এর কন্ট্রোল টাস্ক
-- Ceiling_Locking দিয়ে ডেটা শেয়ারিং

pragma Locking_Policy (Ceiling_Locking);

with Ada.Text_IO;               use Ada.Text_IO;
with System;                    use System;
with Ada.Real_Time;             use Ada.Real_Time;

procedure Multiperiodic_Demo is

   package Int_IO is new Ada.Text_IO.Integer_IO (Integer);

   protected Shared_Sensor is
      pragma Priority (System.Any_Priority'Last);
      procedure Write (V : Integer);
      function Read return Integer;
   private
      Value : Integer := 0;
   end Shared_Sensor;

   protected body Shared_Sensor is
      procedure Write (V : Integer) is
      begin
         Value := V;
      end Write;

      function Read return Integer is
      begin
         return Value;
      end Read;
   end Shared_Sensor;

   task Fast_Sensor is
      pragma Priority (System.Default_Priority + 3);
      pragma Storage_Size (4 * 1024);
   end Fast_Sensor;

   task body Fast_Sensor is
      Next_Release : Time := Clock + Milliseconds (100);
      Period       : constant Time_Span := Milliseconds (100);
      Cycle        : Natural := 0;
   begin
      Put_Line ("[Fast] Sensor reader starts (100ms period)");

      for I in 1 .. 12 loop
         delay until Next_Release;
         Cycle := Cycle + 1;
         Shared_Sensor.Write (Cycle * 10);
         Next_Release := Next_Release + Period;
      end loop;
      Put_Line ("[Fast] Done");
   end Fast_Sensor;

   task Slow_Controller is
      pragma Priority (System.Default_Priority + 2);
      pragma Storage_Size (4 * 1024);
   end Slow_Controller;

   task body Slow_Controller is
      Next_Release : Time := Clock + Milliseconds (150);
      Period       : constant Time_Span := Milliseconds (400);
      Cycle        : Natural := 0;
      Raw          : Integer;
   begin
      Put_Line ("[Slow] Controller starts (400ms period)");

      for I in 1 .. 3 loop
         delay until Next_Release;
         Cycle := Cycle + 1;
         Raw := Shared_Sensor.Read;
         Put_Line ("[Slow] Cycle" & Natural'Image (Cycle) &
                   " reads sensor =" & Integer'Image (Raw));
         Next_Release := Next_Release + Period;
      end loop;
      Put_Line ("[Slow] Done");
   end Slow_Controller;

begin
   Put_Line ("=== Multiperiodic Real-Time System Demo ===");
   Put_Line ("Fast sensor (100ms) x 12 + Slow controller (400ms) x 3");
   Put_Line ("Ceiling_Locking prevents priority inversion on shared data");
   delay until Clock + Milliseconds (2000);
   Put_Line ("Main: done");
end Multiperiodic_Demo;

সিস্টেম গঠন:

নিচের চিত্র স্যাম্পল কোডের release সময় (দ্রুত সেন্সর 100ms period, ধীর কন্ট্রোল 150ms অফসেটের 400ms period)-এর উপর ব্যাখ্যার জন্য ধরে নেওয়া execution time বসিয়ে একটি schedule উদাহরণ। কোডে নিজে 80ms কন্ট্রোল processing নেই, তাই এটা মাপা চিত্র নয়। দ্রুত সেন্সরের priority বেশি হলে ধীর কন্ট্রোল চলার সময় দ্রুত সেন্সরের release এলে ধীর কন্ট্রোল সাময়িক থেমে যায়। চিত্রে পড়তে সুবিধার জন্য প্রতি ধীর কন্ট্রোল period-এ নমুনা হিসেবে একটি preemption আঁকা; বাস্তবে প্রতি 100ms সীমানায় দ্রুত সেন্সর release হয়।

মাল্টি-পিরিয়ডিক সিস্টেমে fast sensor ও slow controllerউচ্চ priority fast sensor slow controller-কে মাঝে মাঝে preempt করে; চিত্রটি ব্যাখ্যার জন্য ধরে নেওয়া execution সময় ব্যবহার করে।ধীর কন্ট্রোল period 3 (release=950ms)ধীর কন্ট্রোল period 2 (release=550ms)ধীর কন্ট্রোল period 1 (release=150ms)1000-1010msদ্রুত সেন্সর #10P+3 বলে preempt হয়950-1000msধীর কন্ট্রোল #3 আগের অংশ1010-1040msধীর কন্ট্রোল #3 পরের অংশ600-610msদ্রুত সেন্সর #6P+3 বলে preempt হয়550-600msধীর কন্ট্রোল #2 আগের অংশ610-640msধীর কন্ট্রোল #2 পরের অংশ150-200msধীর কন্ট্রোল #1 আগের অংশ100-110msদ্রুত সেন্সর #1200-210msদ্রুত সেন্সর #2P+3 বলে preempt হয়210-240msধীর কন্ট্রোল #1 পরের অংশব্যাখ্যার অনুমানদ্রুত সেন্সর: 10ms কাজধীর কন্ট্রোল: 80ms কাজ

এই প্যাটার্ন শিল্প কন্ট্রোল সিস্টেম ও রোবট কন্ট্রোলে ঘন ঘন দেখা “দ্রুত সেন্সর সংগ্রহ + ধীর কন্ট্রোল লুপ”-এর সাধারণ গঠন।

11. Ada-এর রিয়েল-টাইম ফিচার যেখানে সত্যিই কাজে লাগে

Ada-এর রিয়েল-টাইম ফিচার নিচের মতো ক্ষেত্রে বিশেষভাবে মূল্য দেখায়।

Ada Annex D যেসব ক্ষেত্রে কাজে লাগেবিমান ও মহাকাশ, রেল, স্বয়ংচালিত, চিকিৎসা যন্ত্র, শিল্প নিয়ন্ত্রণ ও প্রতিরক্ষায় Ada-এর রিয়েল-টাইম ফিচারের অবস্থান।Ada Annex Dরিয়েল-টাইম ফিচারবিমান ও মহাকাশDO-178CরেলEN 50128 seriesস্বয়ংচালিতISO 26262চিকিৎসা যন্ত্রIEC 62304শিল্প নিয়ন্ত্রণIEC 61508 seriesপ্রতিরক্ষা ও উচ্চ-নির্ভরযোগ্য সিস্টেমফ্লাইট কন্ট্রোলপ্রমাণিত প্রয়োগক্ষেত্রস্যাটেলাইট ও মহাকাশযান কন্ট্রোলসিগন্যালিং সিস্টেমস্বয়ংক্রিয় ট্রেন কন্ট্রোলনিরাপত্তা-সম্পর্কিত ECU-এর candidateC / MISRA-C প্রধান ক্ষেত্রেসীমিত ও বাছাই করা প্রয়োগপেসমেকারইনফিউশন পাম্পরোবট কন্ট্রোলNC মেশিন টুলমিশন কম্পিউটারদীর্ঘমেয়াদি চালু সিস্টেম

চিত্রে শুধু স্বয়ংচালিত ক্ষেত্রে “সীমিত ও বাছাই করা প্রয়োগ” লেখা, কারণ ভাষা হিসেবে মানানসই কি না তার চেয়ে বিদ্যমান ইকোসিস্টেমের আকারই বড় কারণ। গাড়ির সফটওয়্যারে AUTOSAR-এর মতো শিল্প মানক API স্পেসিফিকেশন, সাপ্লায়ারের দেওয়া কোড, সার্টিফিকেশন পাস করা কম্পাইলার ও যাচাই টুল, এমনকি ইঞ্জিনিয়ারের সংখ্যা পর্যন্ত C ও MISRA-C ধরে গড়ে উঠেছে। ভাষা বদলানো মানে, লক্ষ্য একটাই কম্পোনেন্ট হলেও তার আশপাশের টুলসেট আর সংগ্রহ–যাচাই প্রক্রিয়া একসঙ্গে নতুন করে সাজানো। তাই Ada গাড়িজুড়ে মানক হওয়ার বদলে, বিশেষ করে উচ্চ নিশ্চয়তা চাওয়া কিছু কম্পোনেন্টে, অথবা যাদের ইতিমধ্যে Ada অ্যাসেট ও ডেভেলপমেন্ট ব্যবস্থা আছে সেই সংস্থায় বাছাই করে ব্যবহৃত হয়—এমন অবস্থানেই পড়ে সহজে। উল্টোদিকে, বিমান–মহাকাশ বা রেলের মতো যেখানে ইকোসিস্টেমই উচ্চ নির্ভরযোগ্যতার দিকে ঝুঁকে আছে, সেখানে এই বাধা শুরুতেই নেই।

12. সতর্কতা ও সীমা

Ada-এর রিয়েল-টাইম ফিচার শক্তিশালী, কিন্তু সব কিছুর সমাধান নয়।

1. প্ল্যাটফর্ম নির্ভরতা:

  • pragma Priority-এর আসল ম্যাপিং চালানোর পরিবেশের (OS + GNAT runtime) উপর নির্ভর করে। Linux-এ এটা SCHED_FIFO-তে ম্যাপ হয়, কিন্তু Windows-এ পূর্ণ preemption গ্যারান্টি নাও থাকতে পারে।

2. Ravenscar-এর সীমা:

  • Dynamic টাস্ক তৈরি নিষিদ্ধ, তাই সিস্টেম চালুর সময় সব টাস্ক staticভাবে ডিক্লেয়ার করতে হয়। এতে ডিজাইনের স্বাধীনতা কমে।

3. WCET মাপার সীমা:

  • Ada.Execution_Time মাপ, গ্যারান্টি নয়। Cache miss বা pipeline hazard সহ সত্যিকারের WCET আলাদা করে static analysis টুল দিয়ে যাচাই করতে হয়।

4. Overhead:

  • Protected object-এর barrier মূল্যায়ন entry শেষ বা বাতিল হলে, আর protected object থেকে বেরোলে স্বয়ংক্রিয় চলে। ঘন ঘন ডাকা protected object-এ এই overhead হিসাবে রাখতে হয়।

5. টুলচেইনের বাধা:

  • Ada-এর রিয়েল-টাইম ফিচার পুরোপুরি কাজে লাগাতে উপযুক্ত cross-compiler ও runtime লাগে। বিশেষ করে embedded টার্গেটে ভেন্ডর-দেওয়া runtime-এর উপর নির্ভর করতে হয়।

13. সারসংক্ষেপ

এই নিবন্ধে Ada-এর Annex D যে রিয়েল-টাইম ফিচার দেয়, সেগুলো আটটি কোড উদাহরণ দিয়ে ধাপে ধাপে দেখেছি।

ফিচার যে মূল্য দেয়
টাস্ক priority Preemptive, priority-ভিত্তিক scheduling
Ceiling_Locking ভাষায় বসানো priority inversion প্রতিরোধ
delay until Cumulative drift ঠেকানো periodic execution
Ravenscar profile Static analysis করা সহজ টাস্ক subset
Timing event Polling ছাড়া সময়চালিত জাগানো
Protected কিউ Protected object-এর barrier-ভিত্তিক সিঙ্ক
Execution time পরিমাপ টাস্কপ্রতি CPU time monitoring
মাল্টি-পিরিয়ডিক সমন্বয় ভিন্ন period-এর টাস্ক নিরাপদে একসঙ্গে রাখার ডিজাইন

Ada-এর রিয়েল-টাইম ফিচারের সারকথা “পরে জুড়ে দেওয়া নয়“। Priority inversion ঠেকানোর lock নিয়ম, periodic execution-এর সময় নির্দেশ, execution time monitoring—এগুলো ভাষার স্পেসিফিকেশনের অংশ হিসেবে দেওয়া। Deadline পূরণ অবশ্যই ডিজাইন ও বিশ্লেষণে যাচাই করতে হয়, কিন্তু তার ভিত্তি ভাষার runtime গুছিয়ে দেয়—এটাই বড় শক্তি।

পরের ধাপ হিসেবে Ada দিয়ে রিয়েল-টাইম সিস্টেম ডেভেলপমেন্ট নিজে চেষ্টা করতে Alire দিয়ে GNAT টুলচেইন ইনস্টল করে এই নিবন্ধের স্যাম্পল কোড gnatchop + gnatmake দিয়ে বিল্ড করে দেখুন।

Ada concurrency-এর ভিত্তি (টাস্ক, rendezvous, protected object) নিয়ে আগের নিবন্ধ “Ada-তে নিরাপদ concurrency” দেখুন।

14. তথ্যসূত্র

কাছাকাছি বিষয়ে গভীরে যেতে একই ট্যাগযুক্ত সাম্প্রতিক নিবন্ধ।

Windows ভার্চুয়ালাইজেশনের গভীরতা (পর্ব ৩) — সেকেন্ডে বুট হওয়া ভার্চুয়াল মেশিন: WSL2, Windows Sandbox ও কন্টেইনার এত হালকা কেন

WSL2 ও Windows Sandbox সেকেন্ডে শুরু হয়ে এত হালকা মনে হয় কেন? এই নিবন্ধ ডায়নামিক বেস ইমেজ ও ডাইরেক্ট ম্যাপ থেকে ডায়নামিক মেমরি বরাদ্দ...

Windows ভার্চুয়ালাইজেশনের গভীরতা (পর্ব ২) — কার্নেলও দেখতে পায় না এমন মেমরি: VBS, HVCI ও Credential Guard কীভাবে কাজ করে

সামঞ্জস্যপূর্ণ হার্ডওয়্যারে ক্লিন ইনস্টলে VBS ডিফল্টে চালু থাকে এবং হাইপারভাইজার ও SLAT দিয়ে কার্নেলের চেয়ে শক্তিশালী আইসোলেশন তৈরি কর...

Windows ভার্চুয়ালাইজেশনের গভীরতা (পর্ব ১) — আপনার Windows আসলে কোথায় চলছে? হাইপারভাইজার ও পার্টিশন

Hyper-V চালু করলে হোস্ট Windows নিজেই রুট পার্টিশন হিসেবে হাইপারভাইজারের উপর চলে। এই নিবন্ধ VT-x, SLAT ও VMBus-এর ভূমিকা দিয়ে ভার্চুয়াল...

এই পৃষ্ঠাগুলো বিষয়টিকে সেবা ও সিদ্ধান্তের বৃহত্তর প্রেক্ষাপটে স্থাপন করে।

প্রায়শ জিজ্ঞাসিত প্রশ্ন

এই নিবন্ধের বিষয়ে পরামর্শে প্রায়ই আসা প্রশ্ন।

Ada-এর Annex D কী?
Ada ভাষার স্পেসিফিকেশনের অংশ হিসেবে মানক করা রিয়েল-টাইম সিস্টেমের ফিচারসমূহ। FIFO_Within_Priorities দিয়ে priority-ভিত্তিক preemptive scheduling, priority inversion ঠেকানো Ceiling_Locking protocol, delay until দিয়ে absolute time-এর periodic execution, Ravenscar profile, timing event, আর Ada.Execution_Time দিয়ে টাস্কপ্রতি execution time পরিমাপ এর মধ্যে পড়ে। লাইব্রেরি পরে জুড়ে দেওয়া নয়, ভাষার runtime-এই এগুলো বসানো—এটাই বৈশিষ্ট্য।
Priority inversion কী? Ada কীভাবে ঠেকায়?
নিম্ন priority টাস্ক lock ধরে রাখার সময় মধ্যম priority টাস্ক তাকে preempt করে, আর সেই lock-এর অপেক্ষায় থাকা উচ্চ priority টাস্ক অনির্দিষ্টকাল block হয়ে যায়। 1997 সালে Mars Pathfinder-এ এটা সত্যি ঘটেছিল, আর প্রোব বারবার reset হচ্ছিল। Ada-তে Ceiling_Locking protocol ভাষার ফিচার হিসেবে আছে; protected object-এ ঢোকা টাস্ক স্বয়ংক্রিয়ভাবে ceiling priority-তে উঠে যায়, তাই মধ্যম priority টাস্ক তাকে preempt করতে পারে না।
Ravenscar profile কী?
যেসব সিস্টেমে নিরাপত্তা অত্যন্ত গুরুত্বপূর্ণ, সেখানে Ada-এর টাস্ক ফিচারকে statically analyzable ও deterministic subset-এ সীমিত করার profile। Dynamic টাস্ক তৈরি, select statement, abort statement, relative delay, requeue statement ইত্যাদি নিষিদ্ধ। এই সীমাবদ্ধতায় static timing analysis সহজ হয়, আর DO-178C (বিমান সফটওয়্যার) বা ISO 26262 (স্বয়ংচালিত functional safety)-এর মতো নিরাপত্তা মানে চাওয়া বৈশিষ্ট্য পূরণ করা সহজ হয়। GNAT-এ gnat.adc ফাইলে pragma Profile (Ravenscar) লিখে চালু করা হয়।
Periodic টাস্কে delay না লিখে delay until ব্যবহার করার কারণ কী?
Relative time নির্দেশ করা delay-এ প্রতি iteration-এর processing time যোগ হয়ে period ধীরে ধীরে সরে যায়, অর্থাৎ cumulative drift হয়। delay until absolute time ধরে পরের release time ঠিক করে, তাই একবারের কাজ দেরি হলেও পরের release time ঠিক থাকে। তবে কাজ যদি পরের wake time ছাড়িয়ে যায়, delay until প্রায় সঙ্গে সঙ্গে ফিরে আসে; তাই আলাদা করে deadline miss ধরে overload হিসেবে সামলানোর ডিজাইন দরকার।

লেখকের প্রোফাইল

নিবন্ধের লেখকের পরিচিতি পৃষ্ঠা।

Go Komura

KomuraSoft LLC-এর প্রতিনিধি

Windows সফটওয়্যার ডেভেলপমেন্ট, প্রযুক্তিগত পরামর্শ ও বাগ তদন্তে বিশেষজ্ঞ, বিশেষ করে বিদ্যমান সিস্টেমযুক্ত প্রকল্প ও পুনরুৎপাদন করা কঠিন বাগে।

পাবলিক লিঙ্ক

ব্লগে ফিরে যান