সরাসরি পর্বে যান
পর্ব ০৮ · ৬৪-এর মধ্যে · মডিউল ২

প্রসেস — state, PCB আর memory layout

Processes — Memory Layout, the Five- and Seven-State Models, the PCB and Process Queues

Taught in Bangla, this lesson defines a process: program vs process, the memory layout of text, data, BSS, heap and stack verified with a real C program, ASLR, the five-state and seven-state models with the cause of each transition, the PCB and Linux task_struct, process queues and long-, short- and medium-term schedulers, parent-child relations and PID 1, and viewing processes on Linux and Windows.

এই পর্বে যা শিখবেন

  • প্রোগ্রাম আর প্রসেসের পার্থক্য, আর কেন একটি প্রোগ্রাম থেকে একসাথে অনেক প্রসেস হতে পারে
  • একটি প্রসেসের মেমরি কীভাবে সাজানো — একটি আসল C প্রোগ্রাম দিয়ে ঠিকানার ক্রম যাচাই, আর ASLR কী
  • ৫-state আর ৭-state model, প্রতিটি transition-এর সঠিক কারণ, আর পরীক্ষার প্রিয় “এই transition কি সম্ভব?” প্রশ্নের যুক্তি
  • PCB-তে কী কী থাকে, context switch-এর সময় কীভাবে তা সংরক্ষণ ও হালনাগাদ হয়, আর Linux ও xv6-এর আসল struct
  • Process queue, তিন রকম scheduler, parent-child সম্পর্ক, PID 1, আর Linux ও Windows-এ চলমান প্রসেস দেখার কমান্ড

১ · প্রোগ্রাম বনাম প্রসেস — রেসিপির খাতা আর চুলায় রান্না

একটি রেস্তোরাঁর রান্নাঘরের কথা ভাবুন। তাকে রাখা আছে রেসিপির একটি খাতা — “কাচ্চি বিরিয়ানি: চাল ধুয়ে নিন, মাংস মেরিনেট করুন, দম দিন…”। খাতাটি নিজে কিছু রান্না করে না। বছরের পর বছর তাকে পড়ে থাকলেও এক দানা ভাতও হবে না। রান্না শুরু হয় যখন একজন বাবুর্চি খাতা খুলে চুলা ধরান, হাঁড়িতে উপকরণ দেন, আর মনে রাখেন “এখন ধাপ ৪-এ আছি, দম শুরু হয়েছে ১০ মিনিট আগে”।

অপারেটিং সিস্টেমে রেসিপির খাতা হলো প্রোগ্রাম — ডিস্কে রাখা একটি ফাইল, যেমন /usr/bin/firefox বা notepad.exe. এতে আছে machine code আর কিছু প্রাথমিক ডেটা। আর চুলায় চলতে থাকা রান্না হলো প্রসেস — চলমান অবস্থায় থাকা একটি প্রোগ্রাম। পাঠ্যবইয়ের ভাষায়: প্রোগ্রাম একটি নিষ্ক্রিয় (passive) জিনিস, আর প্রসেস একটি সক্রিয় (active) জিনিস।

📄 প্রোগ্রাম (passive)

ডিস্কে একটি ফাইল (Linux-এ ELF, Windows-এ PE ফরম্যাট)। এতে কোড আর শুরুর মানওয়ালা ডেটা আছে। চলছে না, তাই কোনো register-এর মান, খোলা ফাইল বা সময়ের হিসাব নেই। কপি করা যায়, মুছে ফেলা যায়।

রেসিপির খাতা

🔥 প্রসেস (active)

মেমরিতে আনা কোড আর ডেটা, তার সাথে নিজের stack ও heap, program counter-এর বর্তমান মান, খোলা ফাইলের তালিকা, একটি PID, কে তাকে চালু করেছে (parent), আর এ পর্যন্ত কত CPU সময় খরচ হলো।

চুলায় চলতে থাকা রান্না

একটি রেসিপি দিয়ে একই সময়ে তিনটি চুলায় তিন হাঁড়ি বিরিয়ানি রান্না হতে পারে — প্রতিটি আলাদা ধাপে। ঠিক তেমনি একটি প্রোগ্রাম থেকে একসাথে অনেক প্রসেস চলতে পারে। আপনি দুটি টার্মিনাল খুললে /bin/bash প্রোগ্রামের দুটি প্রসেস চলে; কোড একই, কিন্তু একটির working directory /home, অন্যটির /tmp, আর তাদের ইতিহাস, ভ্যারিয়েবল ও PID আলাদা। Chrome বা Edge ব্রাউজার একটিমাত্র প্রোগ্রাম, অথচ Task Manager খুললে দেখবেন ডজনখানেক প্রসেস — প্রতিটি ট্যাব বা কাজের জন্য আলাদা।

আরেকটি দিক: একটি প্রসেস চলার মাঝখানে নিজের প্রোগ্রামই বদলে ফেলতে পারে। Unix-এ exec system call ঠিক এটাই করে — PID একই থাকে, কিন্তু ভেতরের কোড বদলে নতুন প্রোগ্রাম বসে। তাই “প্রসেস = প্রোগ্রাম” নয়; প্রসেস হলো একটি চলমান কাজের পুরো প্রেক্ষাপট, যার ভেতরে এই মুহূর্তে কোনো একটি প্রোগ্রামের কোড চলছে। (fork, exec আর wait নিয়ে বিস্তারিত পর্ব ১০-এ।)

মূল ধারণা

OS-এর কাছে প্রসেস হলো কাজের একক আর সম্পদের মালিকানার একক। মেমরি, খোলা ফাইল, নেটওয়ার্ক সংযোগ, অনুমতি — সবকিছু কোনো না কোনো প্রসেসের নামে বরাদ্দ। প্রসেস শেষ হলে OS সেগুলো ফেরত নেয়। আর একটি প্রসেসের ভেতরে এক বা একাধিক thread থাকতে পারে, যারা সেই সম্পদ ভাগাভাগি করে চলে (পর্ব ১৪)। এই পর্বে আমরা ধরে নিচ্ছি প্রতিটি প্রসেসে একটিমাত্র thread.

২ · প্রসেসের মেমরি — text, data, BSS, heap আর stack

প্রতিটি প্রসেস মনে করে, পুরো একটি মেমরি তার একার। এই নিজস্ব ঠিকানার জগৎকে বলে address space. আসলে virtual memory-র কৌশলে OS প্রতিটি প্রসেসকে আলাদা “মানচিত্র” দেয় (পর্ব ৩০ ও ৩৫)। সেই মানচিত্রে জিনিসপত্র একটি নির্দিষ্ট নিয়মে সাজানো। x86-64 Linux-এ সাধারণ একটি প্রসেসের address space নিচ থেকে ওপরে মোটামুটি এরকম:

একটি প্রসেসের memory layout

Typical x86-64 Linux process address space, low addresses at the bottom

  high addresses
 ┌──────────────────────────┐
 │ kernel space             │  সব প্রসেসে একই; user mode থেকে ছোঁয়া নিষেধ
 ├──────────────────────────┤  <- 0x00007fffffffffff (user অংশের শেষ)
 │ [vvar] [vdso]            │  kernel-এর দেওয়া ছোট library (পর্ব ৫)
 │ stack        (grows v)   │  লোকাল ভ্যারিয়েবল, প্রতি কলে একটি frame
 │                          │
 │     ... free space ...   │
 │                          │
 │ mmap area    (grows v)   │  shared library (libc.so), বড় malloc
 │                          │
 │     ... free space ...   │
 │                          │
 │ heap         (grows ^)   │  malloc / new দিয়ে নেওয়া মেমরি
 ├──────────────────────────┤
 │ BSS                      │  শূন্য বা মানহীন global / static
 │ data                     │  মান দেওয়া global / static
 │ text                     │  machine code (পড়া + চালানো, লেখা নয়)
 └──────────────────────────┘
  low addresses
ছবি ৮.১ — নিচ থেকে ওপরে: text, data, BSS, heap (ওপরে ওঠে), mmap এলাকা, stack (নিচে নামে), আর সবার ওপরে kernel. Heap আর stack-এর মাঝের ফাঁকা জায়গা দুজনকেই বাড়তে দেয়।
অংশকী থাকেC-তে উদাহরণঅনুমতি
Textপ্রোগ্রামের machine codemain, printf-এর কোডপড়া + চালানো (r-x)
Dataশুরুর মান দেওয়া global আর static ভ্যারিয়েবলint rate = 42;পড়া + লেখা (rw-)
BSSমান না দেওয়া বা শূন্য মানের global আর static — শুরুতে সব 0int count;, static char buf[4096];পড়া + লেখা
Heapচলার সময় চেয়ে নেওয়া মেমরি; ওপরের দিকে বাড়েmalloc(100)পড়া + লেখা
mmap এলাকাShared library, ফাইল-map, বড় malloc, thread-এর stacklibc.so.6অংশভেদে আলাদা
Stackপ্রতিটি ফাংশন কলের frame: লোকাল ভ্যারিয়েবল, argument, ফেরার ঠিকানা; নিচের দিকে নামেint x = 5; (ফাংশনের ভেতরে)পড়া + লেখা, চালানো নিষেধ
Kernel spaceKernel-এর কোড ও ডেটা—শুধু kernel mode-এ

কয়েকটি বিষয় খেয়াল করার মতো। BSS নামটি এসেছে পুরোনো একটি assembler directive থেকে — “Block Started by Symbol”. এর চালাকিটা হলো: যে ডেটার সব মান শূন্য, তা প্রোগ্রাম ফাইলে লিখে রাখার দরকার নেই; ফাইলে শুধু লেখা থাকে “এত বাইট শূন্য লাগবে”, আর প্রসেস তৈরির সময় OS শূন্য-ভরা মেমরি দিয়ে দেয়। একটু পরে নিজেই মেপে দেখবেন।

Heap আর stack মুখোমুখি বাড়ে — heap নিচ থেকে ওপরে, stack ওপর থেকে নিচে। মাঝে বিশাল ফাঁকা এলাকা, যাতে দুজনেই নিজের মতো বাড়তে পারে। Stack-এর একটি সীমা থাকে: Linux-এ মূল thread-এর জন্য সাধারণত ৮ MB (ulimit -s দেখায় 8192), Windows-এ MSVC-র ডিফল্ট ১ MB. অসীম recursion করলে stack সেই সীমা পেরিয়ে যায় — Linux-এ ফল “Segmentation fault”, Windows-এ “stack overflow” exception.

একটি দোতলা গুদামঘর ভাবুন, যার নিচতলায় স্থায়ী মাল (text, data, BSS) আর ছাদের নিচে একটি তাক। নতুন মাল এলে নিচতলার মালের ওপর স্তূপ উঁচু হয় (heap), আর ছাদের তাক থেকে মাল ঝুলিয়ে রাখা হয় নিচের দিকে (stack). মাঝখানের ফাঁকা জায়গা যতক্ষণ আছে, কারও অসুবিধা নেই। দুই দিক মিলে গেলে — জায়গা শেষ।

নিজের চোখে ক্রম যাচাই

নিচের C প্রোগ্রামে প্রতিটি অংশ থেকে একটি করে জিনিস নেওয়া হয়েছে: একটি ফাংশন, একটি মান দেওয়া global, একটি static, একটি মানহীন global, একটি malloc ব্লক আর একটি লোকাল ভ্যারিয়েবল। প্রোগ্রামটি সবার ঠিকানা নিয়ে ছোট থেকে বড় ক্রমে সাজায়, আর শুধু নামগুলো ছাপে। কাঁচা ঠিকানা ছাপা হয়নি ইচ্ছে করেই — কেন, তা একটু পরে বুঝবেন।

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int global_init = 42;                /* initialised global -> data */
int global_zero;                     /* zero global        -> BSS  */
static int counter = 7;              /* static variable    -> data */
struct item { const char *name; uintptr_t addr; };

static int by_addr(const void *x, const void *y) {
    const struct item *a = x, *b = y;
    return (a->addr > b->addr) - (a->addr < b->addr);
}
int main(void) {
    int local = 1;                           /* local variable -> stack */
    int *heap = malloc(sizeof *heap);        /* malloc block   -> heap  */
    struct item v[] = {
        { "stack : local",        (uintptr_t)&local },
        { "heap  : malloc block", (uintptr_t)heap },
        { "bss   : global_zero",  (uintptr_t)&global_zero },
        { "data  : global_init",  (uintptr_t)&global_init },
        { "data  : static counter", (uintptr_t)&counter },
        { "text  : function main",  (uintptr_t)&main },
    };
    int n = sizeof v / sizeof v[0];
    qsort(v, n, sizeof v[0], by_addr);
    printf("sorted from LOW address to HIGH address:\n");
    for (int i = 0; i < n; i++) printf("  %d. %s\n", i + 1, v[i].name);
    uintptr_t gap = (uintptr_t)&local - (uintptr_t)heap;
    printf("heap-to-stack gap > 1 GiB? %s\n", gap > (1UL << 30) ? "yes" : "no");
    printf("values: %d %d %d\n", global_init, global_zero, counter);
    free(heap);
    return 0;
}

