এই পর্বে যা শিখবেন
- Asymmetric বনাম symmetric multiprocessing, আর একটি ভাগের queue বনাম প্রতি CPU-র আলাদা queue-এর লাভ-ক্ষতি
- Processor affinity (soft আর hard), cache-এর উষ্ণতা, push/pull migration, work stealing আর NUMA — সঙ্গে C আর bash দিয়ে নিজের চোখে affinity দেখা
- Multicore, SMT (hyper-threading) কীভাবে memory stall লুকায়, আর big.LITTLE বা P/E core-এ scheduler-এর নতুন দায়িত্ব
- Real-time-এর latency, periodic task model, Rate Monotonic, Liu–Layland সীমা (প্রমাণের রূপরেখাসহ) আর response-time analysis
- EDF কেন এক CPU-তে অনুকূলতম, RM আর EDF-এর তুলনামূলক Gantt chart, আর Linux-এর
SCHED_FIFO,SCHED_RR,SCHED_DEADLINE
১ · অনেক CPU, নতুন প্রশ্ন — একটি লাইন, নাকি প্রতি কাউন্টারে আলাদা লাইন
পর্ব ১৬ থেকে ১৯ পর্যন্ত আমরা একটি প্রশ্নের উত্তর খুঁজেছি: ready queue থেকে কাকে পরের পালা দেওয়া হবে — FCFS, SJF, Round Robin, MLFQ. সব হিসাব ছিল একটিমাত্র CPU ধরে, অথচ আজকের ফোনেও ৮টি, আর সার্ভারে শতাধিক logical CPU থাকে। তখন প্রশ্ন দাঁড়ায় দুটি: কাকে চালানো হবে, আর কোন CPU-তে চালানো হবে।
একটি ব্যাংকের শাখার কথা ভাবুন। একটি কাউন্টার থাকলে প্রশ্ন সহজ — টোকেন নম্বর ধরে ডাকা। পাঁচটি কাউন্টার হলে দুটি পথ খোলা। এক, সবার জন্য একটিই টোকেন-লাইন; যে কাউন্টার খালি হয়, সে লাইনের পরের জনকে ডাকে। দুই, প্রতিটি কাউন্টারের সামনে আলাদা লাইন। প্রথম পথে কেউ বৃথা বসে থাকে না, কিন্তু টোকেন-যন্ত্রটির সামনে সব কর্মকর্তার ভিড় লাগে। দ্বিতীয় পথে ভিড় নেই, কিন্তু কোনো লাইন লম্বা আর কোনোটি খালি হয়ে যেতে পারে।
Asymmetric বনাম symmetric multiprocessing
শুরুর দিকের অনেক multiprocessor সিস্টেম সবচেয়ে সরল পথ নিয়েছিল: asymmetric multiprocessing (AMP). একটি প্রধান CPU একাই kernel চালায় — scheduling-এর সিদ্ধান্ত, I/O, system call সব তার হাতে। বাকি CPU-গুলো শুধু তার দেওয়া user কোড চালায়। Kernel-এর ডেটা একজনই ছোঁয় বলে নকশা সরল, কিন্তু প্রধান CPU-টি বাধা (bottleneck) হয়ে দাঁড়ায়।
আজকের Linux, Windows, macOS, Android আর iOS সবই symmetric multiprocessing (SMP): প্রতিটি CPU নিজেই scheduler চালায়, নিজেই kernel-এ ঢুকতে পারে, নিজেই পরের task বাছে। দাম হলো kernel-এর ভাগের ডেটা সুরক্ষিত রাখতে lock আর সতর্ক নকশা (মডিউল ৪)। “Asymmetric” শব্দটি অবশ্য আজও অন্য অর্থে টিকে আছে — যেমন একটি চিপে কয়েকটি core-এ Linux আর একটি ছোট core-এ আলাদা real-time firmware চলা — আর ভিন্ন স্বভাবের core নিয়ে আমরা অংশ ৪-এ কথা বলব।
এক ভাগের run queue বনাম প্রতি CPU-র নিজের run queue
একটি ভাগের queue
সব CPU একটি queue থেকে নেয়। কাজ আপনা থেকেই সমান ভাগ হয় — যে CPU খালি, সে-ই নেয়। কিন্তু queue ছোঁয়ার আগে প্রতিবার একটি lock নিতে হয়; CPU যত বাড়ে, lock-এর জন্য অপেক্ষা (lock contention) তত বাড়ে। আর task যেকোনো CPU-তে গিয়ে পড়ে, ফলে cache বারবার ঠান্ডা।
Linux 2.4 পর্যন্তপ্রতি CPU-র নিজের queue
প্রতিটি CPU সাধারণত শুধু নিজের queue ছোঁয় — lock-এর ভিড় নেই, আর task একই CPU-তে থাকায় cache উষ্ণ থাকে। কিন্তু একটি queue-তে দশজন আর পাশের CPU অলস — এই load imbalance নিজে থেকে মেটে না। তাই আলাদা load balancer লাগে।
Linux 2.6-এর O(1) থেকে আজ পর্যন্ত, Windows, macOSLinux-এর ইতিহাস এখানে শিক্ষণীয়। Linux 2.4-এর scheduler-এ পুরো সিস্টেমে একটিই runqueue ছিল, আর একটি lock. ২০০২-০৩ সালের দিকে Ingo Molnar-এর O(1) scheduler (Linux 2.6-এ মূলধারায়) প্রতিটি CPU-কে নিজের runqueue দেয়। পরের CFS (২০০৭) আর আজকের EEVDF (পর্ব ২১)-ও এই কাঠামো রেখেছে: Linux kernel-এর struct rq প্রতি CPU-তে একটি। Windows আর macOS-ও মূলত প্রতি processor (কিছু ক্ষেত্রে কাছাকাছি কয়েকটি processor-এর একটি দল) ধরে আলাদা ready queue রাখে।
Multiprocessor scheduling-এর পুরোটাই একটি টানাপোড়েন: কাজ সমানভাবে ভাগ রাখা (সব CPU ব্যস্ত) বনাম task-কে এক জায়গায় রাখা (cache উষ্ণ, lock কম)। Affinity, load balancing, NUMA — সবই এই দুইয়ের মধ্যে ভারসাম্য খোঁজার কৌশল।
২ · Processor affinity — যে CPU চেনে, তার কাছেই থাকা
একটি task কিছুক্ষণ CPU 0-তে চললে তার ব্যবহৃত ডেটা আর কোড CPU 0-এর L1 আর L2 cache-এ জমে (পর্ব ৪-এর memory hierarchy মনে করুন)। এই অবস্থাকে বলে warm cache. এখন যদি পরের পালায় তাকে CPU 1-এ পাঠানো হয়, সেখানকার cache-এ তার কিছুই নেই — প্রথম কিছুক্ষণ প্রায় প্রতিটি মেমরি-ছোঁয়া cache miss, আর CPU 0-এর cache-এ পড়ে থাকা পুরোনো কপিগুলো cache coherence-এর নিয়মে বাতিল করতে হয়। এই খরচ এড়াতে scheduler-এর একটি স্বাভাবিক ঝোঁক থাকে: task যে CPU-তে আগে চলেছে, পরেও সেখানেই চালানো। এই ঝোঁকের নাম processor affinity (বা CPU affinity).
পাড়ার দর্জির দোকানে তিনজন কারিগর। আপনার পাঞ্জাবির মাপ, কাপড় আর নকশা প্রথম কারিগরের টেবিলে খোলা পড়ে আছে। পরের দিন ফিটিংয়ের জন্য গেলে তাঁর কাছেই যাওয়া ভালো — নইলে দ্বিতীয় কারিগরকে আবার সব মাপ খুঁজে বের করতে হবে। তবে প্রথম জনের সামনে দশজনের লাইন আর দ্বিতীয় জন বসে আছেন — তখন একটু ঝামেলা মেনে নিয়েও দ্বিতীয় জনের কাছে যাওয়াই বুদ্ধিমানের। এটিই soft affinity. আর যদি দোকানের নিয়ম হয় “শেরওয়ানি শুধু প্রথম কারিগর বানাবেন”, সেটি hard affinity — লাইন যত লম্বাই হোক।
| দিক | Soft affinity | Hard affinity |
|---|---|---|
| নিশ্চয়তা | চেষ্টা মাত্র — দরকার হলে task সরানো হয় | কঠোর — অনুমোদিত CPU-র বাইরে কখনো চলবে না |
| Linux | Scheduler-এর wake-up আর balance-এর হিসাবে “cache-hot” task না সরানোর চেষ্টা | sched_setaffinity(), taskset, cgroup-এর cpuset |
| Windows | প্রতিটি thread-এর “ideal processor” (SetThreadIdealProcessor) | SetThreadAffinityMask, SetProcessAffinityMask, start /affinity |
| macOS | Scheduler নিজে ঠিক করে; QoS শ্রেণি দিয়ে ইঙ্গিত দেওয়া যায় | সাধারণ অ্যাপের জন্য নির্দিষ্ট core-এ বাঁধার কোনো প্রচলিত API নেই |
Hard affinity কেন দরকার? কম-latency সার্ভারে thread-কে সেই core-এ রাখা, যেখানে নেটওয়ার্ক কার্ডের interrupt আসে; benchmark-এ মাপ স্থির রাখা; আর একটি real-time কাজকে এমন core দেওয়া, যেখানে আর কেউ চলে না। পর্ব ৯-এর ping-pong মাপার প্রোগ্রামেও দুটি প্রসেসকে এক CPU-তে বাঁধা হয়েছিল।
নিজে করে দেখা — একটি প্রোগ্রাম নিজেকে CPU 0-তে বাঁধে
Linux-এ affinity হলো একটি CPU-র সেট (cpu_set_t), যার প্রতিটি বিট একটি CPU. নিচের প্রোগ্রাম আগে দেখে নেয় সে কোন কোন CPU ব্যবহার করতে পারে আর এখন কোথায় চলছে (sched_getcpu()). তারপর CPU 0-তে নিজেকে বাঁধে — container বা cgroup কোনো কারণে CPU 0 না দিলে, অনুমোদিত প্রথম CPU-তে। শেষে কিছু হিসাব-ভারী কাজ করে পাঁচবার দেখে, সে কখনো অন্য CPU-তে গিয়েছিল কি না।
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
static volatile unsigned long sink; /* keeps the loop from being optimised away */
static int count_moves(int target) { /* busy work; how often are we elsewhere? */
int away = 0;
for (int i = 0; i < 5; i++) {
for (unsigned long k = 0; k < 20000000UL; k++) sink += k;
if (sched_getcpu() != target) away++;
}
return away;
}
int main(void) {
cpu_set_t allowed, one;
if (sched_getaffinity(0, sizeof allowed, &allowed) != 0) { perror("sched_getaffinity"); return 1; }
printf("online CPUs: %ld, CPUs I may use: %d\n", sysconf(_SC_NPROCESSORS_ONLN), CPU_COUNT(&allowed));
printf("before: running on CPU %d\n", sched_getcpu());
int target = 0; /* want CPU 0, else the first allowed one */
while (target < CPU_SETSIZE - 1 && !CPU_ISSET(target, &allowed)) target++;
CPU_ZERO(&one);
CPU_SET(target, &one);
if (sched_setaffinity(0, sizeof one, &one) != 0) { perror("sched_setaffinity"); return 1; }
int away = count_moves(target);
sched_getaffinity(0, sizeof allowed, &allowed);
printf("pinned to CPU %d, CPUs I may use now: %d\n", target, CPU_COUNT(&allowed));
printf("after: running on CPU %d, checks on another CPU: %d of 5\n", sched_getcpu(), away);
return 0;
}একটি আসল রানের আউটপুট (অনলাইন container-এ, যেখানে ২টি CPU):
online CPUs: 2, CPUs I may use: 2
before: running on CPU 0
pinned to CPU 0, CPUs I may use now: 1
after: running on CPU 0, checks on another CPU: 0 of 5
প্রথম লাইন: container-এ ২টি CPU, আর প্রোগ্রাম দুটিই ব্যবহার করতে পারত — affinity-র সেটে দুটি বিট। before: শুরুর CPU scheduler-এর ইচ্ছা; আরেকটি রানে এসেছিল running on CPU 1. pinned: sched_setaffinity(0, …)-এর প্রথম আর্গুমেন্ট 0 মানে “আমি নিজে” (আসলে ডাকা thread-টি)। এখন সেটে একটিই CPU. শুরুতে CPU 1-এ থাকলে kernel এই কলের ভেতরেই তাকে CPU 0-তে সরিয়ে দেয় — কল ফেরার সময় সে আর পুরোনো CPU-তে নেই।
after: পাঁচবারের পরীক্ষায় একবারও অন্য CPU-তে পাওয়া যায়নি (0 of 5) — hard affinity কঠোর। (sink volatile, যাতে compiler লুপটি মুছে না দেয়।) আরেকটি সূক্ষ্মতা: affinity fork()-এ child পায় আর exec()-এর পরেও থাকে; এজন্যই taskset একটি প্রোগ্রাম চালু করার আগে affinity বসিয়ে দিতে পারে। CPU 0 অনুমোদিত না হলে লুপটি অনুমোদিত প্রথম CPU খুঁজে নিত (অনুশীলন ৩)।
একই তথ্য shell থেকে দেখা যায়। nproc দেখায় এই প্রসেস কতগুলো CPU ব্যবহার করতে পারে, taskset -p দেখায় affinity-র mask (hex-এ, প্রতিটি বিট একটি CPU), আর /proc/<pid>/status-এর Cpus_allowed_list একই কথা তালিকা আকারে বলে।
echo "nproc : $(nproc)"
grep Cpus_allowed_list /proc/self/status # grep's own allowed CPUs
taskset -p $$ # this shell's affinity mask (hex)
taskset -cp $$ # the same mask as a CPU list
taskset -c 0 grep Cpus_allowed_list /proc/self/status # a child started pinned to CPU 0
ps -o pid,psr,comm -p $$ # PSR = CPU it last ran on
chrt -p $$ # scheduling policy of this shell
chrt -f 10 true 2>&1 || echo "-> SCHED_FIFO refused (exit code $?)"একটি আসল রানের আউটপুট:
nproc : 2
Cpus_allowed_list: 0-1
pid 2's current affinity mask: 3
pid 2's current affinity list: 0,1
Cpus_allowed_list: 0
PID PSR COMMAND
2 0 bash
pid 2's current scheduling policy: SCHED_OTHER
pid 2's current scheduling priority: 0
chrt: failed to set pid 0's policy: Operation not permitted
-> SCHED_FIFO refused (exit code 1)
Mask 3 = বাইনারি 11 = CPU 0 আর CPU 1 দুটিই অনুমোদিত, যা তালিকা আকারে 0,1 আর /proc-এ 0-1. পঞ্চম লাইনে taskset -c 0 দিয়ে চালানো grep নিজের status পড়ে দেখাচ্ছে শুধু 0 — child জন্ম থেকেই বাঁধা। PSR কলাম জানায় shell শেষবার কোন CPU-তে চলেছিল (এটি রানভেদে 0 বা 1 হতে পারে)।
শেষ দুটি লাইন এই পর্বের দ্বিতীয় ভাগের পূর্বাভাস। Shell চলছে সাধারণ নীতি SCHED_OTHER-এ (Linux-এর fair scheduler, পর্ব ২১)। chrt -f 10 দিয়ে real-time নীতি SCHED_FIFO চাওয়া হলে kernel ফিরিয়ে দিয়েছে: Operation not permitted. সাধারণ ব্যবহারকারী real-time অগ্রাধিকার নিতে পারে না — নইলে যে কেউ একটি অসীম লুপ দিয়ে পুরো কম্পিউটার দখল করে নিত। অনলাইন container-এ এটি নিষেধ, নিজের মেশিনেও sudo ছাড়া সাধারণত নিষেধ (অংশ ৮)।
Hard affinity সাবধানে: সব thread একটি core-এ বেঁধে দিলে বাকি core অলস বসে থাকে, আর load balancer আইনত কিছুই করতে পারে না। AI বা ফোরামের “performance বাড়াতে সবকিছু pin করুন” ধরনের পরামর্শ প্রায়ই উল্টো ফল দেয়। আগে মাপুন, তারপর শুধু যেখানে প্রমাণিত লাভ, সেখানে বাঁধুন।
PS> $p = Get-Process notepad
PS> $p.ProcessorAffinity # bit mask, e.g. 255 = CPUs 0-7
255
PS> $p.ProcessorAffinity = 1 # hard affinity: CPU 0 only
PS> cmd /c start /affinity 3 notepad.exe # start a new notepad on CPUs 0 and 1 (mask 0x3)৩ · Load balancing — push, pull, work stealing আর NUMA
প্রতি CPU-র আলাদা run queue নিলে অসমতা আসবেই। কেউ নতুন প্রসেস চালু করল, কেউ ডিস্কের অপেক্ষায় ঘুমাল, কেউ শেষ হলো — কিছুক্ষণ পর দেখা যায় এক CPU-তে পাঁচজন অপেক্ষা করছে আর পাশের CPU কিছুই করছে না। Load balancing-এর কাজ এই অসমতা মেটানো। দুটি মৌলিক কৌশল:
- Push migration — নির্দিষ্ট সময় পরপর (যেমন timer tick-এর সাথে) একটি পরীক্ষা চলে; কোনো CPU বেশি ভারী হলে তার queue থেকে task ঠেলে হালকা CPU-তে পাঠানো হয়।
- Pull migration — যে CPU অলস হয়ে পড়ল, সে বসে না থেকে নিজেই সবচেয়ে ব্যস্ত CPU-র queue থেকে একটি task টেনে আনে।
দুটি একসাথেই চলে। Linux-এ নির্দিষ্ট সময় পরপর balance পরীক্ষা হয়, আর কোনো CPU-র queue খালি হওয়া মাত্র “newly idle” balance চেষ্টা করে কাজ টেনে আনতে। কোন task সরাবে, সেটিও হিসাবের ব্যাপার: যে task সবেমাত্র চলেছে (“cache-hot”), তাকে সরালে cache-এর ক্ষতি বেশি, তাই সম্ভব হলে অন্যকে বাছা হয়; আর যার hard affinity অন্য CPU নিষেধ করে, তাকে তো ছোঁয়াই যায় না। নিচের stepper-এ দুটি CPU-র পুরো ঘটনা দেখুন।
1start: CPU0 runs A, CPU1 runs E2E calls read() on disk -> blocks3CPU1 newly idle -> pull from busiest4D starts on CPU1 (cold cache)5disk done: E wakes -> back to CPU16periodic balance: 3 vs 2 -> no move
| CPU0 চলছে | |
| CPU0 queue | |
| CPU1 চলছে | |
| CPU1 queue | |
| runnable: CPU0 / CPU1 | |
| blocked |
- শুরু। CPU0 চালাচ্ছে A, তার queue-তে অপেক্ষায় B, C আর D. B-কে কেউ
taskset -c 0দিয়ে CPU0-তে বেঁধে দিয়েছে (hard affinity). CPU1 চালাচ্ছে E, তার queue খালি। Runnable: ৪ বনাম ১. - E ডিস্ক থেকে পড়তে গিয়ে block হলো। E এখন blocked অবস্থায়, কোনো run queue-তে নেই। CPU1-এর queue খালি, তাই সে অলস। ওদিকে CPU0-তে তিনজন লাইনে — load imbalance.
- Pull migration. অলস হওয়া মাত্র CPU1 সবচেয়ে ব্যস্ত CPU (CPU0) খোঁজে আর তার queue থেকে একজনকে টানতে চায়। B বাঁধা — নেওয়া যাবে না। C সবেমাত্র চলেছিল, তার cache CPU0-তে উষ্ণ — সরালে ক্ষতি বেশি। তাই বেছে নেওয়া হলো D. এখন হিসাব ৩ বনাম ১.
- D CPU1-এ চলে — কিন্তু ঠান্ডা cache নিয়ে। D-এর ডেটা CPU1-এর L1/L2-তে নেই। প্রথম কিছুক্ষণ cache miss বেশি। Migration-এর দাম আছে, কিন্তু অলস CPU-র দাম আরও বেশি।
- E জেগে উঠল — soft affinity. ডিস্কের কাজ শেষ, E আবার runnable. E শেষবার CPU1-এ চলেছিল, তার কিছু ডেটা সেখানকার cache-এ এখনো থাকতে পারে, তাই তাকে CPU1-এর queue-তেই রাখা হলো। হিসাব ৩ বনাম ২.
- নির্দিষ্ট সময় পরের balance-পরীক্ষা (push-এর সুযোগ)। CPU0-তে ৩, CPU1-এ ২. পার্থক্য মাত্র ১ — আরেকজনকে সরালে উল্টো দিকে ২ বনাম ৩ হতো, সঙ্গে একটি উষ্ণ cache নষ্ট। তাই কেউ সরল না। Balancer-এর লক্ষ্য নিখুঁত সমতা নয়, লাভজনক সমতা।
Work stealing — runtime-এর ভেতরের একই ধারণা
Pull migration-এর একটি জনপ্রিয় রূপ প্রোগ্রামের runtime-এ দেখা যায়: work stealing. প্রতিটি worker thread-এর নিজস্ব কাজের সারি (deque); সে নিজের সারির সামনের দিক থেকে কাজ নেয়, আর কারও সারি খালি হলে সে অন্য worker-এর সারির পেছন থেকে কাজ চুরি করে। পেছন থেকে নেওয়ায় দুজনের ঠোকাঠুকি কম হয়। ১৯৯০-এর দশকে MIT-র Cilk প্রকল্প এর তাত্ত্বিক ভিত্তি দেয়; আজ Go-র goroutine scheduler, Java-র ForkJoinPool আর Rust-এর Tokio এই কৌশলে চলে।
NUMA — সব মেমরি সমান দূরে নয়
বড় সার্ভারে দুই বা ততোধিক processor socket থাকে, আর প্রতিটির সাথে আলাদা মেমরি-চ্যানেল। এমন কম্পিউটারকে বলে NUMA (non-uniform memory access): একটি CPU নিজের node-এর মেমরি দ্রুত পায়, অন্য socket-এর মেমরি পেতে আন্তঃসংযোগ (interconnect) পেরোতে হয় — সাধারণত লক্ষণীয়ভাবে ধীর, আর ব্যান্ডউইথও কম। তখন affinity-র প্রশ্ন শুধু cache-এর নয়, মেমরিরও: Linux-এর ডিফল্ট নিয়মে একটি page প্রথম যে CPU ছোঁয়, তার node-এই বরাদ্দ হয় (“first touch”). তাই একটি thread-কে অন্য socket-এ সরালে তার পুরো মেমরি দূরে পড়ে যায়।
এজন্য NUMA-সচেতন scheduler balance করার সময় আগে একই core-এর ভেতরে, তারপর একই socket-এ, আর একেবারে শেষে অন্য socket-এ task সরায় — Linux এই স্তরগুলোকে বলে scheduling domain. Linux-এর automatic NUMA balancing task আর তার page-কে কাছাকাছি আনতে চেষ্টা করে; হাতে নিয়ন্ত্রণের জন্য আছে numactl --cpunodebind=0 --membind=0 ./db.
$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
node 0 size: 64326 MB
node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
node 1 size: 64507 MB
node distances:
node 0 1
0: 10 21
1: 21 10node distances একটি আপেক্ষিক মাপ (নিজের node = ১০), ন্যানোসেকেন্ড নয়; এখানে অন্য node প্রায় দ্বিগুণ “দূরে”। সাধারণ ল্যাপটপে একটিই node থাকে।
৪ · Multicore, SMT আর ভিন্ন-স্বভাবের core
Memory stall আর hardware thread
একটি multicore চিপে প্রতিটি core একটি পূর্ণ CPU. কিন্তু core-গুলো তাদের সময়ের একটি বড় অংশ অপেক্ষায় কাটায়: ডেটা cache-এ না থাকলে মেমরি থেকে আসতে প্রায় ১০০ ন্যানোসেকেন্ড লাগতে পারে — আধুনিক CPU-র কয়েকশো clock cycle. এই অপেক্ষার নাম memory stall. মেমরি-ছড়ানো কাজে (ডেটাবেস, গ্রাফ, বড় hash table) core-এর বড় অংশ সময় এভাবে নষ্ট হয়।
সমাধান: একটি core-এ দুই (বা বেশি) সেট register রাখা — অর্থাৎ দুটি hardware thread. একটি thread মেমরির জন্য থেমে থাকলে core অন্যটির নির্দেশ চালায়। এর নাম SMT (simultaneous multithreading); Intel-এর বাজারি নাম Hyper-Threading, যা ২০০২ সালে সার্ভার প্রসেসরে আসে। OS প্রতিটি hardware thread-কে একটি আলাদা logical CPU দেখে — ৮-core, ১৬-thread চিপে nproc বলবে ১৬.
এক hardware thread: T1 ███▒▒▒▒▒▒███▒▒▒▒▒▒███ (▒ = memory stall, core বসে আছে)
দুই hardware thread: T1 ███▒▒▒▒▒▒███▒▒▒▒▒▒███
T2 ███▒▒▒▒▒▒███▒▒▒▒▒▒███ T1-এর stall-এর সময় core T2 চালায়
দুটি hardware thread মানে দুটি পূর্ণ core নয় — তারা execution unit, L1 আর L2 cache ভাগ করে। কাজের ধরনভেদে লাভ খুব বেশি থেকে প্রায় শূন্য, এমনকি ঋণাত্মকও হতে পারে (দুজনই একই unit চাইলে)। তাই scheduler-এর জন্য দুটি নিয়ম দাঁড়ায়: (১) হাতে যথেষ্ট খালি physical core থাকলে দুটি ব্যস্ত task-কে একই core-এর দুই sibling-এ না রেখে আলাদা core-এ রাখা; (২) একই core-এর sibling-এর মধ্যে task সরানো প্রায় বিনামূল্যে, কারণ cache ভাগের।
ভিন্ন-স্বভাবের core — big.LITTLE আর P/E core
ফোনের চিপে সব core সমান নয়। ARM ২০১১ সালে big.LITTLE ধারণা ঘোষণা করে: কয়েকটি বড়, দ্রুত, বিদ্যুৎ-খেকো core আর কয়েকটি ছোট, ধীর, সাশ্রয়ী core এক চিপে। এটিই heterogeneous multiprocessing. গান শোনা বা নোটিফিকেশন দেখার মতো হালকা কাজ ছোট core-এ চললে ব্যাটারি বাঁচে, আর গেম বা ক্যামেরার প্রসেসিং চায় বড় core. Intel ২০২১ সালের Alder Lake (১২তম প্রজন্ম Core) থেকে ডেস্কটপ আর ল্যাপটপেও একই পথ নেয়: Performance-core (P-core) আর Efficient-core (E-core). Apple-এর M আর A-সিরিজ চিপেও একই ভাগ।
এখন scheduler-এর প্রশ্ন শুধু “কোন CPU খালি” নয়, “কোন ধরনের CPU এই task-এর উপযুক্ত”. Linux-এ এর উত্তরের বড় অংশ Energy Aware Scheduling (EAS), যা মূল kernel-এ এসেছে Linux 5.0 (২০১৯)-এ: প্রতিটি core-এর ক্ষমতা আর বিদ্যুৎ-খরচের মডেল দেখে task রাখা হয়। Intel হার্ডওয়্যারে Thread Director নামের একটি অংশ যোগ করেছে, যা চলমান thread-এর নির্দেশের ধরন দেখে OS-কে ইঙ্গিত দেয় কোন thread P-core-এ বেশি লাভবান হবে; Windows 11 এই ইঙ্গিত ব্যবহার করে, আর Linux-এ এর সমর্থন ধাপে ধাপে যুক্ত হচ্ছে — কোন kernel সংস্করণে কতটুকু, তা নিজের সিস্টেমে মিলিয়ে নেওয়াই নিরাপদ। macOS-এ অ্যাপ core বাছে না, কাজের QoS শ্রেণি জানায় (ভাবনার প্রশ্ন ৪)।
Gang scheduling — একসাথে চালানোর প্রয়োজন
কিছু প্রোগ্রামের thread-গুলো ঘন ঘন একে অপরের জন্য অপেক্ষা করে — যেমন একটি সমান্তরাল হিসাব, যেখানে প্রতিটি ধাপের শেষে সবাই একটি barrier-এ মিলিত হয়। সঙ্গী queue-তে অপেক্ষায় থাকলে চলমান thread barrier-এ বৃথা ঘোরে। Gang scheduling (co-scheduling) এই দলকে একই মুহূর্তে আলাদা আলাদা CPU-তে একসাথে চালায়, আর একসাথে থামায়। ধারণাটি ১৯৮২ সালে John Ousterhout-এর কাজে প্রস্তাবিত; আজ এটি বেশি দেখা যায় বড় সমান্তরাল (HPC) ক্লাস্টার আর কিছু hypervisor-এ, যেখানে একটি virtual machine-এর সব virtual CPU কাছাকাছি সময়ে চালানো দরকার।
৫ · Real-time scheduling-এর ভিত্তি — সময়মতো, গড়ে নয়
পর্ব ২-এ দেখেছেন, real-time সিস্টেম মানে “খুব দ্রুত” নয় — মানে নির্দিষ্ট সময়ের মধ্যে, নিশ্চিতভাবে। hard real-time-এ একটি deadline মিস করাও ব্যর্থতা (airbag, পেসমেকার, বিমানের নিয়ন্ত্রণ); soft real-time-এ মিস হলে মান কমে, কিন্তু সিস্টেম ভাঙে না (ভিডিও কল, গান বাজানো)। Real-time-এর মাপকাঠি গড় নয়, সবচেয়ে খারাপ ক্ষেত্রের সময়।
Latency — ঘটনা থেকে সাড়া পর্যন্ত
একটি সেন্সর সংকেত দিল (যেমন গাড়ির চাকা পিছলে যাচ্ছে)। সাড়া শুরু হওয়া পর্যন্ত মোট সময়কে বলে event latency. এর ভেতরে কয়েকটি টুকরো:
ঘটনা ──► interrupt latency ──► ISR চলে ──► dispatch latency ─────────────────► real-time task চলে
(চলতি নির্দেশ শেষ, (task-কে │ conflict phase │ dispatch │
interrupt বন্ধ থাকলে জাগায়) │ kernel-এর চলতি কাজ │ context │
তার অপেক্ষা) │ preempt; নিচু task-এর │ switch │
│ ধরা resource ছাড়া │ │
- Interrupt latency — interrupt CPU-তে পৌঁছানো থেকে তার ISR শুরু পর্যন্ত। Kernel লম্বা সময় interrupt বন্ধ রাখলে এটি বাড়ে।
- Dispatch latency — scheduler-এর এক task থামিয়ে real-time task চালু করার সময় (পর্ব ৯). এর দুটি অংশ: conflict phase — kernel-এর ভেতরে চলতে থাকা কাজকে preempt করা, আর নিচু অগ্রাধিকারের task-এর ধরে রাখা resource (যেমন একটি lock) ছেড়ে দেওয়ানো; তারপর dispatch phase — আসল context switch.
এখান থেকে দুটি নকশা-নিয়ম আসে: kernel-কে preemptible হতে হবে (নইলে একটি লম্বা system call শেষ না হওয়া পর্যন্ত জরুরি task অপেক্ষা করবে), আর lock-এর ক্ষেত্রে priority inversion ঠেকাতে হবে — নিচু task-এর ধরা lock-এর জন্য উঁচু task আটকে থাকা, যার প্রতিকার priority inheritance (পর্ব ২৯-এ বিস্তারিত)।
Periodic task model
Real-time-এর অনেক কাজ নির্দিষ্ট ছন্দে আসে: প্রতি ১০ ms-এ মোটরের গতি পড়া, প্রতি ২০ ms-এ একটি অডিও বাফার পূরণ। বাস টার্মিনালের সময়সূচির মতো — প্রতি আধা ঘণ্টায় একটি বাস ছাড়ে, আর প্রতিটি বাস পরের বাস ছাড়ার আগেই প্ল্যাটফর্ম খালি করতে হবে। এই ধরনের কাজকে বলে periodic task, আর পাঠ্যবইয়ের মডেলে প্রতিটি task-এর তিনটি সংখ্যা:
| চিহ্ন | নাম | অর্থ |
|---|---|---|
| p | Period | কত সময় পরপর task-এর নতুন একটি job আসে (০, p, ২p, …) |
| c | Execution time | প্রতিটি job-এর সবচেয়ে খারাপ ক্ষেত্রে কত CPU সময় লাগে |
| d | Deadline | আসার কত সময়ের মধ্যে শেষ হতে হবে; এই পর্বে সর্বত্র d = p (পরের job আসার আগে) |
| U = c/p | Utilisation | Task-টি CPU-র কত ভাগ সময় নেয় |
সব task-এর মোট utilisation U = Σ ci/pi. প্রথম, সবচেয়ে সরল সত্য: এক CPU-তে U > ১ হলে কোনো নীতিই সব deadline রাখতে পারবে না — সময়ের চেয়ে কাজ বেশি। আর আগমনের নকশা প্রতি hyperperiod-এ (সব period-এর লসাগু) হুবহু আবার শুরু হয়, তাই একটি hyperperiod ধরে schedule যাচাই করলেই চলে। কোনো task-গুচ্ছ একটি নীতিতে সব deadline রাখবে কি না, তা আগেই হিসাব করে বলার পদ্ধতিকে বলে schedulability test, আর real-time OS নতুন task নেওয়ার আগে এমন পরীক্ষা চালালে তাকে বলে admission control.
৬ · Rate Monotonic — ছোট period, উঁচু অগ্রাধিকার
১৯৭৩ সালে C. L. Liu আর James W. Layland-এর একটি প্রবন্ধ (Journal of the ACM-এ) real-time scheduling-এর ভিত্তি স্থাপন করে। প্রথম নীতিটি হলো Rate Monotonic (RM): প্রতিটি task পায় একটি স্থির অগ্রাধিকার — যার period যত ছোট (অর্থাৎ rate বা আগমনের হার যত বেশি), তার অগ্রাধিকার তত উঁচু। চলার সময় সবচেয়ে উঁচু অগ্রাধিকারের ready job চলে, আর উঁচু কেউ এলে নিচুকে preempt করে। অগ্রাধিকার কখনো বদলায় না — তাই এটি fixed-priority scheduling.
ছোট period-এর task-এর deadline সবসময় কাছে, তাই সে আগে। Liu আর Layland প্রমাণ করেন, deadline = period হলে সব fixed-priority নীতির মধ্যে RM অনুকূলতম: কোনো task-গুচ্ছ কোনো স্থির অগ্রাধিকার-ক্রমে schedule করা গেলে RM ক্রমেও করা যাবে।
Liu–Layland সীমা
| n | ১ | ২ | ৩ | ৪ | ৫ | ১০ | n → ∞ |
|---|---|---|---|---|---|---|---|
| সীমা | ১.০০০ | ০.৮২৮ | ০.৭৮০ | ০.৭৫৭ | ০.৭৪৩ | ০.৭১৮ | ln ২ ≈ ০.৬৯৩ |
এই Liu–Layland bound সম্পর্কে দুটি কথা পরীক্ষায় বারবার আসে। এক, এটি যথেষ্ট শর্ত, আবশ্যক নয়: U সীমার নিচে হলে নিশ্চিত নিরাপদ; সীমার উপরে হলে উত্তর “জানি না” — সেখানে আরও সূক্ষ্ম পরীক্ষা লাগে। দুই, সীমাটি কখনো ln ২-এর নিচে নামে না, তাই যেকোনো সংখ্যক task-এর মোট U ≤ ০.৬৯৩ হলে RM-এ নিরাপদ।
প্রমাণের রূপরেখা — দুটি task-এর ক্ষেত্রে ০.৮২৮ কোথা থেকে
পুরো প্রমাণ লম্বা, কিন্তু n = ২-এর ক্ষেত্রটি এক অনুচ্ছেদে বোঝা যায়। দুটি ধাপ লাগে।
ধাপ ১ — critical instant: Fixed-priority-তে একটি task-এর সবচেয়ে খারাপ response time হয় তখন, যখন সে আর তার চেয়ে উঁচু সব task একই মুহূর্তে আসে। কারণ উঁচু কারও আগমন পিছিয়ে দিলে আমাদের task-এর সামনে বাধা কেবল কমতে পারে, বাড়তে পারে না। তাই t = ০-তে সবাই একসাথে এলে প্রতিটি task-এর প্রথম job-টি deadline রাখলেই যথেষ্ট।
ধাপ ২ — সবচেয়ে কম U-তে “পুরো ঠাসা” গুচ্ছ খোঁজা: T1-এর period p1 ছোট, T2-এর p2, আর ধরুন p1 < p2 ≤ ২p1. Critical instant থেকে p2 সময়ের মধ্যে T1 আসে দুবার (০ আর p1-এ)। এমন c বাছি যাতে CPU ঠিক p2-তে গিয়ে পুরো ঠাসা হয় — অর্থাৎ c একটুও বাড়ালে T2 deadline হারাবে: c1 = p2 − p1 (T1-এর দ্বিতীয় job ঠিক p2-তে শেষ), আর c2 = p2 − ২c1 = ২p1 − p2. এখন r = p2/p1 (১ < r ≤ ২) লিখলে
এর সর্বনিম্ন মান খুঁজতে অবকলন শূন্য করি: ১ − ২/r² = ০ ⇒ r = √২. তখন U = √২ + √২ − ২ = ২(√২ − ১) ≈ ০.৮২৮. অর্থাৎ পুরো ঠাসা দুই-task গুচ্ছের মধ্যে সবচেয়ে কম U ০.৮২৮; এর চেয়ে কম U-র কোনো গুচ্ছ পুরো ঠাসা হতে পারে না, তাই deadline-ও হারায় না। Liu আর Layland একই যুক্তি n টি task-এ বাড়িয়ে n(২১/n − ১) পান (period-অনুপাতগুলো সব ২১/n হলে সবচেয়ে খারাপ)। p2 > ২p1-এর ক্ষেত্রও একই সীমায় নামিয়ে আনা যায় — বিস্তারিত তাঁদের প্রবন্ধে।
সীমা পেরোলে — response-time analysis
সীমার উপরের গুচ্ছের জন্য একটি সঠিক (আবশ্যক ও যথেষ্ট) পরীক্ষা আছে: response-time analysis (RTA). Critical instant থেকে task i-এর response time Ri হলো তার নিজের ci, আর ওই সময়ের মধ্যে উঁচু অগ্রাধিকারের প্রতিটি task j যতবার আসে, ততবার তার cj:
শুরু R = Σ c (নিজের আর উঁচু সবার), তারপর নতুন R বারবার ডান দিকে বসানো — মান না বদলালে থামা। Ri ≤ di হলে নিরাপদ।
এবার তিনটি সমাধান করা উদাহরণ — একটি সীমার নিচে, একটি সীমার উপরে কিন্তু নিরাপদ, আর একটি যেখানে সত্যিই deadline মিস হয়।
সমাধান করা উদাহরণ ১ — সীমার নিচে, তাই নিশ্চিত
প্রশ্ন: A (c = ১, p = ৪), B (c = ১, p = ৫), C (c = ২, p = ১০). RM-এ সব deadline রক্ষা হবে কি?
সমাধান: U = ১/৪ + ১/৫ + ২/১০ = ০.২৫ + ০.২০ + ০.২০ = ০.৬৫. n = ৩-এর সীমা ০.৭৮০. ০.৬৫ ≤ ০.৭৮০, তাই হ্যাঁ, নিশ্চিত — আর কোনো হিসাব লাগে না।
সমাধান করা উদাহরণ ২ — সীমা পেরিয়েছে, তবু নিরাপদ
প্রশ্ন: A (c = ১, p = ৪), B (c = ২, p = ৬), C (c = ৩, p = ১২). RM-এ সব deadline রক্ষা হবে কি?
সমাধান: U = ০.২৫০ + ০.৩৩৩ + ০.২৫০ = ০.৮৩৩ > ০.৭৮০ — সীমার পরীক্ষা কিছুই বলতে পারছে না। RM অগ্রাধিকার: A (p ৪) > B (p ৬) > C (p ১২). এবার RTA:
- A: RA = ১ ≤ ৪ ✔ (কেউ উঁচু নেই)
- B: শুরু ৩ → ২ + ⌈৩/৪⌉·১ = ৩ → স্থির। RB = ৩ ≤ ৬ ✔
- C: নিচের stepper-এ ধাপে ধাপে — ফল RC = ১০ ≤ ১২ ✔
1R0 = 1 + 2 + 3 = 62R1 = 3 + ceil(6/4)*1 + ceil(6/6)*2 = 73R2 = 3 + ceil(7/4)*1 + ceil(7/6)*2 = 94R3 = 3 + ceil(9/4)*1 + ceil(9/6)*2 = 105R4 = 3 + ceil(10/4)*1 + ceil(10/6)*2 = 10
| বসানো R | |
| A-এর আগমন × c | |
| B-এর আগমন × c | |
| নতুন R |
- শুরুর অনুমান। C নিজে ৩ চায়, আর অন্তত একবার করে A (১) আর B (২) তার আগে চলবে — critical instant-এ সবাই t = ০-তে এসেছে। তাই R0 = ১ + ২ + ৩ = ৬.
- R = ৬ বসাই। ০ থেকে ৬-এর মধ্যে A আসে ⌈৬/৪⌉ = ২ বার (০ আর ৪-এ), মোট ২; B আসে ⌈৬/৬⌉ = ১ বার, মোট ২. R1 = ৩ + ২ + ২ = ৭. অনুমান বাড়ল, তাই আবার।
- R = ৭ বসাই। A: ⌈৭/৪⌉ = ২ বার → ২. B: ⌈৭/৬⌉ = ২ বার (০ আর ৬-এ) → ৪. R2 = ৩ + ২ + ৪ = ৯. B-এর দ্বিতীয় job এই জানালায় ঢুকে পড়ল।
- R = ৯ বসাই। A: ⌈৯/৪⌉ = ৩ বার (০, ৪, ৮) → ৩. B: ⌈৯/৬⌉ = ২ → ৪. R3 = ৩ + ৩ + ৪ = ১০.
- R = ১০ বসাই — স্থির। A: ⌈১০/৪⌉ = ৩ → ৩; B: ⌈১০/৬⌉ = ২ → ৪; R4 = ১০, আগের মতোই। পুনরাবৃত্তি থামল: RC = ১০ ≤ ১২. U = ০.৮৩৩ সীমা ছাড়ালেও গুচ্ছটি RM-এ নিরাপদ — টাইমলাইন এঁকে দেখলেও C ঠিক t = ১০-এ শেষ হয় (অংশ ৭-এর simulator-এ দ্বিতীয় গুচ্ছ)।
সমাধান করা উদাহরণ ৩ — RM সত্যিই deadline হারায়
প্রশ্ন: P1 (c = ২৫, p = ৫০), P2 (c = ৩৫, p = ৮০). RM-এ কী হবে?
সমাধান: U = ২৫/৫০ + ৩৫/৮০ = ০.৫০০ + ০.৪৩৭৫ = ০.৯৩৭৫ > ০.৮২৮ — সীমা কিছু বলে না। P1-এর period ছোট, তাই P1 উঁচু। RTA: R2 শুরু ৬০ → ৩৫ + ⌈৬০/৫০⌉·২৫ = ৩৫ + ৫০ = ৮৫ → ৩৫ + ⌈৮৫/৫০⌉·২৫ = ৮৫, স্থির। R2 = ৮৫ > ৮০ — P2 deadline হারাবে। উল্টো অগ্রাধিকার দিলে (P2 উঁচু) কী হয়? তখন R1 = ২৫ + ⌈R/৮০⌉·৩৫: শুরু ৬০ → ২৫ + ৩৫ = ৬০ > ৫০ — এবার P1 হারায়। অর্থাৎ কোনো স্থির অগ্রাধিকারেই এই গুচ্ছ চলে না, যদিও U < ১. টাইমলাইনে দেখুন:
1t=0 : P1#1, P2#1 arrive -> P1 (p 50) first2t=25: P1#1 done -> P2#1 runs3t=50: P1#2 arrives -> preempts P2#14t=75: P1#2 done -> P2#1 resumes5t=80: P2#1 deadline, 5 ms still left
| t | |
| CPU-তে | |
| P1-এর job | |
| P2-এর job |
- t = ০: দুজনেই আসে। RM অগ্রাধিকার: P1-এর period ৫০ ছোট, তাই P1 উঁচু। P1#1 চলে, ২৫ ms লাগবে, deadline ৫০. P2#1 অপেক্ষায় (৩৫ ms, deadline ৮০)।
- t = ২৫: P1#1 শেষ (deadline ৫০-এর অনেক আগে)। এবার P2#1 চলে। তার বাকি ৩৫.
- t = ৫০: P1#2 এল — আর RM তাকে আগে চালায়। P2#1 ততক্ষণে ২৫ ms পেয়েছে, বাকি ১০. কিন্তু P1-এর স্থির অগ্রাধিকার উঁচু, তাই সে P2#1-কে preempt করে। খেয়াল করুন: P1#2-এর deadline ১০০, অথচ P2#1-এর ৮০ — যার তাড়া বেশি, RM তাকে চিনতে পারছে না।
- t = ৭৫: P1#2 শেষ, P2#1 ফিরে আসে। বাকি ১০ ms, কিন্তু deadline ৮০ পর্যন্ত হাতে মাত্র ৫ ms.
- t = ৮০: deadline মিস। P2#1 পেয়েছে ২৫ + ৫ = ৩০ ms, দরকার ছিল ৩৫. বাকি ৫ ms — RTA-র হিসাবেও R2 = ৮৫ ছিল, অর্থাৎ ঠিক ৫ ms দেরি। Hard real-time-এ একটি মিসই ব্যর্থতা।
৭ · EDF — যার deadline সবচেয়ে কাছে, সে আগে
RM-এর দুর্বলতা উদাহরণ ৩-এ স্পষ্ট: t = ৫০-এ P2#1-এর deadline ছিল ৮০, P1#2-এর ১০০ — তবু স্থির অগ্রাধিকারের কারণে P1 আগে গেল। ঈদের আগের রাতে দর্জি যে অর্ডার কাল সকালে দিতে হবে, সেটি আগে সেলাই করেন, পরের সপ্তাহেরটি পরে। এই নীতির নাম Earliest Deadline First (EDF): প্রতি মুহূর্তে, ready job-গুলোর মধ্যে যার absolute deadline সবচেয়ে কাছে, সে চলে। নতুন job এলে তুলনা আবার হয়, আর দরকার হলে preempt.
একটি task-এর অগ্রাধিকার এখানে স্থির নয় — একই P1 কখনো P2-এর আগে, কখনো পরে, নির্ভর করে তাদের এই মুহূর্তের job-এর deadline-এর ওপর। তাই EDF হলো dynamic-priority scheduling. একই P1/P2 গুচ্ছ এবার EDF-এ দেখুন:
1t=0 : P1#1 d=50, P2#1 d=80 -> P12t=25 : P1#1 done -> P2#13t=50 : P1#2 d=100 vs P2#1 d=80 -> P2 stays4t=60 : P2#1 done (d=80 ok) -> P1#25t=80 : P2#2 d=160 vs P1#2 d=100 -> P1 stays6t=85 : P1#2 done (d=100 ok) -> P2#27t=100: P1#3 d=150 vs P2#2 d=160 -> P1 preempts8t=125: P1#3 done -> P2#2, done at 145
| t | |
| CPU-তে | |
| P1-এর job | |
| P2-এর job |
- t = ০. P1#1-এর deadline ৫০, P2#1-এর ৮০. ৫০ কাছে, তাই P1 আগে — শুরুটা RM-এর মতোই।
- t = ২৫: P1#1 শেষ। P2#1 চলে, বাকি ৩৫, deadline ৮০.
- t = ৫০: এখানেই পার্থক্য। P1#2 এল, deadline ১০০. চলমান P2#1-এর deadline ৮০ — বেশি কাছে। তাই EDF P2-কেই চালিয়ে যায়, preemption নেই।
- t = ৬০: P2#1 শেষ, deadline ৮০-এর ২০ ms আগে। এখন একমাত্র ready job P1#2 (বাকি ২৫, deadline ১০০)।
- t = ৮০: P2#2 এল, deadline ১৬০. P1#2-এর বাকি ৫, deadline ১০০ — বেশি কাছে, তাই P1 চলতেই থাকে।
- t = ৮৫: P1#2 শেষ (deadline ১০০-এর আগে)। P2#2 শুরু, বাকি ৩৫.
- t = ১০০: P1#3 এল, deadline ১৫০. P2#2-এর deadline ১৬০ — দূরে। এবার EDF preempt করে: P1#3 চলে; P2#2-এর বাকি ২০. অগ্রাধিকারের ক্রম সময়ের সাথে বদলাচ্ছে — এটিই dynamic priority.
- t = ১২৫: P1#3 শেষ; P2#2 ফিরে এসে ১৪৫-এ শেষ (deadline ১৬০)। ১৪৫–১৫০ CPU অলস। পুরো hyperperiod (৪০০ ms) চালালে মোট কাজ ৮ × ২৫ + ৫ × ৩৫ = ৩৭৫ ms, অলস ২৫ ms, আর একটিও deadline মিস হয় না — নিচের simulator তা দেখায়।
কেন EDF অনুকূলতম — exchange argument
উপপাদ্য: এক CPU-তে, preemption চালু থাকলে, কোনো job-গুচ্ছের জন্য যদি কোনো একটি schedule থাকে যেখানে সব deadline রক্ষা হয়, তাহলে EDF-ও সব deadline রক্ষা করবে। (এই ফল সাধারণত Dertouzos-এর ১৯৭৪ সালের কাজের সাথে যুক্ত; periodic task-এর জন্য Liu আর Layland ১৯৭৩-এ EDF-এর সীমা দেখান।)
প্রমাণের রূপরেখা: ধরুন S একটি সফল schedule, কিন্তু EDF নয়। সময়রেখা ধরে এগোলে প্রথম একটি মুহূর্ত t পাওয়া যাবে, যেখানে S চালাচ্ছে job Jb, অথচ তখন ready আছে আরেকটি job Ja, যার deadline আগে: da < db. S সফল, তাই Ja কোথাও না কোথাও t-এর পরে কিন্তু da-এর আগে চলে শেষ হয়। এখন অদলবদল করি: t-তে Jb-এর পাওয়া এক টুকরো সময় (δ) দিই Ja-কে, আর Ja-এর পরের কোনো এক টুকরো (যা da-এর আগে) দিই Jb-কে। ফল: Ja আগের চেয়ে দেরিতে শেষ হয় না; Jb-এর কাজ সরে গেল এমন সময়ে, যা da-এর আগে, আর da < db — তাই Jb-ও নিরাপদ। বাকি কারও কিছু বদলায়নি। এভাবে প্রতিটি “ভুল-ক্রমের” মুহূর্ত সামনে থেকে একে একে সারালে S বদলে EDF schedule হয়, আর কোনো ধাপে কোনো deadline ভাঙে না। ∎
Periodic task-এ (d = p) এর সরাসরি ফল: U ≤ ১ হলেই EDF সব deadline রাখে, আর U > ১ হলে কেউই পারে না (একটি hyperperiod H-এ কাজ U·H > H). অর্থাৎ EDF-এর schedulability test নিখুঁত — আবশ্যক এবং যথেষ্ট। RM-এর ক্ষেত্রে সীমা ০.৬৯৩ থেকে ১-এর মধ্যে, আর মাঝের এলাকায় RTA লাগে।
নিজে চালিয়ে দেখুন — RM আর EDF simulator
নিচের simulator প্রতিটি ms-এ সিদ্ধান্ত নেয়: RM-এ অগ্রাধিকার = period, EDF-এ অগ্রাধিকার = job-এর deadline (ছোট মানে উঁচু; সমান হলে তালিকার আগের task). পুরো hyperperiod চালিয়ে টাইমলাইন আর deadline মিসের তালিকা ছাপে, সঙ্গে Liu–Layland সীমার পরীক্ষা। Deadline-এ শেষ না হওয়া job-কে বাদ দেওয়া হয় (hard real-time-এ দেরির ফল কাজে লাগে না)। শেষ লাইনে নিজের task-গুচ্ছ বসিয়ে দেখুন।
// Periodic tasks: c = execution time, p = period, deadline = end of period (ms)
function analyse(tasks) {
const gcd = (a, b) => (b ? gcd(b, a % b) : a);
const H = tasks.reduce((h, t) => (h * t.p) / gcd(h, t.p), 1); // hyperperiod
const n = tasks.length, U = tasks.reduce((s, t) => s + t.c / t.p, 0);
const bound = n * (Math.pow(2, 1 / n) - 1);
console.log(tasks.map(t => `${t.name}(c=${t.c},p=${t.p})`).join(" ") + ` hyperperiod=${H}`);
console.log(`U=${U.toFixed(3)} RM bound=${bound.toFixed(3)} -> ` +
(U <= bound ? "RM surely OK" : "bound test inconclusive") + `; EDF needs U<=1: ${U <= 1}`);
for (const policy of ["RM", "EDF"]) {
const jobs = [], segs = [], misses = [];
for (let t = 0; t <= H; t++) {
for (const j of jobs) if (j.left > 0 && j.dl === t) { misses.push(`${j.id} at t=${t}`); j.left = 0; }
if (t === H) break;
tasks.forEach((k, i) => { if (t % k.p === 0) jobs.push({ i, id: `${k.name}#${t / k.p + 1}`, left: k.c, dl: t + k.p }); });
const key = j => (policy === "RM" ? tasks[j.i].p : j.dl); // smaller = higher priority
const ready = jobs.filter(j => j.left > 0).sort((a, b) => key(a) - key(b) || a.i - b.i);
const who = ready.length ? (ready[0].left--, ready[0].id) : "idle";
const last = segs[segs.length - 1];
if (last && last.who === who) last.end = t + 1; else segs.push({ who, start: t, end: t + 1 });
}
console.log(`${policy}: ` + segs.map(s => `${s.start}-${s.end} ${s.who}`).join(" | "));
console.log(` deadline misses: ${misses.length ? misses.join(", ") : "none"}`);
}
}
analyse([{ name: "P1", c: 25, p: 50 }, { name: "P2", c: 35, p: 80 }]);
analyse([{ name: "A", c: 1, p: 4 }, { name: "B", c: 2, p: 6 }, { name: "C", c: 3, p: 12 }]);আউটপুট (simulator নির্ধারিত, তাই প্রতিবার একই):
P1(c=25,p=50) P2(c=35,p=80) hyperperiod=400
U=0.938 RM bound=0.828 -> bound test inconclusive; EDF needs U<=1: true
RM: 0-25 P1#1 | 25-50 P2#1 | 50-75 P1#2 | 75-80 P2#1 | 80-100 P2#2 | 100-125 P1#3 | 125-140 P2#2 | 140-150 idle | 150-175 P1#4 | 175-200 P2#3 | 200-225 P1#5 | 225-235 P2#3 | 235-240 idle | 240-250 P2#4 | 250-275 P1#6 | 275-300 P2#4 | 300-325 P1#7 | 325-350 P2#5 | 350-375 P1#8 | 375-385 P2#5 | 385-400 idle
deadline misses: P2#1 at t=80
EDF: 0-25 P1#1 | 25-60 P2#1 | 60-85 P1#2 | 85-100 P2#2 | 100-125 P1#3 | 125-145 P2#2 | 145-150 idle | 150-175 P1#4 | 175-210 P2#3 | 210-235 P1#5 | 235-240 idle | 240-250 P2#4 | 250-275 P1#6 | 275-300 P2#4 | 300-325 P1#7 | 325-350 P2#5 | 350-375 P1#8 | 375-385 P2#5 | 385-400 idle
deadline misses: none
A(c=1,p=4) B(c=2,p=6) C(c=3,p=12) hyperperiod=12
U=0.833 RM bound=0.780 -> bound test inconclusive; EDF needs U<=1: true
RM: 0-1 A#1 | 1-3 B#1 | 3-4 C#1 | 4-5 A#2 | 5-6 C#1 | 6-8 B#2 | 8-9 A#3 | 9-10 C#1 | 10-12 idle
deadline misses: none
EDF: 0-1 A#1 | 1-3 B#1 | 3-4 C#1 | 4-5 A#2 | 5-6 C#1 | 6-8 B#2 | 8-9 A#3 | 9-10 C#1 | 10-12 idle
deadline misses: none
প্রথম গুচ্ছ: U = ০.৯৩৮, সীমা ০.৮২৮ — সীমার পরীক্ষা কিছু বলতে পারে না। RM-এর টাইমলাইনে 75-80 P2#1 তারপরই 80-100 P2#2 — অর্থাৎ ৮০-তে P2#1 অসমাপ্ত অবস্থায় বাদ পড়েছে, যা শেষ লাইনে ধরা: P2#1 at t=80. দুই stepper-এর সাথে হুবহু মেলে। EDF-এ 25-60 P2#1 — ৫০-এ preemption হয়নি — আর মিস none. EDF-এর অলস সময় যোগ করুন: ৫ + ৫ + ১৫ = ২৫ ms, ঠিক ৪০০ − ৩৭৫. ৩৫০-এ P1#8 আর P2#5-এর deadline দুটিই ৪০০ — সমান হলে তালিকার আগের task (P1) আগে।
দ্বিতীয় গুচ্ছ: উদাহরণ ২ — U = ০.৮৩৩ সীমা ছাড়িয়েছে, তবু RM-এ মিস নেই, আর C#1 শেষ হয়েছে ঠিক t = ১০-এ (9-10 C#1), যা RTA-র RC = ১০-এর সাথে মেলে। অনুশীলন ১ আর ২-এর গুচ্ছ বসিয়ে নিজের উত্তর মেলাতে পারেন।
তাহলে সবাই EDF ব্যবহার করে না কেন?
| দিক | Rate Monotonic | EDF |
|---|---|---|
| অগ্রাধিকার | স্থির — task-প্রতি একবার ঠিক হয় | পরিবর্তনশীল — প্রতিটি job-এর deadline অনুযায়ী |
| এক CPU-তে নিশ্চিত সীমা | U ≤ n(২১/n − ১), নিচে ln ২ ≈ ০.৬৯৩; সঠিক উত্তরে RTA | U ≤ ১ — আবশ্যক এবং যথেষ্ট |
| Overload (U > ১ বা c অনুমানের বেশি) | নিচু অগ্রাধিকারের task আগে হারায় — আগে থেকে জানা | Domino effect: একটি দেরি পরের অনেককে দেরি করায় |
শিল্পে fixed-priority এখনো বেশি প্রচলিত (ভাবনার প্রশ্ন ৩); তবে EDF-ও আছে — Linux-এর SCHED_DEADLINE, আর Zephyr-এর মতো কিছু RTOS-এ ঐচ্ছিকভাবে।
Proportional-share scheduling — এক নজরে
Real-time-এর আরেকটি দৃষ্টিভঙ্গি deadline নয়, ভাগ নিয়ে ভাবে। Proportional-share scheduling-এ মোট T টি share থাকে, আর একটি কাজ N টি share পেলে সে CPU-র N/T অংশ সময়ের নিশ্চয়তা পায়। ধরুন T = ১০০; A, B, C পেল ৫০, ১৫ আর ২০ — অর্থাৎ ৫০%, ১৫% আর ২০%. এখন D ৩০ share চাইলে মোট ১১৫ হয় — তাই admission control D-কে ফিরিয়ে দেয়। বিখ্যাত বাস্তবায়ন lottery scheduling (Waldspurger ও Weihl, ১৯৯৪): share-এর অনুপাতে টিকিট, প্রতি quantum-এ একটি টিকিট টানা। Linux-এর সাধারণ scheduler-এর nice-ভিত্তিক “weight”-ও আসলে একধরনের proportional share (পর্ব ২১)।
৮ · বাস্তব OS-এ — SCHED_FIFO, SCHED_RR আর SCHED_DEADLINE
POSIX মানদণ্ড দুটি real-time নীতি ঠিক করেছে, আর Linux একটি যোগ করেছে। সব real-time নীতিই সাধারণ (SCHED_OTHER, Linux-এর fair scheduler) task-এর আগে চলে — অর্থাৎ একটি runnable real-time task থাকলে সাধারণ কেউ সেই CPU পায় না।
| নীতি | অগ্রাধিকার | কীভাবে চলে | তত্ত্বের সাথে মিল |
|---|---|---|---|
| SCHED_DEADLINE (Linux 3.14, ২০১৪ থেকে) | সবার উপরে | Task জানায় runtime, deadline, period (ns-এ); kernel EDF দিয়ে চালায়, আর প্রত্যেককে তার runtime-এর মধ্যে আটকে রাখে (CBS) | EDF + admission control |
| SCHED_FIFO | ১–৯৯ (বেশি = উঁচু) | উঁচু অগ্রাধিকার আগে; একই অগ্রাধিকারে আগে আসা আগে; নিজে block/yield না করলে বা উঁচু কেউ না এলে চলতেই থাকে | Fixed priority — RM বসাতে period অনুযায়ী অগ্রাধিকার দিতে হয় |
| SCHED_RR | ১–৯৯ | FIFO-র মতো, তবে একই অগ্রাধিকারের task-গুলো একটি time slice পরপর পালা বদলায় (Linux-এ ডিফল্ট ১০০ ms) | Fixed priority + সমানদের মধ্যে Round Robin |
| SCHED_OTHER, SCHED_BATCH, SCHED_IDLE | ০ (ভেতরে nice দিয়ে weight) | সাধারণ fair scheduling (পর্ব ২১) | Proportional share-এর কাছাকাছি |
$ chrt -m # priority range of every policy
SCHED_OTHER min/max priority : 0/0
SCHED_FIFO min/max priority : 1/99
SCHED_RR min/max priority : 1/99
SCHED_BATCH min/max priority : 0/0
SCHED_IDLE min/max priority : 0/0
SCHED_DEADLINE min/max priority : 0/0
$ sudo chrt -f 50 ./motor_ctl # start with SCHED_FIFO, priority 50
$ sudo chrt -r -p 20 4242 # switch running PID 4242 to SCHED_RR, priority 20
$ chrt -p 4242
pid 4242's current scheduling policy: SCHED_RR
pid 4242's current scheduling priority: 20
# SCHED_DEADLINE: 2 ms of CPU every 10 ms, finished within 10 ms (all values in ns)
$ sudo chrt -d --sched-runtime 2000000 --sched-deadline 10000000 \
--sched-period 10000000 0 ./control_loop
$ cat /proc/sys/kernel/sched_rt_runtime_us /proc/sys/kernel/sched_rt_period_us
950000
1000000কয়েকটি বিষয় খেয়াল করুন। এক, অনুমতি: real-time নীতি নিতে root, CAP_SYS_NICE, বা RLIMIT_RTPRIO-এ অনুমোদিত সীমা লাগে — তাই অংশ ২-এ Operation not permitted. দুই, SCHED_DEADLINE-এর জন্য অগ্রাধিকার সবসময় 0, আর runtime ≤ deadline ≤ period হতে হয়; নতুন task নিলে মোট ব্যান্ডউইথ সীমা ছাড়ালে kernel admission control-এ অনুরোধ ফিরিয়ে দেয় (Device or resource busy). তিন, শেষ দুটি সংখ্যা RT throttling: ডিফল্টে প্রতি ১ সেকেন্ডে real-time task-গুলো সর্বোচ্চ ০.৯৫ সেকেন্ড পায়, যাতে একটি ভুল SCHED_FIFO লুপ পুরো সিস্টেম আটকে না দেয়; বাকি ৫% সময়ে অন্তত shell খুলে তাকে থামানো যায়।
প্রোগ্রামের ভেতর থেকে একই কাজ sched_setscheduler() দিয়ে হয় (POSIX), আর SCHED_DEADLINE-এর জন্য Linux-এর নিজস্ব sched_setattr():
struct sched_param sp = { .sched_priority = 50 };
if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0)
perror("sched_setscheduler"); /* EPERM for normal users */
/* real-time loops should also lock memory: page faults add latency */
mlockall(MCL_CURRENT | MCL_FUTURE);Windows-এ ধারণা কাছাকাছি, নাম আলাদা। Thread-এর অগ্রাধিকার ০–৩১; ১৬–৩১ হলো “real-time” স্তর, যা পাওয়া যায় প্রসেসকে REALTIME_PRIORITY_CLASS দিলে (প্রশাসকের অধিকার লাগে)। তবে Windows hard real-time OS নয় — driver-এর interrupt আর DPC-এর সময়ের কঠোর ঊর্ধ্বসীমা নেই। আর সত্যিকারের hard real-time-এর জন্য আলাদা RTOS — FreeRTOS, Zephyr, VxWorks, QNX — যেখানে সাধারণত fixed-priority preemptive scheduling, আর সবচেয়ে খারাপ ক্ষেত্রের latency মাপা ও নথিভুক্ত (পর্ব ৫৪)।
আরও গভীরে: একাধিক CPU-তে real-time আরও কঠিন। Global scheduling-এ (সব CPU একটি queue থেকে) EDF আর অনুকূলতম থাকে না — ১৯৭৮ সালে Dhall আর Liu দেখান, m টি CPU-তে মোট U প্রায় ১-এর একটু বেশি হলেই এমন গুচ্ছ বানানো যায় যা global EDF বা RM-এ deadline হারায় (Dhall's effect). Partitioned scheduling-এ (প্রতিটি task একটি CPU-তে বাঁধা, প্রতিটি CPU-তে আলাদা uniprocessor নীতি) সমস্যাটি bin packing-এ পরিণত হয়, যা NP-hard. Linux-এর SCHED_DEADLINE ডিফল্টে global EDF চালায়, আর cpuset দিয়ে partitioned করা যায়। এক CPU-তে RM-এর জন্য Liu–Layland-এর চেয়ে কড়া একটি সহজ পরীক্ষা আছে — hyperbolic bound (Bini, Buttazzo আর Buttazzo, ২০০১): Π(Ui + ১) ≤ ২; উদাহরণ ১-এ (১.২৫)(১.২)(১.২) = ১.৮ ≤ ২, আর এটিও শুধু যথেষ্ট শর্ত।
৯ · AI-এর যুগে multiprocessor আর real-time scheduling
“কোন core-এ pin করব?” বা “এই task-গুচ্ছ RM-এ চলবে কি?” — AI দ্রুত উত্তর দেয়, ভুলও করে আত্মবিশ্বাসী সুরে। সাধারণ ভুল আর যাচাইয়ের পথ:
| AI-এর ভুল | কেন ভুল | কীভাবে যাচাই করবেন |
|---|---|---|
| “U > ০.৬৯৩, তাই RM-এ চলবে না” | Liu–Layland শুধু যথেষ্ট শর্ত; সীমার উপরেও অনেক গুচ্ছ চলে (উদাহরণ ২) | RTA হাতে করুন, বা simulator-এ hyperperiod চালান |
| “EDF সবসময় RM-এর চেয়ে ভালো” | এক CPU-তে U ≤ ১ পর্যন্ত সত্য; overload আর একাধিক CPU-তে নয় | Overload-এর আচরণ আর CPU-সংখ্যা প্রশ্নে স্পষ্ট লিখুন |
Linux-এর taskset বা chrt macOS-এ দেওয়া | macOS-এ এগুলো নেই, hard affinity-র প্রচলিত API-ও নেই | OS আর সংস্করণ উল্লেখ করুন; man দেখুন |
“সব thread core 0-তে pin করুন”, “sudo chrt -f 99 দিন” বা “sched_rt_runtime_us-এ -1 লিখুন” | বাকি core অলস থাকে; ৯৯-এর FIFO লুপ kernel thread-কেও আটকায়; -1 লিখলে RT throttling-এর নিরাপত্তা-জাল চলে যায় | আগে মাপুন (perf, pidstat); সবচেয়ে কম অনুমতি দিয়ে চেষ্টা করুন |
| Gantt chart-এ ভুল — period আর deadline গুলিয়ে ফেলা, শেষ job ভুলে যাওয়া | প্রতিটি job-এর absolute deadline = আগমন + p | প্রতিটি job-এর আগমন আর deadline-এর একটি তালিকা বানিয়ে মেলান |
AI-কে real-time-এর প্রশ্ন করলে task-এর সংখ্যা, নীতি আর যাচাইয়ের দাবি একসাথে দিন:
AI-এর উত্তর মানার আগে — চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Soft আর hard affinity-র পার্থক্য? — soft: scheduler নিজে আগের CPU-তে রাখতে চেষ্টা করে, দরকারে সরায়; hard: প্রোগ্রাম নির্দিষ্ট CPU-সেট বেঁধে দেয় (
sched_setaffinity), তার বাইরে কখনো চলে না। - Push আর pull migration? — push: নির্দিষ্ট সময় পরপর ভারী CPU থেকে task ঠেলে পাঠানো; pull: অলস CPU নিজে ব্যস্ত CPU থেকে task টেনে আনা।
- RM-এর Liu–Layland সীমা কত, আর এটি কী ধরনের শর্ত? — U ≤ n(২১/n − ১), n = ২-এ ০.৮২৮, n → ∞-এ ln ২ ≈ ০.৬৯৩; যথেষ্ট শর্ত, আবশ্যক নয়।
- EDF কেন এক CPU-তে অনুকূলতম? — exchange argument: যেকোনো সফল schedule-কে deadline না ভেঙে ধাপে ধাপে EDF-এ বদলানো যায়; তাই U ≤ ১ হলেই EDF সফল।
- SCHED_FIFO আর SCHED_RR-এর পার্থক্য? — দুটিই স্থির অগ্রাধিকার (১–৯৯); RR-এ একই অগ্রাধিকারের task-গুলো time slice শেষে পালা বদলায়, FIFO-তে নিজে না ছাড়লে চলতেই থাকে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Linux কেন একটি ভাগের runqueue ছেড়ে প্রতি CPU-র আলাদা runqueue-তে গেল, আর এই সিদ্ধান্ত নতুন কোন সমস্যা তৈরি করল?
Linux 2.4-এর scheduler সরল ছিল: একটি বৈশ্বিক runqueue, আর পরের task বাছতে প্রতিবার পুরো তালিকা ঘুরে প্রত্যেকের একটি “goodness” মান হিসাব। ফলে দুটি সমস্যা হলো। প্রথমত, বাছাইয়ের খরচ runnable task-এর সংখ্যার সমানুপাতিক — O(n). দ্বিতীয়ত, আর multiprocessor-এ এটিই বড়: সব CPU একটি lock-এর জন্য লাইন দিত। চার বা আট CPU-র সার্ভারে, যেখানে হাজারো thread, CPU-গুলো একে অপরের অপেক্ষাতেই অনেক সময় নষ্ট করত, আর task যেকোনো CPU-তে গিয়ে পড়ায় cache-ও বারবার ঠান্ডা হতো।
২০০২ সালের দিকে Ingo Molnar-এর O(1) scheduler (Linux 2.5 উন্নয়ন-শাখায়, তারপর 2.6-এ) এর জবাব দেয়: প্রতিটি CPU-র নিজস্ব runqueue আর নিজস্ব lock, আর অগ্রাধিকার অনুযায়ী সাজানো তালিকা ও bitmap দিয়ে পরের task বাছাই স্থির সময়ে। Task স্বাভাবিকভাবেই নিজের CPU-তে থাকে — soft affinity বিনা খরচে। পরে CFS (২০০৭) আর EEVDF (২০২৩) বাছাইয়ের নীতি বদলেছে, কিন্তু per-CPU কাঠামো রেখে দিয়েছে।
নতুন সমস্যা: queue-গুলো অসমান হয়ে যায়, আর কেউ তা নিজে থেকে মেটায় না। তাই Linux-কে একটি জটিল load balancer বানাতে হলো। আধুনিক হার্ডওয়্যারে “দূরত্ব” স্তরে স্তরে: একই core-এর দুই SMT sibling (cache ভাগ করে, সরানো প্রায় বিনামূল্যে), একই socket-এর আলাদা core (L3 ভাগ), আর আলাদা NUMA node (মেমরিও দূরে)। Linux এই স্তরগুলোকে scheduling domain হিসেবে সাজায় — নিচের স্তরে ঘন ঘন আর সহজে balance, উপরের স্তরে কম আর সাবধানে। ভুল হলে দুই রকম ক্ষতি: খুব কম balance করলে অলস CPU আর লম্বা লাইন একসাথে থাকে; খুব বেশি করলে task লাফালাফি করে, cache আর NUMA-র ক্ষতি বাড়ে। গবেষণায় দেখা গেছে, এমন balancer-এর সূক্ষ্ম বাগে কোনো core দীর্ঘক্ষণ অলস থাকতে পারে, অথচ অন্য core-এ লাইন জমে।
মূল উপলব্ধি: একটি বাধা সরালে আরেকটি সামনে আসে। Per-CPU queue lock-এর ভিড় মেটায় আর cache বাঁচায়, কিন্তু ভারসাম্যের দায় নিয়ে আসে — আর ভালো scheduler সেই দায় হার্ডওয়্যারের দূরত্ব বুঝে মেটায়।
প্র ০২Hyper-threading কখন লাভ দেয়, কখন ক্ষতি করে, আর নিরাপত্তার কারণে কিছু OS কেন একে বন্ধ বা সীমিত করেছে?
SMT-এর মূল যুক্তি অংশ ৪-এ দেখেছেন: একটি hardware thread memory stall-এ থেমে থাকলে core অন্যটির নির্দেশ চালায়। যে কাজে stall বেশি — ডেটাবেস, ওয়েব সার্ভার, compile, অনেক ছোট ছোট অনুরোধ — সেখানে একই core থেকে উল্লেখযোগ্য বাড়তি throughput পাওয়া যায়। কিন্তু যে কাজ ইতিমধ্যেই core-এর execution unit পুরো ব্যস্ত রাখে (ঘন ম্যাট্রিক্স গুণ, ভালোভাবে সাজানো বৈজ্ঞানিক হিসাব), সেখানে দুটি thread একই unit আর একই L1/L2 cache-এর জন্য কাড়াকাড়ি করে; লাভ প্রায় শূন্য, এমনকি প্রতিটি thread আলাদাভাবে ধীর হয়। এজন্য কিছু HPC ক্লাস্টার SMT বন্ধ রাখে।
নিরাপত্তার দিকটি এসেছে ২০১৮ সালে। Spectre/Meltdown-এর পর আরও কিছু ত্রুটি (যেমন L1TF আর MDS পরিবার) দেখাল, একই core-এর দুই sibling-এর একটি কিছু অবস্থায় অন্যটির ডেটার আভাস পেতে পারে, কারণ তারা ভেতরের buffer আর cache ভাগ করে। ২০১৮ সালে OpenBSD ডিফল্টে hyper-threading বন্ধ করে দেয়। Linux আর ক্লাউড প্রদানকারীরা অন্য পথ নেয়: দুটি অবিশ্বস্ত পক্ষের (যেমন দুই গ্রাহকের virtual machine) কোড যেন কখনো একই core-এর দুই sibling-এ একসাথে না চলে। Linux 5.14 (২০২১)-এ যুক্ত হয় core scheduling — একই “cookie”-ওয়ালা, পরস্পর বিশ্বস্ত task-গুলোই কেবল একই core-এ একসাথে চলবে। লক্ষ করুন, এটি আসলে gang scheduling-এর উল্টো পিঠ: কাদের একসাথে চালানো যাবে না, সেই নিয়ম।
Scheduler-এর দৃষ্টিতে SMT তাই দুই রকম তথ্য চায়: কোন logical CPU কোন physical core-এর (topology), আর কোন task কার সাথে sibling ভাগ করতে পারে (নিরাপত্তা)। Linux-এ lscpu topology দেখায়।
মূল উপলব্ধি: SMT বিনামূল্যের core নয় — ভাগ করা সম্পদের ওপর একটি বাজি। কাজের ধরন ঠিক করে বাজি লাভের কি না, আর নিরাপত্তার প্রয়োজন ঠিক করে কাকে কার পাশে বসানো যাবে।
প্র ০৩তত্ত্বে EDF অনুকূলতম (U ≤ ১ পর্যন্ত), RM নয়। তবু গাড়ি, বিমান আর শিল্প-যন্ত্রের RTOS-এ fixed-priority scheduling-ই বেশি কেন?
প্রথম কারণ overload-এর আচরণ। বাস্তবে c-এর মান একটি অনুমান — সবচেয়ে খারাপ ক্ষেত্রের execution time (WCET) মাপা কঠিন, আর কখনো একটি task অনুমানের চেয়ে বেশি চলে, বা একটি অপ্রত্যাশিত interrupt-ঝড় আসে। RM-এ তখন কে ভুগবে, আগে থেকেই জানা: সবচেয়ে নিচু অগ্রাধিকারের task. উঁচু অগ্রাধিকারের গুরুত্বপূর্ণ নিয়ন্ত্রণ-লুপ অক্ষত থাকে। EDF-এ overload হলে একটি দেরি-হওয়া job-এর deadline “সবচেয়ে কাছে” থেকে যায়, সে চলতেই থাকে, আর পরের job-গুলোও একে একে দেরি করে — domino effect; কোন task হারাবে, তা আঁচ করা কঠিন। নিরাপত্তা-প্রকৌশলে আঁচ করতে পারাটাই সবচেয়ে মূল্যবান।
দ্বিতীয় কারণ সরলতা আর ঐতিহ্য। Fixed priority বাস্তবায়নে একটি অগ্রাধিকার-সংখ্যা আর একটি bitmap-ই যথেষ্ট; প্রায় সব RTOS — FreeRTOS, VxWorks, QNX, আর গাড়ির OSEK/AUTOSAR OS — এই মডেলে তৈরি। তাছাড়া RM-এর পক্ষে বড় তাত্ত্বিক কাঠামো জমেছে: Lehoczky, Sha ও Ding-এর ১৯৮৯ সালের কাজ দেখায়, এলোমেলোভাবে বানানো task-গুচ্ছে RM গড়ে প্রায় ৮৮% utilisation পর্যন্ত চলে — অর্থাৎ ০.৬৯৩-এর সীমা অনেক রক্ষণশীল, আর RTA দিয়ে সঠিক উত্তর পাওয়া যায়।
তৃতীয় কারণ সার্টিফিকেশন। বিমানের সফটওয়্যারে DO-178C বা গাড়িতে ISO 26262-এর মতো মানদণ্ডে প্রমাণ দিতে হয় যে সব খারাপ পরিস্থিতিতেও সময় রক্ষা হবে। Fixed-priority-র বিশ্লেষণ-পদ্ধতি আর টুল দীর্ঘদিনের পরীক্ষিত, তাই সেই পথে প্রমাণ দেওয়া তুলনামূলক সহজ।
তবে EDF হারিয়ে যায়নি। যেখানে CPU-র প্রতিটি শতাংশ মূল্যবান আর overload নিয়ন্ত্রিত, সেখানে EDF-এর ১০০% পর্যন্ত ব্যবহার বড় সুবিধা। Linux-এর SCHED_DEADLINE এই ঝুঁকি কমায় একটি কৌশলে: প্রতিটি task-কে তার ঘোষিত runtime-এর বেশি চলতে দেওয়া হয় না (CBS), ফলে একজনের overload অন্যদের ক্ষতি করে না।
মূল উপলব্ধি: “অনুকূলতম” শব্দটি একটি নির্দিষ্ট মডেলের ভেতরে সত্য। বাস্তবের অনিশ্চয়তা, overload আর প্রমাণের দায় যোগ করলে সরল ও আঁচযোগ্য নীতি প্রায়ই জিতে যায়।
প্র ০৪Intel-এর P-core/E-core আর ARM-এর big.LITTLE scheduler-এর কাছে কোন নতুন প্রশ্ন নিয়ে এল, আর OS কেন একা এর উত্তর দিতে পারে না?
সমান core-এর জগতে load balancing-এর লক্ষ্য সহজ: সব CPU-তে সমান লাইন। ভিন্ন-স্বভাবের core-এ “সমান” কথাটাই অর্থহীন — একটি E-core-এ দুটি task আর একটি P-core-এ একটি task হলে কোনটি বেশি ভারী? এখন scheduler-কে তিনটি নতুন প্রশ্নের উত্তর দিতে হয়: task-টি কতটা গুরুত্বপূর্ণ (ব্যবহারকারী কি এর জন্য অপেক্ষা করছেন?), কোন core-এ সে কতটা দ্রুত চলবে, আর কোন core-এ কম বিদ্যুতে কাজ শেষ হবে।
ARM-এর জগতে Linux প্রথমে task-এর সাম্প্রতিক CPU-ব্যবহার মেপে (load tracking) ঠিক করত সে “বড়” কাজ কি না; শেষে Energy Aware Scheduling মূল Linux-এ আসে (5.0, ২০১৯): চিপ-নির্মাতা প্রতিটি core-এর বিভিন্ন গতিতে বিদ্যুৎ-খরচের একটি টেবিল দেন, আর scheduler task রাখার আগে হিসাব করে কোথায় রাখলে মোট বিদ্যুৎ কম লাগবে, অথচ কাজ ধীর হবে না।
Intel-এর Alder Lake (২০২১) একটি অন্য সমস্যা দেখাল: একই নির্দেশ-ধারা P-core আর E-core-এ ভিন্ন অনুপাতে দ্রুত হতে পারে — ভেক্টর-হিসাবে পার্থক্য বেশি, সাধারণ শাখা-ভরা কোডে কম। OS শুধু CPU-ব্যবহার দেখে এটি বুঝতে পারে না; তাই Intel হার্ডওয়্যারে Thread Director যোগ করে, যা চলমান কোডের ধরন পর্যবেক্ষণ করে OS-কে ইঙ্গিত দেয়। Windows 11 এটি শুরু থেকে ব্যবহার করে। শুরুর দিকে কিছু গেমের কপি-সুরক্ষা ব্যবস্থা E-core দেখে বিভ্রান্ত হওয়ার খবরও এসেছিল, যা পরে হালনাগাদে সারানো হয়। macOS-এ Apple অন্য পথে গেছে: অ্যাপ জানায় কাজের QoS (user-interactive, utility, background…), আর OS সেই অনুযায়ী core বাছে; background কাজ সাধারণত efficiency core-এ।
এখানে একটি বড় শিক্ষা: scheduler-এর ভালো সিদ্ধান্তের জন্য তিন পক্ষের তথ্য লাগে — হার্ডওয়্যার (কোন core কেমন, এই কোড কোথায় দ্রুত), OS (কে runnable, কে কোথায় চলেছে), আর অ্যাপ (কোন কাজ ব্যবহারকারীর চোখের সামনে)। কোনো এক পক্ষ একা সব জানে না।
মূল উপলব্ধি: ভিন্ন-স্বভাবের core scheduling-কে “কোথায় জায়গা খালি” থেকে “কোথায় এই কাজ সবচেয়ে উপযুক্ত” প্রশ্নে বদলে দিয়েছে — আর এর উত্তর আসে হার্ডওয়্যার, OS আর অ্যাপের সহযোগিতায়।
প্র ০৫Linux কি hard real-time OS হতে পারে? PREEMPT_RT, SCHED_DEADLINE আর বিশেষ RTOS-এর মধ্যে বাছাই কীভাবে করবেন?
সাধারণ Linux-এর লক্ষ্য throughput আর গড় সাড়া; এর ভেতরে বহু জায়গায় kernel কোড কিছুক্ষণ preemption বা interrupt বন্ধ রাখে, আর সেই সময়টুকু একটি জরুরি task-এর dispatch latency বাড়ায়। বহু বছর ধরে আলাদা patch-গুচ্ছ PREEMPT_RT এই জায়গাগুলো ছোট করেছে: বেশিরভাগ spinlock-কে ঘুমাতে-পারা lock বানানো, interrupt handler-কে thread বানানো (যাতে তাদেরও অগ্রাধিকার দেওয়া যায়), আর lock-এ priority inheritance. প্রায় দুই দশকের কাজের পর ২০২৪ সালে Linux 6.12-এ এটি মূল kernel-এ যুক্ত হয়। এতে সবচেয়ে খারাপ ক্ষেত্রের latency সাধারণত অনেক কমে আর পূর্বানুমেয় হয় — তবে কতটা, তা হার্ডওয়্যার, driver আর কনফিগারেশনের ওপর নির্ভর করে। তাই প্রকৌশলীরা নিজের বোর্ডে cyclictest-এর মতো টুল দিয়ে লোডের মধ্যে ঘণ্টার পর ঘণ্টা latency মাপেন, গড় নয়, সর্বোচ্চ মানটি দেখেন।
Real-time Linux-এ সাধারণ কৌশলগুলো: জরুরি task-কে SCHED_FIFO বা SCHED_DEADLINE; তার জন্য আলাদা core (isolcpus, cpuset) আর সেই core-এ অন্য interrupt না পাঠানো; mlockall() দিয়ে মেমরি আটকে রাখা, যাতে page fault না হয়।
কিন্তু সবচেয়ে কঠোর ক্ষেত্রে — পেসমেকার, ব্রেক-নিয়ন্ত্রণ, বিমানের flight control — এখনো ছোট RTOS বা বিশেষভাবে সার্টিফাইড kernel বেশি প্রচলিত। কারণ Linux-এর কোড-ভিত্তি বিশাল, সব driver-এর সবচেয়ে খারাপ আচরণ প্রমাণ করা প্রায় অসম্ভব, আর DO-178C বা ISO 26262-এর মতো মানদণ্ডে প্রমাণের খরচ বিশাল। অনেক নকশা তাই মিশ্র: একই চিপে Linux চালায় UI, আর আলাদা ছোট core-এ RTOS চালায় নিয়ন্ত্রণ — অংশ ১-এর আধুনিক অর্থের AMP. ১৯৯৭ সালের Mars Pathfinder-এর ঘটনা (VxWorks-এ priority inversion-এর ফলে বারবার reset, পরে priority inheritance চালু করে সমাধান) মনে করিয়ে দেয়, RTOS-ও সঠিক কনফিগারেশন ছাড়া নিরাপদ নয়।
মূল উপলব্ধি: “Real-time” একটি সংখ্যার প্রতিশ্রুতি — কত µs-এর মধ্যে, কোন লোডে, কত নিশ্চয়তায়। প্রয়োজনীয় সংখ্যা, প্রমাণের দায় আর খরচ মিলিয়ে ঠিক করতে হয় PREEMPT_RT-Linux যথেষ্ট, নাকি আলাদা RTOS লাগবে।
অনুশীলন
হাতে হিসাব, Gantt chart, আউটপুট অনুমান আর কমান্ড — আগে নিজে চেষ্টা করুন, তারপর উত্তর মেলান।
-
হাতে হিসাব: দুটি periodic task, deadline = period: T1 (c = ২, p = ৫), T2 (c = ৪, p = ৭). (ক) মোট U কত? (খ) Liu–Layland পরীক্ষা কী বলে? (গ) RTA দিয়ে T2-এর response time বের করুন — RM-এ deadline রক্ষা হবে? (ঘ) EDF-এ?
(ক) U = ২/৫ + ৪/৭ = ০.৪০০ + ০.৫৭১ = ০.৯৭১.
(খ) n = ২-এর সীমা ০.৮২৮; ০.৯৭১ বেশি — পরীক্ষা কিছুই বলতে পারে না (এর মানে “অচল” নয়)।
(গ) RM-এ T1 উঁচু (p ৫ < ৭)। R1 = ২ ≤ ৫. T2: শুরু ২ + ৪ = ৬ → ৪ + ⌈৬/৫⌉·২ = ৪ + ৪ = ৮ → ৪ + ⌈৮/৫⌉·২ = ৮, স্থির। R2 = ৮ > ৭ — T2 deadline হারায়। টাইমলাইন: ০–২ T1, ২–৫ T2, ৫–৭ T1 → t = ৭-এ T2-এর ১ ms বাকি।
(ঘ) U ≤ ১, তাই EDF-এ সব deadline রক্ষা। প্রথম অংশ: ০–২ T1 (d ৫), ২–৬ T2 (৫-এ T1#2-এর d ১০, T2#1-এর d ৭ — T2 চলতে থাকে), ৬–৮ T1#2. অংশ ৭-এর simulator-এ এই গুচ্ছ বসালে hyperperiod ৩৫-এ RM-এর মিস
T2#1 at t=7, আর EDF-এnone. -
Gantt chart আঁকুন: X (c = ২, p = ৪), Y (c = ৩, p = ৬), deadline = period, সবাই t = ০-তে শুরু। Hyperperiod পর্যন্ত RM আর EDF-এর Gantt chart আঁকুন (deadline সমান হলে X আগে)। কোনটিতে মিস হয়, কখন? CPU কতক্ষণ অলস থাকে?
U = ২/৪ + ৩/৬ = ১.০, hyperperiod = লসাগু(৪, ৬) = ১২.
RM : │X 0-2│Y 2-4│X 4-6│ ✗ Y#1 has 1 ms left at its deadline t=6 EDF: │X 0-2│Y 2-5│X 5-7│Y 7-8│X 8-10│Y 10-12│ no miss, no idle timeRM: X-এর period ছোট, তাই সবসময় উঁচু। t = ৪-এ X#2 এসে Y#1-কে (২ ms পেয়েছে) সরিয়ে দেয়; t = ৬-এ Y#1-এর ১ ms বাকি — মিস। (RTA: RY = ৩ + ⌈R/৪⌉·২: ৫ → ৭ → ৭ > ৬.) EDF: t = ৪-এ X#2-এর d = ৮, Y#1-এর d = ৬ — Y চলতে থাকে, ৫-এ শেষ। t = ৬-এ Y#2 (d ১২) এলেও X#2 (d ৮) চলে। t = ৮-এ X#3 আর Y#2 দুজনের d = ১২ — নিয়ম অনুযায়ী X আগে (৮–১০), তারপর Y#2 (১০–১২)। U = ১, তাই ১২ ms-এর পুরোটাই কাজ, অলস সময় ০.
-
আউটপুট অনুমান: অংশ ২-এর
pin_cpu.cএকটি ২-CPU Linux মেশিনে compile করেtaskset -c 1 ./pinদিয়ে চালানো হলো। চারটি লাইনে কী ছাপা হবে? কোন সংখ্যাগুলো নিশ্চিত, আর কোনটি রানভেদে বদলাতে পারে?online CPUs: 2, CPUs I may use: 1 before: running on CPU 1 pinned to CPU 1, CPUs I may use now: 1 after: running on CPU 1, checks on another CPU: 0 of 5taskset -c 1প্রোগ্রাম শুরুর আগেই affinity {1} বসিয়ে দেয়, আরexec()-এর পরেও তা থাকে। তাইonline CPUs২ (মেশিনে আছে), কিন্তুCPUs I may use১. শুরু থেকেই সে কেবল CPU 1-এ চলতে পারে, তাইbefore-এ নিশ্চিতভাবে 1. লুপ CPU 0 খোঁজে, অনুমোদিত সেটে না পেয়ে পরের অনুমোদিত CPU 1 বেছে নেয়, আরsched_setaffinityসফল হয় (একই সেট)। এখানে চারটি লাইনই নিশ্চিত — রানভেদে বদলানোর সুযোগ নেই, কারণ hard affinity একটিমাত্র CPU রেখেছে। (অনলাইন container-এgccদিয়ে compile করে ঠিক এই আউটপুটই এসেছে।)tasksetছাড়া চালালে শুধুbeforeলাইনের সংখ্যা রানভেদে 0 বা 1 হতে পারত। -
কমান্ড লিখুন: (ক)
./serverচালু করুন শুধু CPU 2 আর 3-এ। (খ) চলমান PID 4242-কে শুধু CPU 0-তে সরান। (গ) PID 4242 কোন CPU-গুলোতে চলতে পারে, তালিকা আকারে দেখুন (দুটি উপায়)। (ঘ)./ctl-কে SCHED_FIFO অগ্রাধিকার ৪০-এ চালু করুন। (ঙ) Windows PowerShell-এ চলমানnotepad-কে CPU 0 আর 1-এ সীমিত করুন।taskset -c 2,3 ./server # (a) or: taskset -c 2-3 ./server taskset -cp 0 4242 # (b) taskset -cp 4242 # (c) way 1 grep Cpus_allowed_list /proc/4242/status # (c) way 2 sudo chrt -f 40 ./ctl # (d) (Get-Process notepad).ProcessorAffinity = 3 # (e) PowerShell: mask 0b11 = CPU 0, 1(খ)-তে অন্যের প্রসেস হলে অনুমতি লাগে (নিজের প্রসেসে সাধারণত লাগে না)। (ঘ)-তে
sudoবা CAP_SYS_NICE লাগবে — অনলাইন container-এ এটিOperation not permittedদেবে। অগ্রাধিকার ৯৯ না দিয়ে প্রয়োজনমতো মাঝারি মান দেওয়াই নিরাপদ, যাতে জরুরি kernel thread-গুলোর জায়গা থাকে। (ঙ)-এProcessorAffinityএকটি bit mask: ৩ = বাইনারি ১১.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি সেন্সরের সংকেত থেকে real-time task-এর সাড়া পর্যন্ত ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- সেন্সর সংকেত দেয় — ঘটনা ঘটল
- Interrupt CPU-তে পৌঁছায়; চলতি নির্দেশ শেষ হয় (interrupt latency)
- ISR চলে আর real-time task-কে runnable করে
- Conflict phase: kernel-এর চলতি কাজ preempt হয়, নিচু task-এর ধরা resource ছাড়া হয়
- Dispatch phase: context switch করে real-time task-কে CPU-তে বসানো হয়
- Real-time task সাড়া দেওয়ার কাজ শুরু করে
দুটি periodic task-এর (deadline = period) জন্য Rate Monotonic-এর Liu–Layland সীমা প্রায় কত?
- ০.৬৯৩
- ০.৮২৮
- ০.৭৮০
- ১.০০০
তিনটি task-এর মোট utilisation ০.৮৩৩, আর n = ৩-এর Liu–Layland সীমা ০.৭৮০. Rate Monotonic সম্পর্কে কোন সিদ্ধান্তটি সঠিক?
- RM-এ অবশ্যই কোনো deadline মিস হবে
- RM-এ সব deadline নিশ্চিতভাবে রক্ষা হবে
- সীমার পরীক্ষা থেকে সিদ্ধান্ত হয় না; response-time analysis দিয়ে যাচাই করতে হবে
- U < ১, তাই RM-এও নিশ্চিত চলবে
P1 (c = ২৫, p = ৫০) আর P2 (c = ৩৫, p = ৮০), deadline = period, এক CPU. কোন বিবৃতিটি সঠিক?
- RM-এ P2 প্রথম deadline (t = ৮০) হারায়, কিন্তু EDF-এ কোনো deadline মিস হয় না
- RM আর EDF দুটিতেই deadline মিস হয়
- RM আর EDF দুটিতেই সব deadline রক্ষা হয়
- EDF-এ মিস হয়, কিন্তু RM-এ হয় না
প্রতি CPU-র আলাদা run queue রাখার প্রধান অসুবিধা কোনটি, যা মেটাতে load balancing লাগে?
- সব CPU-কে একটি lock-এর জন্য অপেক্ষা করতে হয়
- Task বারবার নতুন CPU-তে গিয়ে cache ঠান্ডা পায়
- Hard affinity আর কাজ করে না
- কোনো queue-তে লম্বা লাইন আর অন্য CPU অলস — অসমতা তৈরি হতে পারে
একটি core-এ দুটি hardware thread (SMT বা Hyper-Threading) রাখার মূল উদ্দেশ্য কী?
- একটি core-কে দুটি পূর্ণ core-এর সমান গতি দেওয়া
- একটি thread memory stall-এ থেমে থাকলে অন্য thread চালিয়ে core ব্যস্ত রাখা
- ছোট core-এ কাজ পাঠিয়ে বিদ্যুৎ বাঁচানো
- দুটি প্রসেসকে পরস্পর থেকে আরও নিরাপদে আলাদা রাখা
Linux-এর SCHED_FIFO আর SCHED_RR-এর মধ্যে পার্থক্য কোনটি?
- SCHED_RR-এ task deadline জানায়, SCHED_FIFO-তে জানায় না
- শুধু SCHED_FIFO real-time, SCHED_RR সাধারণ নীতি
- SCHED_RR-এ একই অগ্রাধিকারের task-গুলো time slice শেষে পালা বদলায়, SCHED_FIFO-তে বদলায় না
- SCHED_FIFO-তে উঁচু অগ্রাধিকারের task-ও নিচুকে preempt করতে পারে না
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- SMP + প্রতি CPU-র run queue: lock-এর ভিড় কম, cache উষ্ণ; দাম load imbalance, যা মেটায় push আর pull migration (runtime-এ work stealing).
- Affinity: soft = scheduler-এর চেষ্টা; hard =
sched_setaffinity/taskset-এর কঠোর সেট,forkআরexec-এর পরেও থাকে। NUMA-তে মেমরিও দূরে যায়; SMT stall লুকায়, কিন্তু পূর্ণ core নয়। - Periodic task: (c, p), d = p, U = Σ c/p; U > ১ হলে এক CPU-তে কেউ পারে না; একটি hyperperiod দেখাই যথেষ্ট।
- RM: ছোট period = উঁচু স্থির অগ্রাধিকার; U ≤ n(২১/n − ১) (২ → ০.৮২৮, ∞ → ০.৬৯৩) যথেষ্ট, আবশ্যক নয়; সঠিক উত্তর RTA: R = c + Σ⌈R/pj⌉cj.
- EDF: সবচেয়ে কাছের deadline আগে; এক CPU-তে U ≤ ১ হলেই সফল (exchange argument). Linux-এ SCHED_DEADLINE (EDF + CBS), আর SCHED_FIFO/SCHED_RR স্থির অগ্রাধিকার ১–৯৯.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২১ · Linux-এর scheduler — CFS, EEVDF, nice আর cgroupপরবর্তী পর্বPer-CPU runqueue-র ভেতরে আসলে কী চলে।
- অপারেটিং সিস্টেম · কোর্স হোম৬৪টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- পর্ব ০৯ · Context switch আর dispatcherপেছনে ফিরেDispatch latency, cache আর TLB-এর খরচ — migration-এর দাম বোঝার ভিত্তি।
- পর্ব ১৪ · থ্রেড — কেন, কীভাবে আর threading modelপেছনে ফিরেMulticore-এ thread কীভাবে সমান্তরালে চলে।
- DSA · Heap আর priority queueডেটা স্ট্রাকচারEDF আর priority scheduling-এর queue-এর পেছনের ডেটা স্ট্রাকচার।
- C প্রোগ্রামিং · Multithreadingহাতে-কলমেএকাধিক core-এ thread চালানোর C অনুশীলন।