এই পর্বে যা শিখবেন
- একটি
read()কীভাবে file system-এর স্তরগুলো পেরিয়ে ডিস্কের একটি sector-এ পৌঁছায় - ডিস্কে আর মেমরিতে file system কোন কোন কাঠামো রাখে, আর কেন
- Contiguous, linked, FAT আর indexed allocation — সুবিধা, অসুবিধা আর disk access গোনার নিয়ম
- Unix inode-এর সর্বোচ্চ ফাইল-আকার, আর byte offset X পড়তে কতগুলো disk read লাগে — পরীক্ষার খাতার মতো সম্পূর্ণ হিসাব
- Bitmap, linked list, grouping, counting আর space map দিয়ে free space; block size, cache আর লেখার নীতি দিয়ে গতি; ext4, FAT32, NTFS আর exFAT-এর ভেতরের চেহারা
১ · ডিস্ক শুধু block-এর সারি — বাকি সব file system-এর হিসাব
পর্ব ৪০-এ দেখেছেন, ডিস্ক (HDD হোক বা SSD) নিজে শুধু বোঝে নম্বর দেওয়া sector-এর লম্বা সারি: “sector ১১৭০৪ পড়ো” বা “sector ৫০০-তে এই ৫১২ byte লেখো”, ব্যস। ফাইলের নাম, ফোল্ডার, আকার, অনুমতি — এসবের কোনো ধারণা ডিস্কের নেই। পর্ব ৪১ আর ৪২-এ আপনি ব্যবহারকারীর চোখে ফাইল আর directory দেখেছেন। এই পর্বে প্রশ্ন উল্টো দিক থেকে: OS কীভাবে ওই সাদামাটা সারির ওপর ফাইলের পুরো জগৎ বানায়?
File system সাধারণত sector একটি একটি করে নয়, কয়েকটি sector মিলিয়ে একটি block (Windows-এর ভাষায় cluster) হিসেবে কাজ করে — আজকাল সাধারণত ৪ KiB, অর্থাৎ ৮টি ৫১২-byte sector. তারপর তাকে চারটি প্রশ্নের উত্তর রাখতে হয়:
- কোন block কোন ফাইলের, কোন ক্রমে? — allocation (অংশ ৩–৬)।
- কোন block এখনো খালি? — free-space management (অংশ ৭)।
- ফাইলের নাম, আকার, মালিক, অনুমতি কোথায়? — inode বা FCB আর directory (অংশ ২; directory-র বিস্তার পর্ব ৪২-এ)।
- এত কিছু করেও গতি কীভাবে রাখা যায়? — cache, read-ahead, block size (অংশ ৮)।
অফিসের ফাইল ক্যাবিনেটে হাজারটি নম্বর দেওয়া ড্রয়ার — এ-ই ডিস্কের block. নম্বর দেখে কেউ জানবে না কোনটিতে “বেতন-হিসাব”। তাই একজন রেকর্ড-কিপারের রেজিস্টার খাতা: কোন নথি কোন ড্রয়ারে, কোন ক্রমে, কে দেখতে পারবে, কোন ড্রয়ার খালি। File system হলো সেই রেকর্ড-কিপার আর খাতা — খাতাটিও ক্যাবিনেটেরই কয়েকটি ড্রয়ারে রাখা।
স্তরে স্তরে নকশা — layered file system
এত কাজ এক টুকরো কোডে লিখলে তা বোঝা, বদলানো আর নতুন ডিভাইসে চালানো কঠিন হতো। তাই পাঠ্যবই (Silberschatz) file system-কে কয়েকটি স্তরে ভাগ করে দেখায় — layered file system. প্রতিটি স্তর শুধু নিচের স্তরের সেবা ব্যবহার করে, আর ওপরের স্তরকে একটু বেশি বিমূর্ত সেবা দেয়:
| স্তর (ওপর থেকে নিচে) | কী জানে, কী করে | বাস্তব উদাহরণ (Linux) |
|---|---|---|
| Application program | ফাইলের নাম আর fd চেনে; open, read, write ডাকে | আপনার প্রোগ্রাম, cat |
| Logical file system | metadata সামলায়: directory, FCB/inode, অনুমতি পরীক্ষা, fd আর file offset | VFS + ext4-এর directory ও inode কোড |
| File-organisation module | ফাইলের logical block (০, ১, ২…) → ডিস্কের physical block; free-space manager-ও এখানে | ext4-এর extent কোড, block allocator |
| Basic file system | “ডিভাইস X-এর block Y পড়ো/লেখো” — সাধারণ কমান্ড; buffer আর cache সামলায় | page cache, block layer, I/O scheduler |
| I/O control | device driver আর interrupt handler; block নম্বরকে controller-এর কমান্ডে বদলায় | NVMe, SATA (AHCI) driver |
| Devices | আসল ডিস্ক বা SSD আর তার controller | NVMe SSD, হার্ড ডিস্ক |
লাভ: নিচের স্তরগুলো সব file system ভাগ করে নেয় — ext4, FAT বা NTFS যা-ই হোক, “block ১২০৭ পড়ো” একই পথে যায়। দাম: প্রতিটি স্তরে কিছু বাড়তি সময়। বাস্তব kernel এই ভাগ হুবহু মানে না, তবে ভাবনার কাঠামো হিসেবে এটি কাজের; পর্ব ৪২-এর VFS logical স্তরের ওপরে বসে সব file system-কে একই interface দেয়। নিচে একটি read() স্তরগুলো পেরিয়ে নামছে।
- Application
read(fd)byte আর fd - Logical FS
inodemetadata - File-org
block 2→1207logical → physical - Basic FS
cache?block পড়ার কমান্ড - I/O control
LBAdriver - Device
DMAsector পড়া
n = read(fd, buf, 4096); /* fd = 3, file offset now 8192 */ /* no file name, no disk, no block number here */
fd 3 -> per-process table -> open-file entry open-file entry: inode 1442, offset 8192, mode read (permission was checked once, at open time) logical block = 8192 / 4096 = 2
inode 1442 (already in memory): direct[0] = 1200 direct[1] = 1201 direct[2] = 1207 <- logical block 2 physical block = 1207
buffer/page cache: (sda2, block 1207)? -> miss allocate a 4096-byte buffer queue request: READ sda2 block 1207
1 block = 8 sectors of 512 B sector in partition = 1207 x 8 = 9656 partition sda2 starts at LBA 2048 LBA = 2048 + 9656 = 11704, count = 8 -> command to the controller, DMA into buffer
controller reads LBA 11704..11711, DMA -> buffer interrupt -> driver marks request done cache keeps block 1207 for next time copy 4096 bytes to buf, offset = 12288 read() returns 4096
- ধাপ ১: Application. প্রোগ্রাম শুধু চায় fd ৩ থেকে ৪০৯৬ byte. ফাইলের নাম, ডিস্ক বা block — কিছুই এখানে নেই; এই মুহূর্তে file offset ৮১৯২।
- ধাপ ২: Logical file system. fd থেকে open-file table হয়ে inode ১৪৪২ আর offset ৮১৯২ পাওয়া গেল; অনুমতি open-এর সময়েই দেখা হয়েছিল। Logical block = ৮১৯২ ÷ ৪০৯৬ = ২।
- ধাপ ৩: File-organisation module. মেমরিতে থাকা inode-এর direct[২] pointer বলছে, ফাইলের block ২ আছে ডিস্কের block ১২০৭-এ।
- ধাপ ৪: Basic file system. আগে cache-এ খোঁজা — নেই (miss). তাই একটি buffer নিয়ে “sda2-এর block ১২০৭ পড়ো” অনুরোধ queue-তে গেল।
- ধাপ ৫: I/O control. Driver হিসাব করে: ১২০৭ × ৮ = ৯৬৫৬ নম্বর sector partition-এর ভেতরে; partition শুরু LBA ২০৪৮-এ, তাই LBA ১১৭০৪ থেকে ৮টি sector. Controller-কে কমান্ড, DMA-র ব্যবস্থা।
- ধাপ ৬: Device. Controller sector পড়ে DMA দিয়ে buffer-এ রাখে, interrupt দেয়। Block cache-এ থেকে যায়, ৪০৯৬ byte প্রোগ্রামের buf-এ কপি হয়, offset হয় ১২২৮৮, আর read() ফেরত দেয় ৪০৯৬।
ধাপ ৫-এর LBA, DMA আর interrupt পর্ব ৩৯-এর বিষয়; ধাপ ২-এর open-file table পর্ব ৪১-এর। এই পর্বের মূল জায়গা ধাপ ৩: ফাইলের block ২ ডিস্কের ঠিক কোথায় — সেই হিসাব কীভাবে রাখা হয়।
২ · ডিস্কের ওপর আর মেমরিতে — file system-এর কাঠামো
ডিস্কে যা থাকে (on-disk structures)
একটি volume (একটি partition-এ বানানো একটি file system) ফরম্যাট করার সময়, অর্থাৎ Linux-এ mkfs বা Windows-এ Format চালানোর সময়, ডিস্কে কয়েকটি স্থায়ী কাঠামো লেখা হয়। নাম file system-ভেদে আলাদা, কাজ প্রায় একই:
Boot block
Volume-এর শুরুতে; এই volume থেকে বুট করার কোড (পর্ব ৭), বুটযোগ্য না হলে খালি থাকতে পারে।
boot control blockSuperblock
পুরো volume-এর হিসাব: block-এর আকার আর মোট সংখ্যা, খালি block-এর সংখ্যা, inode-এর মোট ও খালি সংখ্যা, free-space কাঠামো কোথায়। এটি নষ্ট হলে পুরো volume পড়া যায় না — তাই ext4 এর কয়েকটি কপি রাখে।
volume control blockInode table (FCB)
প্রতিটি ফাইলের একটি পরিচয়পত্র: ধরন, অনুমতি, মালিক, আকার, সময়, link-সংখ্যা আর data block-এর pointer. Unix-এ inode, NTFS-এ MFT-এর একটি record. ext4-এ inode-এর মোট সংখ্যা ফরম্যাটের সময়েই ঠিক হয়।
file control blockData blocks
বাকি সব জায়গা: ফাইলের ডেটা আর directory-র ডেটা (নাম → inode নম্বর)। কোনটি খালি, জানায় free-space কাঠামো (অংশ ৭)।
ডেটা আর directoryএকটি কথা প্রায়ই অবাক করে: ফাইলের নাম inode-এ থাকে না। নাম থাকে directory-র ভেতরে, “নাম → inode নম্বর” জোড়া হিসেবে। তাই একই inode-এর দুটি নাম (hard link) থাকতে পারে — পর্ব ৪২-এর বিষয়। Inode-এ যা থাকে, তা stat কমান্ড দেখায় (অংশ ৯-এ চালাবেন)। Windows-এর NTFS-এ ব্যাপারটি একটু আলাদা — ফাইলের নাম MFT record-এর একটি attribute হিসেবেও রাখা হয়।
মেমরিতে যা থাকে (in-memory structures)
প্রতিবার ডিস্ক থেকে এসব পড়লে সিস্টেম খুব ধীর হতো, তাই kernel মেমরিতে কয়েকটি কাঠামো রাখে; mount-এর সময় তৈরি হয়, unmount-এ মুছে যায়:
- Mount table: কোন volume কোন directory-তে বসানো (mount), কোন file system-এর ধরন — Linux-এ
/proc/mountsএর একটি জানালা। - Directory-structure cache: সম্প্রতি খোঁজা path-এর অংশ → inode; Linux-এ dentry cache. প্রতিবার
/home/rahim/notes.txtখুলতে চারটি directory ডিস্ক থেকে পড়তে হয় না। - System-wide open-file table: প্রতিটি খোলা ফাইলের জন্য একটি entry — মেমরিতে আনা inode-এর কপি, file offset ইত্যাদি (Linux-এ inode cache আর
struct file). - Per-process open-file table: প্রতিটি প্রসেসের fd → ওপরের table-এর entry. পর্ব ৪১-এ এর পুরো ছবি।
- Buffer cache: সম্প্রতি পড়া বা লেখা block-এর কপি — stepper-এর ধাপ ৪-এ যেখানে প্রথমে খোঁজা হয়েছিল।
নতুন ফাইল তৈরি তাহলে মোটামুটি এই: একটি খালি inode বরাদ্দ, directory-তে “নতুন নাম → এই inode” যোগ, তারপর দুটিই ডিস্কে লেখা। মাঝপথে বিদ্যুৎ গেলে কী হয় — সেই প্রশ্ন পর্ব ৪৪-এর।
| সাধারণ নাম | Unix/ext4 | NTFS | FAT32 |
|---|---|---|---|
| Boot control block | boot block | partition boot sector | boot sector (reserved region) |
| Volume control block | superblock + group descriptor | $MFT-এর কিছু system file ($Volume, $Bitmap…) | boot sector-এর BPB + FSInfo |
| প্রতি ফাইলের FCB | inode | MFT record | directory entry (৩২ byte) |
| Free space-এর হিসাব | block bitmap, inode bitmap | $Bitmap ফাইল | FAT-এর ০ মানের entry |
৩ · Contiguous allocation — পাশাপাশি সিট
প্রথম আর সবচেয়ে সরল পদ্ধতি: contiguous allocation — প্রতিটি ফাইল ডিস্কের পরপর block-এ থাকে। Directory entry-তে শুধু দুটি সংখ্যা লাগে: শুরুর block আর দৈর্ঘ্য। ধরুন mail ফাইলের শুরু ১৯, দৈর্ঘ্য ৬ — তাহলে সে block ১৯ থেকে ২৪ জুড়ে।
ফাইলের logical block i (০ থেকে গোনা) আছে ডিস্কের block (start + i)-এ। Byte offset X-এর জন্য i = ⌊X ÷ block size⌋. উদাহরণ: block ৪ KiB, start ১৯, X = ১০,০০০ → i = ⌊১০০০০ ÷ ৪০৯৬⌋ = ২ → ডিস্কের block ২১, block-এর ভেতরে byte ১০০০০ − ৮১৯২ = ১৮০৮.
সুবিধা: random access-এ একটি হিসাব আর একটি disk access; sequential পড়াও সবচেয়ে দ্রুত, কারণ হার্ড ডিস্কের মাথা প্রায় নড়ে না। তাই CD/DVD-র ISO 9660-এ ফাইল পরপর sector-এ থাকে — একবার লেখা মাধ্যম, ফাইল আর বাড়বে না।
দূরপাল্লার বাসে একটি পরিবার ছয়টি পাশাপাশি সিট চায়। শুরুতে সহজ — বাস খালি। কিন্তু দিনভর যাত্রী ওঠানামার পর বাসে ছড়ানো ছিটানো এক-দুটি ফাঁকা সিট, মোট ফাঁকা হয়তো দশটি, অথচ পাশাপাশি ছয়টি কোথাও নেই। আর যাত্রাপথে পরিবারের আরেকজন যোগ দিলে? পাশের সিটে অন্য যাত্রী — হয় সবাইকে উঠিয়ে অন্য সারিতে নিতে হবে, নয়তো জায়গা নেই।
ঠিক এই দুটি সমস্যা:
- External fragmentation: পর্ব ৩১-এর মেমরির variable partition-এর মতোই। ফাইল তৈরি-মোছার পর খালি জায়গা ছোট ছোট hole-এ ভাগ হয়ে যায়; মোট খালি অনেক হলেও বড় ফাইলের জায়গা হয় না। Hole বাছার নিয়মও একই — first fit, best fit, worst fit. প্রতিকার compaction — ডিস্কে যাকে defragmentation বলে, বড় ডিস্কে ঘণ্টার পর ঘণ্টা লাগতে পারে।
- আকার আগে জানতে হয়: জায়গা কম দিলে পরে বাড়ানো যায় না, বেশি দিলে অপচয়। একটি log ফাইল কত বড় হবে, কে জানে?
Extent — contiguous-এর আধুনিক রূপ
সমাধান হিসেবে অনেক file system একটি মিশ্র নকশা নেয়: ফাইল পুরোটা এক টানা জায়গায় না হয়ে কয়েকটি টানা অংশে থাকতে পারে। প্রতিটি টানা অংশের নাম extent, আর তার বর্ণনা তিনটি সংখ্যা — (ফাইলের কোন block থেকে, ডিস্কের কোন block-এ, কতগুলো)। ফাইল বাড়লে নতুন একটি extent যোগ হয়। ext4, XFS, NTFS (সেখানে নাম “data run”), Btrfs, APFS — আধুনিক প্রায় সব file system extent ব্যবহার করে (অংশ ৫-এর শেষে ext4). Fragmentation পুরো যায় না — extent যত বেশি, ফাইল তত ছড়ানো — তবে অনেক কমে।
Disk access গোনার নিয়ম (পরীক্ষার জন্য): প্রশ্নে সাধারণত ধরা হয়, ফাইলের FCB/directory entry আগে থেকেই মেমরিতে আছে, আর প্রতিটি block পড়া = একটি disk access. Contiguous-এ block i পড়তে ১টি. প্রশ্নে FCB মেমরিতে না থাকলে সব পদ্ধতিতেই আরও ১টি যোগ হয়। পরের দুই অংশে একই নিয়মে linked আর indexed গুনব।
৪ · Linked allocation আর FAT — block-এ block-এ চিরকুট
Contiguous-এর দুই সমস্যা — fragmentation আর আগে থেকে আকার জানা — একসাথে মুছে দেয় linked allocation: ফাইলের block ডিস্কের যেকোনো জায়গায় থাকতে পারে, আর প্রতিটি block-এর শেষ কয়েকটি byte-এ লেখা থাকে পরের block-এর নম্বর — একটি linked list. Directory entry-তে শুধু প্রথম (আর সুবিধার জন্য শেষ) block-এর নম্বর। ফাইল বাড়াতে যেকোনো খালি block নিয়ে শেষ block-এর pointer-এ জুড়ে দিলেই হলো; কোনো খালি block কখনো অব্যবহৃত থাকে না, তাই external fragmentation নেই।
দাম তিনটি:
- Random access ধীর: block i-তে পৌঁছাতে আগে block ০ থেকে i−১ পড়ে pointer দেখতে হয় — block i পড়তে i + ১টি disk access (০ থেকে গুনলে)। Sequential পড়ায় এটি সমস্যা নয়।
- Pointer-এর জায়গা: ৫১২-byte block-এ ৪-byte pointer মানে প্রায় ০.৭৮% অপচয়। প্রতিকার: কয়েকটি block মিলিয়ে একটি cluster, প্রতি cluster-এ একটি pointer — ৪ block-এর cluster-এ প্রায় ০.২০%. দাম: বেশি internal fragmentation.
- নির্ভরযোগ্যতা: একটি pointer নষ্ট হলে chain-এর বাকি অংশ হারায়, বা chain অন্য ফাইলের block-এ ঢুকে পড়ে। আংশিক প্রতিকার: doubly linked list, বা প্রতিটি block-এ ফাইলের পরিচয় আর ক্রমিক নম্বর রাখা।
FAT — চিরকুটগুলো এক খাতায় জড়ো করা
১৯৭০-এর দশকের শেষ দিকে Microsoft-এর একটি BASIC-এর জন্য তৈরি আর পরে MS-DOS-এর মাধ্যমে জনপ্রিয় হওয়া FAT (File Allocation Table) linked allocation-এর একটি চতুর রূপ: pointer-গুলো data block-এর ভেতরে না রেখে volume-এর শুরুতে একটি আলাদা টেবিলে রাখা হয়। টেবিলে প্রতিটি cluster-এর জন্য একটি entry; entry i-এর মান = “cluster i-এর পরের cluster কোনটি”. বিশেষ মান: ০ = খালি cluster, একটি “end of chain” (EOC) চিহ্ন = ফাইলের শেষ cluster, আরেকটি চিহ্ন = নষ্ট (bad) cluster. Directory entry-তে থাকে ফাইলের নাম, আকার আর প্রথম cluster-এর নম্বর।
লাইব্রেরিতে একটি বহু খণ্ডের বই তাকে তাকে ছড়ানো। Linked allocation মানে প্রতিটি খণ্ডের শেষ পাতায় চিরকুট: “পরের খণ্ড ১৭ নম্বর তাকে”। পঞ্চম খণ্ড পড়তে চাইলে আগের চারটি খণ্ড তাক থেকে নামিয়ে চিরকুট পড়তে হবে। FAT মানে সেই সব চিরকুট খণ্ড থেকে সরিয়ে ঢোকার মুখে লাইব্রেরিয়ানের টেবিলের একটি রেজিস্টারে লেখা: “তাক ২-এর পরে তাক ৫, তাক ৫-এর পরে তাক ৩…”। পঞ্চম খণ্ড খুঁজতে এখন শুধু রেজিস্টারের কয়েকটি লাইন পড়া, তারপর সোজা সেই তাকে।
লাভ: FAT ছোট, তাই তার ব্যস্ত অংশ মেমরিতে (cache) রাখা যায়। তখন chain ধরে হাঁটা হয় মেমরিতে, আর ডিস্কে যেতে হয় একবার — আসল data cluster পড়তে। Cache-এ না থাকলে প্রতিটি ধাপে ডিস্কের মাথাকে volume-এর শুরুতে ফিরতে হয়। নির্ভরযোগ্যতার জন্য FAT সাধারণত দুটি কপিতে থাকে। নিচে একটি ১৬ entry-র FAT; cluster ৪ KiB.
| Cluster | ০, ১ | ২ | ৩ | ৪ | ৫ | ৬ | ৭ | ৮ | ৯ | ১০ | ১১ | ১২ | ১৩–১৫ |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| FAT-এর মান | সংরক্ষিত | ৫ | ৯ | ৭ | ৩ | খালি | ৮ | ১২ | EOC | খালি | BAD | EOC | খালি |
Directory-তে দুটি ফাইল: notes.txt (প্রথম cluster ২, আকার ১৩,০০০ byte) আর photo.jpg (প্রথম cluster ৪, আকার ১৫,০০০ byte). FAT-এর প্রথম দুটি entry (০ আর ১) data cluster নয়, তাই নম্বর ২ থেকে শুরু — আসল FAT-এও তা-ই। নিচের stepper-এ notes.txt-এর chain ধরে হাঁটা হচ্ছে।
1 c = dir["notes.txt"].start2 while c != EOC:3 visit(c); c = FAT[c]4 k = 9000 / 4096; hop k times5 f = first FREE; FAT[last] = f
| এখনকার cluster | |
| পাওয়া chain | |
| পড়া FAT entry |
- ধাপ ১: directory entry. notes.txt-এর প্রথম cluster ২, আকার ১৩,০০০ byte; ৪ KiB cluster-এ লাগবে ⌈১৩০০০ ÷ ৪০৯৬⌉ = ৪টি cluster. শুরু cluster ২ থেকে।
- ধাপ ২: FAT[২] = ৫. Cluster ২-এর পরে cluster ৫. Chain এখন ২ → ৫।
- ধাপ ৩: FAT[৫] = ৩. পরের cluster ৩ — ডিস্কে পেছনের দিকে, তাতে কিছু যায় আসে না। Chain ২ → ৫ → ৩।
- ধাপ ৪: FAT[৩] = ৯. Chain ২ → ৫ → ৩ → ৯।
- ধাপ ৫: FAT[৯] = EOC. Chain শেষ: ৪টি cluster, আকারের হিসাবের সাথে মেলে।
- ধাপ ৬: byte ৯০০০ কোথায়? ৯০০০ ÷ ৪০৯৬ = ২ (ভাগফল), তাই ফাইলের cluster নম্বর ২ (০ থেকে গুনে)। শুরু থেকে দুবার FAT দেখা: ২ → ৫ → ৩. উত্তর cluster ৩, তার ভেতরে byte ৮০৮. FAT মেমরিতে থাকলে ডিস্কে যেতে হয় একবারই।
- ধাপ ৭: একটি cluster জোড়া. প্রথম খালি entry FAT[৬]. শেষ cluster ৯-এর entry-তে ৬ লেখা হলো, আর FAT[৬]-এ EOC. ফাইল এখন ২ → ৫ → ৩ → ৯ → ৬; খালি রইল ৪টি cluster.
একই কাজ নিচের ছোট প্রোগ্রাম করে — দুটি ফাইলের chain, random access আর append:
// A tiny FAT: entry i says which cluster comes after cluster i.
const FREE = 0, EOC = -1, BAD = -7; // free, end of chain, bad cluster
const FAT = [null, null, 5, 9, 7, 3, FREE, 8, 12, EOC, FREE, BAD, EOC, FREE, FREE, FREE];
const CLUSTER = 4096;
const dir = [{ name: "notes.txt", start: 2, size: 13000 }, // directory entries
{ name: "photo.jpg", start: 4, size: 15000 }];
function chain(start) { // follow the FAT from the first cluster
const out = [];
for (let c = start; c !== EOC; c = FAT[c]) out.push(c);
return out;
}
for (const f of dir) {
const c = chain(f.start);
console.log(f.name.padEnd(10) + " start " + f.start + ": " + c.join(" -> ") + " -> EOC" +
" (" + c.length + " clusters, need " + Math.ceil(f.size / CLUSTER) + ")");
}
// Random access: which cluster holds byte 9000 of notes.txt?
const k = Math.floor(9000 / CLUSTER);
let c = dir[0].start;
for (let i = 0; i < k; i++) c = FAT[c];
console.log("notes.txt byte 9000 -> cluster #" + k + " of the file = cluster " + c + " (" + k + " FAT lookups)");
// Append one cluster to notes.txt: take the first free entry, link it at the end.
const free = FAT.indexOf(FREE), last = chain(dir[0].start).pop();
FAT[last] = free; FAT[free] = EOC;
console.log("append: FAT[" + last + "] = " + free + ", FAT[" + free + "] = EOC");
console.log("notes.txt now: " + chain(dir[0].start).join(" -> ") + " -> EOC");
console.log("free clusters left: " + FAT.filter((v) => v === FREE).length + ", bad: " + FAT.indexOf(BAD));আউটপুট (নির্ধারিত, প্রতিবার একই):
notes.txt start 2: 2 -> 5 -> 3 -> 9 -> EOC (4 clusters, need 4)
photo.jpg start 4: 4 -> 7 -> 8 -> 12 -> EOC (4 clusters, need 4)
notes.txt byte 9000 -> cluster #2 of the file = cluster 3 (2 FAT lookups)
append: FAT[9] = 6, FAT[6] = EOC
notes.txt now: 2 -> 5 -> 3 -> 9 -> 6 -> EOC
free clusters left: 4, bad: 11
প্রথম দুই লাইনে chain আর আকারের মিল (4 clusters, need 4). তৃতীয় লাইনে random access: 2 FAT lookups — linked allocation হলে এ দুটিই হতো disk read, এখানে মেমরির টেবিল দেখা। Append-এ FAT.indexOf(FREE) প্রথম খালি entry (৬) খোঁজে — FAT নিজেই free-space-এর তালিকা। শেষ লাইনে খালি ৪টি (১০, ১৩, ১৪, ১৫); নষ্ট cluster ১১ কখনো বরাদ্দ হবে না।
FAT12, FAT16, FAT32 — নামের সংখ্যা আর হিসাব
নামের সংখ্যাটি FAT-এর প্রতিটি entry-র বিট-সংখ্যা: FAT12-এ ১২ বিট (ফ্লপি ডিস্কের যুগ), FAT16-এ ১৬, FAT32-এ ৩২ বিট — যার মধ্যে আসলে নিচের ২৮টি বিট cluster নম্বর, ওপরের ৪টি সংরক্ষিত। দুটি হিসাব পরীক্ষায় প্রায়ই আসে।
৩২ GiB-এর একটি FAT32 volume, cluster ৪ KiB. Cluster-সংখ্যা ≈ ৩২ GiB ÷ ৪ KiB = ২৩৫ ÷ ২১২ = ২২৩ = ৮৩,৮৮,৬০৮টি। প্রতিটি entry ৪ byte, তাই একটি FAT ≈ ২২৩ × ৪ = ২২৫ byte = ৩২ MiB; দুটি কপি মিলে ৬৪ MiB — volume-এর প্রায় ০.২%. (সঠিক সংখ্যা একটু কম, কারণ সংরক্ষিত অংশ আর FAT নিজেও জায়গা নেয়।) পুরো ৩২ MiB সবসময় RAM-এ রাখা ছোট যন্ত্রে কঠিন — তাই OS দরকারি অংশ cache করে।
FAT32-এর directory entry-তে ফাইলের আকার রাখা হয় একটি ৩২-বিট unsigned সংখ্যায়। তাই একটি ফাইল সর্বোচ্চ ২৩২ − ১ = ৪২৯,৪৯,৬৭,২৯৫ byte, অর্থাৎ ৪ GiB − ১ byte. Cluster যত বড়ই হোক, এই সীমা বদলায় না। Pen drive-এ ৫ GB-এর একটি ভিডিও কপি করতে গিয়ে “file too large” — এটিই কারণ, ডিস্কে জায়গা থাকলেও।
Volume-এর সীমাও আছে: ৫১২-byte sector-এ FAT32 volume তত্ত্বে প্রায় ২ TiB পর্যন্ত হতে পারে, তবে Windows-এর নিজস্ব format টুল দীর্ঘদিন ৩২ GB-এর বড় FAT32 volume বানাত না (সাম্প্রতিক কিছু সংস্করণে তা শিথিল হয়েছে বলে জানা যায়)। এই সীমাগুলোই অংশ ৯-এর exFAT-এর জন্মের কারণ।
৫ · Indexed allocation আর Unix-এর inode
Linked allocation-এ pointer ছড়ানো block-এ block-এ; FAT সব ফাইলের pointer এক টেবিলে রাখে। Indexed allocation মাঝামাঝি পথ নেয়: প্রতিটি ফাইলের নিজের একটি index block, যার i-তম entry = ফাইলের i-তম block-এর ডিস্ক-ঠিকানা। বইয়ের সূচিপত্রের মতো — “অধ্যায় ৩: পৃষ্ঠা ৫৭”। Directory entry-তে শুধু index block-এর ঠিকানা।
- সুবিধা: random access সরাসরি — index[i] দেখে এক read; external fragmentation নেই।
- অসুবিধা: ২০০ byte-এর ফাইলেও একটি পুরো index block. আর বড় ফাইলে এক index block যথেষ্ট নয়: ৪ KiB block-এ ৪-byte pointer ধরে ১০২৪টি entry, অর্থাৎ মাত্র ৪ MiB.
বড় ফাইলের জন্য তিনটি উপায়:
- Linked scheme: index block-এর শেষ entry পরের index block-এর ঠিকানা — index block-এর linked list.
- Multilevel index: প্রথম স্তরের index block-এর entry দেখায় দ্বিতীয় স্তরের index block, সেগুলো দেখায় data block — পর্ব ৩৩-এর multilevel page table-এর মতোই। দুই স্তরে ১০২৪ × ১০২৪ = ১০,৪৮,৫৭৬টি data block = ৪ GiB.
- Combined scheme (Unix inode): ছোট ফাইল দ্রুত, বড় ফাইলও সম্ভব — দুটোই একসাথে।
Unix-এর combined scheme — ১২ + ৩
BSD-র UFS/FFS আর Linux-এর ext2/ext3-এর inode-এ ১৫টি block pointer (সংখ্যাটি নকশাভেদে আলাদা — যেমন পুরোনো Version 7 Unix-এ direct ছিল ১০টি):
- প্রথম ১২টি direct pointer: সরাসরি data block. ৪ KiB block-এ ১২ × ৪ KiB = ৪৮ KiB পর্যন্ত ফাইলের জন্য কোনো বাড়তি block-ই লাগে না — আর বেশিরভাগ ফাইল ছোট।
- ১৩তম: single indirect: একটি index block-এর ঠিকানা, যাতে ১০২৪টি data block-এর ঠিকানা।
- ১৪তম: double indirect: index block-এর index block — ১০২৪ × ১০২৪টি data block.
- ১৫তম: triple indirect: আরও এক স্তর — ১০২৪৩টি data block.
Block size B, pointer size p ⇒ এক index block-এ n = B ÷ p টি pointer. সর্বোচ্চ ফাইল = (১২ + n + n২ + n৩) × B. B = ৪ KiB, p = ৪ byte ⇒ n = ১০২৪ ⇒ ৪৮ KiB + ৪ MiB + ৪ GiB + ৪ TiB ≈ ৪.০০৪ TiB. B = ১ KiB, p = ৪ ⇒ n = ২৫৬ ⇒ ১২ KiB + ২৫৬ KiB + ৬৪ MiB + ১৬ GiB ≈ ১৬.০৬ GiB.
নিচের calculator প্রতিটি স্তরের অবদান ছাপে, তারপর কয়েকটি offset-এর জন্য বলে কোন স্তর, কোন entry আর কতগুলো disk read (inode মেমরিতে ধরে)। B আর P বদলে নিজের অঙ্ক যাচাই করুন।
// Unix-style inode: 12 direct + single, double, triple indirect pointers.
const B = 4096; // block size in bytes
const P = 4; // pointer (block number) size in bytes
const n = B / P; // pointers that fit in one index block
const fmt = (x) => x.toLocaleString("en-US");
const levels = [["direct (12)", 12], ["single", n], ["double", n ** 2], ["triple", n ** 3]];
console.log("block " + B + " B, pointer " + P + " B -> " + n + " pointers per index block");
let total = 0;
for (const [name, blocks] of levels) {
total += blocks * B;
console.log(" " + name.padEnd(11) + fmt(blocks).padStart(14) + " blocks = " + fmt(blocks * B).padStart(18) + " B");
}
console.log(" max file size = " + fmt(total) + " B (" + (total / 2 ** 40).toFixed(4) + " TiB)");
function locate(X) { // which pointer level serves byte offset X?
let r = Math.floor(X / B); // logical block number inside the file
const lb = r;
for (let lv = 0; lv < 4; lv++) {
const span = lv === 0 ? 12 : n ** lv;
if (r < span) {
const idx = []; // index used inside each index block, top to bottom
for (let k = lv - 1; k >= 0; k--) idx.push(Math.floor(r / n ** k) % n);
const where = lv === 0 ? "direct[" + r + "]" : levels[lv][0] + " [" + idx.join("][") + "]";
return "offset " + fmt(X) + " -> block " + fmt(lb) + ", " + where + ", disk reads = " + (lv + 1);
}
r -= span;
}
return "offset " + fmt(X) + " is beyond the maximum file size";
}
for (const X of [20000, 49152, 2000000, 10000000, 5000000000]) console.log(locate(X));আউটপুট (নির্ধারিত, প্রতিবার একই):
block 4096 B, pointer 4 B -> 1024 pointers per index block
direct (12) 12 blocks = 49,152 B
single 1,024 blocks = 4,194,304 B
double 1,048,576 blocks = 4,294,967,296 B
triple 1,073,741,824 blocks = 4,398,046,511,104 B
max file size = 4,402,345,721,856 B (4.0039 TiB)
offset 20,000 -> block 4, direct[4], disk reads = 1
offset 49,152 -> block 12, single [0], disk reads = 2
offset 2,000,000 -> block 488, single [476], disk reads = 2
offset 10,000,000 -> block 2,441, double [1][381], disk reads = 3
offset 5,000,000,000 -> block 1,220,703, triple [0][167][83], disk reads = 4
ওপরের অংশ সূত্রটিই: ৪৮ KiB, ৪ MiB, ৪ GiB, ৪ TiB; মোট 4,402,345,721,856 B. locate() logical block = ⌊X ÷ ৪০৯৬⌋ বের করে একেক স্তরের ধারণক্ষমতা (১২, ১০২৪, ১০২৪২…) বাদ দিতে দিতে এগোয়। offset 49,152 হলো direct-এর ঠিক পরের প্রথম byte — block ১২, তাই single indirect-এর entry ০. double [1][381] মানে double indirect block-এর entry ১ যে index block দেখায়, তার entry ৩৮১-এ data block-এর ঠিকানা। Disk read = স্তর + ১: direct ১, single ২, double ৩, triple ৪.
প্রশ্নের অনুমান আগে পড়ুন। “Inode মেমরিতে আছে” না বললে inode পড়তেও ১টি read যোগ করুন (direct-এ ২, triple-এ ৫)। কোনো প্রশ্ন index block cache-এ ধরে নেয় — তখন শুধু data block-এর ১টি। আর KB/MB বনাম KiB/MiB: পরীক্ষার “৪ KB block” প্রায় সবসময় ৪০৯৬ byte বোঝায়; উত্তরে কোনটি ধরলেন লিখে দিন।
নিচের stepper তিনটি আলাদা offset-এর জন্য পথটি দেখায় — direct, single indirect আর double indirect. Physical block নম্বরগুলো (১২০৭, ৩৩০০…) উদাহরণের জন্য বানানো; index-এর হিসাব calculator-এর সাথে মেলে।
1 lb = offset / 40962 if lb < 12: read direct[lb]3 lb = lb - 124 if lb < 1024: S = read(single); read S[lb]5 lb = lb - 10246 D = read(double); L = read(D[lb/1024]) read L[lb % 1024]
| offset | |
| lb | |
| pointer | |
| disk read |
- ধাপ ১: offset ২০,০০০. lb = ⌊২০০০০ ÷ ৪০৯৬⌋ = ৪, block-এর ভেতরে byte ৩৬১৬. Inode মেমরিতে আছে, এখনো কোনো disk read হয়নি।
- ধাপ ২: direct. ৪ < ১২, তাই direct[৪] = ১২০৭ — সরাসরি data block পড়া। মোট ১টি read.
- ধাপ ৩: offset ২০,০০,০০০. lb = ৪৮৮, ১২-এর কম নয়; direct-এর ১২টি বাদ দিয়ে ৪৭৬. এটি ১০২৪-এর কম — single indirect.
- ধাপ ৪: single indirect block পড়া. Inode-এর single pointer দেখায় block ৩৩০০; সেটি পড়ে (read ১) তার entry ৪৭৬-এ data block ৫১২০-এর ঠিকানা।
- ধাপ ৫: data block. Block ৫১২০ পড়া (read ২)। মোট ২টি read — index block একটি, data একটি।
- ধাপ ৬: offset ১,০০,০০,০০০. lb = ২৪৪১; ১২ বাদে ২৪২৯; single-এর ১০২৪ বাদে ১৪০৫. এটি ১০২৪ × ১০২৪-এর কম — double indirect.
- ধাপ ৭: double indirect block. Block ৩৩০১ পড়া (read ১); entry ⌊১৪০৫ ÷ ১০২৪⌋ = ১ দেখায় দ্বিতীয় স্তরের index block ৩৪১০।
- ধাপ ৮: দ্বিতীয় স্তর. Block ৩৪১০ পড়া (read ২); entry ১৪০৫ mod ১০২৪ = ৩৮১-এ data block ৯৮৭৬।
- ধাপ ৯: data block. Block ৯৮৭৬ পড়া (read ৩)। মোট ৩টি read — calculator-এর
double [1][381], disk reads = 3-এর সাথে মেলে।
বাস্তবে একবার পড়া index block buffer cache-এ থেকে যায়, তাই পরপর block পড়তে সাধারণত শুধু data block-এর read. নকশার শিক্ষা: ছোট ফাইলের খরচ সবচেয়ে কম, বড় ফাইলের বাড়তি খরচ ধীরে (লগারিদমিক হারে) বাড়ে।
ext4-এর extent — pointer-এর বদলে টানা অংশ
১ GiB-এর একটি ফাইল ext3-এ মানে প্রায় ২,৬২,১৪৪টি আলাদা block pointer, আর সেগুলো রাখার আড়াইশোর বেশি index block — যদিও ফাইলটি হয়তো ডিস্কে প্রায় টানা পড়ে আছে। ext4 (Linux ২.৬.২৮, ২০০৮ থেকে স্থিতিশীল) তাই ডিফল্টে extent ব্যবহার করে: inode-এর ৬০ byte-এর pointer-জায়গায় একটি ১২-byte header আর ৪টি extent, প্রতিটি ১২ byte — (ফাইলের শুরুর block, দৈর্ঘ্য, ডিস্কের শুরুর block). একটি সাধারণ extent সর্বোচ্চ ৩২,৭৬৮টি block, ৪ KiB block-এ ১২৮ MiB ঢাকে। চারটির বেশি লাগলে inode-এর জায়গাটি একটি extent tree-র মূল হয়ে যায়, যার নিচের স্তরগুলো আলাদা block-এ — B-tree-র মতো (DSA কোর্সের B-tree পর্ব)। ext4-এ logical block নম্বর ৩২ বিট, তাই ৪ KiB block-এ সর্বোচ্চ ফাইল ২৩২ × ৪ KiB = ১৬ TiB.
সূক্ষ্ম কথা: ext2/ext3-এর pointer-গণিত ৪ KiB block-এ ৪ TiB দিলেও বাস্তব সীমা সাধারণত ২ TiB বলা হয় — inode-এর আরেকটি ৩২-বিট field (৫১২-byte এককে ব্যবহৃত block) সীমা টানে। পরীক্ষায় প্রশ্নের মডেলেই উত্তর দিন।
৬ · তিন পদ্ধতি পাশাপাশি — একই ফাইল, একই ডিস্ক
একটি ১৬ block-এর ছোট ডিস্ক; block ০, ১, ৩, ৪, ৮ আর ১১ অন্য ফাইলের দখলে। a.txt-এর জন্য ৪টি block দরকার, পরে আরও একটি। নিচের stepper তিন পদ্ধতিতে একই কাজ করে। চিহ্ন: # = অন্যের block, . = খালি, A = a.txt-এর data, I = a.txt-এর index block.
| block 0–7 | |
| block 8–15 | |
| directory entry | |
| file block 3 পড়তে |
- ধাপ ১: শুরুর ডিস্ক. খালি ১০টি block, কিন্তু টানা অংশগুলো ছোট-বড়: [২], [৫–৭], [৯–১০], [১২–১৫]। a.txt-এর ৪টি block লাগবে।
- ধাপ ২: contiguous. First fit-এ প্রথম যে টানা অংশে ৪টি ধরে — ১২ থেকে ১৫. Directory-তে শুধু start ১২, length ৪; block ৩ = ১২ + ৩ = ১৫, একটি access.
- ধাপ ৩: contiguous-এ ফাইল বাড়ে. ১৬ নম্বর block নেই। খালি ৬টি block, কিন্তু সবচেয়ে বড় টানা অংশ ৩টির — পুরো ফাইল সরানোরও জায়গা নেই। এটিই external fragmentation; উপায় compaction.
- ধাপ ৪: linked. যেকোনো খালি block চলে: ২, ৫, ৬, ৭; প্রতিটির শেষে পরেরটির নম্বর। কিন্তু block ৩ পড়তে ২, ৫, ৬ পড়ে তারপর ৭ — ৪টি access.
- ধাপ ৫: linked-এ ফাইল বাড়ে. পরের খালি block ৯ লেখা, আর block ৭-এর pointer ৯-এ বদলানো। সহজ — fragmentation-এর প্রশ্নই নেই।
- ধাপ ৬: indexed. Block ২ হলো index block, তাতে [৫, ৬, ৭, ৯]. block ৩ = index[৩] = ৯ — index মেমরিতে থাকলে একটি access, না থাকলে দুটি। দাম: index-এর জন্য পুরো একটি block.
- ধাপ ৭: indexed-এ ফাইল বাড়ে. পরের খালি block ১০; index-এ একটি entry যোগ: [৫, ৬, ৭, ৯, ১০]. Data block লেখা আর index block হালনাগাদ।
| বৈশিষ্ট্য | Contiguous | Linked | FAT | Indexed (inode) |
|---|---|---|---|---|
| Block i পড়তে disk access (FCB মেমরিতে) | ১ | i + ১ | ১ (FAT cache-এ থাকলে) | ১ + index-স্তরের সংখ্যা (direct-এ ১) |
| External fragmentation | আছে | নেই | নেই | নেই |
| ফাইল বাড়ানো | কঠিন | সহজ | সহজ | সহজ |
| বাড়তি জায়গা | প্রায় শূন্য | প্রতি block-এ pointer | পুরো volume-এর জন্য এক টেবিল | প্রতি ফাইলে index block(গুলো) |
| Sequential পড়া | সবচেয়ে দ্রুত | ভালো, তবে block ছড়ানো হলে seek | ভালো | ভালো |
| একটি pointer নষ্ট হলে | — | chain-এর বাকি অংশ হারায় | chain ভাঙে (দ্বিতীয় কপি সাহায্য করে) | শুধু ওই index-এর অংশ |
| বাস্তব উদাহরণ | ISO 9660 (CD), extent-ভিত্তিক FS-এর ভিত্তি | পাঠ্যবইয়ের মডেল | FAT12/16/32, exFAT | UFS, ext2/ext3; extent-এ ext4, NTFS |
৭ · Free-space management — কোন block খালি?
ফাইল তৈরি বা বড় হলে file system-কে খালি block খুঁজতে হয়, আর মুছলে সেগুলো ফেরত নিতে হয়। এই হিসাব রাখার কাজ free-space management. পাঁচটি পরিচিত পদ্ধতি আছে; প্রথমটি দিয়ে শুরু।
Bit vector (bitmap)
প্রতিটি block-এর জন্য একটি বিট — সব মিলিয়ে একটি লম্বা বিটের সারি, bit vector বা bitmap. এই পর্বে (আর ext4-এ) ১ = ব্যবহৃত, ০ = খালি; Silberschatz-এর পাঠ্যবই উল্টো নিয়ম নেয় (১ = খালি) — প্রশ্নে কোনটি দেওয়া, আগে দেখে নিন।
সিনেমা হলের টিকিট কাউন্টারে একটি সিট-চার্ট: প্রতিটি সিটের ঘরে বিক্রি হলে দাগ, খালি থাকলে ফাঁকা। চার্টটি ছোট — পুরো হলের অবস্থা এক পাতায়। খালি সিট খুঁজতে কাউন্টারের লোক সারি ধরে চোখ বোলান; যে সারি পুরো দাগানো, সেটি এক নজরে বাদ দেন — বাকিগুলোয় খুঁজে প্রথম ফাঁকা ঘরটি।
Bitmap-এর আকার (byte) = মোট block ÷ ৮. ১ TiB ডিস্ক, ৪ KiB block: block = ২৪০ ÷ ২১২ = ২২৮ = ২৬,৮৪,৩৫,৪৫৬টি; bitmap = ২২৮ বিট = ২২৫ byte = ৩২ MiB. ৪টি block মিলিয়ে এক cluster করলে ৮ MiB. ৫১২-byte block হলে একই ডিস্কে ২৫৬ MiB — block size-এর আরেকটি প্রভাব।
প্রথম খালি block খোঁজা: CPU একবারে একটি পুরো word (৩২ বা ৬৪ বিট) তুলনা করতে পারে। তাই word ধরে ধরে এগোনো: যে word-এর সব বিট ১ (পুরো ভরা), এক তুলনাতেই বাদ। প্রথম যে word পুরো ভরা নয়, তার ভেতরে প্রথম ০ বিট খোঁজা — অনেক CPU-তে এর জন্য একটি instruction-ই আছে (x86-এর BSF/TZCNT-এর মতো bit-scan). সূত্র:
প্রথম খালি block = (word-এর বিট-সংখ্যা) × (পুরো ভরা word-এর সংখ্যা) + (প্রথম অ-ভরা word-এ প্রথম ০ বিটের অবস্থান). পাঠ্যবইয়ের নিয়মে (১ = খালি) “পুরো ভরা word”-এর জায়গায় “যে word-এর মান ০” আর “প্রথম ০ বিট”-এর জায়গায় “প্রথম ১ বিট”।
নিচের allocator একটি ৩২ block-এর bitmap চারটি ৮-বিটের word-এ রাখে (ছোট করে দেখার জন্য), প্রথম খালি block খোঁজে, তারপর first fit-এ কয়েকটি টানা অংশ বরাদ্দ করে আর একটি ফাইল মোছে। প্রতিটি কাজের পর পুরো bitmap ছাপে — বাঁ থেকে block ০।
// Free-space bitmap for 32 blocks, stored as four 8-bit words. Bit 1 = in use, 0 = free.
const words = [0b11111111, 0b11100011, 0b11000011, 0b00011111];
const bit = (b) => (words[b >> 3] >> (7 - (b & 7))) & 1; // block b: word b/8, bit b%8
const set = (b, v) => { const m = 1 << (7 - (b & 7));
words[b >> 3] = v ? words[b >> 3] | m : words[b >> 3] & ~m; };
const show = (label) => console.log(label.padEnd(15) +
words.map((w) => w.toString(2).padStart(8, "0")).join(" "));
function firstFree() { // skip full words with one compare each
for (let w = 0; w < words.length; w++) {
if (words[w] === 0xFF) continue;
for (let i = 0; i < 8; i++) if (!bit(w * 8 + i)) return [w * 8 + i, w];
}
return [-1, -1];
}
function allocContig(len) { // first run of len free blocks (first fit)
for (let s = 0, run = 0; s < 32; s++) {
run = bit(s) ? 0 : run + 1;
if (run === len) { for (let b = s - len + 1; b <= s; b++) set(b, 1); return s - len + 1; }
}
return -1;
}
show("start");
const [b, full] = firstFree();
console.log("first free = " + b + " = 8 x " + full + " full words + bit " + (b % 8));
set(b, 1); show("alloc 1 -> " + b);
for (const len of [3, 3, 2]) show("alloc " + len + " -> " + allocContig(len));
set(9, 0); set(10, 0); show("free 9,10");
show("alloc 3 -> " + allocContig(3));
const freeList = [];
for (let k = 0; k < 32; k++) if (!bit(k)) freeList.push(k);
console.log("free blocks: " + freeList.join(",") + " (" + freeList.length + " free, none 3 in a row)");আউটপুট (নির্ধারিত, প্রতিবার একই):
start 11111111 11100011 11000011 00011111
first free = 11 = 8 x 1 full words + bit 3
alloc 1 -> 11 11111111 11110011 11000011 00011111
alloc 3 -> 18 11111111 11110011 11111011 00011111
alloc 3 -> 24 11111111 11110011 11111011 11111111
alloc 2 -> 12 11111111 11111111 11111011 11111111
free 9,10 11111111 10011111 11111011 11111111
alloc 3 -> -1 11111111 10011111 11111011 11111111
free blocks: 9,10,21 (3 free, none 3 in a row)
শুরুতে word ০ পুরো ভরা (11111111) — এক তুলনায় বাদ। Word ১-এ (11100011) প্রথম ০ বিট ৩ নম্বর ঘরে, তাই সূত্রে ৮ × ১ + ৩ = ১১ — দ্বিতীয় লাইন ঠিক এটিই বলে। তারপর first fit: ৩টি টানা খালি block প্রথম পাওয়া গেল ১৮–২০-এ (১২–১৩ মাত্র দুটি), পরের ৩টি ২৪–২৬-এ, আর ২টি ১২–১৩-এ। Block ৯ আর ১০ মুক্ত হওয়ার পর মোট খালি ৩টি (৯, ১০, ২১) — অথচ alloc 3 -> -1: তিনটি টানা নেই। Contiguous allocation-এর external fragmentation একেবারে চোখের সামনে; linked বা indexed হলে এই তিনটি block-ই চলত।
Bitmap সরল, আর পাশাপাশি খালি block খোঁজা সহজ; শর্ত হলো দ্রুততার জন্য এটি মেমরিতে রাখতে হয় — বড় storage array-তে কয়েক GB. তাই ext4 bitmap block group ধরে ধরে আনে (অংশ ৯)।
বাকি চারটি পদ্ধতি
Linked list
সব খালি block একটি linked list-এ; superblock-এ শুধু মাথার ঠিকানা। একটি block নেওয়া দ্রুত, কিন্তু পুরো তালিকা ঘোরা মানে প্রতিটি block পড়া। FAT-এ ০ মানের entry-ই খালি তালিকা।
সহজ, কিন্তু টানা অংশ খোঁজা কঠিনGrouping
প্রথম খালি block-এ পরের n টি খালি block-এর ঠিকানা; n−১টি সত্যিই খালি, শেষটিতে আবার পরের n টির ঠিকানা। এক read-এ অনেক ঠিকানা।
এক read-এ n টি ঠিকানাCounting
খালি জায়গা প্রায়ই টানা অংশে আসে, তাই প্রতিটি অংশের জন্য শুধু (প্রথম block, কতগুলো) — extent-এর আয়না; B-tree-তে রাখলে খোঁজা দ্রুত।
(start, count) জোড়াSpace map (ZFS)
বিশাল pool-এ bitmap অচল, তাই ZFS জায়গাটি বহু “metaslab”-এ ভাগ করে; প্রতিটির space map হলো বরাদ্দ আর মুক্তির ঘটনার একটি log, যা পড়ে মেমরিতে একটি balanced tree বানানো হয়।
লগ + মেমরির treeTRIM — SSD-কে জানানো যে block খালি
ফাইল মুছলে file system শুধু bitmap-এ বিট ০ করে; পুরোনো ডেটা ডিস্কে পড়ে থাকে। হার্ড ডিস্কে তাতে ক্ষতি নেই। কিন্তু SSD-র flash-এ ওপরে লেখা যায় না, আগে বড় erase block মুছতে হয় (পর্ব ৪০); SSD না জানলে garbage collection-এর সময় মৃত ডেটাকেও “জীবিত” ভেবে কপি করে — ধীর হয়, flash দ্রুত ক্ষয় হয়। TRIM (SATA-র কমান্ড; NVMe-তে Deallocate, SCSI-তে UNMAP) দিয়ে file system SSD-কে জানায়, “এই block-গুলো খালি”. Linux-এ fstrim কমান্ড বা discard mount option (অনেক distro সপ্তাহে একবার fstrim.timer চালায়); Windows-এ “Optimize Drives” নিয়মিত retrim করে।
৮ · দক্ষতা আর গতি — block size, cache আর লেখার নীতি
Block size-এর আপস — একটি হিসাব
বড় block মানে প্রতিটি disk access-এ বেশি ডেটা — হার্ড ডিস্কে seek আর ঘোরার অপেক্ষা (ধরুন ১২ ms) প্রায় একই থাকে, তাই throughput বাড়ে। কিন্তু প্রতিটি ফাইলের শেষ block গড়ে অর্ধেক খালি থাকে — internal fragmentation. ধরুন একটি ডিস্ক: positioning ১২ ms, transfer ১০০ MB/s (১ MB = ১০৬ byte), আর প্রতিটি block আলাদা জায়গায় (সবচেয়ে খারাপ ক্ষেত্র)। আর আটটি ফাইল, আকার ৩০০; ১,৫০০; ২,৫০০; ৫,০০০; ৯,০০০; ৪০,০০০; ৭০,০০০ আর ১০,০০,০০০ byte (মোট ১১,২৮,৩০০ byte). একটি scratch script-এ হিসাব:
| Block size | এক block পড়ার সময় | কার্যকর গতি | ১০,০০,০০০-byte ফাইল: block আর সময় | আট ফাইলে অপচয় (বরাদ্দ জায়গার %) |
|---|---|---|---|---|
| ১ KiB | ১২.০১০ ms | ০.০৮৫ MB/s | ৯৭৭টি · ১১.৭৩৪ s | ৪,২৪৪ byte (০.৪%) |
| ৪ KiB | ১২.০৪১ ms | ০.৩৪০ MB/s | ২৪৫টি · ২.৯৫০ s | ২২,৬৭৬ byte (২.০%) |
| ৬৪ KiB | ১২.৬৫৫ ms | ৫.১৭৯ MB/s | ১৬টি · ০.২০২ s | ৪,৪৪,৫৬৪ byte (২৮.৩%) |
| ১ MiB | ২২.৪৮৬ ms | ৪৬.৬৩৩ MB/s | ১টি · ০.০২২ s | ৭২,৬০,৩০৮ byte (৮৬.৫%) |
হিসাবের ধাপ: এক block-এর সময় = ১২ ms + block size ÷ ১০০ MB/s; যেমন ৪ KiB-এ ১২ + ৪০৯৬ ÷ ১০৮ s = ১২ + ০.০৪১ = ১২.০৪১ ms; কার্যকর গতি = ৪০৯৬ byte ÷ ১২.০৪১ ms ≈ ০.৩৪০ MB/s. অপচয় = (প্রতিটি ফাইলের ⌈আকার ÷ block⌉ × block) − আসল আকার, সবগুলোর যোগফল; শতাংশটি মোট বরাদ্দ জায়গার। ছোট block জায়গা বাঁচায়, বড় block সময় বাঁচায়। আজকের ৪ KiB ডিফল্ট এই আপসের ফল, আর page-এর আকারের (পর্ব ৩২) সাথে মেলে। SSD-তে positioning প্রায় নেই, তাই ছোট block-এর শাস্তি কম।
Unified buffer cache বনাম double caching
পুরোনো অনেক Unix-এ দুটি আলাদা cache ছিল: read()/write()-এর জন্য block-ভিত্তিক buffer cache, আর mmap (পর্ব ৩৫) করা ফাইলের জন্য virtual memory-র page cache. একটি ফাইল দুই পথেই ব্যবহৃত হলে একই ডেটা RAM-এ দুবার — double caching: RAM-এর অপচয়, CPU-র কপির খরচ, আর দুই কপির মধ্যে অমিলের ঝুঁকি। সমাধান unified buffer cache: দুই পথই একই page cache ব্যবহার করে। Solaris, Linux (২.৪-এর সময় থেকে মোটামুটি) আর Windows এমন একীভূত নকশা নেয়। Linux-এ free কমান্ডের “buff/cache” কলাম এর আকার দেখায় (পর্ব ৩৮)।
Read-ahead আর free-behind
বেশিরভাগ ফাইল শুরু থেকে শেষ পর্যন্ত পড়া হয় (sequential). File system তা টের পেলে দুটি কৌশল নেয়:
- Read-ahead: অনুরোধ করা block-এর সাথে পরের কয়েকটিও এক অনুরোধে পড়ে রাখা — হার্ড ডিস্কে seek একবার, বাকিটা টানা transfer. পর্ব ৪-এর prefetching-এর file system রূপ; Linux পড়ার ধরন দেখে জানালা বড়-ছোট করে।
- Free-behind: পরের page চাওয়া হলেই আগের page-কে cache থেকে সরানোর প্রার্থী করা — sequential পড়ায় সেটি আর লাগবে না, অথচ LRU (পর্ব ৩৬) তাকেই সবচেয়ে মূল্যবান ভাবে।
Synchronous বনাম asynchronous লেখা
write() সাধারণত asynchronous: ডেটা page cache-এ কপি হয়, page dirty চিহ্নিত হয়, আর system call সঙ্গে সঙ্গে ফেরে; ডিস্কে আসল লেখা পরে writeback-এ, দলবেঁধে আর ভালো ক্রমে। দাম: বিদ্যুৎ চলে গেলে শেষ কয়েক সেকেন্ডের লেখা হারাতে পারে। Synchronous লেখা — O_SYNC/O_DSYNC দিয়ে খোলা ফাইল, বা লেখার পর fsync(fd) — ডেটা ডিস্কে (ডিভাইসের নিজের cache পেরিয়ে) না পৌঁছানো পর্যন্ত ফেরে না। Database-এর commit আর পর্ব ৪৪-এর journal-এর জন্য এটি দরকার।
৯ · বাস্তবের file system — ext4, FAT32, NTFS আর exFAT
ext4 — block group, bitmap আর extent
Linux-এর সবচেয়ে প্রচলিত file system ext4 পুরো volume-কে সমান আকারের block group-এ ভাগ করে। ৪ KiB block-এ একটি block bitmap (একটিমাত্র block) ৪০৯৬ × ৮ = ৩২,৭৬৮টি block-এর হিসাব রাখতে পারে, তাই প্রতিটি group সাধারণত ৩২,৭৬৮ block = ১২৮ MiB. প্রতিটি group-এ:
- কিছু group-এ superblock আর group descriptor-এর ব্যাকআপ কপি;
- একটি block bitmap আর একটি inode bitmap — কোন data block আর কোন inode খালি;
- ওই group-এর inode table (ডিফল্টে প্রতিটি inode ২৫৬ byte);
- বাকি জায়গায় data block.
লক্ষ্য locality: ফাইলের inode আর data একই group-এ, যাতে হার্ড ডিস্কের মাথা কম নড়ে — BSD-র Fast File System (১৯৮৪)-এর cylinder group-এর উত্তরসূরি; “flex_bg” কয়েকটি group-এর metadata একসাথে রাখে। Inode-এর মোট সংখ্যা mkfs-এর সময় ঠিক হয়, তাই জায়গা থাকতেও inode ফুরিয়ে “No space left on device” আসতে পারে। নিচের কমান্ডগুলো এই container-এর file system দেখায়:
echo "== the file system that holds this directory"
stat -f .
echo
echo "== inode usage (df -i)"
df -i .
echo
printf 'hi\n' > tiny.txt # 3 bytes
head -c 10000 /dev/zero > ten_k.bin # 10,000 bytes
truncate -s 1M sparse.bin # size 1 MiB, nothing written yet
printf 'x' | dd of=sparse.bin bs=1 seek=900000 conv=notrunc status=none
echo "== per-file: size vs. blocks really used (%b counts 512-byte units)"
stat -c '%n: size=%s B, %b blocks of %B B' tiny.txt ten_k.bin sparse.binএকটি আসল run-এর আউটপুট (খালি block আর inode-এর সংখ্যা প্রতিবার একটু বদলায়):
== the file system that holds this directory
File: "."
ID: b9552bbea56a87eb Namelen: 255 Type: ext2/ext3
Block size: 4096 Fundamental block size: 4096
Blocks: Total: 14599048 Free: 3472925 Available: 2831092
Inodes: Total: 15551872 Free: 15220960
== inode usage (df -i)
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda2 15551872 330912 15220960 3% /home/wandbox
== per-file: size vs. blocks really used (%b counts 512-byte units)
tiny.txt: size=3 B, 8 blocks of 512 B
ten_k.bin: size=10000 B, 24 blocks of 512 B
sparse.bin: size=1048576 B, 8 blocks of 512 B
পড়ার নিয়ম: stat -f . superblock-এর তথ্য দেখায় — Block size: 4096, মোট আর খালি block, মোট আর খালি inode. Type: ext2/ext3 লেখা থাকলেও এটি ext4 (container-এর mount তালিকায় তা-ই দেখা যায়): তিনটির superblock-এর “magic number” একই, তাই stat আলাদা করে না। df -i inode-এর ব্যবহার — এখানে মাত্র ৩%. তারপর তিনটি ফাইল: ৩ byte-এর tiny.txt-এ 8 blocks of 512 B = ৪০৯৬ byte — পুরো একটি file-system block, অর্থাৎ internal fragmentation চোখের সামনে। ১০,০০০ byte-এর ফাইলে ২৪ × ৫১২ = ১২,২৮৮ byte = ৩টি block (⌈১০০০০ ÷ ৪০৯৬⌉ = ৩)। আর sparse.bin-এর আকার ১ MiB, কিন্তু ব্যবহৃত মাত্র ৮ × ৫১২ = এক block — শুধু যেখানে একটি byte লেখা হয়েছে। বাকিটা sparse file-এর “hole”: inode বা extent-এ ওই অংশের কোনো block-ই বরাদ্দ নেই, পড়লে শূন্য ফেরে। খেয়াল করুন, %b সবসময় ৫১২-byte একক গোনে — file system-এর block size যা-ই হোক।
FAT32 — সরল, সর্বজনীন, কিন্তু পুরোনো
অংশ ৪-এর পুরো ছবিটিই FAT32: সামনে reserved অংশ (boot sector, FSInfo), তারপর FAT-এর দুটি কপি, তারপর data অংশ, যেখানে root directory-ও একটি সাধারণ cluster chain. কোনো অনুমতি (owner, rwx) নেই, journaling নেই, ফাইল সর্বোচ্চ ৪ GiB − ১ byte. তবু প্রায় সব OS আর যন্ত্র এটি পড়তে পারে; UEFI-র EFI system partition (পর্ব ৭) এ কারণেই FAT32-এ।
NTFS — সবকিছুই একটি ফাইল
Windows NT ৩.১ (১৯৯৩)-এর সাথে আসা NTFS-এর কেন্দ্র MFT (Master File Table) — নিজেও একটি ফাইল, যার প্রতিটি record (সাধারণত ১ KiB) একটি ফাইল বা directory-র বর্ণনা: নাম, সময়, নিরাপত্তা, আর ডেটা — সব “attribute” হিসেবে। ছোট ফাইলের ডেটা এত ছোট যে MFT record-এর ভেতরেই ধরে যায় — একে resident ডেটা বলে, তখন আলাদা কোনো cluster লাগে না (কয়েকশো byte পর্যন্ত; সঠিক সীমা record-এর বাকি attribute-এর ওপর নির্ভর করে)। বড় ফাইলে ডেটা non-resident: record-এ থাকে “data run”-এর তালিকা — অর্থাৎ extent. Free space একটি bitmap-এ, যেটিও একটি ফাইল ($Bitmap); journal থাকে $LogFile-এ (পর্ব ৪৪)।
exFAT — flash-এর জন্য বড় FAT
বড় SD card আর pen drive-এ FAT32-এর ৪ GiB সীমা অসহনীয় হয়ে উঠছিল, অথচ NTFS ছোট যন্ত্রের জন্য ভারী। Microsoft ২০০৬ সালে আনে exFAT: ফাইলের আকার ৬৪-বিট (৪ GiB সীমা নেই), খালি জায়গার জন্য FAT-এর বদলে একটি আলাদা allocation bitmap, আর একটি চতুর বিকল্প — ফাইল পুরো টানা থাকলে directory entry-তে একটি flag দিয়ে বলা যায় “FAT chain দেখার দরকার নেই”, তখন ফাইলটি কার্যত contiguous allocation. SD Association বড় (SDXC) card-এর জন্য exFAT-কে মানদণ্ড করেছে। Microsoft ২০১৯ সালে এর specification প্রকাশ করার পর Linux kernel-এ (৫.৪-এ staging-এ, ৫.৭ থেকে মূল অংশে) exFAT driver এসেছে।
আরও গভীরে: ext4 ডিফল্টে delayed allocation করে: write()-এর সময় ডিস্কের block বাছে না, শুধু page cache-এ রাখে; writeback-এর সময়, যখন কতটা ডেটা জমেছে জানা, তখন multi-block allocator (mballoc, buddy-ধাঁচের হিসাব রাখে) একবারে বড় টানা extent দেয় — ফলে fragmentation কম। XFS খালি জায়গা allocation group-এ ভাগ করে, প্রতিটিতে দুটি B+tree (শুরুর block আর আকার দিয়ে সাজানো), যাতে “এর কাছাকাছি” আর “এত বড়” — দুই প্রশ্নই দ্রুত। Btrfs, ZFS আর APFS-এর মতো copy-on-write file system কখনো জায়গায় লেখে না; তাদের allocation আর crash consistency একসূত্রে বাঁধা — পর্ব ৪৪.
১০ · AI-এর যুগে file system-এর হিসাব
AI এই অঙ্কগুলো মুহূর্তে করে দেয়, আর প্রায়ই সূক্ষ্মভাবে ভুল করে:
| AI-এর ভুল | কেন ভুল | কীভাবে যাচাই করবেন |
|---|---|---|
| Direct pointer-এর অংশ (১২ × B) বাদ দেওয়া, বা n২-এর জায়গায় ২n লেখা | Double indirect মানে n টি index block, প্রতিটিতে n টি pointer | সূত্র (১২ + n + n২ + n৩) × B হাতে লিখুন; calculator-এ B, P বসান |
| KB/KiB, MB/MiB মিশিয়ে ফেলা | ৪ KB-কে ৪০০০ ধরলে উত্তর অন্য | প্রশ্নের একক আর উত্তরের একক স্পষ্ট লিখুন |
| Disk read গোনায় inode-এর read যোগ বা বাদ — প্রশ্নের অনুমান না দেখে | অনুমান বদলালে উত্তর ১ বাড়ে-কমে | প্রশ্নে “inode মেমরিতে” আছে কি না দেখুন |
| “FAT32-এ ফাইল ৪ GB পর্যন্ত, cluster বড় করলে বেশি” | সীমা আসে ৩২-বিট size field থেকে, cluster থেকে নয় | Directory entry-র গঠন দেখুন; ৫ GB ফাইল কপি করে দেখুন |
Disk ভরে গেছে ভেবে “ফাইল মুছুন” — অথচ df -h-এ জায়গা আছে | Inode ফুরোতে পারে | df -i চালান |
“sudo chmod -R 777 দিন” বা “fsck চালান” — mount করা volume-এ | 777 নিরাপত্তা ভাঙে; mount করা volume-এ fsck ক্ষতি করতে পারে | আগে man page পড়ুন; fsck শুধু unmount করা volume-এ বা recovery mode-এ |
Linux-এর stat -c macOS-এ দেওয়া | macOS-এর BSD stat-এর option আলাদা | OS জানিয়ে প্রশ্ন করুন; man stat |
অঙ্ক দেওয়ার সময় সব অনুমান স্পষ্ট লিখুন, আর যাচাইযোগ্য ধাপ চান:
AI-এর উত্তর মানার আগে — চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Contiguous, linked আর indexed allocation-এর তুলনা করুন। — contiguous: random access-এ ১টি access, কিন্তু external fragmentation; linked: fragmentation নেই, কিন্তু block i-তে i + ১টি access আর pointer হারানোর ঝুঁকি; indexed: সরাসরি access, কিন্তু index block-এর বাড়তি জায়গা।
- ৪ KiB block, ৪ byte pointer, ১২ direct + single + double + triple — সর্বোচ্চ ফাইল? — n = ১০২৪; ৪৮ KiB + ৪ MiB + ৪ GiB + ৪ TiB ≈ ৪ TiB.
- FAT কীভাবে linked allocation-এর random access সমস্যা কমায়? — সব pointer এক টেবিলে, যা মেমরিতে cache করা যায়; chain ধরে হাঁটা মেমরিতে, ডিস্কে একবার।
- ১ TiB ডিস্ক, ৪ KiB block — bitmap কত বড়? — ২২৮ বিট = ৩২ MiB.
- ডিস্কে জায়গা থাকতেও “No space left on device” কেন? — inode ফুরিয়ে গেছে (
df -i), বা কোটা/সংরক্ষিত block-এর সীমা।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১FAT-এর জন্ম ফ্লপি ডিস্কের যুগে। চার দশক পরেও প্রায় প্রতিটি pen drive, মেমরি কার্ড আর UEFI-র EFI partition কেন FAT-পরিবারের?
FAT-এর শুরু ১৯৭০-এর দশকের শেষে, Microsoft-এর একটি BASIC-এর ডিস্ক সংস্করণের জন্য; তারপর MS-DOS-এর সাথে FAT12 ফ্লপি আর প্রথম হার্ড ডিস্কে ছড়ায়। ডিস্ক বড় হতেই entry-র বিট বাড়ল — ১৯৮০-এর দশকে FAT16, আর ১৯৯৬ সালে Windows 95-এর একটি পরের সংস্করণ (OSR2)-এর সাথে FAT32. মাঝে Windows 95 লম্বা ফাইলনাম যোগ করে (VFAT) এমনভাবে, যাতে পুরোনো DOS তখনো ডিস্কটি পড়তে পারে।
এই পর্বের দৃষ্টিতে FAT প্রযুক্তিগতভাবে দুর্বল: অনুমতি নেই, journaling নেই, ৪ GiB ফাইল-সীমা, বড় volume-এ বিশাল FAT. তবু টিকে আছে একটি কারণে — সরলতা থেকে আসা সর্বজনীনতা. একটি FAT driver কয়েক হাজার লাইনে লেখা যায়; একটি ক্যামেরা, প্রিন্টার, গাড়ির মিউজিক প্লেয়ার বা microcontroller-এর ছোট firmware-ও তা বহন করতে পারে। ফলে প্রতিটি OS FAT পড়ে-লেখে, আর এই কারণে যন্ত্র-নির্মাতারা FAT বাছেন — একটি স্ব-শক্তিশালী চক্র। UEFI specification-ও EFI system partition-এর জন্য FAT-পরিবারকে মানদণ্ড করেছে: firmware-এর পক্ষে NTFS বা ext4 বোঝা ভারী, FAT সহজ।
কিন্তু সীমাগুলো সত্যিকারের সমস্যা হয়ে উঠছিল যখন ক্যামেরা ৪ GiB-এর বেশি ভিডিও বানাতে শুরু করল। Microsoft-এর উত্তর exFAT (২০০৬) — FAT-এর সরল কাঠামো রেখে ৬৪-বিট আকার আর bitmap-ভিত্তিক free space. বহু বছর এটি patent আর লাইসেন্সের আওতায় ছিল, তাই Linux-এ ভালো সমর্থন আসতে দেরি হয়েছে; ২০১৯ সালে Microsoft specification প্রকাশ করে সেই পথ খোলে।
শিক্ষা: আদান-প্রদানের মাধ্যমে “সবচেয়ে ভালো” নকশা নয়, “সবাই যা বোঝে” নকশা জেতে — ফটোকপির দোকানের পুরোনো কম্পিউটারে আপনার pen drive খুলবে FAT32 বা exFAT হলেই।
মূল উপলব্ধি: file system বাছাই শুধু কারিগরি গুণের প্রশ্ন নয় — কে কে এটি পড়বে, সেই interoperability প্রায়ই সবচেয়ে বড় শর্ত।
প্র ০২Unix inode-এ কেন “১২টি direct আর তিনটি indirect” — এই অসম নকশার পেছনে কোন পর্যবেক্ষণ, আর ext4 কেন extent-এ সরে গেল?
১৯৭০-এর দশকের শুরুর Unix-এর inode ছিল ছোট — কয়েকটি block ঠিকানা; Ritchie আর Thompson-এর ১৯৭৪ সালের বিখ্যাত গবেষণাপত্রে বর্ণিত নকশায় বড় ফাইলের জন্য inode-এর ঠিকানাগুলো indirect block দেখাত। পরে Version 7 Unix-এ এই মিশ্র নকশা পরিচিত রূপ পায় — কয়েকটি direct (সেখানে ১০টি) আর single, double, triple indirect. BSD-র Fast File System (McKusick ও সহকর্মীরা, ১৯৮৪) direct সংখ্যা ১২ করে, আর ext2 সেটিই নেয়।
নকশার পেছনের পর্যবেক্ষণটি বহু গবেষণায় বারবার এসেছে: বেশিরভাগ ফাইল ছোট, কিন্তু বেশিরভাগ বাইট অল্প কয়েকটি বড় ফাইলে. তাই ছোট ফাইলের পথ সবচেয়ে সস্তা হওয়া চাই — inode পড়লেই data block-এর ঠিকানা, কোনো বাড়তি index block নেই। ১২টি direct ৪ KiB block-এ ৪৮ KiB ঢাকে, যা সাধারণ ডেস্কটপ বা সার্ভারের বড় একটি অংশ ফাইলের জন্য যথেষ্ট। বড় ফাইলের জন্য খরচ ধীরে বাড়ে — একটি বাড়তি index block প্রতি ১০২৪ block-এ, আর স্তর বাড়লে read লগারিদমিক হারে। Inode-এর আকারও সীমিত রাখতে হয় (inode table-এ লাখ লাখ inode), তাই ১৫টি pointer একটি ভারসাম্য — বেশি direct দিলে inode মোটা, কম দিলে ছোট ফাইলেও indirect.
দুর্বলতা দেখা দিল ফাইল যখন GB মাপে বড় হলো। এই পর্বেই দেখেছেন, ১ GiB মানে প্রায় ২.৬ লাখ আলাদা pointer, যদিও ফাইলটি হয়তো ডিস্কে প্রায় টানা। মুছতে গেলে সবগুলো ঘুরে block মুক্ত করতে হয়, তাই ext3-এ বড় ফাইল মোছা লক্ষণীয়ভাবে ধীর ছিল। Extent সেই টানা অংশকে এক entry-তে বর্ণনা করে। XFS (SGI, ১৯৯০-এর দশক) শুরু থেকেই extent-ভিত্তিক; ext4 সেই পথ নেয়। Extent tree-ও আসলে একটি combined scheme — inode-এ চারটি extent, বেশি লাগলে tree; শুধু “একক” বদলাল।
মূল উপলব্ধি: ভালো data structure-এর নকশা আসে বাস্তব ব্যবহারের পরিসংখ্যান থেকে — সাধারণ ক্ষেত্র দ্রুত, বিরল ক্ষেত্র সম্ভব; আর পরিসংখ্যান বদলালে (বড় ফাইল) নকশাও বদলায়।
প্র ০৩Contiguous allocation-এর external fragmentation এত বড় সমস্যা হলে আধুনিক file system কেন এখনো “টানা” জায়গা পেতে এত চেষ্টা করে? SSD-তে কি fragmentation আর গুরুত্বপূর্ণ?
খাঁটি contiguous allocation — প্রতিটি ফাইল ঠিক একটি টানা অংশে, আকার আগে থেকে জানা — সাধারণ-উদ্দেশ্যের ডিস্কে প্রায় মৃত; তা কেবল CD/DVD-র মতো একবার লেখা মাধ্যমে টিকে আছে। কিন্তু “যতটা সম্ভব টানা” লক্ষ্যটি একেবারেই মরেনি, কারণ হার্ড ডিস্কে একটি seek আর ঘোরার অপেক্ষা কয়েক মিলিসেকেন্ড, অথচ সেই সময়ে টানা পড়লে শত শত KB আসে। অংশ ৮-এর হিসাবে দেখেছেন: ৪ KiB-এর ছড়ানো block পড়লে কার্যকর গতি সেকেন্ডে আধা MB-রও কম, টানা পড়লে তার শত গুণ।
তাই আধুনিক file system contiguous-এর সুবিধা নেয় অন্য পথে: extent, delayed allocation (লেখা জমিয়ে একবারে বড় অংশ বরাদ্দ), preallocation (fallocate() দিয়ে অ্যাপ্লিকেশন আগেই জায়গা চায় — database আর ভিডিও রেকর্ডার এটি করে), আর block group-এ সম্পর্কিত ফাইল কাছাকাছি রাখা। Windows নির্ধারিত সময়ে হার্ড ডিস্ক defragment করে।
SSD-তে গল্প বদলায়, কিন্তু শূন্য হয় না। কোনো মাথা নেই, তাই random read প্রায় sequential-এর কাছাকাছি দ্রুত, আর fragmentation-এর বড় শাস্তি উধাও। তার ওপর SSD-র ভেতরের flash translation layer নিজেই logical block-কে যেখানে খুশি রাখে — file system যাকে “টানা” ভাবে, flash-এ তা হয়তো ছড়ানো। ফলে SSD-তে defragment করা মূলত অপ্রয়োজনীয় লেখা, যা flash-এর আয়ু খায়; Windows-এর Optimize Drives SSD-তে defrag-এর বদলে মূলত TRIM চালায়। তবে খুব বেশি টুকরো হলে প্রতিটি টুকরো আলাদা I/O অনুরোধ, আলাদা metadata — CPU আর অনুরোধ-সংখ্যার খরচ থাকে। আর extent-সংখ্যা বাড়লে extent tree বড় হয়।
তাই প্রশ্নগুলো শুধু বদলায় — seek-এর বদলে write amplification, TRIM আর erase block-এর সাথে মিল। F2FS (Samsung, Linux ৩.৮-এর সময় যোগ) এমনই একটি flash-সচেতন file system, যা অনেক Android ফোনে ব্যবহৃত হয়।
মূল উপলব্ধি: allocation নীতি সবসময় নিচের যন্ত্রের খরচ-মডেলের প্রতিফলন — হার্ড ডিস্কে seek, SSD-তে erase আর লেখার পরিমাণ; মডেল বদলালে “ভালো” allocation-এর সংজ্ঞাও বদলায়।
প্র ০৪Asynchronous লেখা আর delayed allocation গতি দেয়, কিন্তু crash-এ ডেটা হারানোর ঝুঁকি বাড়ায়। ext4-এর ২০০৯ সালের “শূন্য-byte ফাইল” বিতর্ক থেকে কী শেখা যায়?
ext4 যখন বিভিন্ন Linux distro-তে ডিফল্ট হতে শুরু করল (২০০৯-এর আশেপাশে), কিছু ব্যবহারকারী একটি অদ্ভুত সমস্যা জানালেন: সিস্টেম হঠাৎ বন্ধ হলে (বিদ্যুৎ চলে যাওয়া বা driver crash) পরের বুটে কিছু অ্যাপ্লিকেশনের config ফাইল শূন্য byte — ফাইল আছে, ভেতরে কিছু নেই। ext3-এ একই অ্যাপ্লিকেশন বছরের পর বছর ঠিক চলেছে।
কারণ এই পর্বের দুটি ধারণার মিলন। অ্যাপ্লিকেশনগুলো একটি পরিচিত কৌশলে ফাইল হালনাগাদ করত: নতুন ডেটা একটি অস্থায়ী ফাইলে লেখা, তারপর rename দিয়ে পুরোনোটির জায়গায় বসানো — কিন্তু মাঝে fsync ছাড়া। ext3-এর ডিফল্ট মোডে metadata journal-এ যাওয়ার আগে ফাইলের data ডিস্কে লেখা হয়ে যেত, আর এমনিতেই কয়েক সেকেন্ড পরপর সব লেখা ডিস্কে যেত — তাই অনুপস্থিত fsync ধরা পড়ত না। ext4-এর delayed allocation-এ data-র block বরাদ্দ আর লেখা অনেক পরে হতে পারত (কয়েক দশ সেকেন্ড), অথচ rename-এর metadata দ্রুত journal-এ commit হয়ে যেত। Crash হলে: নতুন নাম আছে, কিন্তু তার data কখনো ডিস্কে পৌঁছায়নি।
বিতর্কটি দুই দিকের ছিল। ext4-এর রক্ষণাবেক্ষণকারী Ted Ts'o যুক্তি দেন, POSIX কখনো fsync ছাড়া স্থায়িত্বের প্রতিশ্রুতি দেয়নি — অ্যাপ্লিকেশন ভুল ছিল। অন্যরা বললেন, বাস্তবে লাখো প্রোগ্রাম এই ধারণার ওপর দাঁড়িয়ে; file system-কে ব্যবহারকারীর ডেটা রক্ষা করতে হবে। ফল একটি আপস: ext4-এ heuristic যোগ হয় (rename দিয়ে কোনো ফাইল প্রতিস্থাপন হলে তার data আগে লেখা শুরু করা — auto_da_alloc), আর অ্যাপ্লিকেশন-লেখকেরাও fsync যোগ করতে শুরু করেন।
উল্টো দিকও আছে: ext3-এ একটি fsync অনেক সময় পুরো journal ডিস্কে ঠেলে দিত, তাই ঘন ঘন fsync পুরো সিস্টেমকে থমকে দিত — synchronous লেখার খরচও বাস্তব।
মূল উপলব্ধি: asynchronous লেখা গতির জন্য দরকারি, কিন্তু স্থায়িত্ব চাইলে অ্যাপ্লিকেশনকে স্পষ্ট করে চাইতে হয় (fsync); file system-এর “অলিখিত” আচরণের ওপর ভরসা করা কোড অন্য file system-এ ভেঙে পড়ে।
প্র ০৫ext4-এ inode-এর সংখ্যা ফরম্যাটের সময় স্থির, XFS বা Btrfs-এ দরকারমতো বাড়ে। কোনটি ভালো, আর বাস্তবে “inode ফুরিয়ে যাওয়া” কখন ঘটে?
ext2 থেকে ext4 পর্যন্ত পরিবারের নকশায় প্রতিটি block group-এ একটি নির্দিষ্ট আকারের inode table, যা mkfs-এর সময় তৈরি হয়। কতগুলো inode হবে, তা ঠিক হয় একটি অনুপাত থেকে — প্রতি এত byte জায়গার জন্য একটি inode (mke2fs-এর -i বা সরাসরি সংখ্যা -N). সুবিধা অনেক: inode নম্বর থেকে সরাসরি হিসাব করা যায় সেটি কোন group-এর কোন block-এ — কোনো খোঁজাখুঁজি নেই; inode bitmap ছোট আর সরল; crash-এর পর fsck জানে inode কোথায় থাকার কথা। দাম: জায়গা আর inode — দুটি আলাদা সম্পদ, যেকোনোটি আগে ফুরোতে পারে।
বাস্তবে কখন ফুরোয়? যেখানে কোটি কোটি ছোট ফাইল: পুরোনো ধাঁচের mail server (প্রতিটি email আলাদা ফাইল), cache বা session ফাইলের ডিরেক্টরি, ছোট thumbnail-এর ভান্ডার, বা container image আর JavaScript-এর node_modules জমে যাওয়া build সার্ভার। তখন df -h বলে ৪০% জায়গা খালি, অথচ প্রতিটি নতুন ফাইলে “No space left on device”; df -i দেখায় IUse% ১০০. সমাধান: অপ্রয়োজনীয় ছোট ফাইল মোছা, বা নতুন volume বেশি inode দিয়ে ফরম্যাট করা — ext4-এ চলমান volume-এ inode সংখ্যা সহজে বাড়ানো যায় না।
XFS ভিন্ন পথ নেয়: inode দরকার হলে ৬৪টির একটি দল (chunk) হিসেবে data-র মতোই জায়গা থেকে বরাদ্দ হয়, সর্বোচ্চ একটি শতাংশ-সীমা পর্যন্ত। Btrfs আর ZFS-এ inode-জাতীয় metadata tree বা object হিসেবে থাকে, যার কোনো আলাদা স্থির table নেই। লাভ: ফরম্যাটের সময় ভবিষ্যদ্বাণী করতে হয় না। দাম: inode খুঁজতে একটি সূচক (tree) লাগে, আর metadata ডিস্কে ছড়িয়ে পড়তে পারে।
এটি এই পর্বের পুরোনো আপসেরই আরেক রূপ — স্থির, পূর্ব-বরাদ্দ কাঠামো সরল আর দ্রুত, কিন্তু ভবিষ্যৎ ভুল আন্দাজ করলে বিপদ; গতিশীল কাঠামো নমনীয়, কিন্তু একটি বাড়তি পরোক্ষ ধাপ।
মূল উপলব্ধি: সম্পদ দুই রকম হলে (byte আর ফাইল-সংখ্যা) দুটিই মাপতে হয়; স্থির বরাদ্দ সরলতা কেনে, গতিশীল বরাদ্দ নমনীয়তা — আর কোনটি ঠিক, তা workload বলে দেয়।
অনুশীলন
হাতে হিসাব, আউটপুট অনুমান আর ব্যাখ্যা — আগে নিজে চেষ্টা করুন, তারপর উত্তর মেলান।
-
হাতে হিসাব: Inode-এ ১২টি direct, একটি single, একটি double আর একটি triple indirect pointer; block ১ KiB, pointer ৪ byte; inode মেমরিতে আছে। (ক) সর্বোচ্চ ফাইল-আকার কত? (খ) byte offset ১০,০০০, ৩,০০,০০০ আর ৭,০০,০০,০০০ পড়তে কোন স্তর, কোন entry আর কতগুলো disk read?
(ক) n = ১০২৪ ÷ ৪ = ২৫৬. সর্বোচ্চ = (১২ + ২৫৬ + ২৫৬২ + ২৫৬৩) × ১০২৪ = ১২ KiB + ২৫৬ KiB + ৬৪ MiB + ১৬ GiB = ১৭,২৪,৭২,৫২,৪৮০ byte ≈ ১৬.০৬ GiB.
(খ) calculator-এ
B = 1024বসিয়ে যাচাই করা:offset 10,000 -> block 9, direct[9], disk reads = 1 offset 300,000 -> block 292, double [0][24], disk reads = 3 offset 70,000,000 -> block 68,359, triple [0][9][251], disk reads = 4হাতে: ৩,০০,০০০ ÷ ১০২৪ = ২৯২; ১২ বাদে ২৮০ — ২৫৬-এর বেশি, তাই single নয়; ২৫৬ বাদে ২৪ → double [২৪ ÷ ২৫৬ = ০][২৪ mod ২৫৬ = ২৪]. ৭,০০,০০,০০০ ÷ ১০২৪ = ৬৮,৩৫৯; ১২, ২৫৬ আর ৬৫,৫৩৬ বাদে ২,৫৫৫ = ০ × ৬৫,৫৩৬ + ৯ × ২৫৬ + ২৫১ → triple [০][৯][২৫১].
-
আউটপুট অনুমান: FAT chain follower প্রোগ্রামে FAT-এর ৫ নম্বর entry ভুল করে ৩-এর বদলে ৯ হয়ে গেল (অর্থাৎ
FAT[5] = 9). এখন প্রোগ্রাম কী কী ছাপবে? Cluster ৩-এর কী হলো?বদলানো প্রোগ্রাম চালিয়ে পাওয়া:
notes.txt start 2: 2 -> 5 -> 9 -> EOC (3 clusters, need 4) photo.jpg start 4: 4 -> 7 -> 8 -> 12 -> EOC (4 clusters, need 4) notes.txt byte 9000 -> cluster #2 of the file = cluster 9 (2 FAT lookups) append: FAT[9] = 6, FAT[6] = EOC notes.txt now: 2 -> 5 -> 9 -> 6 -> EOC free clusters left: 4, bad: 11Chain এখন মাত্র ৩টি cluster, অথচ আকার বলছে ৪টি দরকার — অসংগতি। Byte ৯০০০ এখন cluster ৯-এ খোঁজা হয়, যেখানে আসলে ফাইলের শেষ অংশ ছিল — অর্থাৎ ভুল ডেটা ফেরত, কোনো ত্রুটি-বার্তা ছাড়াই। আর cluster ৩ কোনো chain-এ নেই, কিন্তু খালিও নয় (তার entry ৯, ০ নয়) — একটি “lost cluster”. chkdsk বা
fsck.fatএমন cluster খুঁজে আলাদা ফাইলে জমা করে বা মুক্ত করে — linked/FAT-এর নির্ভরযোগ্যতার দুর্বলতা চোখের সামনে। -
হাতে হিসাব — bitmap: (ক) ২ TiB ডিস্ক, ৪ KiB block — bitmap কত বড়? ৪টি block-এর cluster হলে? (খ) ৩২-বিটের word-এ bitmap (১ = ব্যবহৃত, বাঁয়ের বিট আগে): word ০, ১, ২ =
0xFFFFFFFF, word ৩ =0xFFFF0FFF. প্রথম খালি block কোনটি?(ক) block = ২৪১ ÷ ২১২ = ২২৯; bitmap = ২২৯ ÷ ৮ = ২২৬ byte = ৬৪ MiB. Cluster ৪ block হলে ২২৭ cluster → ১৬ MiB.
(খ) প্রথম তিনটি word পুরো ভরা — তিনটি তুলনায় বাদ। Word ৩ = ১১১১ ১১১১ ১১১১ ১১১১ ০০০০ ১১১১ ১১১১ ১১১১ — প্রথম ০ বিট ১৬ নম্বর ঘরে (০ থেকে গুনে)। প্রথম খালি block = ৩২ × ৩ + ১৬ = ১১২ (একটি ছোট script দিয়ে যাচাই করা)। পাঠ্যবইয়ের নিয়মে (১ = খালি) একই বিট-সারির অর্থ উল্টো হতো — তখন প্রথম খালি block হতো ০.
-
Disk I/O গুনুন: একটি ফাইলে ১০০টি block; তার FCB (আর indexed-এ index block) মেমরিতে আছে। Contiguous-এ ফাইলের পরে খালি জায়গা আছে, আগে নেই। Linked-এ FCB প্রথম আর শেষ block-এর ঠিকানা রাখে। একটি নতুন block যোগ করতে কতগুলো disk I/O (প্রতিটি block পড়া বা লেখা = ১) লাগবে — (ক) শুরুতে, (খ) মাঝখানে (৫০তম block-এর পরে), (গ) শেষে — contiguous, linked আর indexed-এ? নতুন block-এর ডেটা মেমরিতে আছে।
যোগ Contiguous Linked Indexed শুরুতে ২০১ ১ ১ মাঝখানে ১০১ ৫২ ১ শেষে ১ ৩ ১ Contiguous: শুরুতে যোগ করতে ১০০টি block এক ঘর পিছিয়ে দিতে হয় — প্রতিটি পড়া আর লেখা (২০০), তারপর নতুন block লেখা (১) = ২০১. মাঝখানে: ৫১ থেকে ১০০ নম্বর ৫০টি block সরানো (১০০) + নতুন (১) = ১০১. শেষে: শুধু নতুন block লেখা = ১. Linked: শুরুতে নতুন block লেখা, তার pointer পুরোনো প্রথম block-এ; FCB মেমরিতে বদলায় = ১. মাঝখানে: ৫০তম block পর্যন্ত পৌঁছাতে ৫০টি read, নতুন block লেখা (১), ৫০তম block-এর pointer বদলে আবার লেখা (১) = ৫২. শেষে: শেষ block পড়া (১), নতুন লেখা (১), শেষ block-এর pointer বদলে লেখা (১) = ৩. Indexed: যেকোনো জায়গায় শুধু নতুন data block লেখা = ১; index-এর entry সরানো মেমরিতেই (index block ডিস্কে ফেরত লেখাও গুনলে প্রতিটিতে ২)। অনুমান বদলালে সংখ্যা বদলায় — খাতায় অনুমান আগে লিখুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমLayered file system-এর স্তরগুলো ওপর (অ্যাপ্লিকেশন) থেকে নিচে (ডিভাইস) সাজান (▲▼ চেপে সরান)
- Application program
- Logical file system (metadata, FCB, অনুমতি)
- File-organisation module (logical → physical block)
- Basic file system (block পড়া-লেখার সাধারণ কমান্ড, cache)
- I/O control (device driver, interrupt handler)
- Device (ডিস্ক বা SSD)
একটি inode-এ ১২টি direct, একটি single, একটি double আর একটি triple indirect pointer. Block ৪ KiB, pointer ৪ byte. সর্বোচ্চ ফাইল-আকার প্রায় কত?
- ৪ GiB
- ১৬ GiB
- প্রায় ৪ TiB (৪৮ KiB + ৪ MiB + ৪ GiB + ৪ TiB)
- ৪৮ KiB + ৪ MiB + ৮ MiB + ১২ MiB
FAT32 volume-এ একটি ফাইলের সর্বোচ্চ আকার কত, আর কেন?
- ৩২ GB, কারণ Windows ৩২ GB-এর বড় FAT32 volume বানায় না
- ৪ GiB − ১ byte, কারণ ফাইলের আকার রাখার field ৩২ বিটের
- ২৫৬ MiB, কারণ cluster নম্বর ২৮ বিটের
- কোনো সীমা নেই, volume যত বড় ফাইলও তত বড়
Linked allocation-এ (FAT নয়) একটি ফাইলের FCB মেমরিতে আছে। ফাইলের ৩০তম block (০ থেকে গুনলে block ২৯) পড়তে কতগুলো disk access লাগবে?
- ১
- ২
- ২৯
- ৩০
১ TiB (২৪০ byte) ডিস্কে block ৪ KiB. প্রতিটি block-এর জন্য এক বিটের free-space bitmap কত বড়?
- ৩২ MiB
- ২৫৬ MiB
- ৮ MiB
- ৪ GiB
কোন file allocation পদ্ধতিতে external fragmentation হয়, আর সাধারণ প্রতিকার কী?
- Linked allocation; প্রতিকার doubly linked list
- Indexed allocation; প্রতিকার multilevel index
- Contiguous allocation; প্রতিকার compaction বা extent-ভিত্তিক নকশা
- FAT; প্রতিকার FAT-এর দ্বিতীয় কপি
Unified buffer cache মূলত কোন সমস্যার সমাধান?
- ডিস্কের external fragmentation
- একই ফাইল-ডেটা দুটি আলাদা cache-এ রাখা (double caching)
- Crash-এর পর ডেটা হারানো
- SSD-কে খালি block জানানো
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- স্তর আর কাঠামো: application → logical FS → file-organisation module → basic FS → I/O control → device. ডিস্কে boot block, superblock, inode table (FCB) আর data block; মেমরিতে mount table, directory cache, open-file table আর buffer cache.
- Contiguous: block i = start + i, এক access; কিন্তু external fragmentation আর আকার আগে জানার সমস্যা — আধুনিক রূপ extent.
- Linked আর FAT: fragmentation নেই, block i-তে i + ১টি access; FAT pointer-গুলো এক টেবিলে রেখে (cache করে) random access দ্রুত করে। FAT32: entry ৩২ বিট (২৮ বিট cluster নম্বর), ফাইল সর্বোচ্চ ৪ GiB − ১ byte.
- Indexed/inode: সর্বোচ্চ ফাইল = (১২ + n + n২ + n৩) × B, n = B ÷ p; ৪ KiB/৪ byte-এ ≈ ৪ TiB. Disk read = স্তর + ১ (inode মেমরিতে)। ext4 extent দিয়ে টানা অংশ এক entry-তে।
- Free space আর গতি: bitmap = block ÷ ৮ byte (১ TiB/৪ KiB-এ ৩২ MiB), প্রথম খালি = word-বিট × ভরা word + অবস্থান; linked list, grouping, counting, space map, TRIM. Block size-এ গতি বনাম অপচয়; unified buffer cache, read-ahead, free-behind, আর async লেখা বনাম fsync.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৪ · Crash consistency — fsck, journaling আর copy-on-write file systemপরবর্তী পর্বএকাধিক জায়গায় লেখার মাঝে crash হলে file system কীভাবে বাঁচে।
- অপারেটিং সিস্টেম · কোর্স হোম৬৪টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- পর্ব ৪২ · Directory, link আর mounting — VFSপেছনে ফিরেনাম থেকে inode, hard link আর mount.
- পর্ব ৪০ · ডিস্ক আর SSD — গঠন আর disk schedulingপেছনে ফিরেSeek, rotation আর SSD-র erase block — block size-এর হিসাবের ভিত্তি।
- পর্ব ৬০ · Numerical সমস্যা — scheduling, page replacement, Banker's আর diskপরীক্ষার প্রস্তুতিআরও numerical অনুশীলনী।
- DBMS · Storage internalsডেটাবেসDatabase কীভাবে নিজের page আর file সাজায়।
- DSA · B-treeডেটা স্ট্রাকচারExtent tree আর XFS-এর free-space tree-র ভিত্তি।