একটি আসল রানের আউটপুট:

sorted from LOW address to HIGH address:
  1. text  : function main
  2. data  : global_init
  3. data  : static counter
  4. bss   : global_zero
  5. heap  : malloc block
  6. stack : local
heap-to-stack gap > 1 GiB? yes
values: 42 0 7

কোডে নামগুলো উল্টো ক্রমে লেখা ছিল (stack আগে), কিন্তু ঠিকানা অনুযায়ী সাজানোর পর ঠিক ছবির ক্রম এসেছে: সবার নিচে text (ফাংশন main), তারপর data-র দুই ভ্যারিয়েবল, তারপর BSS, তারপর heap, আর সবার ওপরে stack. দ্বিতীয় শেষ লাইন বলছে heap আর stack-এর মাঝে এক GiB-রও বেশি ফাঁকা — আসলে x86-64 Linux-এ সাধারণত কয়েক TiB. global_zero-এর মান 0, যদিও আমরা কোনো মান দিইনি — BSS-এর নিয়ম। (একই data অংশের ভেতরে global_init আর counter কে আগে বসবে, তা compiler-এর ইচ্ছা; সেটির ওপর নির্ভর করবেন না।) Windows বা macOS-এও মূল ভাগগুলো একই, তবে নাম (.text, .data, .bss section, macOS-এ __TEXT, __DATA) আর বিন্যাসের খুঁটিনাটি আলাদা।

ASLR — ঠিকানা কেন প্রতিবার বদলায়

উপরের প্রোগ্রামে ঠিকানাগুলো ছাপলে প্রতিবার আলাদা সংখ্যা দেখতেন। কারণ আধুনিক OS ইচ্ছে করে প্রতিটি প্রসেসের stack, heap, mmap এলাকা আর (PIE প্রোগ্রামে) text-এর শুরুর ঠিকানা এলোমেলো করে দেয়। একে বলে ASLR (address space layout randomization). উদ্দেশ্য প্রতিরক্ষামূলক: কোনো বাগ কাজে লাগিয়ে আক্রমণকারী যদি নির্দিষ্ট ঠিকানায় লাফ দিতে চায়, সে ঠিকানাটি আগে থেকে জানবে না। লক্ষ করুন, ASLR ঠিকানা বদলায়, ক্রম নয় — তাই উপরের প্রোগ্রাম প্রতিবার একই ক্রম ছাপে। Linux-এ /proc/sys/kernel/randomize_va_space ফাইলে 2 মানে পূর্ণ ASLR চালু; Windows-এ Vista (২০০৭) থেকে ASLR আছে। নিরাপত্তার দিক থেকে ASLR-এর শক্তি আর সীমা নিয়ে বিস্তারিত পর্ব ৪৮-এ।

উপরের layout একটি সাধারণ চিত্র, আইন নয়। 32-bit Linux-এ user অংশ সাধারণত নিচের ৩ GB, kernel ওপরের ১ GB; 32-bit Windows-এ ডিফল্ট ভাগ ২ GB আর ২ GB. কিছু CPU আর OS-এ stack ওপরের দিকেও বাড়ে (বিরল)। তাই পরীক্ষায় “stack নিচে নামে” লিখলে বলে দিন “x86 বা ARM-এর প্রচলিত OS-এ”।

চলার সময় heap আর stack কীভাবে বাড়ে-কমে

এবার একটি ছোট প্রোগ্রাম ধাপে ধাপে চালাই। ঠিকানাগুলো বোঝার সুবিধার জন্য ছোট করে বানানো (আসল ঠিকানা অনেক বড়, আর malloc প্রতিটি ব্লকের সাথে কিছু হিসাবের বাইট রাখে — এখানে সেটি বাদ দেওয়া হয়েছে)। Stack pointer (SP) আর heap-এর ব্যবহৃত সীমা কীভাবে বদলায়, খেয়াল করুন:

1int main(void) {2    char *p = malloc(100);3    f(1);4    return 0;5}6void f(int n) {7    int x = n;8    g();9}10void g(void) {11    char *q = malloc(4000);12    free(q);13}
মেমরির অবস্থা (সরলীকৃত ঠিকানা)
stack frame
SP
heap-এ ব্লক
heap-এর সীমা
মাঝের ফাঁকা (বাইট)
  1. শুরু। main চলা শুরু করল; stack-এ শুধু তার frame, SP = 0x7ff0. Heap এখনো খালি, তার সীমা 0x5000. মাঝে ফাঁকা 0x7ff0 − 0x5000 = 12272 বাইট।
  2. malloc(100) — heap ওপরে উঠল। 100 বাইট = 0x64, তাই সীমা হলো 0x5000 + 0x64 = 0x5064. Stack একটুও নড়েনি। ফাঁকা কমল ঠিক 100 বাইট: 12172.
  3. f(1) ডাকা — stack নিচে নামল। f-এর নতুন frame-এ থাকে argument n, লোকাল x আর main-এ ফেরার ঠিকানা। ধরা যাক frame-টি 0x30 = 48 বাইট, তাই SP = 0x7ff0 − 0x30 = 0x7fc0. ফাঁকা কমল 48: 12124.
  4. f ডাকে g — আরও নিচে। g-এর frame 0x40 = 64 বাইট, SP = 0x7fc0 − 0x40 = 0x7f80. এখন stack-এ তিনটি frame — main-এর ওপর f, তার ওপর g. প্রতিটি ফেরার ঠিকানা মনে রাখে কোথায় ফিরতে হবে।
  5. malloc(4000) — heap অনেকটা উঠল। 4000 = 0xFA0, নতুন সীমা 0x5064 + 0xFA0 = 0x6004. q নামের পয়েন্টারটি নিজে কিন্তু stack-এ (g-এর frame-এ); শুধু সে যে ৪০০০ বাইটের দিকে নির্দেশ করে, সেটি heap-এ। ফাঁকা: 0x7f80 − 0x6004 = 8060.
  6. free(q) — ব্লক খালি, সীমা একই। malloc library ব্লকটি “খালি” চিহ্ন দিয়ে রাখে, পরের malloc-এ আবার দেবে। Heap-এর সীমা সাধারণত সঙ্গে সঙ্গে নামে না, তাই ফাঁকা এখনো 8060. (বিস্তারিত পর্ব ৩৮-এ।)
  7. g ফিরে গেল — stack frame উধাও। SP আবার 0x7fc0. g-এর লোকাল ভ্যারিয়েবল, এমনকি q পয়েন্টারও এখন অর্থহীন; কোনো ফাংশন যদি লোকাল ভ্যারিয়েবলের ঠিকানা ফেরত দেয়, ঠিক এই কারণেই তা বিপজ্জনক। ফাঁকা বাড়ল 64: 8124.
  8. f ফিরল, main শেষের পথে। SP = 0x7ff0, ঠিক শুরুর মতো। কিন্তু heap-এ p এখনো বরাদ্দ — stack নিজে নিজে খালি হয়, heap হয় না। p কখনো free না করলে প্রসেস শেষ হওয়া পর্যন্ত সেটি থেকে যায়; দীর্ঘ সময় চলা সার্ভারে একেই বলে memory leak.

শেষ ধাপের শিক্ষাটি গুরুত্বপূর্ণ: stack-এর মেমরি ফাংশন ফিরলেই নিজে থেকে ফেরত যায়; heap-এর মেমরি প্রোগ্রামারকে নিজে ফেরত দিতে হয় (C-তে free, অথবা Java, Python-এর মতো ভাষায় garbage collector). একটি ভুল ধারণাও এখানে দূর করে নিন: প্রসেসের “heap” আর ডেটা স্ট্রাকচারের heap (priority queue) সম্পূর্ণ আলাদা জিনিস — শুধু নামের মিল।

BSS কেন ফাইলে জায়গা নেয় না — মেপে দেখুন

নিচের bash স্ক্রিপ্ট তিনটি ছোট C প্রোগ্রাম বানিয়ে compile করে: একটিতে কিছু নেই, একটিতে ১ MB-র শূন্য অ্যারে, আর একটিতে একই ১ MB অ্যারে, তবে প্রথম ঘরে 1. তারপর size কমান্ড দিয়ে প্রতিটির text, data আর BSS-এর আকার, আর stat দিয়ে ডিস্কে ফাইলের আকার দেখায়:

# three tiny programs: the same 1 MB array, placed differently
cat > none.c <<'EOF'
int main(void) { return 0; }
EOF
cat > zero.c <<'EOF'
int big[262144];                    /* 1 MB of zeros   -> BSS  */
int main(void) { return big[0]; }
EOF
cat > init.c <<'EOF'
int big[262144] = {1};              /* 1 MB, one non-zero -> data */
int main(void) { return big[0]; }
EOF
for p in none zero init; do gcc -O0 -o "$p" "$p.c"; done
echo "== size: text, data and bss of each program (bytes) =="
size none zero init | awk '{printf "%-8s %-8s %-9s %s\n", $1, $2, $3, $6}'
echo
echo "== file size on disk (bytes) =="
for p in none zero init; do printf "%-5s %s\n" "$p" "$(stat -c %s "$p")"; done

একটি আসল রানের আউটপুট:

== size: text, data and bss of each program (bytes) ==
text     data     bss       filename
1228     544      8         none
1229     544      1048608   zero
1229     1049136  8         init

== file size on disk (bytes) ==
none  15776
zero  15808
init  1064400

তিনটি প্রোগ্রামের text প্রায় সমান (১২২৮–১২২৯ বাইট), কারণ কোড প্রায় একই। পার্থক্য দেখুন বাকি দুই কলামে। zero-তে ১ MB অ্যারে গেছে BSS-এ (১০৪৮৬০৮ বাইট = ১০৪৮৫৭৬ বাইট অ্যারে + আগের কিছু বাইট ও alignment), আর init-এ একটিমাত্র ঘরে 1 থাকায় পুরো অ্যারে গেছে data-তে (১০৪৯১৩৬). এবার ডিস্কে ফাইলের আকার: zero ফাইল none-এর চেয়ে মাত্র ৩২ বাইট বড় — ১ MB শূন্য ফাইলে লেখাই হয়নি, শুধু “এত বাইট লাগবে” লেখা আছে। কিন্তু init ফাইল প্রায় ১০ লাখ বাইট বড় (মোট ১০৬৪৪০০ বাইট), কারণ data-র প্রতিটি বাইট (বেশিরভাগ শূন্য হলেও) ফাইলে রাখতে হয়। প্রসেস চালু হলে দুই ক্ষেত্রেই মেমরিতে ১ MB লাগবে; পার্থক্য শুধু ফাইলে আর লোড করার সময়ে। সংখ্যাগুলো compiler-এর সংস্করণভেদে কয়েক বাইট বদলাতে পারে, অনুপাত বদলায় না।

৩ · প্রসেসের পাঁচ অবস্থা — new থেকে terminated

একটি প্রসেস সারাক্ষণ CPU-তে চলে না। একটি সাধারণ কম্পিউটারে একসাথে কয়েকশো প্রসেস থাকে, অথচ CPU core হয়তো চার বা আটটি। তাই যেকোনো মুহূর্তে বেশিরভাগ প্রসেস হয় নিজের পালার অপেক্ষায়, নয়তো কোনো ঘটনার অপেক্ষায় (কিবোর্ডে চাপ, ডিস্ক থেকে ডেটা, নেটওয়ার্কের প্যাকেট)। প্রসেস এখন কোন দশায় আছে, তাকে বলে process state. পাঠ্যবইয়ের সবচেয়ে প্রচলিত মডেলে অবস্থা পাঁচটি:

৫-state process model

Five-state process model with its six transitions

           admit               dispatch                exit
 ┌─────┐ ---------> ┌───────┐ ------------> ┌─────────┐ ------> ┌────────────┐
 │ new │            │ ready │               │ running │         │ terminated │
 └─────┘            └───────┘ <------------ └─────────┘         └────────────┘
                        ^       timeout or       │
                        │       interrupt        │ I/O or
                        │                        │ event wait
                        │      ┌─────────┐       │
                        └───── │ waiting │ <─────┘
                 I/O or event  └─────────┘
                     done
