এই পর্বে যা শিখবেন
- CPU–I/O burst চক্র, burst-এর দৈর্ঘ্যের সাধারণ বণ্টন, আর CPU-bound বনাম I/O-bound প্রসেস চেনা — নিজের C প্রোগ্রামে মেপে
- Scheduler ঠিক কোন চারটি মুহূর্তে সিদ্ধান্ত নেয়, আর preemptive ও non-preemptive scheduling-এর পার্থক্য ও দাম
- প্রতিটি মাপকাঠির নিখুঁত সংজ্ঞা ও সূত্র, কোনটি বাড়াতে আর কোনটি কমাতে হয়, আর লক্ষ্যগুলোর পরস্পর-বিরোধ
- Arrival time আর idle ফাঁকসহ Gantt chart আঁকা, আর CT, TAT, WT, RT, গড়, throughput ও utilisation পুরোটা হিসাব করা
- Scheduling algorithm বিচারের চার পদ্ধতি — Little-এর সূত্রের প্রমাণ আর অঙ্কসহ — আর সামনের algorithm-গুলোর মানচিত্র
১ · CPU–I/O burst চক্র — প্রসেস আসলে কীভাবে চলে
একটি ব্যাংকের শাখার কথা ভাবুন। টোকেন নিয়ে গ্রাহকেরা বসে আছেন, কাউন্টার একটি। কেউ এসেছেন শুধু একটি চেক জমা দিতে — কাউন্টারে দুই মিনিট, তারপর চলে যান বা পাশের ডেস্কে স্বাক্ষর যাচাইয়ের অপেক্ষায় বসেন। কেউ এসেছেন পুরো একটি ঋণের ফাইল নিয়ে — কাউন্টারে আধা ঘণ্টা। কাকে আগে ডাকবেন, তা ঠিক করতে আগে জানতে হবে গ্রাহকেরা সাধারণত কতক্ষণ কাউন্টার দখল করেন। CPU-র ক্ষেত্রেও প্রথম প্রশ্ন এটিই।
পর্ব ৮-এ দেখেছেন, একটি প্রসেস running, ready আর waiting অবস্থার মধ্যে ঘোরে। একটু দূর থেকে দেখলে প্রায় সব প্রসেসের জীবন একই ছন্দে চলে: কিছুক্ষণ CPU-তে হিসাব, তারপর কোনো I/O-র অপেক্ষা (ফাইল পড়া, নেটওয়ার্ক থেকে উত্তর, কিবোর্ডের চাপ), তারপর আবার হিসাব। হিসাবের একটানা অংশকে বলে CPU burst, অপেক্ষার একটানা অংশকে I/O burst. প্রসেস শুরু হয় একটি CPU burst দিয়ে, আর শেষও হয় একটি CPU burst দিয়ে — শেষটিতে সে exit() ডাকে। এই ছন্দের নাম CPU–I/O burst cycle.
Scheduler-এর কাছে এর গুরুত্ব সরাসরি: প্রসেস একটি I/O burst-এ ঢুকলেই CPU খালি হয়, আর তখন অন্য কাউকে দিতে হয়। আর একটি প্রসেস কতটা সময় একটানা CPU চাইবে — অর্থাৎ পরের CPU burst কত লম্বা — সেটি জানা থাকলে অনেক ভালো সিদ্ধান্ত নেওয়া যেত (পর্ব ১৭-এর SJF ঠিক এই ধারণা থেকে আসে)।
Burst-এর দৈর্ঘ্য — অনেক ছোট, অল্প কিছু লম্বা
বহু দশক ধরে নানা সিস্টেমে CPU burst-এর দৈর্ঘ্য মাপা হয়েছে। প্রোগ্রাম আর কম্পিউটার-ভেদে সংখ্যা বদলায়, কিন্তু বণ্টনের আকৃতি মোটামুটি একই থাকে: খুব বেশি সংখ্যক ছোট burst, আর খুব অল্প সংখ্যক লম্বা burst. পাঠ্যবইগুলো এই আকৃতিকে সাধারণত exponential বা hyperexponential ধাঁচের বলে বর্ণনা করে। নিচের ছবিটি শুধু আকৃতি বোঝানোর জন্য — এটি কোনো নির্দিষ্ট মাপা তথ্য নয়।
CPU burst-এর দৈর্ঘ্য বনাম কতবার ঘটে (আকৃতি বোঝানোর ছবি)
Typical shape: many short CPU bursts, a long thin tail of long ones
frequency
│█
│█ █
│█ █ █
│█ █ █ ▆
│█ █ █ █ ▅ ▃
│█ █ █ █ █ █ ▂ ▂ ▁ ▁ ▁ ▁
└──────────────────────────────────────▶ burst length (ms)
0 4 8 12 16 20 24 ... 40
↑ most bursts are short ↑ a few are long
এই আকৃতির পেছনে দুই জাতের প্রসেস। I/O-bound প্রসেস — টেক্সট এডিটর, shell, ব্রাউজারের সাধারণ কাজ, ডেটাবেসের ছোট প্রশ্ন — অনেক বার অল্প সময়ের জন্য CPU চায়, তারপর অপেক্ষায় যায়। CPU-bound প্রসেস — ভিডিও এনকোড, বড় compile, মেশিন লার্নিং-এর প্রশিক্ষণ — অল্প কয়েকবার কিন্তু লম্বা সময়ের জন্য CPU চায়। সাধারণ কম্পিউটারে I/O-bound প্রসেসই বেশি, তাই ছোট burst-ও বেশি।
নিচের stepper-এ দুটি প্রসেসের জীবনের প্রথম কয়েকটি burst পাশাপাশি দেখুন — যেন প্রত্যেকে একাই একটি কম্পিউটারে চলছে। C মানে CPU burst, I মানে I/O burst, সংখ্যা মিলিসেকেন্ডে।
1CPU burst : editor 2 ms, encoder 90 ms2I/O burst : editor 50 (key), encoder 4 (disk)3CPU burst : editor 1 ms, encoder 110 ms4I/O burst : editor 120, encoder 35CPU burst : editor 3 ms, encoder 95 ms6summary : share of time on the CPU
| editor-এর ধারা | |
| editor CPU / I/O | |
| encoder-এর ধারা | |
| encoder CPU / I/O | |
| গড় CPU burst |
- দুজনেই CPU burst দিয়ে শুরু। Editor একটি কি-চাপের ফল স্ক্রিনে আঁকতে মাত্র ২ ms CPU নেয়। Encoder ভিডিওর প্রথম অংশ সংকোচন করতে টানা ৯০ ms হিসাব করে।
- এবার I/O burst. Editor পরের কি-চাপের অপেক্ষায় — ৫০ ms ধরে তার CPU-র কোনো দরকার নেই। Encoder ডিস্ক থেকে পরের অংশ পড়তে মাত্র ৪ ms অপেক্ষা করে। এই অপেক্ষার সময়েই CPU অন্য প্রসেসকে দেওয়া যায়।
- দ্বিতীয় CPU burst. Editor নেয় ১ ms, encoder ১১০ ms. এখন পর্যন্ত গড় CPU burst: editor (২ + ১) ÷ ২ = ১.৫ ms, encoder (৯০ + ১১০) ÷ ২ = ১০০ ms.
- দ্বিতীয় I/O burst. Editor-এর অপেক্ষা ১২০ ms — মানুষ পরের অক্ষর ভাবছেন। Encoder-এর ৩ ms. Editor-এর মোট I/O এখন ১৭০ ms, CPU মাত্র ৩ ms.
- তৃতীয় CPU burst. Editor ৩ ms, encoder ৯৫ ms. মোট CPU: editor ৬, encoder ২৯৫ ms. গড় CPU burst: editor ৬ ÷ ৩ = ২ ms, encoder ২৯৫ ÷ ৩ ≈ ৯৮.৩ ms.
- ফল। Editor তার মোট সময়ের ৬ ÷ ১৭৬ ≈ ৩.৪% CPU-তে কাটিয়েছে — স্পষ্টতই I/O-bound. Encoder ২৯৫ ÷ ৩০২ ≈ ৯৭.৭% — CPU-bound. দুজন একই CPU-তে থাকলে আর encoder আগে পেলে editor-কে প্রতিবার প্রায় ১০০ ms অপেক্ষা করতে হতো, অথচ তার দরকার মাত্র ২ ms — এই অসামঞ্জস্যই scheduling-এর মূল সমস্যা।
I/O-bound প্রসেসকে দ্রুত CPU দিলে খরচ সামান্য (তার burst ছোট), কিন্তু লাভ বড় — সে তাড়াতাড়ি আবার I/O শুরু করে, ফলে ডিস্ক-নেটওয়ার্কও ব্যস্ত থাকে আর ব্যবহারকারী দ্রুত সাড়া পান। প্রায় সব বাস্তব scheduler কোনো-না-কোনো ভাবে এই নীতি মানে।
নিজের প্রোগ্রামে মাপা — user time, system time আর switch
একটি প্রসেস কোন জাতের, তা Linux নিজেই হিসাব রাখে। getrusage() system call জানায় প্রসেস এখন পর্যন্ত user mode-এ কত সময় CPU পেয়েছে (ru_utime), kernel mode-এ তার হয়ে কত সময় কাজ হয়েছে (ru_stime), আর কতবার সে নিজে অপেক্ষায় গিয়ে CPU ছেড়েছে (voluntary switch, ru_nvcsw) বা জোর করে সরানো হয়েছে (involuntary, ru_nivcsw). নিচের প্রোগ্রাম তিনটি অংশ চালিয়ে প্রতিটির আগে-পরে এই সংখ্যাগুলো মাপে: টানা হিসাব, অনেকগুলো ছোট system call, আর অনেকগুলো ছোট অপেক্ষা।
#include <fcntl.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/resource.h>
static struct rusage r0; static struct timespec w0;
static double ms(struct timeval t) { return t.tv_sec * 1e3 + t.tv_usec / 1e3; }
static void start(void) { getrusage(RUSAGE_SELF, &r0); clock_gettime(CLOCK_MONOTONIC, &w0); }
static void report(const char *name) {
struct rusage r; struct timespec w;
getrusage(RUSAGE_SELF, &r); clock_gettime(CLOCK_MONOTONIC, &w);
double wall = (w.tv_sec - w0.tv_sec) * 1e3 + (w.tv_nsec - w0.tv_nsec) / 1e6;
printf("%-8s wall %6.1f user %6.1f sys %6.1f ms vol %3ld invol %3ld\n", name, wall,
ms(r.ru_utime) - ms(r0.ru_utime), ms(r.ru_stime) - ms(r0.ru_stime),
r.ru_nvcsw - r0.ru_nvcsw, r.ru_nivcsw - r0.ru_nivcsw);
}
int main(void) {
volatile unsigned long x = 0;
start(); /* 1. CPU-bound: pure arithmetic */
for (unsigned long i = 0; i < 100000000UL; i++) x += i % 7;
report("compute"); (void)x;
int fd = open("/dev/null", O_WRONLY); /* 2. many tiny system calls */
start();
for (int i = 0; i < 300000; i++) if (write(fd, "x", 1) != 1) return 1;
report("syscalls"); close(fd);
struct timespec one_ms = {0, 1000000}; /* 3. I/O-bound-like: 200 short waits */
start();
for (int i = 0; i < 200; i++) nanosleep(&one_ms, NULL);
report("waiting");
return 0;
}একটি আসল রানের আউটপুট (সংখ্যা প্রতিবার আর প্রতি মেশিনে বদলাবে):
compute wall 224.7 user 224.0 sys 0.0 ms vol 0 invol 11
syscalls wall 249.1 user 64.6 sys 184.2 ms vol 0 invol 5
waiting wall 234.3 user 1.8 sys 5.3 ms vol 200 invol 0
compute: একটি খাঁটি CPU burst — ঘড়ির সময় (wall) প্রায় পুরোটাই user time, kernel-এর কাজ শূন্য, আর প্রসেস একবারও নিজে CPU ছাড়েনি (vol 0). ১১টি involuntary switch মানে container-এর অন্য কোনো কাজ মাঝে মাঝে তাকে সরিয়েছে — preemption.
syscalls: ৩ লাখ write()-এর বেশিরভাগ সময় কেটেছে kernel-এ (sys ১৮৪ ms), তবু voluntary switch শূন্য। /dev/null-এ লেখা কখনো অপেক্ষা করে না, তাই system call মানেই I/O burst নয় — এগুলো শুধু mode switch (পর্ব ৯), প্রসেস CPU-তেই থেকেছে। waiting: ২০০টি ছোট ঘুম, ২০০টি voluntary switch; ২৩৪ ms-এর মধ্যে CPU লেগেছে মাত্র প্রায় ৭ ms (প্রায় ৩%) — উপরের editor-এর মতো I/O-bound ছবি। প্রতিটি ১ ms-এর ঘুম গড়ে প্রায় ১.১৭ ms নিয়েছে, কারণ timer আর জেগে আবার CPU পাওয়ার সময়ও যোগ হয়। সংখ্যাগুলো প্রতিবার বদলাবে; প্রবণতা বদলাবে না।
২ · কখন সিদ্ধান্ত নিতে হয় — চারটি মুহূর্ত, preemptive আর non-preemptive
CPU scheduling মানে ready অবস্থার প্রসেসগুলোর মধ্যে কে পরের বার CPU পাবে আর কতক্ষণ, তা ঠিক করা। কিন্তু scheduler সারাক্ষণ ভাবে না; নির্দিষ্ট কিছু ঘটনার পর তাকে ডাকা হয়। পর্ব ৮-এর প্রসেস-অবস্থার ছবিতে ফিরে গেলে এমন মুহূর্ত ঠিক চারটি:
| মুহূর্ত | উদাহরণ | নতুন কাউকে বাছতেই হবে? | কোন scheduler এখানে সিদ্ধান্ত নেয় |
|---|---|---|---|
| ১. running → waiting | ডিস্ক থেকে read(), child-এর জন্য wait(), lock না পাওয়া | হ্যাঁ — CPU খালি হয়ে গেছে | দুই ধরনই |
| ২. running → ready | Timer interrupt, quantum শেষ | না — চাইলে একই প্রসেস চলতে পারে | শুধু preemptive |
| ৩. waiting → ready | ডিস্কের কাজ শেষ, interrupt এল; ঘুমন্ত প্রসেস জাগল | না — চলতি প্রসেস চলতে পারে | শুধু preemptive (জেগে ওঠা প্রসেস বেশি জরুরি হলে) |
| ৪. running → terminated | exit(), মারাত্মক exception | হ্যাঁ — CPU খালি হয়ে গেছে | দুই ধরনই |
১ আর ৪-এ বিকল্প নেই — চলতি প্রসেস নিজেই আর চলতে পারছে না, তাই অন্য কাউকে বাছতেই হবে (ready queue খালি থাকলে CPU idle থাকে)। ২ আর ৩-এ সিদ্ধান্ত ঐচ্ছিক। যে scheduler শুধু ১ আর ৪-এ সিদ্ধান্ত নেয়, সে non-preemptive (বা cooperative): একবার CPU পেলে প্রসেস নিজে না ছাড়া পর্যন্ত রাখে। যে scheduler ২ আর ৩-এও সিদ্ধান্ত নিতে পারে, সে preemptive: চলতি প্রসেসের কাছ থেকে CPU কেড়ে নিতে পারে (preemption). কেড়ে নেওয়ার যন্ত্রটি timer interrupt, যা পর্ব ৯-এ দেখেছেন; Windows 3.x বা Classic Mac OS-এর cooperative যুগের গল্পও সেখানে আছে।
বাস টার্মিনালের টিকিট-কাউন্টার। Non-preemptive নিয়মে যাত্রী নিজে উঠে না গেলে (টাকা আনতে গেলেন, বা কাজ শেষ) পরের জনকে ডাকা হয় না। Preemptive নিয়মে প্রতি পাঁচ মিনিটে ঘণ্টা বাজে, তখন চলতি যাত্রীকে একপাশে বসিয়ে অন্য কাউকে ডাকা যায়; আর যাঁর বাস এখনই ছাড়ছে, তাঁর জন্য ঘণ্টার অপেক্ষাও লাগে না।
নিচের stepper-এ তিনটি প্রসেস A, B, C নিয়ে চারটি মুহূর্ত একে একে দেখুন। ধরা হয়েছে সিস্টেমটি preemptive আর A একটি interactive প্রসেস, তাই তার অগ্রাধিকার বেশি; প্রতিটি ধাপে non-preemptive হলে কী হতো, সেটিও লেখা আছে।
1A calls read() on the disk (running -> waiting)2timer interrupt while B runs (running -> ready)3disk interrupt: A's data ready (waiting -> ready)4A calls exit() (running -> terminated)
| running | |
| ready queue | |
| waiting | |
| বাছাই বাধ্যতামূলক? | |
| non-preemptive হলে |
- A ডিস্কের অপেক্ষায় গেল। A একটি ফাইল পড়তে চাইল; ডেটা আসতে সময় লাগবে, তাই A waiting অবস্থায়। CPU খালি — scheduler-কে কাউকে বাছতেই হবে। Ready queue-এর মাথা থেকে B চলল, queue-তে বাকি C. যেকোনো scheduler এখানে একই কাজ করত।
- Timer interrupt, B-এর quantum শেষ। B তখনো চলতে প্রস্তুত, কিন্তু preemptive scheduler তাকে ready queue-এর পেছনে পাঠিয়ে C-কে দিল: running C, ready [B]. Non-preemptive scheduler এই interrupt-কে scheduling-এর সুযোগ হিসেবে ব্যবহার করত না — B নিজে না ছাড়া পর্যন্ত চলত।
- ডিস্ক শেষ, A জাগল। Interrupt handler A-কে waiting থেকে ready করল। A-এর অগ্রাধিকার C-এর চেয়ে বেশি, তাই preemptive scheduler C-কে সরিয়ে সঙ্গে সঙ্গে A-কে দিল: running A, ready [B, C]. Non-preemptive হলে A ready queue-তে বসে C-এর শেষ বা অপেক্ষার জন্য দাঁড়িয়ে থাকত — interactive প্রসেসের জন্য এটিই ধীরতার উৎস।
- A শেষ। A
exit()ডাকল, CPU আবার খালি — বাছাই বাধ্যতামূলক। Queue-এর মাথা থেকে B চলল, বাকি [C]. মুহূর্ত ১-এর মতোই এখানেও দুই ধরনের scheduler একই আচরণ করে।
Preemption-এর দাম
Preemptive scheduling ছাড়া আধুনিক interactive সিস্টেম কল্পনা করা যায় না — একটি অসীম লুপ পুরো কম্পিউটার জমিয়ে দিতে পারত। কিন্তু এর দামও আছে। প্রথমত, প্রতিটি অতিরিক্ত পালাবদল একটি context switch, যার খরচ পর্ব ৯-এ মেপেছেন। দ্বিতীয়ত, প্রসেস এখন যেকোনো দুই নির্দেশের মাঝখানে থামতে পারে। দুটি প্রসেস বা thread একই ডেটা ভাগ করলে, একজন অর্ধেক লেখা অবস্থায় থামলে অন্যজন ভুল মান পড়তে পারে — এটি race condition, যার সমাধান মডিউল ৪-এর বিষয়। তৃতীয়ত, kernel-এর নিজের ডেটা-কাঠামোও একই ঝুঁকিতে পড়ে; তাই আধুনিক kernel lock আর “preemption বন্ধ” অংশ দিয়ে নিজেকে রক্ষা করে।
প্রচলিত গুলিয়ে ফেলা: “Non-preemptive মানে প্রসেস শেষ না হওয়া পর্যন্ত CPU ধরে রাখে।” পুরোপুরি ঠিক নয়। Non-preemptive scheduler-এ প্রসেস তার বর্তমান CPU burst শেষ না হওয়া পর্যন্ত CPU রাখে — I/O-তে গেলে (মুহূর্ত ১) CPU ছেড়ে দেয়। পরীক্ষার অঙ্কে সাধারণত প্রতিটি প্রসেসের একটিই CPU burst ধরা হয়, তাই সেখানে burst শেষ আর প্রসেস শেষ একই কথা।
৩ · Scheduler কোথায় চলে — স্তর, dispatcher আর dispatch latency
পর্ব ৮-এ তিন স্তরের scheduler দেখেছেন। সংক্ষেপে মনে করিয়ে দিই, কারণ “CPU scheduling” বলতে এই মডিউলে কেবল একটিকেই বোঝানো হবে:
Long-term (job) scheduler
কোন কাজ সিস্টেমে ঢুকবে, অর্থাৎ একসাথে কতগুলো প্রসেস থাকবে, ঠিক করে। আজ মূলত ক্লাস্টারের job queue-তে দেখা যায়।
মিনিট বা সেকেন্ডে একবারMedium-term scheduler
মেমরির চাপে কিছু প্রসেস সাময়িকভাবে সরিয়ে রাখে (swap out) আর পরে ফেরায়; আজ কাজটি virtual memory-র ভেতরে হয় (মডিউল ৫)।
মাঝে মাঝেShort-term (CPU) scheduler
Ready queue থেকে পরের প্রসেস বা thread বাছে। প্রতি সেকেন্ডে শত-হাজারবার চলে, তাই একে খুব দ্রুত হতে হয়। মডিউল ৩ পুরোটাই এর গল্প।
মিলিসেকেন্ডেShort-term scheduler সিদ্ধান্ত নেয় কে চলবে; সিদ্ধান্তটি কাজে পরিণত করে dispatcher — পুরোনোর অবস্থা রাখা, নতুনের অবস্থা ফেরানো, user mode-এ ফিরে তার সংরক্ষিত PC-তে লাফ। এক প্রসেস থামানো থেকে আরেকটি চালু পর্যন্ত সময় dispatch latency. এর খুঁটিনাটি পর্ব ৯-এ ধাপে ধাপে দেখেছেন; এখানে শুধু একটি কথা মনে রাখুন — Gantt chart-এর অঙ্কে সাধারণত dispatch latency শূন্য ধরা হয়। প্রশ্নে switch-এর খরচ দেওয়া থাকলে তা chart-এ আলাদা ঘর হিসেবে বসাতে হয়, যেমন পর্ব ৯-এর Round Robin উদাহরণে।
Scheduler কি আলাদা একটি প্রোগ্রাম?
অনেকে কল্পনা করেন, scheduler একটি আলাদা প্রসেস, যে পাশে বসে সবার দিকে নজর রাখে। বাস্তবে Linux-এ এমন কোনো প্রসেস নেই। Scheduler হলো kernel-এর কিছু ফাংশন (মূলটি schedule()), আর তা চলে সেই CPU-তেই, সেই মুহূর্তের প্রসেসের kernel-অংশ হিসেবে: প্রসেস block হতে গিয়ে নিজেই schedule() ডাকে (মুহূর্ত ১, ৪), অথবা interrupt বা system call থেকে ফেরার পথে kernel “reschedule দরকার” পতাকা দেখে ডাকে (মুহূর্ত ২, ৩)। প্রতিটি CPU-র নিজস্ব run queue থাকে — ready queue-র বাস্তব রূপ — আর প্রতিটি CPU নিজের জন্য নিজেই বাছাই করে; মাঝে মাঝে CPU-গুলোর মধ্যে কাজের ভার সমান করা হয় (পর্ব ২০)। Windows-এও আলাদা কোনো “scheduler thread” নেই — dispatcher-এর কোড প্রতিটি প্রসেসরে kernel-এর ভেতরেই চলে। শিক্ষণ-OS xv6 ব্যতিক্রম: সেখানে প্রতিটি CPU-র একটি নিজস্ব scheduler-লুপ আছে (পর্ব ৫৮)।
Linux প্রতিটি task-এর scheduling-এর হিসাবও রাখে, আর /proc-এ দেখায়। /proc/<pid>/schedstat ফাইলে তিনটি সংখ্যা: task মোট কত ন্যানোসেকেন্ড CPU-তে চলেছে, কত ন্যানোসেকেন্ড run queue-তে CPU-র অপেক্ষায় ছিল, আর কতগুলো আলাদা পালা (timeslice) পেয়েছে (schedstat). দ্বিতীয় সংখ্যাটিই আসলে এই পর্বের “waiting time”-এর বাস্তব রূপ। আর ps দিয়ে দেখা যায় প্রসেসের scheduling class, nice মান, অগ্রাধিকার আর সে শেষবার কোন CPU-তে চলেছে।
show() { # three numbers: ns on CPU, ns waiting in the run queue, timeslices
read run wait slices < /proc/$$/schedstat
echo "$1 on-CPU $((run / 1000000)) ms, waited $((wait / 1000000)) ms, slices $slices"
}
show "start :"
for ((i = 0; i < 300000; i++)); do :; done # a CPU burst
show "after loop :"
sleep 0.2 # an I/O-like wait
show "after sleep:"
echo
ps -o pid,cls,ni,pri,psr,comm -p $$ # this shell
nice -n 10 sh -c 'ps -o pid,cls,ni,pri,psr,comm -p $$' # a "nicer" childএকটি আসল রানের আউটপুট:
start : on-CPU 3 ms, waited 1 ms, slices 7
after loop : on-CPU 752 ms, waited 49 ms, slices 424
after sleep: on-CPU 754 ms, waited 49 ms, slices 427
PID CLS NI PRI PSR COMMAND
2 TS 10 9 0 bash
PID CLS NI PRI PSR COMMAND
5 TS 19 0 1 sh
লুপের পরে: CPU-তে সময় ৩ থেকে ৭৫২ ms — একটি লম্বা CPU burst. কিন্তু সে CPU পেয়েছে ৪১৭টি আলাদা টুকরোয়, আর মাঝে ৪৮ ms run queue-তে অপেক্ষা করেছে — একই CPU-তে অন্য কাজও চলছিল, তাই scheduler পালা করে দিয়েছে। ঘুমের পরে: CPU মাত্র +২ ms, আর “waited” একটুও বাড়েনি। ২০০ ms ঘুম run queue-র অপেক্ষা নয় — এটি অংশ ৪-এর WT আর I/O-র পার্থক্যের বাস্তব রূপ।
ps: CLS TS মানে সাধারণ time-sharing নীতি (SCHED_OTHER), PSR শেষবার চলা CPU-র নম্বর। মজার ব্যাপার, এই container-এ shell নিজেই nice ১০-এ চলছে; তাই nice -n 10 দিয়ে চালানো child পেল ১০ + ১০ = ২০ নয়, সর্বোচ্চ সীমা ১৯. PRI কলাম এখানে ১৯ − NI (৯ আর ০); এই স্কেল ps-এর নিজস্ব, kernel-এর ভেতরের অগ্রাধিকার-সংখ্যা আলাদা (পর্ব ২১)। আপনার মেশিনে সাধারণত NI 0 দেখবেন।
৪ · মাপকাঠি — কোন সংখ্যা দিয়ে scheduler-কে বিচার করবেন
“কোন scheduling algorithm ভালো?” — এই প্রশ্নের উত্তর দিতে আগে ঠিক করতে হয় ভালো মানে কী। পাঠ্যবইগুলো কয়েকটি scheduling criteria ব্যবহার করে। প্রথমে প্রতিটি প্রসেসের জন্য চারটি মৌলিক সময় লিখে নিন:
- Arrival time (AT): প্রসেস কখন ready queue-তে পৌঁছাল।
- Burst time (BT): তার মোট কতটুকু CPU সময় দরকার।
- Completion time (CT): কখন তার কাজ পুরো শেষ হলো (কিছু বইয়ে finish বা exit time).
- প্রথম CPU পাওয়ার সময়: সে প্রথমবার কখন CPU-তে উঠল (কিছু বইয়ে start time).
এই চারটি থেকেই সব প্রসেস-ভিত্তিক মাপকাঠি আসে:
Waiting time (WT) = TAT − BT
Response time (RT) = প্রথম CPU পাওয়ার সময় − AT
| মাপকাঠি | সংজ্ঞা ও সূত্র | লক্ষ্য | কার কাছে সবচেয়ে জরুরি |
|---|---|---|---|
| CPU utilisation | মোট সময়ের কত ভাগ CPU কাজে ব্যস্ত = ব্যস্ত সময় ÷ মোট সময় × ১০০% | বাড়ানো | সার্ভার, batch — দামি হার্ডওয়্যার অলস রাখা চলে না |
| Throughput | একক সময়ে কতগুলো প্রসেস শেষ = শেষ হওয়া প্রসেস ÷ সময় | বাড়ানো | Batch, ডেটা-প্রক্রিয়াকরণ, ওয়েব সার্ভার |
| Turnaround time | আসা থেকে পুরো শেষ = CT − AT; এতে CPU-তে চলা, ready queue-তে অপেক্ষা আর I/O — সব ধরা | কমানো | Batch কাজ: “আমার রিপোর্ট কখন পাব?” |
| Waiting time | শুধু ready queue-তে CPU-র অপেক্ষায় কাটানো মোট সময় = TAT − BT (একটিমাত্র CPU burst হলে) | কমানো | সবার; scheduler সরাসরি এটিই নিয়ন্ত্রণ করে |
| Response time | আসা থেকে প্রথমবার CPU পাওয়া = প্রথম CPU − AT | কমানো | Interactive: editor, গেম, ফোনের UI |
| Fairness | তুলনীয় প্রসেসগুলো তুলনীয় ভাগ পায় কি না; কেউ অনির্দিষ্টকাল বঞ্চিত (starvation) হয় কি না | নিশ্চিত করা | বহু-ব্যবহারকারীর সার্ভার, ক্লাউড |
| Predictability | একই কাজ প্রতিবার মোটামুটি একই সময় নেয় কি না — গড়ের চেয়ে বিচ্যুতি (variance) কম | বিচ্যুতি কমানো | Interactive আর real-time; মানুষ অনিয়মিত ধীরতা বেশি টের পান |
সূত্রগুলোর পেছনের যুক্তি
কেন WT = TAT − BT? একটি প্রসেস আসার পর থেকে শেষ হওয়া পর্যন্ত (TAT) প্রতিটি মুহূর্তে সে দুটির একটি করছে: CPU-তে চলছে, অথবা ready queue-তে অপেক্ষা করছে — যদি তার একটিই CPU burst থাকে, অর্থাৎ মাঝে কোনো I/O নেই। চলার মোট সময় ঠিক BT. তাই বাকিটা অপেক্ষা: WT = TAT − BT. প্রসেসের মাঝে I/O burst থাকলে TAT-এর মধ্যে তৃতীয় একটি অংশ ঢোকে — I/O-তে কাটানো সময় — তখন সঠিক সূত্র: WT = TAT − (মোট CPU burst) − (মোট I/O সময়)। পরীক্ষার বেশিরভাগ অঙ্কে একটিই CPU burst থাকে, তাই প্রথম সূত্রটিই চলে; কিন্তু অনুশীলন ২-এ দেখবেন I/O থাকলে সেটি কীভাবে ভুল উত্তর দেয়।
Response time আর waiting time কবে সমান? Non-preemptive scheduling-এ প্রসেস একবার CPU পেলে শেষ না হওয়া পর্যন্ত নামে না, তাই তার সব অপেক্ষা প্রথম CPU পাওয়ার আগেই — RT = WT. Preemptive scheduling-এ প্রসেস মাঝপথে নামলে আবার অপেক্ষা করে, তাই WT ≥ RT. খেয়াল করুন, পাঠ্যবইয়ের সংজ্ঞায় response time হলো “প্রথম সাড়া শুরু হওয়া পর্যন্ত সময়”, প্রোগ্রামের পুরো উত্তর ছাপা শেষ হওয়া পর্যন্ত নয়। Gantt chart-এর অঙ্কে একে প্রথমবার CPU পাওয়া হিসেবেই মাপা হয়।
Throughput আর utilisation কোন সময়-সীমায়? সাধারণত পুরো chart-এর সময়-সীমায় — t = 0 থেকে শেষ প্রসেসের CT পর্যন্ত। কিছু বই প্রথম প্রসেস আসার মুহূর্ত থেকে গোনে। প্রথম প্রসেস t = 0-তে এলে দুটি একই; না এলে প্রশ্নের নিয়ম দেখে নিন, আর উত্তরে কোন সীমা নিয়েছেন তা লিখে দিন।
গড় নাকি সবচেয়ে খারাপ? পাঠ্যবইয়ের অঙ্ক সাধারণত গড় চায়। বাস্তবে প্রতি একশো অনুরোধের একটি ৩ সেকেন্ড নিলে ব্যবহারকারীরা সেটিই মনে রাখেন, তাই গড়ের পাশাপাশি p99-এর মতো tail latency-ও মাপা হয় (ভাবনার প্রশ্ন ৫)।
৫ · লক্ষ্যের টানাপোড়েন — সবকিছু একসাথে ভালো করা যায় না
সব মাপকাঠি একসাথে সর্বোত্তম করা সম্ভব হলে একটিই “সেরা” algorithm থাকত। বাস্তবে এগুলো একে অপরের বিরুদ্ধে টানে। তিনটি গুরুত্বপূর্ণ সংঘাত:
- Throughput বনাম response time: Response কমাতে ঘন ঘন পালাবদল দরকার — প্রত্যেকে দ্রুত একটু CPU পাক। কিন্তু প্রতিটি পালাবদল একটি context switch, আর cache-TLB আবার ভরার খরচ; ফলে উপকারী কাজ কমে, throughput পড়ে। উল্টো দিকে, প্রত্যেককে লম্বা সময় দিলে throughput বাড়ে, কিন্তু অন্যেরা অপেক্ষায় থাকে।
- গড় waiting বনাম fairness: সবচেয়ে ছোট কাজ আগে চালালে গড় waiting time সবচেয়ে কম হয় (পর্ব ১৭-এ এর প্রমাণ), কিন্তু ছোট কাজের স্রোত চলতে থাকলে লম্বা কাজটি হয়তো কখনোই সুযোগ পায় না — starvation.
- Utilisation বনাম predictability: CPU-কে সবসময় ১০০% ব্যস্ত রাখলে নতুন কাজ এসে লম্বা সারিতে পড়ে; অপেক্ষার সময় খুব অনিশ্চিত হয়ে যায় (অংশ ৭-এর queueing তত্ত্ব দেখাবে, utilisation ১০০%-এর কাছে গেলে অপেক্ষা হঠাৎ লাফিয়ে বাড়ে)। Real-time সিস্টেম তাই ইচ্ছা করে কিছু CPU খালি রাখে।
তাই কোন মাপকাঠি সবচেয়ে জরুরি, তা সিস্টেমের ধরন ঠিক করে:
| সিস্টেম | প্রধান লক্ষ্য | কী ছাড় দেওয়া যায় | উদাহরণ |
|---|---|---|---|
| Batch | Throughput, utilisation, কম turnaround | Response time — কেউ পর্দার সামনে বসে নেই | রাতের বেতন-হিসাব, ব্যাংকের দিনশেষের হিসাব মেলানো, বৈজ্ঞানিক simulation |
| Interactive / time-sharing | কম response time, predictability, fairness | কিছুটা throughput (বেশি switch) | ল্যাপটপ, ফোন, টার্মিনালে কাজ |
| Real-time | প্রতিটি deadline রক্ষা, সবচেয়ে খারাপ ক্ষেত্রেও | গড় throughput, এমনকি utilisation | গাড়ির ব্রেক নিয়ন্ত্রণ, পেসমেকার, কারখানার রোবট |
| সার্ভার / ক্লাউড | Throughput আর tail latency দুটোই; ব্যবহারকারী বা container-এর মধ্যে fairness | নির্দিষ্ট একটি অনুরোধের গতি, ভাগ ঠিক রাখতে | ওয়েব সার্ভার, ডেটাবেস, একাধিক গ্রাহকের VM |
রেস্তোরাঁর রান্নাঘরে একটি চুলা। দুপুরের ভিড়ে মালিক চান প্রতি ঘণ্টায় যত বেশি সম্ভব প্লেট বের হোক — throughput. টেবিলে বসা প্রতিটি গ্রাহক চান তাঁর অন্তত চা-টা তাড়াতাড়ি আসুক — response time. আর বিয়ের অনুষ্ঠানের অর্ডার ঠিক সন্ধ্যা ৭টায় পৌঁছাতে হবে, এক মিনিট দেরিও চলবে না — deadline. এক নিয়মে তিনজনকেই পুরো খুশি করা যায় না; কাকে বেশি গুরুত্ব, সেটিই “scheduling policy”.
৬ · Gantt chart পড়া ও আঁকা — একটি পূর্ণ সমাধান
Scheduling-এর অঙ্কের প্রধান যন্ত্র Gantt chart — একটি সময়রেখা, যেখানে প্রতিটি ঘরে লেখা থাকে কোন সময় থেকে কোন সময় পর্যন্ত CPU-তে কে ছিল। নামটি এসেছে Henry Gantt থেকে, যিনি বিংশ শতকের শুরুতে প্রকল্পের কাজ সাজাতে এমন চার্ট জনপ্রিয় করেন। ঠিকভাবে আঁকার নিয়ম:
- সময়রেখা শুরু হয় t = 0 থেকে, আর প্রতিটি ঘরের দুই প্রান্তে সময় লেখা থাকে। পাশাপাশি দুটি ঘরের মাঝের সংখ্যাটি একটির শেষ আর অন্যটির শুরু।
- কোনো প্রসেস তার arrival time-এর আগে CPU পেতে পারে না। Scheduler কেবল সেই মুহূর্তে ready queue-তে যারা আছে, তাদের মধ্য থেকেই বাছে।
- CPU খালি থাকলে তা দেখাতে হবে। Ready queue খালি আর পরের প্রসেস তখনো আসেনি — এমন সময় chart-এ একটি “idle” ঘর বসে। এই ঘর ভুলে গেলে পরের প্রত্যেকের CT আর সব গড় ভুল হয়।
- প্রতিটি ঘরের দৈর্ঘ্য যোগ করে মেলান: প্রতিটি প্রসেসের ঘরগুলোর মোট দৈর্ঘ্য তার burst time-এর সমান হতে হবে, আর chart-এর মোট দৈর্ঘ্য = সব burst-এর যোগফল + সব idle ঘর।
- সমান অবস্থায় নিয়ম আগে ঠিক করুন — যেমন দুজন একই সময়ে এলে ছোট নম্বরের প্রসেস আগে। প্রশ্নে বলা না থাকলে এই সাধারণ নিয়মটি লিখে দিন।
উদাহরণ হিসেবে সবচেয়ে সরল নীতিটি নেওয়া হলো — FCFS (first-come, first-served): যে আগে এসেছে, সে আগে পাবে, আর একবার পেলে শেষ না হওয়া পর্যন্ত রাখবে। নীতিটির বিশ্লেষণ পর্ব ১৭-এ; এখানে লক্ষ্য শুধু chart আঁকা আর মাপা। প্রসেসের টেবিল (সময় ms-এ):
| প্রসেস | Arrival time (AT) | Burst time (BT) |
|---|---|---|
| P1 | ০ | ৫ |
| P2 | ১ | ৩ |
| P3 | ২ | ২ |
| P4 | ১২ | ৪ |
1t=0 : P1 arrives, CPU free -> P1 runs2t=1 : P2 arrives -> ready queue3t=2 : P3 arrives -> ready queue4t=5 : P1 done -> P2 runs5t=8 : P2 done -> P3 runs6t=10: P3 done, queue empty -> idle7t=12: P4 arrives -> P4 runs8t=16: P4 done -> chart complete
| t | |
| CPU-তে | |
| ready queue | |
| এখনো আসেনি | |
| শেষ (CT) |
- t = ০: শুধু P1 আছে। CPU খালি, ready queue-তে একজনই — P1 চলা শুরু করল। বাকি তিনজন তখনো সিস্টেমে আসেনি, তাই তাদের কথা ভাবার প্রশ্নই নেই।
- t = ১: P2 এল। P1 চলছে, আর FCFS non-preemptive — তাই P2 ready queue-তে বসল। এই মুহূর্ত থেকে P2-এর অপেক্ষা গোনা শুরু।
- t = ২: P3 এল। সে P2-এর পেছনে বসল: queue [P2, P3]. P1 এখনো চলছে, তার বাকি ৩ ms.
- t = ৫: P1 শেষ। P1-এর ৫ ms burst পুরো হলো — CT(P1) = ৫. Queue-এর মাথায় P2, তাই সে CPU পেল। Chart-এ প্রথম পূর্ণ ঘর │P1 0-5│.
- t = ৮: P2 শেষ। P2 চলল ৫ থেকে ৮ — ৩ ms, তার burst-এর সমান। CT(P2) = ৮. এবার P3.
- t = ১০: P3 শেষ, CPU খালি। CT(P3) = ১০. Ready queue খালি, আর P4 আসবে t = ১২-তে। এখন P4-কে চালানো যাবে না — সে আসেইনি। তাই chart-এ একটি idle ঘর শুরু হলো।
- t = ১২: P4 এল, সঙ্গে সঙ্গে চলল। কেউ অপেক্ষায় নেই, তাই P4 আসার মুহূর্তেই CPU পেল। idle ঘরটি ১০ থেকে ১২ — ২ ms.
- t = ১৬: সব শেষ। CT(P4) = ১৬. পুরো chart: │P1 0-5│P2 5-8│P3 8-10│idle 10-12│P4 12-16│. যাচাই: burst-এর যোগফল ৫ + ৩ + ২ + ৪ = ১৪, সঙ্গে idle ২ — মোট ১৬, chart-এর দৈর্ঘ্যের সঙ্গে মিলল।
এবার এই chart থেকে প্রতিটি প্রসেসের মাপকাঠি বের করুন। কৌশলটি সবসময় একই: প্রথমে টেবিলে AT, BT আর chart থেকে CT লিখুন, তারপর একে একে সূত্র বসান।
1table : AT, BT given; CT and first-run from the chart2P1 : TAT = 5-0, WT = TAT-5, RT = 0-03P2 : TAT = 8-1, WT = TAT-3, RT = 5-14P3 : TAT = 10-2, WT = TAT-2, RT = 8-25P4 : TAT = 16-12, WT = TAT-4, RT = 12-126totals: averages, throughput, utilisation
| AT / BT | |
| CT (প্রথম CPU) | |
| TAT | |
| WT | |
| RT |
- টেবিল সাজানো। AT আর BT প্রশ্নে দেওয়া। Chart থেকে পড়ুন প্রত্যেকে কখন শেষ হলো (CT: ৫, ৮, ১০, ১৬) আর প্রথম কখন CPU পেল (০, ৫, ৮, ১২)। বাকি সব এখান থেকে শুধু বিয়োগ।
- P1. TAT = ৫ − ০ = ৫. WT = ৫ − ৫ = ০ — আসামাত্র চলেছে, কোনো অপেক্ষা নেই। RT = ০ − ০ = ০.
- P2. TAT = ৮ − ১ = ৭. এর মধ্যে ৩ ms চলেছে, তাই WT = ৭ − ৩ = ৪ — t = ১ থেকে ৫ পর্যন্ত P1-এর পেছনে। RT = ৫ − ১ = ৪. Non-preemptive, তাই RT = WT.
- P3. TAT = ১০ − ২ = ৮, WT = ৮ − ২ = ৬ (t = ২ থেকে ৮), RT = ৮ − ২ = ৬. মাত্র ২ ms-এর কাজের জন্য ৬ ms অপেক্ষা — বড় কাজের পেছনে ছোট কাজ আটকে থাকার এই ছবিটি পর্ব ১৭-এ “convoy effect” নামে ফিরে আসবে।
- P4. TAT = ১৬ − ১২ = ৪, WT = ৪ − ৪ = ০, RT = ১২ − ১২ = ০. লক্ষ করুন, ১০ থেকে ১২-এর idle ফাঁক P4-এর অপেক্ষা নয় — তখন সে সিস্টেমেই ছিল না। এটিই সবচেয়ে প্রচলিত ভুল।
- গড় আর সিস্টেমের মাপ। গড় TAT = ২৪ ÷ ৪ = ৬ ms, গড় WT = ১০ ÷ ৪ = ২.৫ ms, গড় RT = ২.৫ ms. Throughput = ৪টি প্রসেস ÷ ১৬ ms = ০.২৫ প্রসেস প্রতি ms (অর্থাৎ প্রতি সেকেন্ডে ২৫০). CPU utilisation = ব্যস্ত ১৪ ÷ ১৬ = ৮৭.৫%.
হিসাবটি কোড দিয়ে মেলান
নিচের ছোট calculator একটি প্রসেস টেবিল আর একটি নির্দিষ্ট Gantt ক্রম নেয় — কোন প্রসেস কত ms চলবে, পরপর — আর arrival দেখে দরকার হলে idle ঘর বসায়। এটি কোনো algorithm নয়; ক্রমটি আপনি দেন, সে শুধু মাপে। একটি প্রসেসকে কয়েক টুকরোয় ভাগ করেও দিতে পারেন (preemptive chart), যেমন ["P1", 2], ["P2", 3], ["P1", 3]. দুটি ক্রম চালানো হয়েছে: FCFS-এর ক্রম, আর P2-P3 অদলবদল করা আরেকটি ক্রম।
// Scheduling metrics from a process table and a GIVEN Gantt order (deterministic).
const procs = { P1: [0, 5], P2: [1, 3], P3: [2, 2], P4: [12, 4] }; // name: [arrival, burst] in ms
function evaluate(label, order) { // order: [process, run length] slices, in time order
let t = 0, busy = 0;
const gantt = [], ct = {}, first = {}, ran = {};
for (const [p, len] of order) {
const at = procs[p][0];
if (t < at) { gantt.push(`idle ${t}-${at}`); t = at; } // CPU must wait for the arrival
if (!(p in first)) first[p] = t; // first time on the CPU
gantt.push(`${p} ${t}-${t + len}`);
t += len; busy += len; ran[p] = (ran[p] || 0) + len; ct[p] = t;
}
console.log(`--- ${label}\nGantt: |${gantt.join("|")}|`);
const row = (cells) => console.log(cells.map((v) => String(v).padStart(4)).join(""));
row(["P", "AT", "BT", "CT", "TAT", "WT", "RT"]);
const names = Object.keys(procs), sum = { tat: 0, wt: 0, rt: 0 };
for (const p of names) {
const [at, bt] = procs[p];
if (ran[p] !== bt) throw new Error(`${p}: slices add up to ${ran[p]}, burst is ${bt}`);
const tat = ct[p] - at, wt = tat - bt, rt = first[p] - at;
sum.tat += tat; sum.wt += wt; sum.rt += rt;
row([p, at, bt, ct[p], tat, wt, rt]);
}
const n = names.length, span = t - Math.min(...names.map((p) => procs[p][0]));
console.log(`avg TAT ${sum.tat / n}, avg WT ${sum.wt / n}, avg RT ${sum.rt / n}`);
console.log(`throughput ${n}/${span} = ${(n / span).toFixed(3)} per ms, ` +
`CPU busy ${busy}/${span} = ${((100 * busy) / span).toFixed(1)}%`);
}
evaluate("FCFS order", [["P1", 5], ["P2", 3], ["P3", 2], ["P4", 4]]);
evaluate("another order", [["P1", 5], ["P3", 2], ["P2", 3], ["P4", 4]]);আউটপুট (calculator নির্ধারিত, তাই প্রতিবার একই):
--- FCFS order
Gantt: |P1 0-5|P2 5-8|P3 8-10|idle 10-12|P4 12-16|
P AT BT CT TAT WT RT
P1 0 5 5 5 0 0
P2 1 3 8 7 4 4
P3 2 2 10 8 6 6
P4 12 4 16 4 0 0
avg TAT 6, avg WT 2.5, avg RT 2.5
throughput 4/16 = 0.250 per ms, CPU busy 14/16 = 87.5%
--- another order
Gantt: |P1 0-5|P3 5-7|P2 7-10|idle 10-12|P4 12-16|
P AT BT CT TAT WT RT
P1 0 5 5 5 0 0
P2 1 3 10 9 6 6
P3 2 2 7 5 3 3
P4 12 4 16 4 0 0
avg TAT 5.75, avg WT 2.25, avg RT 2.25
throughput 4/16 = 0.250 per ms, CPU busy 14/16 = 87.5%
প্রথম অংশ stepper-এর প্রতিটি সংখ্যার সঙ্গে হুবহু মেলে — idle ঘরটিও calculator নিজে বসিয়েছে, কারণ t = ১০-এ P4-এর arrival ১২. দ্বিতীয় ক্রমে শুধু P2 আর P3-এর জায়গা বদল, তাতে গড় WT ২.৫ থেকে ২.২৫-এ নামল: P3 (২ ms) ৩ ms কম অপেক্ষা করল, P2 (৩ ms) ২ ms বেশি — ছোট কাজকে আগে নিলে মোট অপেক্ষা কমে। এই পর্যবেক্ষণটিই পর্ব ১৭-এর SJF-এর ভিত্তি।
কিন্তু শেষ লাইন দুটি ক্রমেই একই: throughput ০.২৫, utilisation ৮৭.৫%, শেষ সময় ১৬. এটি কাকতাল নয়। যতক্ষণ scheduler কাজ থাকতে CPU অলস রাখে না (একে বলে work-conserving), ততক্ষণ যেকোনো মুহূর্তে “বাকি থাকা মোট কাজ” প্রতিটি ক্রমে সমান: প্রতিটি arrival-এ তা সেই প্রসেসের burst পরিমাণ বাড়ে, আর বাকি কাজ থাকলে প্রতি ms-এ ঠিক ১ করে কমে — কে চলছে তাতে কিছু আসে-যায় না। তাই CPU কখন ব্যস্ত আর কখন idle, তা ক্রমের ওপর নির্ভর করে না; বদলায় শুধু কোন প্রসেস কখন শেষ হয়, আর তাই গড় TAT, WT, RT. একটি CPU-তে, একটিমাত্র burst-এর অঙ্কে, ক্রম বদলে throughput আর utilisation বদলানো যায় না, গড় অপেক্ষা বদলানো যায়।
৭ · Algorithm বিচারের চার পদ্ধতি
নতুন কোনো scheduling algorithm প্রস্তাব করলে প্রশ্ন ওঠে: সে কি আগেরটির চেয়ে ভালো? উত্তর খোঁজার চারটি পথ আছে, প্রতিটি আগেরটির চেয়ে বাস্তবের কাছাকাছি — আর বেশি খরচের।
১. Deterministic modelling — একটি নির্দিষ্ট উদাহরণে হাতে হিসাব
একটি নির্দিষ্ট প্রসেস টেবিল নিয়ে প্রতিটি algorithm-এর Gantt chart আঁকা আর গড় waiting বা turnaround তুলনা করা — ঠিক যা অংশ ৬-এ করলেন। এটিই deterministic modelling. সুবিধা: সরল, দ্রুত, সংখ্যাগুলো নিখুঁত, আর ধারণা তৈরিতে দারুণ — পরীক্ষার প্রায় সব scheduling-অঙ্ক এই ধরনের। দুর্বলতা: উত্তর শুধু সেই টেবিলের জন্য সত্য। অন্য burst বা arrival-এ ফল উল্টে যেতে পারে। তবে অনেক উদাহরণে একই প্রবণতা দেখলে সাধারণ সূত্রের ইঙ্গিত মেলে, যা পরে প্রমাণ করা যায় (যেমন SJF-এর সর্বোত্তমতা, পর্ব ১৭)।
২. Queueing model — গড় আচরণের গণিত
বাস্তব সিস্টেমে প্রসেস আসে এলোমেলোভাবে, burst-ও এলোমেলো। কিন্তু তাদের বণ্টন মাপা যায় — গড়ে প্রতি সেকেন্ডে কতগুলো আসে (arrival rate λ), গড় burst কত। কম্পিউটারকে তখন কয়েকটি “সার্ভার” (CPU, ডিস্ক) আর তাদের সামনের সারির একটি জাল হিসেবে দেখা যায়, আর queueing তত্ত্ব দিয়ে গড় সারির দৈর্ঘ্য, গড় অপেক্ষা, utilisation হিসাব করা যায় — এটিই queueing model. এর সবচেয়ে সাধারণ আর সবচেয়ে কাজের ফলটি Little-এর সূত্র:
n = সারিতে গড় প্রসেস-সংখ্যা · λ = গড় আগমন-হার · W = সারিতে গড় অপেক্ষা
সূত্রটি অবাক করার মতো সাধারণ: আগমনের বণ্টন যা-ই হোক, সারির নিয়ম (FCFS, priority, যেকোনো) যা-ই হোক, সিস্টেম দীর্ঘ সময়ে স্থিতিশীল থাকলেই (যত আসে, গড়ে তত বেরোয়) এটি সত্য। “সারি” যেকোনো সীমানা দিয়ে আঁকা যায় — শুধু ready queue, শুধু ডিস্কের সারি, বা পুরো সিস্টেম; তিনটি রাশি একই সীমানার জন্য মাপলেই হলো।
কেন সত্য — একটি সরল যুক্তি: লম্বা একটি সময় T ধরে প্রতিটি মুহূর্তে সারিতে কতজন আছে, তা একটি লেখচিত্রে আঁকুন (n(t)). লেখচিত্রের নিচের ক্ষেত্রফলকে দুইভাবে গোনা যায়। এক, প্রতিটি মুহূর্তের n(t) যোগ করে: ক্ষেত্রফল = (গড় n) × T. দুই, প্রতিটি প্রসেস সারিতে যতক্ষণ ছিল, ততক্ষণ সে লেখচিত্রে এক একক উচ্চতা যোগ করেছে; তাই ক্ষেত্রফল = সব প্রসেসের অপেক্ষার যোগফল = (আসা প্রসেসের সংখ্যা) × (গড় W). আসা প্রসেসের সংখ্যা ≈ λ × T. দুই হিসাব সমান করে: (গড় n) × T = λ × T × W, অর্থাৎ n = λW. (শুরু আর শেষে আংশিক থাকা প্রসেসগুলোর জন্য সামান্য ভুল থাকে, যা T লম্বা হলে নগণ্য হয়ে যায়; John D. C. Little ১৯৬১ সালে এর কঠোর প্রমাণ প্রকাশ করেন।)
সমাধান করা উদাহরণ: একটি সার্ভারে গড়ে প্রতি সেকেন্ডে ৭টি প্রসেস ready queue-তে আসে, আর প্রতিটি সেখানে গড়ে ২ সেকেন্ড অপেক্ষা করে। গড়ে queue-তে কতজন থাকে? n = ৭ × ২ = ১৪টি প্রসেস. উল্টো দিক থেকে: একটি ওয়েব সার্ভার প্রতি সেকেন্ডে ৫০টি অনুরোধ নেয়, আর যেকোনো মুহূর্তে গড়ে ১০টি অনুরোধ ভেতরে থাকে (মাপা যায়)। একটি অনুরোধ গড়ে কতক্ষণ থাকে? W = n ÷ λ = ১০ ÷ ৫০ = ০.২ সেকেন্ড = ২০০ ms. খেয়াল করুন, এখানে কোনো scheduling algorithm জানতে হয়নি।
আরও গভীর queueing মডেল (যেমন আগমন Poisson আর সেবা-সময় exponential ধরলে, যাকে M/M/1 বলে) একটি গুরুত্বপূর্ণ সত্য দেখায়: utilisation ρ যত ১-এর কাছে যায়, গড় অপেক্ষা তত দ্রুত বাড়ে — ঐ মডেলে সিস্টেমে গড় সময় সেবা-সময়ের ১/(১ − ρ) গুণ। ৫০% ব্যস্ত CPU-তে তা ২ গুণ, ৯০%-এ ১০ গুণ, ৯৯%-এ ১০০ গুণ। অংশ ৫-এর “utilisation বনাম predictability” টানাপোড়েনের গাণিতিক রূপ এটিই। দুর্বলতা: মডেলগুলো বাস্তবের তুলনায় অনেক সরল অনুমান করে, আর জটিল algorithm-এর জন্য গণিত দ্রুত কঠিন হয়ে যায়।
৩. Simulation — কম্পিউটারে কম্পিউটার
একটি প্রোগ্রাম বানানো হয়, যেখানে একটি ঘড়ি, ready queue আর scheduler-এর নিয়ম আছে; ঘড়ি এগোয়, প্রসেস আসে, চলে, শেষ হয়, আর প্রোগ্রাম সব মাপকাঠি গোনে — এটি simulation. প্রসেসের ধারা আসতে পারে random-সংখ্যা থেকে (মাপা বণ্টন অনুযায়ী), অথবা আরও নির্ভরযোগ্যভাবে একটি আসল সিস্টেমের রেকর্ড করা ঘটনার তালিকা (trace) থেকে — তখন একই বাস্তব কাজের ধারা দিয়ে একাধিক algorithm-কে হুবহু একই পরীক্ষায় ফেলা যায়। দুর্বলতা: ভালো simulator বানাতে সময় লাগে, আর তার সরলীকরণ (যেমন context switch-এর খরচ শূন্য ধরা) ফল বাঁকাতে পারে।
৪. Implementation — আসল OS-এ বসিয়ে মাপা
শেষ পরীক্ষা: algorithm-টি আসল kernel-এ লিখে, আসল কাজ চালিয়ে মাপা। এটিই সবচেয়ে নির্ভুল — হার্ডওয়্যার, cache, interrupt, সব বাস্তব। কিন্তু খরচ বেশি: kernel কোড লেখা আর পরীক্ষা কঠিন, ভুল হলে পুরো সিস্টেম অস্থির হয়। আরেকটি সূক্ষ্ম সমস্যা: প্রোগ্রামাররা নিয়ম শিখে আচরণ বদলান — scheduler ছোট burst-কে অগ্রাধিকার দিলে কেউ লম্বা হিসাবের মাঝে অপ্রয়োজনীয় ছোট I/O বসিয়ে নিজেকে “interactive” দেখাতে পারেন। Linux-এ নতুন scheduler বা বড় পরিবর্তন ঢোকার আগে বহু benchmark আর বাস্তব কাজের ফল দেখানো হয়; আর ২০২৪ সালে Linux 6.12-এ যুক্ত হওয়া sched_ext scheduling নীতিকে BPF প্রোগ্রাম হিসেবে লিখে চলন্ত kernel-এ পরীক্ষা করার সুযোগ দিয়েছে (পর্ব ২১, ৫৫)।
| পদ্ধতি | ইনপুট | সুবিধা | দুর্বলতা |
|---|---|---|---|
| Deterministic modelling | একটি নির্দিষ্ট প্রসেস টেবিল | সরল, নিখুঁত, শেখার জন্য সেরা | শুধু ঐ টেবিলের জন্য সত্য |
| Queueing model | আগমন আর সেবার বণ্টন | সাধারণ সূত্র (Little), দ্রুত অনুমান | সরল অনুমান; জটিল নীতিতে গণিত কঠিন |
| Simulation | Random ধারা বা আসল trace | নমনীয়, একই ধারায় ন্যায্য তুলনা | বানাতে সময়; সরলীকরণের ঝুঁকি |
| Implementation | আসল কাজ, আসল হার্ডওয়্যার | সবচেয়ে বাস্তব | খরচ ও ঝুঁকি বেশি; ব্যবহারকারী আচরণ বদলান |
৮ · সামনে কী — algorithm-গুলোর মানচিত্র
এই পর্বের মাপকাঠি আর Gantt chart নিয়ে এবার আপনি পরের পাঁচটি পর্বের জন্য প্রস্তুত। প্রতিটি পর্ব একটি প্রশ্নের উত্তর খোঁজে:
পর্ব ১৭ · FCFS, SJF আর SRTF
আসার ক্রমে চালানো কতটা খারাপ হতে পারে? সবচেয়ে ছোট কাজ আগে চালালে গড় অপেক্ষা কেন সর্বনিম্ন, আর পরের burst-এর দৈর্ঘ্য না জেনে কীভাবে অনুমান করা যায়?
গড় waiting-এর লড়াইপর্ব ১৮ · Priority আর Round Robin
গুরুত্ব অনুযায়ী CPU দিলে বঞ্চিতদের কী হয়? সবাইকে পালা করে ছোট সময় দিলে response কীভাবে ভালো হয়, আর quantum কত হওয়া উচিত?
response বনাম overheadপর্ব ১৯ · Multilevel queue আর MLFQ
আগে থেকে burst না জেনেও কীভাবে I/O-bound আর CPU-bound প্রসেস আলাদা করে চেনা যায় — আচরণ দেখে সারি বদলে?
অতীত দেখে শেখাপর্ব ২০ · Multiprocessor আর real-time
অনেকগুলো core-এ কাজ কীভাবে ভাগ হয়, আর deadline রক্ষার নিশ্চয়তা (Rate Monotonic, EDF) কীভাবে প্রমাণ করা যায়?
একাধিক CPU, কঠোর সময়আর পর্ব ২১-এ সব মিলবে একটি আসল scheduler-এ — Linux-এর CFS, তার উত্তরসূরি EEVDF, nice আর cgroup.
আরও গভীরে: Linux-এর scheduler আসলে একটি নয়, কয়েকটি “scheduling class”-এর একটি অগ্রাধিকার-ক্রম: deadline class (SCHED_DEADLINE), তারপর real-time class (SCHED_FIFO, SCHED_RR), তারপর সাধারণ fair class (SCHED_OTHER, SCHED_BATCH, SCHED_IDLE), আর সবশেষে প্রতিটি CPU-র idle task. pick_next_task() উপরের class থেকে খোঁজা শুরু করে, আর যে class-এ কেউ runnable আছে, সেখান থেকেই নেয় — তাই একটি real-time task থাকলে সাধারণ task-গুলো অপেক্ষা করে। ps-এর cls কলামের TS, FF, RR, B, IDL, DLN এই নীতিগুলোরই সংক্ষিপ্ত নাম। /proc/<pid>/sched ফাইলে (kernel-এর সেটিং অনুযায়ী) আরও বিস্তারিত হিসাব থাকে, আর kernel-এর Documentation/scheduler/sched-stats.rst নথিতে schedstat-এর প্রতিটি সংখ্যার অর্থ লেখা আছে।
৯ · AI-এর যুগে scheduling-এর হিসাব
Scheduling-এর অঙ্ক AI-কে দিলে উত্তর দ্রুত আসে, আর দেখতে নিখুঁত টেবিলের মতো লাগে। কিন্তু এখানে ছোট একটি ভুল — একটি idle ফাঁক বাদ পড়া, বা arrival বিয়োগ না করা — পুরো উত্তর নষ্ট করে, আর টেবিল দেখে তা ধরা যায় না। এই পর্বের নিয়মগুলো দিয়ে যাচাই করুন:
| AI-এর ভুল | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| Idle ফাঁক ছাড়াই পরের প্রসেস চালানো (P4-কে t = ১০-এ শুরু) | Arrival-এর আগে কোনো প্রসেস চলতে পারে না | প্রতিটি ঘরের শুরু ≥ সেই প্রসেসের AT কি না দেখুন |
| WT = CT − BT লেখা | Arrival বিয়োগ বাদ পড়েছে; সঠিক WT = CT − AT − BT | যে প্রসেস আসামাত্র চলেছে, তার WT ০ আসছে কি না দেখুন |
| Response time আর turnaround গুলিয়ে ফেলা | RT মাপে প্রথম CPU পাওয়া পর্যন্ত, TAT মাপে পুরো শেষ পর্যন্ত | RT ≤ WT ≤ TAT — এই ক্রম ভাঙলে ভুল |
| I/O থাকা প্রসেসেও WT = TAT − BT বসানো | I/O-র সময়কেও অপেক্ষা ধরা হচ্ছে | Chart-এ শুধু ready queue-র সময় গুনুন |
| Little-এর সূত্রে একক না মেলানো (λ প্রতি ঘণ্টায়, W মিনিটে) | λ আর W একই সময়-এককে না হলে উত্তর ৬০ গুণ ভুল | গুণ করার আগে একক লিখে মেলান |
Scheduling class দেখতে Windows-এ ps -o cls বা Linux-এ PowerShell কমান্ড দেওয়া; বা “sudo nice -n -20 দিয়ে সব ঠিক করুন” | OS ভুল; আর অগ্রাধিকার বাড়ানো অন্যদের বঞ্চিত করতে পারে | প্রম্পটে OS লিখুন; অগ্রাধিকার বদলের আগে কারণ মাপুন |
AI-কে scheduling-এর অঙ্ক করাতে চাইলে নিয়মগুলো স্পষ্ট করে দিন, আর তাকে নিজের উত্তর যাচাই করতে বলুন:
AI-এর উত্তর মানার আগে — চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Preemptive আর non-preemptive scheduling-এর পার্থক্য? — Non-preemptive শুধু running → waiting আর terminate-এ সিদ্ধান্ত নেয়; preemptive running → ready আর waiting → ready-তেও CPU কেড়ে নিতে পারে।
- Turnaround, waiting আর response time-এর সূত্র? — TAT = CT − AT; WT = TAT − BT (একটিমাত্র CPU burst হলে); RT = প্রথম CPU পাওয়া − AT.
- কোন মাপকাঠি বাড়াতে আর কোনগুলো কমাতে হয়? — CPU utilisation ও throughput বাড়াতে; turnaround, waiting ও response time কমাতে।
- Little-এর সূত্র কী? λ = ২০ প্রসেস/সেকেন্ড, W = ০.৫ সেকেন্ড হলে n? — n = λ × W = ১০টি প্রসেস।
- Dispatcher আর scheduler কি একই? — না; scheduler বাছে কে চলবে, dispatcher তাকে CPU-তে বসায়; মাঝের সময় dispatch latency.
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Batch যুগে turnaround time-ই ছিল প্রধান মাপকাঠি। Time-sharing আসার পর response time কেন এত গুরুত্বপূর্ণ হয়ে উঠল?
১৯৫০ আর ১৯৬০-এর দশকের শুরুতে বড় কম্পিউটারগুলো চলত batch পদ্ধতিতে (পর্ব ২)। প্রোগ্রামার তাঁর কাজ পাঞ্চ কার্ডে জমা দিতেন, অপারেটর অনেক কাজ একসাথে চালাতেন, আর ফল আসত কয়েক ঘণ্টা বা পরের দিন। এখানে মানুষের কাছে একটিই প্রশ্ন ছিল: “আমার কাজ কখন ফেরত পাব?” — অর্থাৎ turnaround time. মালিকের প্রশ্ন ছিল দামি মেশিন কতটা ব্যস্ত থাকে — utilisation আর throughput. প্রোগ্রাম চলার মাঝখানে কেউ পর্দার সামনে বসে ছিলেন না, তাই প্রথম সাড়া কখন এল, তাতে কারও কিছু যেত-আসত না।
১৯৬১ সালে MIT-তে Fernando Corbató-র দল CTSS (Compatible Time-Sharing System)-এর প্রথম প্রদর্শনী করেন, আর পরের কয়েক বছরে time-sharing ছড়িয়ে পড়ে। এখন অনেক মানুষ একসাথে টার্মিনালে বসে কম্পিউটারের সঙ্গে “কথা বলছেন” — একটি কমান্ড লিখে উত্তরের অপেক্ষা করছেন। এখানে turnaround প্রায় অর্থহীন: একটি সম্পাদনা-সেশন ঘণ্টার পর ঘণ্টা চলতে পারে। আসল প্রশ্ন হলো প্রতিটি কি-চাপ বা কমান্ডের পর কত দ্রুত সাড়া আসে। মানুষের মনোযোগের সীমা মোটামুটি পরিচিত: প্রায় এক-দশমাংশ সেকেন্ডের মধ্যে সাড়া এলে তা তাৎক্ষণিক লাগে, এক সেকেন্ডের বেশি হলে চিন্তার ধারা ভাঙে।
এই বদল scheduling-এর নকশাই বদলে দিল। CTSS ব্যবহার করত কয়েক স্তরের সারি, যেখানে ছোট কাজ দ্রুত সাড়া পেত আর লম্বা কাজ নিচের স্তরে নেমে লম্বা কিন্তু কম ঘন ঘন সময় পেত — মাল্টিলেভেল ফিডব্যাক কিউ-এর পূর্বসূরি (পর্ব ১৯)। Round Robin আর timer interrupt-চালিত preemption-এর পুরো যুক্তিই response time কমানো। বিনিময়ে throughput কিছুটা কমে, কারণ পালাবদলের খরচ বাড়ে — অংশ ৫-এর টানাপোড়েন।
আজকের ফোনে মাপকাঠিটি আরও কঠোর: স্ক্রিন প্রতি সেকেন্ডে ৬০ বার নতুন ছবি আঁকলে একটি ফ্রেমের কাজ ১৬.৭ ms-এর মধ্যে শেষ না হলে স্ক্রল “আটকে” যায়। অথচ ডেটা-সেন্টারের রাতের হিসাবে আজও turnaround আর throughput-ই আসল।
মূল উপলব্ধি: মাপকাঠি আসে ব্যবহারের ধরন থেকে। কে অপেক্ষা করছে — মানুষ, নাকি একটি রিপোর্ট — সেই প্রশ্নের উত্তরই ঠিক করে scheduler কোন সংখ্যাটি ছোট রাখার চেষ্টা করবে।
প্র ০২Little-এর সূত্র এত সরল, তবু ইঞ্জিনিয়াররা কেন একে সিস্টেম-নকশার সবচেয়ে কাজের যন্ত্রগুলোর একটি মনে করেন?
John D. C. Little ১৯৬১ সালে Operations Research জার্নালে L = λW সূত্রের একটি সাধারণ প্রমাণ প্রকাশ করেন। সূত্রটি queueing তত্ত্বে আগে থেকেই ব্যবহৃত হতো, কিন্তু Little দেখালেন এটি প্রায় কোনো শর্ত ছাড়াই সত্য — আগমনের বণ্টন, সেবা-সময়ের বণ্টন বা সারির নিয়ম যা-ই হোক, সিস্টেম দীর্ঘ সময়ে স্থিতিশীল থাকলেই হলো। এই সাধারণতাই এর শক্তি: তিনটি রাশির যেকোনো দুটি মাপা গেলে তৃতীয়টি বিনা পরিশ্রমে পাওয়া যায়।
সার্ভারের নকশায় এর ব্যবহার প্রতিদিনের। ধরুন একটি ডেটাবেসে প্রতি সেকেন্ডে ৫০০টি প্রশ্ন আসে, আর প্রতিটি গড়ে ২০ ms নেয়। তাহলে যেকোনো মুহূর্তে গড়ে ৫০০ × ০.০২ = ১০টি প্রশ্ন চলছে — অর্থাৎ connection pool বা thread pool-এ অন্তত ১০টির মতো জায়গা লাগবে, আর হঠাৎ ভিড়ের জন্য বাড়তি। উল্টোভাবে, pool-এ মাত্র ৮টি জায়গা থাকলে সর্বোচ্চ throughput = ৮ ÷ ০.০২ = ৪০০ প্রশ্ন প্রতি সেকেন্ড; এর বেশি এলে সারি অনির্দিষ্টভাবে বাড়বে। একটি ডিস্কে গড়ে ৪টি অনুরোধ সারিতে আর প্রতি সেকেন্ডে ২০০টি অনুরোধ — তাহলে গড় অপেক্ষা ৪ ÷ ২০০ = ২০ ms.
সূত্রটি একটি সতর্কবার্তাও দেয়। Latency কমাতে অনেকে thread বা সংযোগ বাড়ান। কিন্তু λ যদি CPU-র সেবা-ক্ষমতার কাছাকাছি হয়, বেশি thread মানে শুধু সারিতে বেশি কাজ — n বাড়ে, আর λ একই থাকায় W-ও বাড়ে। অর্থাৎ ক্ষমতার বেশি কাজ ঢুকতে দিলে latency খারাপই হয়। এ কারণে ভালো সার্ভার ক্ষমতার সীমায় পৌঁছালে নতুন কাজ ফিরিয়ে দেয় বা ধীর করে (load shedding, backpressure) — ঠিক যেমন long-term scheduler একসময় degree of multiprogramming নিয়ন্ত্রণ করত।
সতর্কতা: সূত্রটি গড়ের সম্পর্ক, প্রতিটি অনুরোধের নয়; আর তিনটি রাশি একই সীমানা আর একই সময়-জানালায় মাপতে হয়। সিস্টেম স্থিতিশীল না হলে (সারি ক্রমাগত বাড়ছে) গড় W-এর কোনো স্থির মান নেই।
মূল উপলব্ধি: Little-এর সূত্র কোনো algorithm-এর ব্যাপারে কিছু বলে না, কিন্তু যেকোনো সিস্টেমের ভেতরের কাজ, তার গতি আর অপেক্ষাকে এক সুতোয় বাঁধে — তাই প্রথম হিসাব আর ক্ষমতা-পরিকল্পনার জন্য এটি অমূল্য।
প্র ০৩I/O-bound প্রসেসকে অগ্রাধিকার দিলে CPU-bound প্রসেসের ক্ষতি সামান্য, অথচ সবার লাভ বেশি — কেন? আর বাস্তব OS কীভাবে I/O-bound প্রসেস চেনে?
একটি ছোট চিন্তা-পরীক্ষা নিন। একটি CPU, একটি ডিস্ক। একটি CPU-bound প্রসেস (প্রতি burst ১০০ ms) আর দশটি I/O-bound প্রসেস (প্রতি burst ১ ms, তারপর ডিস্কে ১০ ms). FCFS-এ CPU-bound প্রসেস CPU পেলে ১০০ ms ধরে রাখে; এই সময়ে দশটি I/O-bound প্রসেস ডিস্কের কাজ শেষ করে একে একে ready queue-তে জমে, আর ডিস্ক অলস বসে থাকে। তারপর CPU-bound প্রসেস ডিস্কে গেলে উল্টো ছবি: I/O-bound প্রসেসগুলো ঝটপট নিজেদের ১ ms সেরে ডিস্কের সারিতে দাঁড়ায়, আর CPU অলস। একটি ধীর গাড়ির পেছনে লম্বা গাড়ির সারির মতো এই ছবিকে বলে convoy effect — দুটি যন্ত্রই পালা করে অলস থাকে।
এবার I/O-bound প্রসেসকে আগে দিন। প্রত্যেকে মাত্র ১ ms CPU নিয়ে ডিস্কে চলে যায়; CPU-bound প্রসেস বাকি সময়টুকু পায়। তার সমাপ্তি কিছুটা পিছায় — দশটি ছোট burst মিলে কয়েক ms — কিন্তু ডিস্ক আর CPU দুটোই প্রায় সারাক্ষণ ব্যস্ত থাকে। মোট throughput বাড়ে, I/O-bound প্রসেসের response অনেক ভালো হয়, আর CPU-bound প্রসেসের ক্ষতি তার মোট সময়ের তুলনায় নগণ্য। এটিই অংশ ১-এর মূল ধারণার গাণিতিক রূপ: ছোট burst-কে আগে দেওয়ার “দাম” ছোট, লাভ বড়।
সমস্যা হলো, OS আগে থেকে জানে না কে কোন জাতের। তাই সে অতীত আচরণ দেখে। পুরোনো Unix scheduler সম্প্রতি বেশি CPU খাওয়া প্রসেসের অগ্রাধিকার কমিয়ে দিত, আর সময়ের সঙ্গে সেই হিসাব ধীরে ধীরে ভুলে যেত (decay) — ফলে বারবার ঘুমিয়ে পড়া প্রসেস স্বাভাবিকভাবে উপরে থাকত। MLFQ (পর্ব ১৯) একই কাজ করে সারি বদলে। Linux ২.৬-এর প্রথম দিকের O(1) scheduler-এ ঘুমের সময় দেখে “interactivity” অনুমানের জটিল নিয়ম ছিল, যা কিছু ক্ষেত্রে ভুল করত আর বোঝা কঠিন ছিল; ২০০৭ সালের CFS সেই অনুমান বাদ দিয়ে সহজ নিয়ম আনল — যে কম CPU পেয়েছে, সে আগে পাবে — যাতে ঘুমিয়ে থাকা প্রসেস জাগলে স্বাভাবিকভাবেই সামনে থাকে (পর্ব ২১)।
মূল উপলব্ধি: Scheduling শুধু CPU-র ব্যাপার নয় — ভালো scheduler CPU আর I/O যন্ত্রগুলোকে একসাথে ব্যস্ত রাখে। আর কার burst ছোট, তা না জেনেও অতীত দেখে বেশ ভালো অনুমান করা যায়।
প্র ০৪“Fairness” আসলে কী মাপে? প্রতিটি প্রসেসকে সমান ভাগ দেওয়াই কি ন্যায্য — আর starvation কেন এত গুরুতর?
Fairness-এর সহজ সংজ্ঞা: তুলনীয় কাজ তুলনীয় সুযোগ পাবে, আর কেউ অনির্দিষ্টকাল বঞ্চিত হবে না। কিন্তু “তুলনীয়” কে? ধরুন একটি সার্ভারে দুজন ব্যবহারকারী। একজন চালাচ্ছেন ১টি প্রসেস, আরেকজন ৯৯টি। প্রতি প্রসেসে সমান ভাগ দিলে দ্বিতীয়জন পান CPU-র ৯৯% — প্রসেসের হিসাবে ন্যায্য, মানুষের হিসাবে নয়। তাই আধুনিক OS ভাগটিকে স্তরে স্তরে করতে দেয়: আগে ব্যবহারকারী বা দলের মধ্যে, তারপর দলের ভেতরের প্রসেসে। Linux-এ এটি cgroup দিয়ে হয় (পর্ব ২১, ৫১)। ২০১১ সালে Linux 2.6.38-এ যুক্ত হওয়া “autogroup” বৈশিষ্ট্য ডেস্কটপে প্রতিটি টার্মিনাল-সেশনকে একটি দল বানিয়ে দেয় — ফলে এক টার্মিনালে বহু thread দিয়ে বড় compile চালালেও অন্য জানালার কাজ মসৃণ থাকে।
অন্যদিকে সমান ভাগ সবসময় কাম্যও নয়। একটি অডিও প্লেয়ার মোট CPU-র খুব সামান্য চায়, কিন্তু ঠিক সময়ে চায়; একটি ব্যাকআপ প্রোগ্রাম অনেক চায়, কিন্তু কখন পায় তাতে কিছু যায় আসে না। Nice মান, অগ্রাধিকার আর real-time class (অংশ ৮) এই অসমতাকে ইচ্ছাকৃতভাবে প্রকাশের উপায়। তাই fairness মানে “সবাই সমান” নয়, বরং “সবাই নীতি অনুযায়ী প্রাপ্য ভাগ পায়, আর কেউ শূন্যে আটকে থাকে না।”
Starvation সেই শেষ শর্তটির ভাঙন: একটি প্রসেস প্রস্তুত, কিন্তু অন্যদের স্রোতের কারণে কখনোই CPU পায় না। কঠোর priority বা ছোট-কাজ-আগে নীতিতে এটি সহজেই ঘটে। পাঠ্যবইয়ে একটি প্রচলিত গল্প আছে: ১৯৭৩ সালে MIT-র IBM 7094 কম্পিউটার বন্ধ করার সময় নাকি একটি কম-অগ্রাধিকারের কাজ পাওয়া গিয়েছিল, যা ১৯৬৭ সালে জমা পড়েছিল আর তখনো চলেনি। গল্পটি পুরোপুরি যাচাইযোগ্য না হলেও শিক্ষাটি বাস্তব। সাধারণ সমাধান aging: যত বেশি অপেক্ষা, অগ্রাধিকার তত বাড়ে (পর্ব ১৮)।
মূল উপলব্ধি: Fairness একটি নীতিগত সিদ্ধান্ত — কার মধ্যে সমতা, আর কতটা ইচ্ছাকৃত অসমতা। কিন্তু একটি নিয়ম প্রায় সর্বজনীন: কোনো প্রস্তুত কাজ চিরকাল বঞ্চিত থাকবে না।
প্র ০৫গড় response time ভালো হলেও ব্যবহারকারীরা কেন অভিযোগ করেন? Predictability আর tail latency কেন আধুনিক সিস্টেমের মূল মাপকাঠি হয়ে উঠেছে?
ধরুন দুটি সিস্টেম। প্রথমটিতে প্রতিটি কমান্ডের সাড়া আসে ঠিক ২০০ ms-এ। দ্বিতীয়টিতে ৯০% সাড়া আসে ৫০ ms-এ, আর ১০% আসে ১.৫ সেকেন্ডে — গড় দাঁড়ায় ০.৯ × ৫০ + ০.১ × ১৫০০ = ১৯৫ ms, প্রথমটির চেয়ে সামান্য ভালো। তবু বেশিরভাগ মানুষ প্রথমটিকে পছন্দ করবেন। কারণ মানুষ ধারাবাহিকতায় অভ্যস্ত হন, আর হঠাৎ দীর্ঘ থেমে যাওয়া তাঁদের মনে বেশি দাগ কাটে। পাঠ্যবই তাই interactive সিস্টেমে গড় কমানোর চেয়ে response time-এর বিচ্যুতি (variance) কমানোকে অনেক সময় বেশি গুরুত্বপূর্ণ বলে — এটিই predictability.
বড় পরিসরের সার্ভারে সমস্যাটি গুণিত হয়। ২০১৩ সালে Jeffrey Dean আর Luiz André Barroso “The Tail at Scale” প্রবন্ধে দেখান: একটি ওয়েব অনুরোধ যদি একসাথে ১০০টি সার্ভার থেকে উত্তর সংগ্রহ করে, আর প্রতিটি সার্ভার ১% ক্ষেত্রে ধীর হয়, তাহলে পুরো অনুরোধের ধীর হওয়ার সম্ভাবনা ১ − ০.৯৯১০০ ≈ ৬৩%. অর্থাৎ প্রতিটি সার্ভারের “বিরল” খারাপ ক্ষেত্রটিই পুরো সিস্টেমের সাধারণ অভিজ্ঞতা হয়ে যায়। এ কারণে আজকের ইঞ্জিনিয়াররা গড়ের বদলে p99 বা p99.9 latency (ধীরতম ১% বা ০.১%-এর সীমা) লক্ষ্য হিসেবে ধরেন।
Scheduler এই tail-এর বড় উৎস হতে পারে। একটি অনুরোধ যে thread-এ আছে, সে যদি ভুল মুহূর্তে preempt হয়, বা ব্যস্ত CPU-র run queue-তে কয়েক ms অপেক্ষা করে, বা অন্য core-এ সরানোয় তার cache ঠান্ডা হয়ে যায় — প্রতিটি ঘটনা বিরল, কিন্তু মিলে tail তৈরি করে। Schedstat-এর “run queue-তে অপেক্ষা” সংখ্যা বা perf sched latency এই অপেক্ষা মাপতে সাহায্য করে। সমাধানগুলোরও দাম আছে: utilisation ইচ্ছা করে ১০০%-এর অনেক নিচে রাখা (অংশ ৭-এর ১/(১ − ρ)), বা একই অনুরোধ দুই সার্ভারে পাঠিয়ে আগের উত্তরটি নেওয়া (বাড়তি কাজ)।
Real-time সিস্টেমে predictability আরও কঠোর রূপ নেয়: সেখানে সবচেয়ে খারাপ ক্ষেত্রের একটি প্রমাণিত ঊর্ধ্বসীমা লাগে (পর্ব ২০)।
মূল উপলব্ধি: গড় একটি সংখ্যা, অভিজ্ঞতা একটি বণ্টন। Scheduler-কে বিচার করার সময় গড়ের পাশাপাশি বিচ্যুতি আর সবচেয়ে খারাপ ক্ষেত্রও দেখতে হয় — বিশেষত যখন অনেক অংশ মিলে একটি উত্তর তৈরি হয়।
অনুশীলন
হাতে Gantt chart, সূত্রের ফাঁদ, Little-এর সূত্র আর কমান্ড — আগে নিজে চেষ্টা করুন, তারপর উত্তর মেলান।
-
হাতে হিসাব (FCFS): P1 (AT ২, BT ৪), P2 (AT ৩, BT ৩), P3 (AT ১০, BT ২), P4 (AT ১১, BT ৫) — সময় ms-এ। FCFS-এর Gantt chart আঁকুন, প্রতিটি প্রসেসের CT, TAT, WT, RT বের করুন, গড়গুলো দিন, আর t = 0 থেকে ধরে throughput ও CPU utilisation হিসাব করুন।
│idle 0-2│P1 2-6│P2 6-9│idle 9-10│P3 10-12│P4 12-17│দুটি idle ঘর: শুরুতে (০–২, কেউ আসেনি) আর ৯–১০ (P2 শেষ, P3 তখনো আসেনি)। CT: ৬, ৯, ১২, ১৭. TAT = CT − AT: ৪, ৬, ২, ৬ → যোগ ১৮, গড় ৪.৫ ms. WT = TAT − BT: ০, ৩, ০, ১ → যোগ ৪, গড় ১ ms. Non-preemptive, তাই RT = WT, গড় ১ ms.
Throughput (t = 0 থেকে) = ৪ ÷ ১৭ ≈ ০.২৩৫ প্রসেস প্রতি ms. ব্যস্ত সময় ৪ + ৩ + ২ + ৫ = ১৪, তাই utilisation = ১৪ ÷ ১৭ ≈ ৮২.৪%. প্রথম arrival (t = ২) থেকে গুনলে হতো ৪ ÷ ১৫ ≈ ০.২৬৭ আর ১৪ ÷ ১৫ ≈ ৯৩.৩% — তাই কোন সীমা নিয়েছেন, তা উত্তরে লিখে দিন। Calculator-এ
procsআর ক্রম বদলে মিলিয়ে নিতে পারেন। -
সূত্রের ফাঁদ (হাতে trace): দুটি প্রসেস t = 0-তে আসে, FCFS, P আগে। P-এর ধারা: CPU ৩ ms → I/O ৫ ms → CPU ২ ms. Q-এর ধারা: শুধু CPU ৪ ms. Gantt chart আঁকুন। P-এর TAT কত? “WT = TAT − মোট CPU burst” সূত্র P-এর জন্য কত দেয়, আর P আসলে ready queue-তে কতক্ষণ অপেক্ষা করেছে? Q-এর WT কত?
│P 0-3│Q 3-7│idle 7-8│P 8-10│ (P is in I/O from 3 to 8)P প্রথম burst শেষে t = ৩-এ I/O-তে যায় — এটি মুহূর্ত ১, CPU খালি, তাই Q চলে ৩ থেকে ৭. P-এর I/O শেষ হয় t = ৮-এ; ৭ থেকে ৮ CPU-তে কেউ নেই — idle. তারপর P ৮ থেকে ১০.
P-এর TAT = ১০ − ০ = ১০. সূত্র দেয় ১০ − (৩ + ২) = ৫ — কিন্তু এই ৫ ms পুরোটাই I/O, অপেক্ষা নয়। P আসলে ready queue-তে ছিল ০ ms (আসামাত্র চলেছে, I/O থেকে ফিরে CPU খালি পেয়েছে)। সঠিক সূত্র: WT = TAT − CPU − I/O = ১০ − ৫ − ৫ = ০. Q-এর TAT = ৭, WT = ৭ − ৪ = ৩ (t = ০ থেকে ৩ পর্যন্ত P-এর পেছনে)।
-
Little-এর সূত্র: (ক) একটি ব্যাংকের শাখায় ঘণ্টায় গড়ে ৩০ জন গ্রাহক আসেন, আর প্রত্যেকে শাখায় গড়ে ১২ মিনিট থাকেন। যেকোনো মুহূর্তে শাখায় গড়ে কতজন থাকেন? (খ) একটি ওয়েব সার্ভার প্রতি সেকেন্ডে ১০০০টি অনুরোধ সামলায়, আর মাপে দেখা গেল যেকোনো মুহূর্তে গড়ে ২০০টি অনুরোধ ভেতরে আছে। একটি অনুরোধের গড় সময় কত?
(ক) আগে একক মেলান: ১২ মিনিট = ০.২ ঘণ্টা। n = λ × W = ৩০ × ০.২ = ৬ জন. (একক না মিলিয়ে ৩০ × ১২ = ৩৬০ লিখলে উত্তর ৬০ গুণ ভুল।)
(খ) W = n ÷ λ = ২০০ ÷ ১০০০ = ০.২ সেকেন্ড = ২০০ ms. গড় latency কমাতে হলে হয় প্রতিটি অনুরোধ দ্রুত শেষ করতে হবে, নয়তো একই throughput-এ ভেতরে জমে থাকা কাজ (n) কমাতে হবে — thread বাড়িয়ে n বাড়ালে উল্টো W বাড়ে।
-
কমান্ড লিখুন: (ক) Linux-এ আপনার নিজের সব প্রসেসের PID, scheduling class, nice, অগ্রাধিকার, শেষ চলা CPU আর নাম দেখার একটি
psকমান্ড। (খ) PID 1234-এর schedstat পড়ে প্রতি পালায় গড়ে কত ন্যানোসেকেন্ড run queue-তে অপেক্ষা হয়েছে, তা bash-এ হিসাব করুন। (গ) Windows-এ একটি প্রসেসের অগ্রাধিকার-শ্রেণি দেখার PowerShell কমান্ড।ps -u "$USER" -o pid,cls,ni,pri,psr,comm # (a) read run wait slices < /proc/1234/schedstat; echo $((wait / slices)) # (b) ns per slice Get-Process -Id 1234 | Select-Object Name, Id, PriorityClass # (c) PowerShell(খ)-তে
slicesশূন্য হলে ভাগে ত্রুটি হবে — সদ্য তৈরি প্রসেসে এমন হতে পারে, তাই আগে পরীক্ষা করে নেওয়া ভালো। এর কোনোটিতেইsudoলাগে না। Windows-এ PriorityClass (Normal, BelowNormal, High…) Linux-এর nice-এর মতো ধারণা হলেও স্কেল আর নিয়ম আলাদা — একটিকে আরেকটিতে সরাসরি অনুবাদ করবেন না (পর্ব ৫৬)।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি scheduling-অঙ্কে মাপকাঠি বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- প্রসেস টেবিল থেকে AT আর BT লেখা
- Arrival আর idle ফাঁক মেনে Gantt chart আঁকা
- Chart থেকে প্রত্যেকের CT আর প্রথম CPU পাওয়ার সময় পড়া
- TAT = CT − AT হিসাব করা
- WT = TAT − BT আর RT = প্রথম CPU − AT হিসাব করা
- গড়, throughput আর CPU utilisation বের করা
একটি non-preemptive scheduler কোন দুটি মুহূর্তেই কেবল নতুন প্রসেস বাছাই করে?
- Running → ready আর waiting → ready
- Waiting → ready আর terminate
- Running → waiting আর terminate
- শুধু timer interrupt-এ
FCFS scheduling-এ তিনটি প্রসেস: P1 (arrival 0, burst 4), P2 (arrival 1, burst 3), P3 (arrival 2, burst 1), সময় ms-এ। গড় waiting time কত?
- ৩ ms
- প্রায় ২.৬৭ ms
- প্রায় ৫.৩৩ ms
- ৩.৫ ms
একটি সিস্টেমে গড়ে প্রতি সেকেন্ডে ২০টি প্রসেস আসে, আর প্রতিটি ready queue-তে গড়ে ০.৫ সেকেন্ড অপেক্ষা করে। Little-এর সূত্র অনুযায়ী queue-তে গড়ে কতটি প্রসেস থাকে?
- ১০টি
- ৪০টি
- ২০টি
- ০.০২৫টি
একটি interactive সিস্টেমে (যেমন টেক্সট এডিটর বা ফোনের UI) সবচেয়ে গুরুত্বপূর্ণ কোন মাপকাঠিটি কমানো?
- Throughput
- CPU utilisation
- Turnaround time
- Response time
একটি প্রসেস t = 3-এ আসে, t = 7-এ প্রথমবার CPU পায়, তার burst 5, আর preemption-সহ কয়েকবার চলে t = 15-এ শেষ হয়। তার turnaround, waiting আর response time যথাক্রমে কত?
- ১৫, ১০, ৭
- ১২, ৭, ৪
- ১২, ৭, ৭
- ১২, ৪, ৪
একটি Gantt chart ০ থেকে ২০ ms পর্যন্ত, যার মধ্যে মোট ৪ ms CPU idle ছিল, আর এই সময়ে ৫টি প্রসেস শেষ হয়েছে। CPU utilisation আর throughput কত?
- ২০%, প্রতি ms-এ ০.২৫টি
- ৮০%, প্রতি ms-এ ০.৩১২৫টি
- ৮০%, প্রতি ms-এ ০.২৫টি
- ১০০%, প্রতি ms-এ ০.২৫টি
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- প্রসেস চলে CPU burst আর I/O burst-এর পালায়; বেশিরভাগ burst ছোট, অল্প কিছু লম্বা। I/O-bound প্রসেসের burst ছোট আর ঘন ঘন, CPU-bound-এর লম্বা।
- সিদ্ধান্তের চার মুহূর্ত: running → waiting, running → ready, waiting → ready, terminate. শুধু ১ ও ৪-এ সিদ্ধান্ত = non-preemptive; ২ ও ৩-এও = preemptive.
- TAT = CT − AT, WT = TAT − BT, RT = প্রথম CPU − AT; utilisation আর throughput বাড়াতে, বাকি তিনটি কমাতে। Non-preemptive-এ RT = WT.
- Gantt chart-এ arrival-এর আগে কেউ চলে না, ফাঁক থাকলে idle ঘর; যোগফল মিলিয়ে দেখুন। ক্রম বদলালে গড় অপেক্ষা বদলায়, কিন্তু (একটি CPU, work-conserving) throughput আর utilisation বদলায় না।
- বিচারের চার পথ: deterministic, queueing (n = λ × W), simulation, implementation — প্রতিটি আগেরটির চেয়ে বাস্তব, আর দামি।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৭ · FCFS, SJF আর SRTF — Gantt chart দিয়ে হিসাবপরবর্তী পর্বপ্রথম তিনটি algorithm, পূর্ণ সমাধানসহ।
- অপারেটিং সিস্টেম · কোর্স হোম৬৪টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- পর্ব ০৯ · Context switch আর dispatcherপেছনে ফিরেDispatcher-এর ধাপ, dispatch latency আর switch-এর খরচ।
- পর্ব ০৮ · প্রসেস — state, PCB আর memory layoutপেছনে ফিরেপ্রসেসের অবস্থা, queue আর তিন স্তরের scheduler.
- DSA · Queue আর dequeডেটা স্ট্রাকচারReady queue-এর পেছনের ডেটা স্ট্রাকচার।
- DSA · Heap আর priority queueডেটা স্ট্রাকচারঅগ্রাধিকার অনুযায়ী পরের কাজ দ্রুত বের করার কৌশল — পর্ব ১৮-এর প্রস্তুতি।