LEVEL 4
অপারেটিং সিস্টেম
Operating Systems
এই মডিউল যে প্রশ্নের উত্তর দেয়আমার program একা মনে করে পুরো machine তার — এই বিভ্রম OS কীভাবে বানায়?
OS আসলে কী? এটা hardware-কে multiplex করে, isolation দেয়, আর abstraction তৈরি করে — process, file, socket, address space। প্রতিটা abstraction-এর নিচে কী আছে সেটাই এই module-এর মূল বিষয়। Linux হবে আমাদের laboratory।
লেসন
- 01অপারেটিং সিস্টেম আসলে কী — bare metal থেকে kernel পর্যন্তOS একটা রহস্যময় 'সিস্টেম সফটওয়্যার' না — এটা তিনটা সুনির্দিষ্ট কাজ করে: একটা hardware-কে বহু ব্যবহারকারীর মধ্যে multiplex করা, তাদের একে অপরের থেকে isolate করা, আর কাঁচা hardware-এর উপর ব্যবহারযোগ্য abstraction (process, file, socket, address space) তৈরি করা। এই লেসনে আমরা bare metal থেকে শুরু করব — যেখানে কোনো OS নেই — তারপর পুরো boot chain ধরে kernel-এ পৌঁছাব, monolithic বনাম microkernel বিতর্ক দেখব, আর শেষে libc ছাড়া একটা প্রোগ্রাম লিখে হাতেকলমে দেখব OS আর library-র সীমারেখা ঠিক কোথায়।
- 02Kernel space বনাম user space — দেয়ালটা কে বানায়গত লেসনে বারবার বলেছি isolation 'hardware দিয়ে বলপূর্বক প্রয়োগ করা হয়' — এই লেসনে সেই বলপ্রয়োগের যন্ত্রটা খুলে দেখব। CPU-র ভেতরে দুই bit-এর একটা privilege level, যা প্রতিটা instruction আর প্রতিটা memory access-এ যাচাই হয়। আমরা দেখব একটা privileged instruction চালানোর চেষ্টা করলে ঠিক কী ঘটে, kernel কেন প্রতিটা process-এর address space-এ থাকে, আর ২০১৮ সালের Meltdown কীভাবে সেই ৫০ বছরের পুরনো নকশাটাকে রাতারাতি অনিরাপদ বানিয়ে দিয়েছিল।
- 03System call — দেয়ালের দরজাটা ভেতর থেকে খোলাগত লেসনে দেখলাম kernel আর user space-এর মাঝে একটা hardware দেয়াল আছে, আর তাতে তিনটা দরজা। এই লেসনে প্রথম দরজাটা — system call — খুলে ভেতরটা দেখব। একটা `syscall` instruction থেকে শুরু করে kernel-এর handler পর্যন্ত প্রতিটা ধাপ, কেন argument গুলো ঠিক ওই register-এ, ৪০০ ন্যানোসেকেন্ড কোথায় যায়, আর `errno` আসলে কোথা থেকে জন্মায়। শেষে libc-কে সম্পূর্ণ বাদ দিয়ে খালি হাতে তিনটা syscall করা একটা প্রোগ্রাম বানাব।
- 04Process-এর শরীর — task_struct থেকে address space পর্যন্তগত লেসনে দেখেছি syscall কীভাবে kernel-এ ঢোকে, কিন্তু এড়িয়ে গেছি একটা প্রশ্ন — kernel 'আপনার process' বলতে ঠিক কী বোঝে? এই লেসনে সেই প্রশ্নের উত্তর দুই দিক থেকে দেব। প্রথমে kernel-এর দৃষ্টিতে — `task_struct` নামের ~৭ KB-র একটা struct, যেখানে PID থেকে page table-এর ঠিকানা পর্যন্ত সবকিছু থাকে। তারপর process-এর নিজের দৃষ্টিতে — তার virtual address space, যেখানে code, data, heap আর stack নিজেদের জায়গা করে নেয়। শেষে দেখব process কীভাবে অবস্থা বদলায়, আর কেন কিছু অবস্থা থেকে ফেরা অসম্ভব।
- 05fork, exec, wait — যে function দুইবার return করেগত লেসনে প্রশ্ন রেখে গিয়েছিলাম — একটা নতুন process কোথা থেকে আসে? উত্তর একটা অদ্ভুত function-এ, যেটা একবার ডাকা হয় কিন্তু দুইবার return করে। এই লেসনে আমরা দেখব fork() কীভাবে একটা প্রায়-অভিন্ন কপি তৈরি করে অথচ পুরো address space কপি করে না (copy-on-write), exec() কীভাবে সেই কপির ভেতরে সম্পূর্ণ নতুন একটা প্রোগ্রাম বসিয়ে দেয়, আর wait() কীভাবে parent-কে child-এর মৃত্যুর খবর পৌঁছে দেয় — আর কী হয় যখন সেই খবর কখনো নেওয়া হয় না।
- 06Threads — একই address space-এ একাধিক execution contextThread হলো একই address space-এর ভেতরে একাধিক স্বাধীন execution context — নিজের stack আর register আছে, কিন্তু heap, global আর file descriptor সবার সাথে ভাগাভাগি। Linux-এ এই ধারণাটা আলাদা কোনো object না: thread আর process দুটোই একই `task_struct`, শুধু `clone()`-এ কোন flag পাস হয়েছে তাতেই পার্থক্য। এই লেসনে শেয়ার-বনাম-প্রাইভেট বিভাজন, clone flag-এর প্রকৃত অর্থ, TLS-এর জন্য `fs` register-এর ব্যবহার, আর 1:1 বনাম M:N model-এর ইতিহাস — সবকিছু চালানো-যায় এমন কোড দিয়ে প্রমাণ করে দেখা হবে।
- 07Context Switching — যা সংরক্ষিত হয়, আর যার প্রকৃত দামগত লেসনে দুইটা সংখ্যা ব্যবহার করেছি প্রমাণ ছাড়াই — thread switch-এর খরচ '~১-৩ μs', আর একই thread group-এ switch সস্তা কারণ 'CR3 বদলায় না'। এই লেসনে সেই দাবিগুলো প্রমাণ করব — ঠিক কোন বাইটগুলো সংরক্ষিত হয়, সরাসরি খরচ কত, আর তার চেয়েও বড় পরোক্ষ খরচ (cache, TLB, branch predictor) কত। শেষে নিজের মেশিনে মেপে দেখব।
- 08CPU Scheduling — কে চলবে, কতক্ষণ, আর কোন মূল্যেগত লেসনে দেখেছি switch করাটা ব্যয়বহুল — তবু scheduler ঘন ঘন switch করে, কারণ কখন আর কাকে CPU দেওয়া হবে সেই সিদ্ধান্তটাই ব্যবহারকারীর অভিজ্ঞতা নির্ধারণ করে। এই লেসনে classic scheduling policy — FIFO, SJF, Round Robin, priority — হাতে-কলমে তুলনা করব, দেখব turnaround আর response time কীভাবে একে অপরের বিরুদ্ধে টানাটানি করে, আর Mars Pathfinder-এর প্রায়-বিপর্যয় দিয়ে বুঝব কেন শুধু priority scheduling যথেষ্ট নয়।
- 09Virtual Memory পরিচিতি — এক প্রোগ্রাম, এক বিভ্রমগত লেসনের শেষে একটা প্রশ্ন ঝুলিয়ে রাখা হয়েছিল — প্রতিটা process কীভাবে ভাবে সে পুরো machine-এর একমাত্র মালিক? এই লেসনে virtual memory-র মূল প্রতিশ্রুতি তিনটা — isolation, বেশি memory-র বিভ্রম, আর flexibility — ধারণাগতভাবে বোঝা, আর সেই বিভ্রম তৈরি করা হার্ডওয়্যার ইউনিট MMU-র সাথে পরিচিত হওয়া। প্রতিটা memory access কীভাবে ঠিক অনুবাদিত হয় তার বিস্তারিত যন্ত্রপাতি (page table) পরের লেসনের বিষয়।
- 10Paging আর Page Table — ভার্চুয়াল ঠিকানাকে সত্যিকারের RAM-এ অনুবাদ করার যন্ত্রVirtual address কীভাবে সত্যিকারের DRAM-এর ঠিকানায় পরিণত হয় — তার পুরো যন্ত্রপাতি। x86-64-এ একটা ৪৮-বিট ঠিকানা ভেঙে যায় চারটা ৯-বিট index আর একটা ১২-বিট offset-এ, আর MMU সেই index ধরে চারটা table-এর মধ্য দিয়ে হেঁটে (PML4 → PDPT → PD → PT) শেষ physical frame-এ পৌঁছায়। কেন এতগুলো স্তর, প্রতিটা table entry-র বিটগুলো কী করে, huge page কী বাঁচায় — সব হাতে-কলমে, /proc/self/pagemap দিয়ে যাচাইযোগ্য।
- 11TLB — Page Walk-এর খরচ থেকে বাঁচার ক্যাশগত লেসনে দেখা চার-ধাপ page walk প্রতিটা মেমরি অ্যাক্সেসকে পাঁচ গুণ ব্যয়বহুল করে দিত — বাস্তবে তা হয় না, কারণ CPU-র ভেতরে একটা ছোট, অত্যন্ত দ্রুত associative cache (TLB) সাম্প্রতিক অনুবাদগুলো মনে রাখে। এই লেসনে TLB-র স্তরবিন্যাস (L1 iTLB/dTLB, L2 STLB), TLB reach-এর গণিত, বহু-core সিস্টেমে shootdown-এর ব্যয়, আর PCID কীভাবে context switch-কে সস্তা রাখে — সব হাতে-কলমে perf আর একটা নিজের-লেখা stride benchmark দিয়ে যাচাইযোগ্য।
- 12Page Fault ও Demand Paging — OS-এর সবচেয়ে গুরুত্বপূর্ণ hookগত লেসনের শেষে একটা লাইন খোলা রেখে আসা হয়েছিল — page walk শেষে P=0 পাওয়া গেলে কী হয়? এই লেসনে সেই প্রশ্নের উত্তর: page fault handler-এর সিদ্ধান্ত-বৃক্ষ, minor বনাম major fault-এর নাটকীয় খরচ-পার্থক্য, demand paging আর copy-on-write PTE-স্তরে, আর Linux-এর page replacement কীভাবে LRU optimal-না-হওয়ার তাত্ত্বিক সীমা মেনেও কাজ চালায় — সব হাতে-কলমে perf, /usr/bin/time, cgroup আর নিজের-লেখা একটা SIGSEGV হ্যান্ডলার দিয়ে যাচাইযোগ্য।
- 13Memory Allocation — malloc আসলে কী করেগত লেসনে দেখা গিয়েছিল demand paging কীভাবে mmap()-কে lazy রাখে — কিন্তু userspace-এ malloc(100) ডাকলে ঠিক কোন পথে সেই মেমরি আসে? এই লেসনে brk/sbrk বনাম mmap-এর সিদ্ধান্ত, glibc-র ptmalloc2-র ভেতরের গঠন (chunk, bin, arena, coalescing), আর fragmentation কেন long-running প্রসেসের RSS-কে চিরকাল উঁচু রাখে — সব strace আর নিজের-লেখা একটা ছোট bump allocator দিয়ে হাতে-কলমে দেখব।
- 14Stack বনাম Heap — memory-র দুই সম্পূর্ণ ভিন্ন দর্শনগত লেসনে আমরা heap-এর ভেতরের জগৎ দেখেছি — chunk, bin, arena, brk বনাম mmap। এই লেসনে stack-কে সরাসরি পাশে রেখে তুলনা করব: কেন stack allocation প্রায় বিনামূল্যে অথচ heap-এর প্রতিটা allocation-এ বই রাখতে হয়, কীভাবে guard page একটা runaway recursion-কে একটা নিয়ন্ত্রিত crash-এ পরিণত করে, alloca/VLA কেন বিপজ্জনক, আর কীভাবে sigaltstack দিয়ে stack overflow-কেই catch করা যায়।
- 15File Descriptor — একটা ছোট সংখ্যার পেছনে তিন স্তরের টেবিল`open()` একটা ছোট অ-ঋণাত্মক সংখ্যা ফেরত দেয় — কিন্তু সেই সংখ্যার পেছনে কার্নেলে তিনটা আলাদা টেবিল আছে, আর প্রায় প্রতিটা fd-সংক্রান্ত বিভ্রান্তির উৎস এই তিন স্তরকে এক করে ফেলা। এই লেসনে সেই তিন স্তর সঠিকভাবে আঁকা হবে, তারপর তার সরাসরি ফলাফল দেখা হবে: dup আর দুইবার open কেন ভিন্ন আচরণ করে, O_APPEND কেন race ঠেকায়, fd কীভাবে exec-এর ওপারে leak হয়, আর EMFILE কখন আসে।
- 16Inode ও Filesystem গঠন — যেখানে ফাইলের নাম-ই থাকে নাfd-র তৃতীয় স্তর ছিল inode, কিন্তু ভেতরটা তখন খোলা হয়নি। এই লেসনে ডিস্কের প্রকৃত বিন্যাস দেখা হবে -- superblock থেকে data block পর্যন্ত -- আর সবচেয়ে গুরুত্বপূর্ণ আবিষ্কারটা করা হবে: inode-এ ফাইলের নাম থাকে না, নামটা থাকে directory নামের একটা সাধারণ ফাইলে। এই একটা সিদ্ধান্ত থেকেই hard link, rm-এর প্রকৃত অর্থ (unlink), আর ব্লক-অ্যাড্রেসিং-এর পুরো ডিজাইন বেরিয়ে আসে।
- 17ext4 আর Journaling — ক্র্যাশ হলে কী বাঁচে, কী হারায়একটা ফাইলে ডেটা append করা কার্নেলের কাছে একটা atomic অপারেশন মনে হয়, কিন্তু আসলে তিনটা আলাদা ডিস্ক-write -- inode size, block bitmap, data block। মাঝপথে power গেলে এই তিনটা অসামঞ্জস্যপূর্ণ অবস্থায় থেকে যেতে পারে। এই লেসনে সেই সমস্যাটা সংখ্যা দিয়ে দেখা হবে, journaling কীভাবে সমাধান করে তা বোঝা হবে, আর write()/fsync()-এর প্রকৃত durability contract সম্পূর্ণ খোলা হবে -- সহ ২০০৯ সালের সেই বিখ্যাত ext4 ডেটা-হারানোর গল্প যেটা আজকের delayed-allocation নিয়মগুলো তৈরি করেছে।
- 18Virtual Filesystem Switch — কেন একটাই read() ext4, NFS, tmpfs আর FUSE-এ সমানভাবে কাজ করেএকটা read() syscall যেভাবে ext4-এ কাজ করে, ঠিক সেভাবেই NFS, tmpfs বা /proc-এ কাজ করে -- অ্যাপ্লিকেশন কোড কখনো জানেই না নিচে কোন filesystem চলছে। এই জাদুটার নাম Virtual Filesystem Switch (VFS): superblock, inode, dentry, আর file -- এই চারটা object আর তাদের function-pointer struct দিয়ে C ভাষায় বাস্তবায়িত একটা polymorphic interface। এই লেসনে dentry cache-এর গতি, mount tree-র গঠন, pseudo-filesystem-এর কৌশল, আর FUSE-এর userspace round-trip খরচ -- সব একসাথে দেখব।
- 19I/O মডেল — blocking থেকে epoll পর্যন্ত, আর C10K সমস্যা যেটা এই পুরো ইতিহাসটা চালিয়েছেপ্রতিটা I/O অপারেশনে একটা মৌলিক প্রশ্ন লুকিয়ে থাকে -- ডেটা প্রস্তুত না থাকলে প্রক্রিয়াটার কী হবে? এই একটা প্রশ্নের পাঁচটা ভিন্ন উত্তর থেকে পাঁচটা I/O model তৈরি হয়েছে, আর প্রতিটার জন্ম হয়েছে আগেরটার একটা নির্দিষ্ট সীমাবদ্ধতা ভাঙতে গিয়ে। thread-per-connection মেমরি আর context-switch-এ ডুবে যায়; select 1024 fd-তে আটকে যায় আর প্রতি কলে O(n) স্ক্যান করে; poll সীমা তোলে কিন্তু O(n) থেকেই যায়; epoll কার্নেল-পাশে একটা registered interest list রেখে O(1) readiness দেয় -- আর এটাই ছিল C10K সমস্যার আসল সমাধান। এই লেসনে পাঁচটা মডেল, তিনটা বাস্তবায়ন, আর একটা প্রোডাকশন-গ্রেড echo server।
- 20io_uring — যখন syscall নিজেই bottleneck, তখন ring buffer দিয়ে kernel-এর সাথে কথা বলাepoll আপনাকে বলে 'fd এখন প্রস্তুত' — কিন্তু আসল I/O করতে তবুও একটা syscall লাগে, আর regular file-এ সেটা কখনোই কাজ করেনি কারণ regular file সবসময় 'প্রস্তুত'। io_uring এই সমস্যাটাকে উল্টো দিক থেকে ধরে: userspace আর kernel দুইটা lock-free ring buffer শেয়ার করে, আপনি সেখানে কাজের অনুরোধ (SQE) লিখে রাখেন আর ফলাফল (CQE) তুলে নেন — steady state-এ syscall সংখ্যা শূন্যে নামানো সম্ভব। এই লেসনে ring-এর প্রকৃত মেকানিক্স, liburing দিয়ে একটা batch-read প্রোগ্রাম, আর io_uring-এর কুখ্যাত নিরাপত্তা-ইতিহাস সৎভাবে দেখব।
- 21Interrupt এবং Device Driver — hardware কীভাবে kernel-কে বাধা দেয় ঠিক সময়েগত লেসনের শেষে একটা প্রশ্ন ঝুলে ছিল -- io_uring-এ একটা read জমা দেওয়ার পর disk থেকে ডেটা এলে kernel সেটা কীভাবে জানে? উত্তর: interrupt, একটা hardware সংকেত যা CPU-কে যা করছিল তা মাঝপথে থামিয়ে একটা নির্দিষ্ট handler-এ পাঠায়। এই লেসনে সেই পুরো পথটা device থেকে IDT পর্যন্ত খুলে দেখব, top half/bottom half বিভাজনের যুক্তি বুঝব, আর নিজের হাতে একটা character device driver লিখে kernel-এ লোড করব।
- 22Pipe এবং FIFO — দুইটা process-এর মধ্যে সবচেয়ে পুরনো IPC, যেটা এখনো সবচেয়ে বেশি ব্যবহৃত`ls | grep foo | wc -l` -- প্রতিদিন টাইপ করা এই একটা লাইনের পেছনে একটা সরল কিন্তু কঠোর কাঠামো আছে: kernel-মেমরিতে একটা ৬৪ KB ring buffer, দুইটা fd (read end, write end), আর একটা কড়া নিয়ম -- সব write-end বন্ধ না হওয়া পর্যন্ত reader কখনো EOF পায় না। এই লেসনে সেই buffer-এর প্রকৃত আচরণ, শেলের wiring-এর নিচের প্রক্রিয়া, SIGPIPE/EPIPE, PIPE_BUF-এর atomicity গ্যারান্টি, আর সবচেয়ে গুরুত্বপূর্ণ -- কেন একটা পূর্ণ pipe-এ block হয়ে যাওয়াটা আসলে একটা ফিচার -- দেখব।
- 23Shared Memory এবং Message Queue — কপি বাদ দিয়ে সবচেয়ে দ্রুত IPC, আর সেই গতির লুকানো মূল্যPipe-এ প্রতিটা বাইট দুইবার kernel অতিক্রম করে। Shared memory এই কপি সম্পূর্ণ বাদ দেয় -- দুইটা process-এর page table-এর একটা entry একই physical frame নির্দেশ করে, তাই read/write মানে শুধু normal load/store, কোনো syscall না। এই লেসনে memfd_create + mmap (আধুনিক), POSIX shm_open (পোর্টেবল), আর System V shmget (legacy) তিনটাই দেখব, একটা কাজ-করা shared ring buffer বানাব -- আর সততার সাথে স্বীকার করব যে multi-writer race condition-টা এখনো খোলা, কারণ সমাধানের যন্ত্রপাতি (mutex, condition variable) এখনো হাতে নেই। তারপর message queue দেখব -- kernel-মধ্যস্থতা রেখে দেওয়া একটা মধ্যপন্থা, যা synchronization আর message boundary দুটোই বিনামূল্যে দেয়।
- 24Signal — process-কে asynchronous ভাবে বাধা দেওয়ার প্রোটোকলCtrl-C চাপলে, kill কমান্ড দিলে, বা একটা প্রোগ্রাম ভুল মেমরিতে হাত দিলে — kernel সেই process-কে একটা signal পাঠায়, যা ঠিক hardware interrupt-এর মতোই normal control flow মাঝপথে থামিয়ে একটা handler চালায়। এই লেসনে standard signal set, default action, নিজের handler লেখা, আর সবচেয়ে বিপজ্জনক অংশ — handler-এর ভেতরে ঠিক কী করা নিরাপদ (async-signal-safety) — দেখব, আর কেন malloc() নিজেই সেখানে বিপজ্জনক তা কংক্রিটভাবে দেখব।
- 25Synchronization Primitives — mutex, semaphore, condition variable: কোনটা ঠিক কীসের জন্যএকাধিক thread একই ডেটায় হাত দিলে কী ভাঙে, আর সেই ভাঙা ঠেকানোর যন্ত্রপাতিগুলো ঠিক কী কী — mutex (মালিকানা আছে), semaphore (শুধু একটা counter, মালিকানা নেই), condition variable (সবসময় mutex-এর সঙ্গে, সবসময় while loop-এ), read-write lock, barrier, আর spinlock। প্রতিটার জন্য কোন সমস্যাটা এটা সমাধান করে আর কোনটা করে না — সেটাই এই লেসনের কেন্দ্র। শেষে mutex + দুইটা condvar দিয়ে একটা সঠিক bounded blocking queue বানিয়ে লেসন ২৪-এর অসমাপ্ত ring buffer শেষ করব।
- 26Race Condition ও Deadlock — যখন সমান্তরালতা ভুল হয়ে যায়গত লেসনে দুইটা প্রশ্ন ইচ্ছাকৃতভাবে খোলা রাখা হয়েছিল — counter++ ঠিক কীভাবে হারায়, আর কেন একাধিক lock বিপজ্জনক। এই লেসনে দুইটাই সমাধান করব। প্রথমে দেখব data race কেন শুধু 'ভুল উত্তর' নয়, বরং compiler-কে অদ্ভুত আচরণ করার অনুমতি দেওয়া একটা undefined behaviour। তারপর deadlock-কে দেখব ঠিক যেভাবে Level 0-তে শিখেছি — একটা graph-এর cycle — আর দেখব কেন lock-এর উপর একটা total order চাপালেই সেই cycle কাঠামোগতভাবে অসম্ভব হয়ে যায়।
- 27Futex — কীভাবে একটা mutex আসলে বানানো হয়গত লেসন পর্যন্ত mutex ছিল একটা black box — ডাকলাম, ব্লক হলাম, জাগলাম। আজ সেই box খুলে দেখব। শুরু হবে hardware-এর atomic instruction থেকে, তারপর একটা spinlock হাতে লিখব, আর দেখব কেন সেটা প্রায় কখনোই userspace-এ সঠিক পছন্দ নয়। কেন্দ্রে থাকবে futex — Linux-এর সেই চতুর নকশা যা নিশ্চিত করে uncontended lock/unlock-এ কখনো kernel-এ ঢুকতেই হয় না — আর একটা সম্পূর্ণ mutex আমরা শূন্য থেকে বানাব, raw futex syscall দিয়ে।
- 28Namespace ও Cgroups — একটা 'container' আসলে কীগত লেসনের শেষে একটা প্রতিশ্রুতি দেওয়া হয়েছিল — একটা process-কে সম্পূর্ণ বিভ্রম দেওয়া যায় যে সে একাই মেশিনে আছে, যদিও শত শত অন্য process একই kernel শেয়ার করছে। আজ সেই বিভ্রম কীভাবে বানানো হয় তা দেখব — namespace (কী দেখা যায়) আর cgroups (কতটুকু ব্যবহার করা যায়), দুইটা সম্পূর্ণ ভিন্ন, orthogonal kernel mechanism। শেষে raw clone()/unshare() আর /sys/fs/cgroup দিয়ে Docker ছাড়াই একটা বাস্তব, ন্যূনতম container বানাব — আর প্রমাণ করব কেন kernel-এ আসলে 'container' নামে কিছুই নেই।
- 29/proc আর /sys — Kernel-এর ভেতরটা যখন শুধুই একটা ফাইল পড়াএই পুরো module জুড়ে আমরা বারবার /proc ব্যবহার করেছি — VmRSS পড়েছি, utime/stime পড়েছি, /proc/interrupts দেখেছি, /proc/PID/task/ ঘেঁটেছি — কিন্তু কখনো থেমে জিজ্ঞাসা করিনি এটা আসলে কী জিনিস। এই শেষ লেসনে কোনো নতুন mechanism নেই — শুধু পুরো module-এ যা বানানো হয়েছে (process, thread, memory, fd, namespace, cgroup) সেটাকে এখন সরাসরি, লাইভ চোখে দেখা, আর ps/top/lsof/ss-এর মতো টুল যে কোনো জাদু না — শুধু ফরম্যাট-করা ফাইল-পড়া — সেটা নিজের হাতে প্রমাণ করা।
যা যা থাকছে
- OS কী, কেন দরকার — bare metal থেকে kernel
- Kernel space vs user space, privilege rings
- System calls — mechanism ও cost
- Process — address space, PCB, lifecycle
- fork, exec, wait, exit
- Threads — kernel vs user threads
- Context switching-এর প্রকৃত খরচ
- CPU scheduling — FIFO, RR, MLFQ, CFS/EEVDF
- Virtual memory ও address translation
- Paging, page tables, multi-level page tables
- TLB ও TLB miss
- Page fault handling, demand paging, swapping
- Memory allocation — brk, mmap, malloc internals
- Stack vs heap
- File systems — inodes, directories, journaling
- ext4, VFS layer
- File descriptors ও open file table
- I/O — blocking, non-blocking, epoll, io_uring
- Interrupts ও device drivers
- IPC — pipes, FIFOs, shared memory, message queues
- Signals
- Synchronization — mutex, semaphore, condition variable
- Race conditions ও deadlock
- Futex ও lock implementation
- Namespaces ও cgroups
- /proc ও /sys দিয়ে kernel introspection
প্রজেক্ট
Build a Shell
●●●○○fork/exec/pipe/redirect/job control সহ নিজের shell।
Memory Allocator
●●●●○mmap-এর উপর নিজের malloc/free — free list, coalescing।
Scheduler Simulator
●●●○○FIFO/RR/MLFQ তুলনা করে turnaround ও response time মাপা।
Unix Tools from Scratch
●●○○○cat, ls, wc, grep, head — syscall দিয়ে সরাসরি।
Toy Kernel
●●●●●Bootloader → protected mode → interrupt → simple scheduler।