ছবি ৮.২ — ছয়টি বৈধ তীর। Waiting থেকে running-এ কোনো তীর নেই, ready থেকে waiting-এও নেই।
অবস্থামানেব্যাংকের উপমা
Newপ্রসেস তৈরি হচ্ছে: PCB বানানো হচ্ছে, মেমরি এখনো পুরো দেওয়া হয়নিগেটে ঢুকে ফর্ম পূরণ করছেন
Readyচলার জন্য সব প্রস্তুত, শুধু CPU খালি নেইটোকেন হাতে বসে আছেন, নম্বর ডাকার অপেক্ষা
Runningএখন CPU-তে instruction চলছে; প্রতিটি core-এ একসাথে একটিইকাউন্টারে কাজ চলছে
Waiting (blocked)কোনো ঘটনার অপেক্ষায়; CPU দিলেও কিছু করার নেইকর্মকর্তা বললেন “স্বাক্ষর যাচাই হয়ে আসুক”, আপনি পাশে সরে বসলেন
Terminatedকাজ শেষ বা বন্ধ করা হয়েছে; OS সম্পদ ফেরত নিচ্ছেকাজ শেষ, ব্যাংক থেকে বেরিয়ে যাচ্ছেন

প্রতিটি তীরের সঠিক কারণ

পরীক্ষায় শুধু অবস্থার নাম নয়, প্রতিটি transition কেন ঘটে আর কে ঘটায়, তা জানতে চাওয়া হয়। মোট ছয়টি বৈধ তীর:

Transitionনামকারণকে ঘটায়
new → readyadmitপ্রসেস তৈরি সম্পূর্ণ, মেমরি ও PCB প্রস্তুত, ready queue-তে যোগOS (ঐতিহাসিকভাবে long-term scheduler)
ready → runningdispatchScheduler এই প্রসেসকে বেছে নিল; dispatcher তার register CPU-তে বসালShort-term scheduler + dispatcher
running → readytimeout / preemptবরাদ্দ সময় (time quantum) শেষ হয়ে timer interrupt, অথবা বেশি অগ্রাধিকারের কেউ ready হলোKernel (preemption)
running → waitingI/O wait / event waitপ্রসেস নিজে এমন system call করল যার ফল এখনই নেই — read() ডিস্ক থেকে, sleep(), wait(), lock-এর অপেক্ষাপ্রসেসের নিজের অনুরোধ
waiting → readyI/O done / event occursঅপেক্ষার ঘটনা ঘটল — ডিস্কের interrupt, টাইমার শেষ, child শেষ হলোKernel (interrupt handler বা অন্য প্রসেসের কাজ)
running → terminatedexitexit() ডাকল, main থেকে ফিরল, বা কোনো signal-এ বন্ধ হলোপ্রসেস নিজে বা kernel
পরীক্ষার প্রিয় প্রশ্ন

Waiting থেকে কি সরাসরি running-এ যাওয়া যায়? না। I/O শেষ হয় একটি interrupt-এর মাধ্যমে, আর সেই মুহূর্তে CPU-তে অন্য কেউ চলছে (বা kernel-এর interrupt handler). Handler শুধু প্রসেসটিকে “ready” করে ready queue-তে রাখে; কে CPU পাবে তা ঠিক করার অধিকার একমাত্র scheduler-এর। Scheduler হয়তো সঙ্গে সঙ্গেই তাকে বেছে নেয় (বিশেষত যদি তার অগ্রাধিকার বেশি হয়), কিন্তু পথটি তখনো waiting → ready → running — ready-তে থাকাটা মাইক্রোসেকেন্ডের হলেও।

একইভাবে ready → waiting অসম্ভব: I/O চাইতে হলে system call করতে হয়, আর instruction চালাতে পারে শুধু running প্রসেস। new → running সরাসরি হয় না: আগে admit হয়ে ready queue-তে আসতে হয়। আর terminated থেকে কোথাও ফেরা নেই।

নিচের ডায়াগ্রামে একটি প্রসেস P-এর (ধরা যাক একটি গান বাজানোর অ্যাপ) পুরো জীবন দেখুন। সাথে আরেকটি প্রসেস Q আছে, আর CPU একটিই। প্রতিটি ধাপে কোন ঘটনা ঘটল, আর queue-গুলো কীভাবে বদলাল খেয়াল করুন:

1shell: fork() + exec("player")2kernel: setup done -> admit P3scheduler: pick P (dispatch)4timer interrupt: P's slice over5scheduler: pick P again6P: read(fd, buf, 4096) from disk7disk interrupt: data ready8scheduler: pick P9P: exit(0)
প্রতিটি ঘটনার পর
P-এর state
transition
CPU-তে
ready queue
disk queue
  1. জন্ম — new. আপনি টার্মিনালে player লিখলেন। Shell fork() দিয়ে নিজের কপি বানাল, কপিটি exec() দিয়ে player প্রোগ্রাম বসাল। Kernel P-এর জন্য PCB আর PID বানাচ্ছে। CPU-তে এখন অন্য প্রসেস Q, ready queue খালি।
  2. admit — new থেকে ready. মেমরির মানচিত্র তৈরি, PCB সম্পূর্ণ। P-কে ready queue-তে রাখা হলো। P চলার জন্য পুরো প্রস্তুত, কিন্তু CPU এখনো Q-এর হাতে।
  3. dispatch — ready থেকে running. Q-এর সময় শেষ হলো (Q চলে গেল ready queue-তে), scheduler বেছে নিল P-কে। Dispatcher P-এর সংরক্ষিত register CPU-তে বসিয়ে user mode-এ লাফ দিল। P-এর প্রথম instruction চলছে।
  4. timeout — running থেকে ready. P কোনো ভুল করেনি, শুধু তার ভাগের সময় (ধরা যাক ৪ ms) শেষ। Timer interrupt এল, kernel P-কে সরিয়ে ready queue-র শেষে রাখল, Q পেল CPU. এটিই preemption — P-এর ইচ্ছা জিজ্ঞেস করা হয় না।
  5. আবার dispatch. Q-এর পালা শেষে P আবার CPU পেল, এবং ঠিক যেখানে থেমেছিল সেখান থেকেই চলল — PCB-তে সব সংরক্ষিত ছিল বলে (পরের অংশে বিস্তারিত)।
  6. I/O wait — running থেকে waiting. P গানের ফাইল পড়তে read() ডাকল। ডেটা ডিস্ক থেকে আসতে কয়েক মিলিসেকেন্ড লাগবে — CPU-র হিসাবে লাখ লাখ instruction-এর সময়। তাই kernel P-কে disk queue-তে রেখে waiting করল, আর CPU দিল Q-কে। এখানে ready queue খালি, কারণ Q চলছে আর P অপেক্ষায়।
  7. I/O done — waiting থেকে ready, running নয়! ডিস্ক ডেটা এনে interrupt দিল। Kernel ডেটা P-এর buffer-এ রেখে P-কে ready queue-তে পাঠাল। CPU-তে কিন্তু এখনো Q — P-কে নিজের পালার জন্য অপেক্ষা করতে হবে। এ কারণেই waiting → running সরাসরি হয় না।
  8. dispatch — শেষবারের মতো running. Scheduler P-কে বেছে নিল, Q গেল ready queue-তে। P পড়া ডেটা দিয়ে গান বাজানোর কাজ সারল।
  9. exit — terminated. P exit(0) ডাকল। Kernel তার মেমরি আর খোলা ফাইল ফেরত নিল; শুধু PCB-র ছোট একটি অংশ (exit status) রাখল, যতক্ষণ না parent shell wait() দিয়ে সেটি সংগ্রহ করে। মাঝের এই অবস্থাকেই Unix-এ zombie বলে (অংশ ৭)। CPU আবার Q-এর।

এবার একই নিয়ম একটি ছোট সিমুলেটরে চালিয়ে দেখুন। কোডটি তিনটি প্রসেস A, B, C-এর জন্য ঘটনার একটি তালিকা প্রক্রিয়া করে, প্রতিটি ঘটনার পর state-এর টেবিল ছাপে, আর অবৈধ ঘটনা প্রত্যাখ্যান করে — যেমন waiting প্রসেসকে dispatch করা, বা CPU ব্যস্ত থাকতে আরেকজনকে running করা (এক CPU ধরে):

// legal transitions of the 5-state model: event -> [from, to]
const RULES = {
  admit:    ['new', 'ready'],         dispatch: ['ready', 'running'],
  timeout:  ['running', 'ready'],     io_wait:  ['running', 'waiting'],
  io_done:  ['waiting', 'ready'],     exit:     ['running', 'terminated'],
};
const state = {}, used = {};            // pid -> state, event -> count
const events = [
  'create A', 'create B', 'create C', 'admit A', 'admit B', 'dispatch A',
  'admit C', 'io_wait A', 'dispatch A', 'dispatch B', 'io_done A',
  'dispatch A', 'timeout B', 'dispatch A', 'exit A', 'dispatch C',
  'io_wait C', 'dispatch B', 'exit B', 'io_done C', 'dispatch C', 'exit C',
];
const pad = s => s.padEnd(11);
console.log((pad('event') + pad('A') + pad('B') + 'C'));
for (const ev of events) {
  const [what, p] = ev.split(' ');
  let note = '';
  if (what === 'create') state[p] = 'new';
  else {
    const [from, to] = RULES[what];
    const busy = Object.keys(state).find(q => state[q] === 'running');
    if (state[p] !== from) note = `  REJECTED: ${p} is ${state[p]}, not ${from}`;
    else if (to === 'running' && busy) note = `  REJECTED: CPU busy with ${busy}`;
    else { state[p] = to; used[what] = (used[what] || 0) + 1; }
  }
  console.log((pad(ev) + ['A', 'B', 'C'].map(q => pad(state[q] || '-')).join('')).trimEnd() + note);
}
console.log('accepted: ' + Object.entries(used).map(([e, n]) => `${e} x${n}`).join(', '));

আসল আউটপুট:

event      A          B          C
create A   new        -          -
create B   new        new        -
create C   new        new        new
admit A    ready      new        new
admit B    ready      ready      new
dispatch A running    ready      new
admit C    running    ready      ready
io_wait A  waiting    ready      ready
dispatch A waiting    ready      ready  REJECTED: A is waiting, not ready
dispatch B waiting    running    ready
io_done A  ready      running    ready
dispatch A ready      running    ready  REJECTED: CPU busy with B
timeout B  ready      ready      ready
dispatch A running    ready      ready
exit A     terminated ready      ready
dispatch C terminated ready      running
io_wait C  terminated ready      waiting
dispatch B terminated running    waiting
exit B     terminated terminated waiting
io_done C  terminated terminated ready
dispatch C terminated terminated running
exit C     terminated terminated terminated
accepted: admit x3, dispatch x6, io_wait x2, io_done x2, timeout x1, exit x3

দুটি প্রত্যাখ্যান দেখুন। প্রথমটি পরীক্ষার প্রশ্নের উত্তর কোডে: A waiting অবস্থায় dispatch চাওয়া হয়েছিল — নিয়মে dispatch শুধু ready থেকে হয়, তাই বাতিল। দ্বিতীয়টি: io_done A-র পর A ready হলো, কিন্তু CPU তখন B-র হাতে, তাই A-কে timeout B পর্যন্ত অপেক্ষা করতে হলো। পুরো সময়ে কোনো সারিতে running একটির বেশি নেই — এক CPU-র মূল শর্ত। শেষ লাইনের হিসাবটিও মিলিয়ে নিন: তিনটি প্রসেস, তাই admit আর exit তিনবার করে; আর I/O শেষ হওয়ার পর প্রতিবার আবার একটি dispatch লেগেছে বলে dispatch সবচেয়ে বেশি (৬)। events তালিকা বদলে নিজে নতুন ক্রম চালিয়ে দেখুন — যেমন 'io_done B' দিলে কী হয়।

৪ · Suspended অবস্থা — ৭-state model

পাঁচ অবস্থার মডেল ধরে নেয়, সব প্রসেস সবসময় RAM-এ থাকে। কিন্তু RAM ফুরিয়ে গেলে? ধরুন অনেকগুলো প্রসেস waiting অবস্থায় RAM দখল করে বসে আছে, আর নতুন প্রসেস চালানোর জায়গা নেই। পুরোনো সিস্টেমগুলোর সমাধান ছিল swapping: কিছু প্রসেসের পুরো মেমরি ডিস্কের swap এলাকায় সরিয়ে দেওয়া। এমন প্রসেসকে বলে suspended — সে আছে, কিন্তু RAM-এ নেই, তাই চলতে পারে না। এ জন্য William Stallings-এর পাঠ্যবইসহ অনেক বইয়ে দুটি নতুন অবস্থা যোগ করে ৭-state model দেখানো হয়:

৭-state model — দুটি প্রশ্ন, চারটি অবস্থা

Seven-state model: waiting or not × in RAM or swapped out

                  IN RAM                              ON DISK (suspended)
             ┌───────────┐     suspend ------>      ┌─────────────────┐
 not waiting │   ready   │                          │  ready/suspend  │
             └───────────┘     <------ activate     └─────────────────┘
                   ^                                         ^
      event occurs │                                         │ event occurs
                   │                                         │
             ┌───────────┐     suspend ------>      ┌─────────────────┐
 waiting     │  blocked  │                          │ blocked/suspend │
             └───────────┘     <------ activate     └─────────────────┘

 plus the 5-state arrows: new -> ready (or new -> ready/suspend when RAM is short),
 ready <-> running, running -> blocked, running -> terminated
ছবি ৮.৩ — বাঁ দিকের দুই অবস্থা ৫-state model-এরই ready আর waiting (blocked); ডান দিকে তাদের ডিস্কে সরানো রূপ। Suspend আর activate তীর medium-term scheduler-এর কাজ।

ছবিটি জটিল দেখালেও মূল কথা সহজ। দুটি প্রশ্ন আলাদা করে ভাবুন: (১) প্রসেস কি কোনো ঘটনার অপেক্ষায়? (২) প্রসেস কি RAM-এ আছে? দুই প্রশ্নের চার রকম উত্তর থেকে চারটি অবস্থা:

RAM-এ আছেডিস্কে সরানো (suspended)
অপেক্ষা নেইReadyReady/Suspend — RAM-এ ফিরলেই চলতে পারবে
ঘটনার অপেক্ষায়BlockedBlocked/Suspend — ঘটনাও ঘটতে হবে, RAM-এও ফিরতে হবে

নতুন transition-গুলো:

  • blocked → blocked/suspend (suspend): RAM-এর চাপ হলে OS সবচেয়ে আগে অপেক্ষারত প্রসেস সরায় — সে তো এমনিতেও চলছে না।
  • blocked/suspend → ready/suspend (event occurs): ডিস্কে থাকা অবস্থাতেই তার অপেক্ষার ঘটনা ঘটে গেল। এখন সে প্রস্তুত, শুধু RAM-এ ফেরা বাকি।
  • ready/suspend → ready (activate): RAM খালি হলে OS তাকে ফিরিয়ে আনে।
  • ready → ready/suspend (suspend): অনেক বড় একটি প্রসেসের জায়গা করতে বা অনেক কম অগ্রাধিকারের প্রসেস সরাতে, ready প্রসেসকেও সরানো হতে পারে।
  • new → ready/suspend (admit): RAM কম থাকলে নতুন প্রসেস সরাসরি ডিস্কেই রাখা হয়।
  • blocked/suspend → blocked (activate): বিরল — OS যদি মনে করে ঘটনাটি শিগগিরই ঘটবে আর প্রসেসটি গুরুত্বপূর্ণ। কিছু বইয়ে running → ready/suspend তীরও থাকে।

বাস্তবের সাথে মিলিয়ে নেওয়ার সময় সাবধান। আধুনিক Linux আর Windows সাধারণত পুরো প্রসেস একবারে ডিস্কে সরায় না; তারা page ধরে ধরে কম ব্যবহৃত মেমরি সরায় (paging, পর্ব ৩৫–৩৭), আর প্রসেসের state-এ তার কোনো আলাদা নাম থাকে না। তবে “suspend” শব্দটি অন্য অর্থে বাস্তবে আছে: Linux-এ টার্মিনালে Ctrl+Z চাপলে প্রসেস SIGTSTP পেয়ে stopped (ps-এ T) হয় — RAM-এই থাকে, শুধু চলা বন্ধ (পর্ব ১১, ১৩)। Windows-এর Task Manager-এ Store অ্যাপ minimize করলে Status কলামে “Suspended” লেখা আসে — অ্যাপের thread-গুলো থামানো, মেমরি RAM-এই। পাঠ্যবইয়ের ৭-state model-এর মূল ভাবনা — “অপেক্ষা” আর “RAM-এ আছে কি না” দুটি আলাদা প্রশ্ন — তবু আজও কাজের।

৫ · PCB — প্রসেসের পরিচয়পত্র, আর Linux-এর task_struct

Timer interrupt-এ P থেমে গেল, পরে আবার ঠিক সেখান থেকেই চলল — কীভাবে? কারণ kernel প্রতিটি প্রসেসের জন্য একটি তথ্যের রেকর্ড রাখে: process control block (PCB). প্রসেস সম্পর্কে kernel যা জানে, সব এখানে বা এখান থেকে যুক্ত অন্য struct-এ।

সরকারি অফিসের ফাইল ক্যাবিনেটের কথা ভাবুন। একজন কেরানি দশটি আবেদন নিয়ে কাজ করেন। একটি আবেদনের অর্ধেক কাজ করে টিফিনে গেলেন — ফাইলের ওপর একটি নোট লিখে রাখলেন: “পৃষ্ঠা ৩ পর্যন্ত যাচাই শেষ, স্বাক্ষর বাকি, জমির দলিল অপেক্ষায়”। ফিরে এসে অন্য ফাইল ধরলেন, পরে আবার এই ফাইল খুলে নোট দেখে ঠিক সেখান থেকে শুরু। ফাইলের ওপরের ওই নোট আর আবেদনকারীর তথ্যপত্র মিলে PCB. কেরানি হলেন CPU, আর ক্যাবিনেট kernel-এর মেমরি।

পাঠ্যবই (Silberschatz-দের Operating System Concepts) PCB-র উপাদানগুলো এভাবে তালিকা করে — পরীক্ষায় এই তালিকাটি প্রায় হুবহু চাওয়া হয়:

উপাদানকী থাকেকেন লাগে
Process ID (PID), parent-এর PIDঅনন্য পরিচয় নম্বরkill, wait, ps — সব কমান্ড এই নম্বর দিয়ে প্রসেস চেনে
Process statenew, ready, running, waiting…Scheduler কাকে বিবেচনা করবে
Program counterপরের instruction-এর ঠিকানাথামার জায়গা থেকে আবার শুরু
CPU registerসাধারণ register, stack pointer, flagsহিসাবের মাঝখানের মানগুলো হারিয়ে না যাওয়া
CPU-scheduling তথ্যঅগ্রাধিকার (priority), কোন queue-তে, scheduling নীতিকে আগে CPU পাবে (মডিউল ৩)
Memory-management তথ্যPage table-এর ঠিকানা, base/limit register, মেমরি এলাকার তালিকাপ্রসেস বদলালে মেমরির মানচিত্রও বদলাতে হয়
Accounting তথ্যকত CPU সময় খরচ হলো, কখন শুরু, সীমাtop, time, বিলিং, সীমা প্রয়োগ
I/O status তথ্যখোলা ফাইলের তালিকা, বরাদ্দ ডিভাইস, অপেক্ষারত I/O অনুরোধপ্রসেস শেষে সব বন্ধ করা, অনুমতি যাচাই

PCB-র আসল পরীক্ষা হয় context switch-এর সময়: এক প্রসেসের register PCB-তে সংরক্ষণ, আরেকটির PCB থেকে register ফিরিয়ে আনা। পরের পর্বে (পর্ব ৯) এর খরচ আর CPU-স্তরের খুঁটিনাটি দেখবেন; এখানে শুধু দেখুন কোন ঘর কখন বদলায়। ঠিকানাগুলো ছোট করে বানানো:

1P1 running in user mode2timer interrupt -> kernel3save P1 registers -> PCB(P1)4PCB(P1): state, CPU time, queue5scheduler picks P26load PCB(P2) -> CPU, to user mode7P2: read() -> waiting; P1 back
CPU আর দুটি PCB
CPU: PC · SP · rax
mode
PCB P1: state
PCB P1: register
PCB P1: CPU সময়
PCB P2: state
PCB P2: register
PCB P2: I/O
ready queue
  1. P1 চলছে। CPU-র register-এ P1-এর জীবন্ত মান: PC = 0x1a20, SP = 0x7f80, rax = 7. P1-এর PCB-র register-ঘর এখন পুরোনো বা ফাঁকা — আসল মান CPU-তেই। P2 ready queue-তে, তার PCB-তে আগের বার থামার সময়ের register সংরক্ষিত আছে।
  2. Timer interrupt. হার্ডওয়্যার P1-কে থামিয়ে mode kernel করল, আর kernel-এর interrupt handler চালু হলো। CPU নিজে P1-এর PC আর flags kernel stack-এ রেখে দেয়, যাতে কিছুই হারিয়ে না যায়। rax-এ তখনো P1-এর 7.
  3. Register সংরক্ষণ। Kernel P1-এর সব register — PC 0x1a20, SP 0x7f80, rax 7 এবং বাকিগুলো — P1-এর জন্য রাখা জায়গায় লিখে রাখল। এখন CPU-র register অন্য কাজে ব্যবহার করা নিরাপদ।
  4. PCB হালনাগাদ। P1-এর state running থেকে ready, accounting-এর CPU সময় 36 থেকে 40 ms (এইমাত্র ৪ ms চলেছে), আর P1 গেল ready queue-র শেষে: queue এখন P2, P1.
  5. Scheduler বেছে নিল P2. Queue-র মাথায় P2, তাই সে নির্বাচিত; তার PCB-তে state হলো running. Queue-তে বাকি শুধু P1.
  6. P2-এর register ফিরিয়ে আনা। Dispatcher P2-এর PCB থেকে PC 0x22f0, SP 0x7e40, rax 3 CPU-তে বসাল, মেমরির মানচিত্রও P2-এর করল, আর user mode-এ ফিরল। P2-এর মনে হবে সে কখনো থামেইনি।
  7. P2 I/O চাইল, P1 ফিরে এল। কিছুক্ষণ পর P2 read() ডাকল (x86-64 Linux-এ read-এর syscall নম্বর 0, তাই rax 0). ডেটা নেই, তাই kernel P2-এর register (PC 0x2310, SP 0x7e20) সংরক্ষণ করল, state waiting আর I/O-ঘরে লিখল “fd 3-এ read অপেক্ষায়”। তারপর P1-কে dispatch করল: CPU-তে আবার PC 0x1a20, SP 0x7f80, rax 7 — ঠিক যেখানে থেমেছিল।

বাস্তবের PCB — xv6 আর Linux

MIT-র শিক্ষণ-OS xv6-এ (পর্ব ৫৮) PCB-র নাম struct proc, আর সেটি এতই ছোট যে পুরোটা পড়া যায়। Linux-এ একই কাজ করে task_struct, যা include/linux/sched.h ফাইলে কয়েকশো লাইনের একটি struct. নিচে দুটো পাশাপাশি দেখুন:

enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };

// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
};
struct task_struct {
    unsigned int          __state;    /* TASK_RUNNING, TASK_INTERRUPTIBLE, ... */
    void                 *stack;      /* this task's kernel stack */
    int                   prio, static_prio;   /* priority; nice maps here */
    unsigned int          policy;     /* SCHED_NORMAL, SCHED_FIFO, ... */
    struct sched_entity   se;         /* fair-scheduler bookkeeping */
    struct mm_struct     *mm;         /* address space: page tables, areas */
    pid_t                 pid;        /* id of this task (thread) */
    pid_t                 tgid;       /* thread group id = what getpid() returns */
    struct task_struct __rcu *real_parent;  /* who created us */
    struct list_head      children;   /* our children */
    struct list_head      sibling;    /* link in parent's children list */
    u64                   utime, stime;     /* CPU time: user / kernel mode */
    unsigned long         nvcsw, nivcsw;    /* voluntary / involuntary switches */
    char                  comm[TASK_COMM_LEN];  /* name, 16 bytes */
    struct fs_struct     *fs;         /* root and current directory */
    struct files_struct  *files;      /* open file table */
    struct signal_struct *signal;     /* signal state shared by threads */
    /* ... many more fields ... */
    struct thread_struct  thread;     /* CPU-specific state: saved sp, ... */
};

পাঠ্যবইয়ের তালিকার সাথে মিলিয়ে নিন। State: xv6-এর state, Linux-এর __state (Linux 5.14-এর আগে নাম ছিল state). PID ও parent: pid, parent / real_parent. Register: xv6-এ trapframe (user mode-এর register) আর context (kernel-এর ভেতরে switch-এর register); Linux-এ user register থাকে প্রসেসের kernel stack-এ, আর thread-এ kernel-এর stack pointer-এর মতো জিনিস। মেমরি: xv6-এর pagetable ও sz, Linux-এর mm. I/O: xv6-এর ofile অ্যারে, Linux-এর files. Accounting: Linux-এর utime, stime, আর nvcsw/nivcsw — যা পর্ব ৫-এ /proc/self/status-এ voluntary আর nonvoluntary context switch হিসেবে দেখেছিলেন।

একটি সূক্ষ্ম কথা: Linux-এ প্রসেস আর thread দুটোই একই task_struct দিয়ে চলে। প্রতিটি thread-এর নিজের pid, কিন্তু একই প্রসেসের সব thread-এর tgid এক — আর getpid() ফেরত দেয় সেই tgid. একই প্রসেসের thread-গুলো একটি mm আর একটি files ভাগ করে। Windows-এ ব্যবস্থাটি আলাদা: প্রসেসের জন্য EPROCESS, প্রতিটি thread-এর জন্য আলাদা ETHREAD struct, আর scheduling হয় thread ধরে (পর্ব ৫৬)।

৬ · Process queue আর তিন রকম scheduler

PCB-গুলো kernel-এর মেমরিতে এলোমেলো পড়ে থাকে না; অবস্থা অনুযায়ী বিভিন্ন queue বা তালিকায় যুক্ত থাকে (সাধারণত PCB-র ভেতরের একটি link ঘর দিয়ে, যেমন একটি linked list). ক্লাসিক পাঠ্যবইয়ে তিন রকম queue:

  • Job queue: সিস্টেমের সব প্রসেস — পুরোনো batch সিস্টেমে ডিস্কে জমা থাকা, চালানোর অপেক্ষায় থাকা কাজও।
  • Ready queue: যারা RAM-এ আছে আর চলার জন্য প্রস্তুত। ব্যাংকের টোকেন-কাউন্টারের সামনের সারি।
  • Device queue (wait queue): প্রতিটি ডিভাইস বা ঘটনার জন্য আলাদা সারি — ডিস্কের queue, কিবোর্ডের queue, নেটওয়ার্ক socket-এর queue. ঘটনা ঘটলে kernel সেই queue-র প্রসেসদের ready queue-তে পাঠায়।

Queueing diagram

How a process moves between the ready queue, the CPU and the wait queues

 new process --> [ ready queue ] --dispatch--> ( CPU ) --exit--> done
                    ^  ^  ^  ^                     │
                    │  │  │  └───── timeout <──────┤
                    │  │  └─ [ disk queue ]  <─────┤ read() / write()
                    │  └──── [ child-exit wait ] <─┤ wait()
                    └─────── [ sleep / timer ] <───┘ sleep()
ছবি ৮.৪ — CPU থেকে বেরোনোর চারটি পথ: সময় শেষ (timeout), ডিস্কের অপেক্ষা, child শেষ হওয়ার অপেক্ষা, ঘুম। প্রতিটি পথ শেষে আবার ready queue-তে ফেরা।

কোন queue থেকে কে কখন অন্য জায়গায় যাবে, সেই সিদ্ধান্ত নেয় scheduler. ক্লাসিক বইয়ে তিন স্তরের scheduler:

⏳ Long-term (job) scheduler

Job queue থেকে কোন কাজ RAM-এ এনে admit করা হবে, ঠিক করে — অর্থাৎ degree of multiprogramming নিয়ন্ত্রণ। ভালো মিশ্রণ রাখে: কিছু CPU-bound, কিছু I/O-bound. চলে কদাচিৎ — মিনিটে বা সেকেন্ডে একবার।

new → ready

⚡ Short-term (CPU) scheduler

Ready queue থেকে পরের প্রসেস বেছে CPU দেয়। চলে খুব ঘন ঘন — প্রতি কয়েক মিলিসেকেন্ডে, তাই একে অত্যন্ত দ্রুত হতে হয়। FCFS, SJF, Round Robin-এর মতো algorithm এরই (মডিউল ৩)।

ready → running

🔄 Medium-term scheduler

RAM-এর চাপ বুঝে কিছু প্রসেসকে সাময়িক ডিস্কে সরায় (swap out), পরে ফেরত আনে (swap in). ৭-state model-এর suspend আর activate তীর এরই কাজ।

suspend ↔ activate

আজকের Linux, Windows বা macOS-এর মতো time-sharing সিস্টেমে আলাদা long-term scheduler প্রায় নেই: আপনি প্রোগ্রাম চালালেই সেটি সরাসরি ready হয়। লোড সামলানোর কাজটি এখন ব্যবহারকারী (বেশি ধীর লাগলে কিছু বন্ধ করেন), সার্ভারের job queue (যেমন HPC ক্লাস্টারের Slurm), বা container-এর সীমা (cgroup, পর্ব ২১ ও ৫১) করে। Medium-term-এর কাজটি ঘটে page ধরে ধরে memory management-এর ভেতরে। কিন্তু short-term scheduler সবখানেই আছে, আর এটিই মডিউল ৩-এর মূল বিষয়। Linux-এ প্রতিটি CPU-র নিজের একটি run queue থাকে (ready queue-র বাস্তব রূপ), আর অপেক্ষার জন্য kernel-এর নানা জায়গায় wait_queue.

৭ · প্রসেসের পরিবার — parent, child আর PID 1

Unix-ধরনের সিস্টেমে প্রতিটি প্রসেস তৈরি করে আরেকটি প্রসেস। যে তৈরি করে সে parent, যাকে তৈরি করা হলো সে child. ফলে সব প্রসেস মিলে একটি গাছ (process tree). গাছের শিকড়ে PID 1 — বুটের সময় kernel যে প্রথম user-space প্রোগ্রাম চালায় (init; আজকের বেশিরভাগ Linux-এ systemd, macOS-এ launchd; পর্ব ৭)। Kernel-এর নিজের কাজের জন্য থাকে kernel thread — Linux-এ তাদের অভিভাবক kthreadd, যার PID সাধারণত 2.

$ pstree -p        (উদাহরণ আউটপুট, ছোট করে দেখানো)
systemd(1)─┬─sshd(812)───sshd(3301)───bash(3307)───pstree(3420)
           ├─cron(640)
           ├─NetworkManager(598)─┬─{NetworkManager}(611)
           │                     └─{NetworkManager}(613)
           └─systemd-journal(301)

পড়ার নিয়ম: pstree নিজে child of bash, bash child of sshd (দূর থেকে লগইন), আর সবার শিকড় systemd (1). কোঁকড়া বন্ধনীর {NetworkManager} হলো একই প্রসেসের thread. দুটি বিশেষ ঘটনা পরীক্ষায় আর বাস্তবে দুই জায়গাতেই গুরুত্বপূর্ণ:

🧟 Zombie প্রসেস

Child শেষ হয়ে গেছে, কিন্তু parent এখনো wait() দিয়ে তার exit status নেয়নি। মেমরি ও ফাইল ফেরত গেছে, শুধু PCB-র ছোট একটি অংশ আর PID আটকে আছে। ps-এ Z আর <defunct>. Zombie-কে kill করা যায় না — সে তো মৃতই; parent wait করলে বা parent নিজে শেষ হলে সমাধান।

👶 Orphan প্রসেস

Parent আগে শেষ হয়ে গেল, child তখনো চলছে। Linux তাকে PID 1-এর (বা কাছের কোনো “subreaper” প্রসেসের) সন্তান করে দেয়। নতুন অভিভাবক নিয়মিত wait() করে, তাই অনাথ প্রসেস শেষ হলে zombie হয়ে পড়ে থাকে না।

Windows-এ ব্যবস্থাটি একটু আলাদা। প্রতিটি প্রসেস তৈরির সময় parent-এর PID লেখা থাকে, কিন্তু kernel কোনো কঠোর গাছ রক্ষা করে না: parent শেষ হলে child-কে কারও কাছে হস্তান্তর করা হয় না, আর Unix-এর মতো zombie অবস্থাও নেই (প্রসেস object থাকে যতক্ষণ কেউ তার handle খোলা রাখে)। Windows-এ PID 4 হলো “System” প্রসেস, যার ভেতরে kernel-এর thread চলে, আর PID 0 “System Idle Process”.

৮ · Linux আর Windows-এ প্রসেস চোখে দেখা

Linux-এ প্রতিটি প্রসেসের জন্য /proc/<pid>/ নামের একটি ফোল্ডার থাকে — kernel পড়ার মুহূর্তে এর “ফাইল”গুলো PCB থেকে তৈরি করে দেয় (procfs). status ফাইলে state, PID, parent, মেমরি; maps ফাইলে address space-এর প্রতিটি এলাকা। আর ps কমান্ড এগুলোই পড়ে সুন্দর করে দেখায়। কিন্তু আগে একটি গুরুত্বপূর্ণ টেবিল — Linux kernel পাঠ্যবইয়ের নামগুলো কোন অক্ষরে দেখায়:

অক্ষর/proc/…/status-এমানেপাঠ্যবইয়ের অবস্থা
Rrunningচলছে বা run queue-তে চলার অপেক্ষায়running + ready
SsleepingInterruptible sleep: ঘটনার অপেক্ষা, signal এলে জেগে ওঠেwaiting
Ddisk sleepUninterruptible sleep: সাধারণত I/O-র মাঝখানে, signal-এও জাগে নাwaiting
T / tstopped / tracing stopCtrl+Z বা SIGSTOP-এ থামানো / debugger থামিয়ে রেখেছে(বাস্তবের “suspend”)
Zzombieশেষ, কিন্তু parent এখনো wait করেনিterminated
Iidleকাজহীন kernel thread (load average-এ গোনা হয় না)waiting

প্রথম সারিটি সবচেয়ে চমকপ্রদ: Linux ready আর running আলাদা করে না; দুটোই R, ভেতরে TASK_RUNNING. কে এই মুহূর্তে আসলে CPU-তে, তা বোঝা যায় শুধু প্রতিটি CPU-র “current” প্রসেস দেখে। ps-এর STAT কলামে মূল অক্ষরের পর আরও কিছু চিহ্ন বসতে পারে: s = session leader, N = কম অগ্রাধিকার (nice), < = বেশি অগ্রাধিকার, l = multi-threaded, + = টার্মিনালের foreground দলে।

এবার এই পাতার Linux রানারে সব একসাথে দেখি: নিজের state, নিজের মেমরির এলাকা, আর পটভূমিতে ঘুমিয়ে থাকা একটি প্রসেস:

echo "== /proc/self/status (self = grep) =="
grep -E '^(Name|State|Pid|PPid|Threads)' /proc/self/status
echo
echo "== segments in /proc/self/maps, low to high (self = awk) =="
awk '$6 ~ /awk$/ && $2 ~ /x/  {print "text  ", $2, $6}
     $6 == "[heap]"           {print "heap  ", $2, $6}
     $6 == "[stack]"          {print "stack ", $2, $6}
     $6 == "[vdso]"           {print "vdso  ", $2, $6}' /proc/self/maps
echo
echo "== a sleeping background process =="
sleep 2 &
sleep 0.2
ps -o pid,ppid,stat,cmd
echo
echo "shell PID \$\$ = $$, background sleep PID \$! = $!"
kill $!
wait $! 2>/dev/null      # hide bash "Terminated" notice
echo "after kill: exit status $? (128 + 15 = SIGTERM)"

একটি আসল রানের আউটপুট (PID প্রতিবার বা প্রতি মেশিনে আলাদা হতে পারে):

== /proc/self/status (self = grep) ==
Name:	grep
State:	R (running)
Pid:	3
PPid:	2
Threads:	1

== segments in /proc/self/maps, low to high (self = awk) ==
text   r-xp /usr/bin/mawk
heap   rw-p [heap]
stack  rw-p [stack]
vdso   r-xp [vdso]

== a sleeping background process ==
    PID    PPID STAT CMD
      1       0 SNs  /run/podman-init -- /bin/bash prog.sh
      2       1 SN   /bin/bash prog.sh
      5       2 SN   sleep 2
      7       2 RN   ps -o pid,ppid,stat,cmd

shell PID $$ = 2, background sleep PID $! = 5
after kill: exit status 143 (128 + 15 = SIGTERM)

প্রথম অংশ: /proc/self পড়ছে grep নিজে, আর পড়ার মুহূর্তে সে তো CPU-তে চলছে — তাই State: R (running). তার parent (PPid 2) হলো আমাদের bash স্ক্রিপ্ট। দ্বিতীয় অংশ: awk প্রোগ্রামের নিজের মেমরির মানচিত্র। maps ফাইল ঠিকানার ছোট থেকে বড় ক্রমে লেখা, তাই অংশ ২-এর ছবির ক্রমই এসেছে: text (r-xp — পড়া ও চালানো যায়, লেখা নয়), তারপর heap, তারপর stack (দুটোই rw-p, চালানো নিষেধ), আর stack-এর ওপরে vdso. তৃতীয় অংশ: sleep 2-এর STAT S — সে টাইমারের অপেক্ষায় waiting, CPU চাইছে না। ps নিজে R, কারণ তালিকা বানানোর সময় সে-ই চলছিল। প্রতিটি সারিতে N বলছে এই রানার সবকিছু কম অগ্রাধিকারে (nice) চালায়, আর PID 1-এর s মানে session leader. লক্ষ করুন, এই container-এ PID 1 systemd নয় — podman-init নামের একটি ছোট init, আর আমাদের স্ক্রিপ্ট তার child (container-এর নিজস্ব PID-জগৎ নিয়ে পর্ব ৫১-এ)। শেষে sleep-কে kill করার পর exit status 143 = 128 + 15, অর্থাৎ SIGTERM signal-এ শেষ — signal নিয়ে বিস্তারিত পর্ব ১৩-এ।

নিজের Linux মেশিনে আরও কিছু কাজের কমান্ড (এই পাতায় চালানোর দরকার নেই; আপনার মেশিনে আউটপুট আলাদা হবে):

ps -eo pid,ppid,stat,cmd --forest | head -20   # every process, as a tree
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'     # find zombies
ps -eo pid,stat,wchan:20,cmd | awk '$2 ~ /^D/' # who is stuck in D, and where
cat /proc/1/status | head -8                   # PID 1's PCB, as text
cat /proc/$$/maps                              # this shell's memory map

Windows-এ: Task Manager আর PowerShell

Windows-এ Ctrl+Shift+Esc চেপে Task Manager খুলুন, তারপর Details ট্যাব। সেখানে PID, Status (Running বা Suspended), User name, CPU আর Memory কলাম দেখবেন; কলামের শিরোনামে ডান-ক্লিক করে আরও কলাম (যেমন Threads, Handles) যোগ করা যায়। Windows-এ scheduling হয় thread ধরে, তাই প্রসেসের “Running” মানে তার অন্তত একটি thread চলার উপযুক্ত — Linux-এর মতো R/S/D অক্ষর এখানে নেই। PowerShell-এ একই তথ্য (উদাহরণ আউটপুট, আপনার মেশিনে সংখ্যা আলাদা):

PS> Get-Process -Name notepad, explorer | Select-Object Id, ProcessName, CPU, @{n='Threads';e={$_.Threads.Count}}

   Id ProcessName       CPU Threads
   -- -----------       --- -------
 6140 explorer      152.375      96
 9212 notepad         0.531       8

PS> Get-CimInstance Win32_Process -Filter "Name='notepad.exe'" | Select-Object ProcessId, ParentProcessId, Name

ProcessId ParentProcessId Name
--------- --------------- ----
     9212            6140 notepad.exe

দ্বিতীয় কমান্ড দেখাচ্ছে notepad-এর parent হলো explorer (৬১৪০) — Start মেনু বা ডেস্কটপ থেকে চালালে এমনই হয়। পুরোনো Windows PowerShell 5.1-এ Get-Process parent দেখায় না, তাই Win32_Process লাগে; PowerShell 7-এ (Get-Process notepad).Parent দিয়েও পাওয়া যায়। Sysinternals-এর Process Explorer প্রসেসগুলো গাছ আকারে দেখায় — Windows-এর pstree বলতে পারেন।

আরও গভীরে: Linux-এ context switch-এর সময় user mode-এর register “PCB-তে” নয়, প্রতিটি task-এর নিজস্ব kernel stack-এর মাথায় একটি struct pt_regs-এ জমা হয়; task_struct-এর thread ঘরে থাকে মূলত kernel-এর stack pointer আর কিছু architecture-নির্ভর register. আসল switch করে __schedule() থেকে ডাকা context_switch(), যা আগে মেমরির মানচিত্র (mm) আর পরে register বদলায় (পর্ব ৯)। Linux-এর D state এত বিখ্যাত কারণ এমন প্রসেসকে kill -9-ও থামাতে পারে না; এর সমাধান হিসেবে Linux 2.6.25-এ TASK_KILLABLE এসেছে — শুধু মারাত্মক signal-এ জেগে ওঠা ঘুম। আর অন্য Unix-এর তুলনায় Linux-এর load average-এ D state-এর প্রসেসও গোনা হয়, তাই ধীর ডিস্ক বা আটকে যাওয়া নেটওয়ার্ক ফাইল সিস্টেমে CPU অলস থেকেও load অনেক বেশি দেখাতে পারে।

৯ · AI-এর যুগে প্রসেস নিয়ে কাজ

“সার্ভার ধীর, কোন প্রসেস দায়ী?” বা “zombie প্রসেস কীভাবে মুছব?” — AI চটপট কমান্ড দেবে। অনেক সময় ঠিকও হবে। কিন্তু এই বিষয়ে AI-এর ভুলগুলো বেশ নির্দিষ্ট, আর এই পর্বের ধারণা থাকলে আপনি সেগুলো ধরতে পারবেন:

AI-এর ভুলআসল সত্যকীভাবে যাচাই করবেন
“Zombie মুছতে kill -9 <pid> দিন”Zombie আগেই মৃত; signal কাজ করে না। Parent-কে wait করাতে হয় বা parent বন্ধ করতে হয়ps -o ppid= -p <pid> দিয়ে parent খুঁজুন
“D state-এর প্রসেস kill -9 দিলেই যাবে”Uninterruptible sleep-এ signal পৌঁছায় না; নিচের I/O (ডিস্ক, NFS) ঠিক করতে হয়wchan কলাম আর dmesg দেখুন
“ps-এ ready আর running আলাদা দেখাবে”Linux-এ দুটোই Rman ps-এর PROCESS STATE CODES অংশ
Linux-এর কমান্ড Windows-এর জন্য দেওয়া, বা উল্টোWindows-এ /proc নেই; PowerShell-এর ps আসলে Get-Process-এর alias, অপশন আলাদাপ্রম্পটে OS আর shell-এর নাম লিখুন
“এই ভ্যারিয়েবলের ঠিকানা সবসময় 0x601040”ASLR আর PIE-তে ঠিকানা প্রতিবার বদলায়; শুধু ক্রম স্থিরএকই প্রোগ্রাম দুবার চালিয়ে দেখুন
“Permission denied? sudo দিয়ে ps চালান”অন্য ব্যবহারকারীর প্রসেস দেখতে সাধারণত sudo লাগে না; কিছু /proc ফাইল ইচ্ছে করেই সুরক্ষিতন্যূনতম অনুমতির পথ খুঁজুন; নিরাপত্তা-সেটিং বন্ধ করবেন না

সমস্যা নির্ণয়ে AI-কে কাজে লাগানোর সময় তথ্য দিন, আর যাচাইয়ের পথও চেয়ে নিন:

আমার পরিবেশ: Ubuntu 24.04 সার্ভার, Linux kernel 6.8, bash. root ছাড়া সাধারণ ব্যবহারকারী। সমস্যা: load average 12, অথচ top-এ CPU প্রায় অলস দেখাচ্ছে। নিচে ps -eo pid,ppid,stat,wchan:20,cmd-এর আউটপুট দিলাম। [আউটপুট এখানে] তুমি বলো: ১. কোন প্রসেসগুলো D বা Z state-এ আছে, আর প্রতিটির parent কে। ২. load বেশি কিন্তু CPU অলস — এর সম্ভাব্য কারণ কী, আর তোমার ধারণা প্রমাণ বা বাতিল করতে কোন কমান্ড চালাব। ৩. কোনো পরামর্শে kill -9, sudo বা রিবুট থাকলে আগে তার ঝুঁকি আর বিকল্প বলো। প্রতিটি দাবির পাশে লেখো সেটি কোন man page বা kernel ডকুমেন্টেশনে মিলিয়ে দেখা যায়।

AI-এর প্রসেস-সংক্রান্ত পরামর্শ মানার আগে — চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • প্রোগ্রাম আর প্রসেসের পার্থক্য? — প্রোগ্রাম ডিস্কের নিষ্ক্রিয় ফাইল; প্রসেস চলমান প্রোগ্রাম, যার নিজস্ব মেমরি, PC, register, খোলা ফাইল আর PID আছে।
  • Process state diagram আঁকুন, প্রতিটি transition-এর কারণসহ। — new→ready (admit), ready→running (dispatch), running→ready (timeout/interrupt), running→waiting (I/O বা ঘটনার অপেক্ষা), waiting→ready (I/O শেষ), running→terminated (exit).
  • Waiting থেকে সরাসরি running সম্ভব? — না; ঘটনা শেষে প্রসেস ready queue-তে যায়, CPU কে পাবে তা ঠিক করে শুধু scheduler.
  • PCB-তে কী কী থাকে? — PID, state, program counter, CPU register, scheduling তথ্য, memory-management তথ্য, accounting তথ্য, I/O status (খোলা ফাইল)।
  • Long-, short- আর medium-term scheduler-এর পার্থক্য? — admit নিয়ন্ত্রণ (কদাচিৎ চলে), CPU বরাদ্দ (মিলিসেকেন্ডে চলে), swap out/in (মাঝে মাঝে)।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১পাঠ্যবই ready আর running আলাদা করে, অথচ Linux দুটোকেই R বলে। কোনটি “ঠিক”, আর এই পার্থক্য কেন?

দুটোই ঠিক — তারা আলাদা প্রশ্নের উত্তর দেয়। পাঠ্যবইয়ের মডেল একটি ধারণাগত ছবি: প্রসেসের জীবনে কোন কোন দশা আসে, আর কোন ঘটনায় এক দশা থেকে আরেকটিতে যায়। সেখানে ready আর running আলাদা না করলে timeout বা dispatch-এর মতো তীরই আঁকা যায় না, আর scheduling-এর পুরো আলোচনা অর্থহীন হয়ে যায়।

Linux kernel-এর প্রশ্ন বাস্তব প্রকৌশলের: “এই task-কে কি CPU দেওয়া যায়?” যদি যায়, সে TASK_RUNNING, আর run queue-তে থাকে। এই মুহূর্তে আসলেই কে চলছে, তা জানতে আলাদা state লাগে না — প্রতিটি CPU-র একটি current পয়েন্টার আছে, যা চলমান task দেখায়। একটি task থেকে আরেকটিতে যেতে তাই state বদলাতে হয় না, শুধু পয়েন্টার বদলায়। মাল্টিকোর মেশিনে এটি আরও সুবিধাজনক: ৬৪টি core থাকলে ৬৪টি task একসাথে “running”, আর কোনটি কখন কোন core-এ, সেটি scheduler-এর নিজের হিসাব। প্রতিটি dispatch আর preemption-এ state লেখার কাজ বাঁচে, lock-এর ঝামেলাও কমে।

অন্যদিকে Windows ঠিক উল্টো পথে আরও বেশি অবস্থা রাখে — তবে প্রসেসের নয়, thread-এর: Initialized, Ready, Standby (পরের জন হিসেবে একটি নির্দিষ্ট CPU-র জন্য বাছাই করা), Running, Waiting, Transition (চলার জন্য প্রস্তুত, কিন্তু তার kernel stack মেমরিতে নেই), Terminated, আর DeferredReady. Standby আর DeferredReady মাল্টিপ্রসেসর scheduling-এর বাস্তব প্রয়োজন থেকে এসেছে। আর xv6 পাঠ্যবইয়ের কাছাকাছি থাকে: RUNNABLE আর RUNNING আলাদা, কারণ এর উদ্দেশ্যই শেখানো।

পরীক্ষার খাতায় তাই পাঠ্যবইয়ের মডেল লিখবেন, আর বাস্তবে ps-এর R দেখে বুঝবেন সেটি “ready অথবা running”. কোনো প্রসেস আসলেই কতক্ষণ CPU পেল জানতে /proc/<pid>/stat-এর utime/stime বা top-এর %CPU দেখতে হয়।

মূল উপলব্ধি: মডেল হলো চিন্তার হাতিয়ার, বাস্তবায়ন হলো প্রকৌশলের আপস। একই ধারণা বিভিন্ন OS ভিন্ন ভিন্ন সংখ্যক state দিয়ে প্রকাশ করে — মূল প্রশ্ন একই থাকে: কে চলছে, কে চলতে পারে, আর কে অপেক্ষায়।

প্র ০২D state-এর প্রসেসকে kill -9-ও কেন থামাতে পারে না? এটি কি নকশার ভুল?

D (uninterruptible sleep) মানে প্রসেসটি kernel-এর ভেতরে এমন একটি কাজের মাঝখানে, যেখান থেকে হঠাৎ বেরিয়ে গেলে kernel-এর নিজের ডেটা অসংগত হয়ে যেতে পারে। ধরুন একটি ফাইল সিস্টেমের কোড ডিস্ক থেকে একটি block পড়ার অপেক্ষায়, আর সেই block এলে কিছু lock ছাড়তে হবে, কিছু তালিকা হালনাগাদ করতে হবে। এই মুহূর্তে signal এলে কাজটি অর্ধেক থাকে; প্রতিটি জায়গায় “বাতিল হলে সব আগের মতো ফিরিয়ে আনা”-র কোড লেখা জটিল, আর পুরোনো ড্রাইভারে প্রায়ই তা ছিল না। তাই নিরাপদ পথ: এই ঘুম signal-এ ভাঙবে না, ঘটনা ঘটলেই শুধু জাগবে। স্বাভাবিক ডিস্কে এই ঘুম মিলিসেকেন্ডের, তাই কেউ টেরই পায় না।

সমস্যা হয় যখন ঘটনাটি আর আসে না। সবচেয়ে বিখ্যাত উদাহরণ NFS — নেটওয়ার্কের ওপর দিয়ে mount করা ফাইল সিস্টেম। সার্ভার বন্ধ হলে, “hard” mount-এ ক্লায়েন্টের প্রসেস চিরকাল D-তে অপেক্ষা করে, কারণ Unix-এর পুরোনো দর্শন ছিল “ডেটা হারানোর চেয়ে অপেক্ষা ভালো”। ব্যবহারকারী kill -9 দেন, কিছু হয় না; শেষে অনেকে রিবুট করেন। নষ্ট হতে থাকা ডিস্ক বা আটকে যাওয়া USB ডিভাইসেও একই দৃশ্য দেখা যায়।

Linux এর মাঝামাঝি সমাধান আনে ২০০৮ সালের দিকে, kernel 2.6.25-এ: TASK_KILLABLE. এই ঘুম সাধারণ signal-এ ভাঙে না, কিন্তু প্রসেস মেরে ফেলার মতো মারাত্মক signal (যেমন SIGKILL) এলে ভাঙে — কারণ প্রসেস যেহেতু মরেই যাচ্ছে, ফলাফল কাউকে ফেরত দেওয়ার দায় নেই, শুধু পরিষ্কার করে বেরোলেই হয়। NFS-সহ অনেক কোড ধীরে ধীরে এতে সরে গেছে। আরেকটি সমস্যা ছিল কাজহীন kernel thread — তারা D-তে ঘুমালে load average অকারণে বাড়ত; তাই এসেছে TASK_IDLE, ps-এ যা I.

সুতরাং এটি ভুল নয়, একটি সচেতন আপস: kernel-এর সংগতি রক্ষা বনাম ব্যবহারকারীর নিয়ন্ত্রণ। বাস্তবে D-তে আটকে থাকা প্রসেস দেখলে প্রশ্নটি “কীভাবে মারব” নয়, বরং “সে কিসের অপেক্ষায়” — wchan কলাম, /proc/<pid>/stack (root লাগে) আর dmesg-এর ডিস্ক বা নেটওয়ার্ক ত্রুটি দেখে উত্তর খুঁজতে হয়।

মূল উপলব্ধি: প্রতিটি “অপেক্ষা” দুই রকম হতে পারে — বাধা দেওয়া যায় এমন, আর যায় না। কোনটি ব্যবহার হবে, তা ঠিক করে কাজটি মাঝপথে থামালে কতটা ক্ষতি হয় তার ওপর।

প্র ০৩ASLR প্রতিবার ঠিকানা এলোমেলো করে — তাহলে কি এটি সব আক্রমণ থামায়? এর ইতিহাস আর সীমা কী?

ASLR-এর পেছনের চিন্তা সহজ: অনেক মেমরি-বাগ কাজে লাগাতে আক্রমণকারীকে জানতে হয় কোনো দরকারি কোড বা ডেটা ঠিক কোন ঠিকানায় আছে। প্রতিটি প্রসেসে ঠিকানা এলোমেলো হলে সেই জ্ঞান আর আগে থেকে থাকে না — ভুল অনুমান করলে প্রোগ্রাম সাধারণত crash করে, আক্রমণ ব্যর্থ হয়। এটি বাগ সারায় না; বাগকে কাজে লাগানো কঠিন করে। তাই একে বলা হয় “mitigation” বা ক্ষতি-কমানোর ব্যবস্থা।

ইতিহাসের দিক থেকে, Linux-এর জন্য PaX নামের একটি নিরাপত্তা-প্যাচ প্রকল্প ২০০১ সালের দিকে ASLR জনপ্রিয় করে। OpenBSD ২০০৩ সালের দিকে এটি ডিফল্ট করে। মূল Linux kernel-এ stack আর mmap-এর এলোমেলোকরণ আসে ২০০৫ সালের দিকে, পরে heap-ও। Microsoft Windows Vista-তে (২০০৭) ASLR আনে, তবে শুরুতে শুধু যেসব প্রোগ্রাম নিজে থেকে রাজি হতো তাদের জন্য। Text অংশ এলোমেলো করতে প্রোগ্রামটিকে position-independent executable (PIE) হিসেবে compile করতে হয় — আধুনিক Ubuntu, Fedora-র gcc এখন ডিফল্টভাবেই তা করে। এজন্যই আজকের Linux-এ একটি সাধারণ প্রোগ্রামের main-এর ঠিকানাও প্রতিবার বদলায়।

সীমাও আছে। 32-bit সিস্টেমে ঠিকানার জায়গা কম, তাই এলোমেলোকরণের সম্ভাবনা (entropy) কম — বারবার চেষ্টা করে মিলিয়ে ফেলা সম্ভব হতো। আর প্রোগ্রাম যদি কোনো বাগের কারণে একটিমাত্র ঠিকানাও “ফাঁস” করে দেয় (information leak), বাকি সব হিসাব করে ফেলা যায়, কারণ একটি এলাকার ভেতরের ক্রম আর দূরত্ব স্থির — ঠিক যেমন উপরের C প্রোগ্রামে ক্রম স্থির ছিল। তাই ASLR একা যথেষ্ট নয়; এর সাথে থাকে non-executable stack (maps-এ stack-এর অনুমতি rw-p, x নেই), stack canary, আর নতুন CPU-র হার্ডওয়্যার সুরক্ষা।

প্রতিরক্ষার কাজে নিজের সিস্টেমে যাচাই করুন: cat /proc/sys/kernel/randomize_va_space 2 দেখায় কি না, আর আপনার প্রোগ্রাম PIE কি না (file ./prog “pie executable” বলে কি না)। Debugging-এর সুবিধার জন্য সাময়িকভাবে ASLR বন্ধ করার পরামর্শ দেখলেও সার্ভারে তা কখনো স্থায়ী করবেন না। বিস্তারিত পর্ব ৪৮-এ।

মূল উপলব্ধি: ASLR “গোপন ঠিকানা”-র ওপর ভরসা করে, তাই এটি একটি গোপনীয়তা যা একটি ফাঁসেই ভেঙে যেতে পারে। নিরাপত্তা আসে স্তরে স্তরে — কোনো একটি ব্যবস্থা একা সব কাজ করে না।

প্র ০৪প্রসেস শেষ হলেও Unix কেন zombie রেখে দেয়? Container-এ PID 1 নিয়ে এত আলোচনা কেন?

Unix-এর নকশায় parent জানতে পারে তার child কীভাবে শেষ হলো — স্বাভাবিকভাবে exit 0, ত্রুটিতে exit 1, নাকি কোনো signal-এ মারা গেল। Shell এই তথ্য দিয়েই $? দেখায়, আর make ঠিক করে পরের ধাপ চলবে কি না। কিন্তু child শেষ হওয়ার মুহূর্তে parent হয়তো অন্য কাজে ব্যস্ত। তথ্যটি কোথাও রাখতে হবে, আর PID-টিও অন্য কাউকে দেওয়া যাবে না — নইলে parent ভুল প্রসেসের খবর পেত। তাই kernel মেমরি, ফাইল সব ফেরত নেয়, শুধু PCB-র ছোট একটি অংশ রাখে: সেটিই zombie. Parent wait() ডাকলে তথ্য পায়, তখন zombie মুছে যায় — একে বলে “reaping”.

একটি-দুটি zombie ক্ষতিকর নয়। সমস্যা হয় যখন কোনো বাগওয়ালা সার্ভার হাজার হাজার child বানায় আর কখনো wait করে না: PID-এর সংখ্যা সীমিত (/proc/sys/kernel/pid_max-এ লেখা; অনেক সিস্টেমে ডিফল্ট 32768, অনেক আধুনিক distro-তে আরও বেশি), তাই একসময় নতুন প্রসেসই তৈরি করা যায় না। আর parent আগে শেষ হলে child অনাথ হয়, তখন PID 1 তাকে দত্তক নেয় — তাই PID 1-কে নিয়মিত wait করতেই হয়। Linux 3.4 থেকে কোনো প্রসেস নিজেকে “child subreaper” ঘোষণা করে PID 1-এর এই দায়িত্ব নিজের উত্তরসূরিদের জন্য নিতে পারে; systemd-এর ব্যবহারকারী-সেশন এভাবেই চলে।

Container-এ এই সমস্যা নতুন রূপে আসে। Docker বা Podman container-এর নিজস্ব PID-জগতে প্রথম প্রসেসটিই PID 1. যদি সেটি আপনার অ্যাপ হয় — ধরুন একটি Node.js বা Python সার্ভার — তাহলে অনাথ প্রসেসগুলো তারই ঘাড়ে পড়ে, আর সে wait করতে জানে না; zombie জমতে থাকে। তাছাড়া Linux PID 1-কে বিশেষ সুরক্ষা দেয়: যে signal-এর জন্য সে নিজে handler বসায়নি, তার ডিফল্ট আচরণ (যেমন SIGTERM-এ বন্ধ হওয়া) প্রয়োগ হয় না, তাই docker stop-এর SIGTERM-এ অ্যাপ বন্ধ হয় না, কয়েক সেকেন্ড পর SIGKILL খেতে হয়। এর সমাধান হিসেবে ছোট init প্রোগ্রাম আছে — tini, dumb-init, আর Docker-এর --init অপশন। এই পাতার bash রানারের আউটপুটেই দেখেছেন, PID 1 ছিল podman-init — ঠিক এই কাজের জন্য।

মূল উপলব্ধি: Zombie কোনো ত্রুটি নয়, একটি চুক্তি: “আমি মারা গেছি, কিন্তু খবরটি না দিয়ে যাচ্ছি না।” চুক্তি মানতে হবে parent-কে, আর অভিভাবকহীনদের জন্য PID 1-কে।

প্র ০৫একটি ব্রাউজার প্রোগ্রাম কেন ইচ্ছে করে ডজন ডজন প্রসেস বানায়? প্রসেস ধারণার কোন বৈশিষ্ট্য এখানে কাজে লাগে?

২০০৮ সালে Google Chrome যখন আসে, তার সবচেয়ে আলোচিত নকশা-সিদ্ধান্ত ছিল multi-process architecture: মূল ব্রাউজার একটি প্রসেস, আর ওয়েবপেজ দেখানোর কাজ (renderer) আলাদা আলাদা প্রসেসে। তখনকার বেশিরভাগ ব্রাউজার সব ট্যাব এক প্রসেসে চালাত, ফলে একটি পেজের বাগ বা ভারী স্ক্রিপ্টে পুরো ব্রাউজার আটকে যেত বা crash করত। Chrome-এ একটি ট্যাব crash করলে শুধু সেই ট্যাবে “Aw, Snap!” আসে, বাকি সব চলে।

এখানে প্রসেসের তিনটি বৈশিষ্ট্য কাজে লাগে, যা এই পর্বেই দেখলেন। প্রথম, আলাদা address space: এক প্রসেস আরেকটির মেমরি ছুঁতে পারে না, তাই একটি পেজের মেমরি-বাগ অন্য পেজের ডেটা নষ্ট করে না। দ্বিতীয়, সম্পদের মালিকানা: প্রসেস শেষ হলে kernel তার সব মেমরি আর ফাইল নিশ্চিতভাবে ফেরত নেয় — দীর্ঘক্ষণ খোলা ট্যাবের জমে থাকা memory leak ট্যাব বন্ধ করলেই পরিষ্কার। তৃতীয়, আলাদা অনুমতি: renderer প্রসেসকে খুব কম অনুমতিতে, sandbox-এর ভেতরে চালানো যায় (পর্ব ৪৯), যাতে কোনো দুষ্ট পেজ বাগ কাজে লাগালেও ফাইল সিস্টেমে পৌঁছাতে না পারে। ২০১৮ সালে Spectre-ধরনের CPU-দুর্বলতা প্রকাশের পর Chrome “Site Isolation” আরও জোরদার করে — ভিন্ন ওয়েবসাইট যাতে আলাদা প্রসেসে থাকে, কারণ একই address space-এ থাকলে হার্ডওয়্যার-স্তরের ফাঁস দিয়ে একে অপরের ডেটা পড়ার ঝুঁকি থাকে। পরে Firefox-সহ অন্য ব্রাউজারও একই পথে গেছে।

দাম আছে: প্রতিটি প্রসেসের নিজস্ব PCB, page table আর library-র কিছু কপি লাগে, তাই মেমরি বেশি খরচ হয় — “Chrome RAM খায়” অভিযোগের একটি কারণ এটিই। প্রসেসে প্রসেসে কথা বলতে IPC লাগে (পর্ব ১২), যা thread-এর মধ্যে সরাসরি মেমরি ভাগ করার চেয়ে ধীর। তাই কম RAM-এর যন্ত্রে ব্রাউজার একাধিক সাইট একটি প্রসেসে একত্র করে ভারসাম্য রাখে।

মূল উপলব্ধি: প্রসেস শুধু “চলমান প্রোগ্রাম” নয়, OS-এর দেওয়া সবচেয়ে শক্ত দেয়াল। কোথায় দেয়াল তুলবেন আর কোথায় ভাগাভাগি করবেন — এই সিদ্ধান্তই আধুনিক সফটওয়্যারের নিরাপত্তা আর গতির মূল আপস।

অনুশীলন

হাতে ট্রেস, আউটপুট অনুমান আর কমান্ড লেখা — আগে নিজে চেষ্টা করুন, তারপর উত্তর মেলান।

  1. হাতে ট্রেস: এক CPU-র একটি সিস্টেমে দুটি প্রসেস X আর Y, দুজনেই শুরুতে new. ঘটনাগুলো এই ক্রমে আসে: admit X, admit Y, dispatch Y, io_wait Y, dispatch X, io_done Y, dispatch Y, timeout X, dispatch Y, exit X. প্রতিটি ঘটনার পর X আর Y-এর state লিখুন, আর কোন কোন ঘটনা ৫-state model-এ অবৈধ, কারণসহ বলুন।
    ঘটনাXYমন্তব্য
    admit Xreadynew
    admit Yreadyready
    dispatch Yreadyrunning
    io_wait YreadywaitingCPU খালি
    dispatch Xrunningwaiting
    io_done Yrunningready
    dispatch Yrunningreadyঅবৈধ: CPU X-এর হাতে, এক CPU-তে দুজন running হতে পারে না
    timeout Xreadyready
    dispatch Yreadyrunningএবার বৈধ
    exit Xreadyrunningঅবৈধ: exit শুধু running থেকে হয়; X এখন ready

    শেষে X ready, Y running. দ্বিতীয় অবৈধ ঘটনাটি নিয়ে একটি সূক্ষ্ম কথা: বাস্তবে ready প্রসেসও মারা যেতে পারে — কেউ kill পাঠালে। তবে Linux-এ signal-এর কাজ হয় যখন প্রসেসটি আবার চলার সুযোগ পায় (বা kernel তার হয়ে কাজ করে); পাঠ্যবইয়ের ৫-state model-এ exit তীর শুধু running থেকেই আঁকা হয়।

  2. আউটপুট অনুমান: x86-64 Linux-এ নিচের প্রোগ্রাম কী ছাপবে? (ঠিকানা নয়, শুধু ছাপা লেখা।)
    #include <stdio.h>
    #include <stdint.h>
    static uintptr_t prev = 0;
    void dive(int depth) {
        static int calls = 0;            /* lives in data/BSS, not on the stack */
        int here;                        /* a new copy on the stack each call   */
        calls++;
        if (prev) printf("depth %d: %s, calls = %d\n", depth,
                         (uintptr_t)&here < prev ? "lower" : "higher", calls);
        prev = (uintptr_t)&here;
        if (depth < 3) dive(depth + 1);
    }
    int main(void) { dive(1); return 0; }
    depth 2: lower, calls = 2
    depth 3: lower, calls = 3

    depth 1-এ prev 0, তাই কিছু ছাপে না। প্রতিটি নতুন কলের frame আগেরটির নিচে তৈরি হয় (stack নিচের দিকে নামে), তাই নতুন here-এর ঠিকানা আগেরটির চেয়ে ছোট: “lower”. অন্যদিকে static int calls stack-এ নেই; একটিমাত্র কপি data/BSS-এ (এটি শূন্য দিয়ে শুরু, তাই আসলে BSS-এ), তাই প্রতিটি কলে মান জমতে থাকে: 2, 3. লোকাল ভ্যারিয়েবল হলে প্রতিবার 1 ছাপত। (কঠোরভাবে বললে C মানদণ্ড ভিন্ন object-এর ঠিকানা তুলনার ফল নির্দিষ্ট করে না, তাই এখানে পূর্ণসংখ্যায় রূপান্তর করে তুলনা করা হয়েছে; ফলটি প্রচলিত x86-64 আর ARM64 সিস্টেমের আচরণ।)

  3. কমান্ড লিখুন: একটি Linux সার্ভারে (ক) সব zombie প্রসেস আর তাদের parent-এর PID দেখানোর একটি কমান্ড লিখুন। (খ) PID 4321 প্রসেসটি এই মুহূর্তে কোন state-এ আর কে তার parent, তা /proc থেকে পড়ার কমান্ড লিখুন। (গ) একটি zombie পেলে প্রথমে কী করবেন?

    (ক) ps -eo pid,ppid,stat,cmd | awk 'NR==1 || $3 ~ /^Z/' — NR==1 শিরোনামের সারি রাখে, আর তৃতীয় কলাম Z দিয়ে শুরু হলে ছাপে।

    (খ) grep -E '^(State|PPid)' /proc/4321/status. প্রসেস না থাকলে “No such file or directory” আসবে — প্রসেসটি ইতিমধ্যে শেষ।

    (গ) Zombie-কে kill করা অর্থহীন। তার PPID দেখে parent খুঁজুন। Parent-কে SIGCHLD পাঠালে (kill -s SIGCHLD <ppid>) সঠিকভাবে লেখা প্রোগ্রাম wait করে নেয়। কাজ না হলে, আর parent বন্ধ করা নিরাপদ হলে, parent-কে সাধারণ kill দিয়ে বন্ধ করুন — zombie তখন PID 1-এর হাতে যায় আর মুছে যায়। আসল সমাধান parent প্রোগ্রামের বাগ (wait না করা) ঠিক করা।

  4. হিসাব: (ক) একটি ৪-core কম্পিউটারে ১০টি প্রসেস আছে, সবাই CPU-bound, কেউ I/O করছে না। একই মুহূর্তে সর্বোচ্চ কতটি running, আর তখন কমপক্ষে কতটি ready? সবাই যদি একসাথে ডিস্কের অপেক্ষায় থাকে, তাহলে কতটি running? (খ) একটি প্রোগ্রামের size আউটপুট: text 1520, data 600, bss 4096. প্রসেস শুরুর সময় এই তিন অংশের জন্য মোট কত বাইট মেমরি লাগবে, আর ফাইলে এদের জন্য মোটামুটি কত বাইট থাকে?

    (ক) প্রতিটি core-এ একসাথে একটিই চলে, তাই সর্বোচ্চ running = ৪. বাকি সবাই CPU চায় আর কেউ waiting নয়, তাই ready = ১০ − ৪ = ৬. সবাই ডিস্কের অপেক্ষায় থাকলে running = ০, waiting = ১০, আর CPU অলস (idle) — Linux-এ তখন প্রতিটি core-এ একটি বিশেষ idle task চলে।

    (খ) মেমরিতে লাগবে ১৫২০ + ৬০০ + ৪০৯৬ = ৬২১৬ বাইট (বাস্তবে প্রতিটি অংশ page-এর সীমায় গোল করা হয়, তাই কিছু বেশি)। ফাইলে থাকে শুধু text আর data-র বিষয়বস্তু: ১৫২০ + ৬০০ = ২১২০ বাইট, সাথে header ইত্যাদি। BSS-এর ৪০৯৬ বাইট ফাইলে লেখা থাকে না — শুধু তার আকার লেখা থাকে, OS শুরুর সময় শূন্য দিয়ে দেয়।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমTimer interrupt-এ প্রসেস P1 থেকে P2-তে CPU যাওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Timer interrupt আসে, CPU kernel mode-এ যায়
  2. P1-এর program counter আর register সংরক্ষণ করা হয়
  3. P1-এর PCB-তে state ready করা হয়, P1 ready queue-তে যায়
  4. Scheduler ready queue থেকে P2-কে বেছে নেয়
  5. P2-এর PCB থেকে register CPU-তে ফিরিয়ে আনা হয়
  6. CPU user mode-এ ফেরে, P2 যেখানে থেমেছিল সেখান থেকে চলে

পাঁচ অবস্থার (new, ready, running, waiting, terminated) process state model-এ নিচের কোন transition সরাসরি ঘটতে পারে না?

  • running → ready
  • waiting → ready
  • waiting → running
  • running → terminated

নিচের কোনটি সাধারণত একটি প্রসেসের PCB (process control block)-এর অংশ নয়?

  • Program counter-এর সংরক্ষিত মান
  • খোলা ফাইলের তালিকা
  • Scheduling priority
  • প্রোগ্রামের C source code

C প্রোগ্রামে সব ফাংশনের বাইরে লেখা int count; (কোনো মান দেওয়া হয়নি) প্রসেসের মেমরির কোন অংশে থাকে?

  • Stack
  • BSS
  • Heap
  • Text

Linux-এ ps-এর STAT কলামে একটি প্রসেসের অবস্থা D দেখালে তার মানে কী?

  • Uninterruptible sleep — সাধারণত I/O-র অপেক্ষা, signal-এ জাগে না
  • প্রসেস শেষ, parent এখনো wait করেনি
  • Ctrl+Z চেপে থামানো প্রসেস
  • CPU-তে চলছে বা run queue-তে আছে

RAM-এর চাপ কমাতে কিছু প্রসেসকে সাময়িক ডিস্কে সরিয়ে (swap out) পরে ফেরত আনার সিদ্ধান্ত ক্লাসিক মডেলে কোন scheduler নেয়?

  • Long-term scheduler
  • Short-term scheduler
  • Medium-term scheduler
  • Dispatcher

Linux-এ একটি প্রসেসের parent আগে শেষ হয়ে গেলে, এখনো চলতে থাকা child প্রসেসটির কী হয়?

  • Child-ও সঙ্গে সঙ্গে বন্ধ হয়ে যায়
  • PID 1 বা কোনো subreaper তার নতুন parent হয়
  • Child সঙ্গে সঙ্গে zombie হয়ে যায়
  • Child একটি নতুন PID পায়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. প্রোগ্রাম নিষ্ক্রিয় ফাইল, প্রসেস সক্রিয় চলমান কাজ; এক প্রোগ্রাম থেকে অনেক প্রসেস হতে পারে, প্রতিটির নিজের PID আর মেমরি।
  2. Memory layout নিচ থেকে: text → data → BSS → heap ↑ … mmap … ↓ stack → kernel; ASLR ঠিকানা বদলায়, ক্রম নয়।
  3. ৫ অবস্থা আর ৬ তীর: admit, dispatch, timeout, I/O wait, I/O done, exit; waiting → running আর ready → waiting অসম্ভব। ৭-state model-এ যোগ হয় ready/suspend আর blocked/suspend.
  4. PCB = PID, state, PC ও register, scheduling, memory, accounting, I/O; Linux-এ task_struct, xv6-এ struct proc.
  5. Job, ready, device queue; long-term (admit), short-term (CPU), medium-term (swap) scheduler; Linux-এ R S D Z T I অক্ষর, আর R মানে ready অথবা running.

পরের পর্বে: এই পর্বে দেখলেন PCB-তে register জমা হয় আর ফিরে আসে। কিন্তু ঠিক কোন instruction-এ, কত সময়ে, আর তাতে cache আর TLB-র কী ক্ষতি হয়? Context switch আর dispatcher-এর ভেতরটা — আর কেন একটি switch-এর দাম মাইক্রোসেকেন্ডে মাপা হয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান