এই পর্বে যা শিখবেন
- Directory-র ছয়টি কাজ আর পাঁচ রকম গঠনের সুবিধা-অসুবিধা, cycle কেন বিপজ্জনক
- Hard link আর symlink-এর ভেতরের পার্থক্য — মোছা, নাম বদলানো, অন্য file system-এ সরানোর সময় কী ঘটে
- Directory-র ভেতরের গঠন (linear list, hash, B-tree) আর তার খরচের হিসাব
- Kernel কীভাবে একটি path-কে inode-এ পৌঁছে দেয়, আর mounting কীভাবে অনেক ডিস্ককে একটি গাছ বানায়
- VFS-এর চারটি বস্তু, আর Linux-এর ফোল্ডার-মানচিত্র (FHS)
১ · Directory আসলে কী — নাম থেকে inode-এর একটি তালিকা
পর্ব ৪১-এ দেখেছেন একটি ফাইল খোলা, পড়া আর লেখা কীভাবে হয়। কিন্তু open("/home/rahim/notes.txt") লিখলে kernel ফাইলটি খুঁজে পায় কীভাবে? উত্তর হলো directory — যাকে Windows-এ সাধারণত “ফোল্ডার” বলা হয়। আর cd, ls, absolute আর relative path ব্যবহারের নিয়ম আপনি পর্ব ০৩-এ (Linux টার্মিনাল) শিখেছেন; এখানে আমরা দেখব পর্দার পেছনে কী ঘটে।
একটি বড় লাইব্রেরির কথা ভাবুন। বইগুলো তাকে সাজানো, প্রতিটি তাকের একটি নম্বর আছে। প্রবেশপথে আছে ক্যাটালগ — প্রতিটি কার্ডে লেখা “বইয়ের নাম → তাক নম্বর”. বইয়ের ভেতরে কিন্তু ক্যাটালগের কথা লেখা থাকে না। Directory হলো এই ক্যাটালগ, তাক নম্বর হলো inode নম্বর, আর বই হলো ফাইলের আসল তথ্য। একই বইয়ের জন্য দুটি কার্ড রাখা যায় (“বাংলা ব্যাকরণ” আর “ব্যাকরণ, বাংলা”) — এটিই পরে দেখবেন hard link.
Unix-ধাঁচের file system-এ (ext4, XFS, APFS…) একটি ফাইলের সব তথ্য — মালিক, permission, আকার, সময়, ডেটা-ব্লকের ঠিকানা — থাকে তার inode-এ (inode-এর ভেতরের গঠন পর্ব ৪৩-এ)। Directory নিজেও একটি বিশেষ ধরনের ফাইল, যার ডেটা হলো কয়েকটি directory entry. ext4-এ প্রতিটি entry-তে থাকে inode নম্বর (৪ বাইট), entry-র দৈর্ঘ্য, নামের দৈর্ঘ্য, ফাইলের ধরন আর সর্বোচ্চ ২৫৫ বাইটের নাম। প্রতিটি directory-তে দুটি বিশেষ entry থাকে: . (নিজে) আর .. (মা-directory).
/home/rahim (inode 2050) — ডেটা-ব্লকে এই entry-গুলো:
inode type name
2050 dir .
131 dir ..
2101 dir docs
2207 file notes.txt
2300 link latestসব file system এমন নয়। FAT32 (পুরোনো USB stick, memory card) কোনো inode রাখে না — নাম, আকার, সময় আর প্রথম cluster-এর ঠিকানা সবই directory entry-র ৩২ বাইটের ভেতরে। তাই FAT-এ একই ফাইলের দুটি নাম (hard link) রাখা যায় না: তথ্য দুই জায়গায় থাকলে একটি বদলালে অন্যটি পুরোনো থেকে যেত। NTFS প্রতিটি ফাইলের জন্য Master File Table (MFT)-এ একটি রেকর্ড রাখে — ভূমিকায় inode-এর কাছাকাছি।
Directory-র ওপর ছয়টি মৌলিক কাজ
OS-এর বইগুলো সাধারণত directory-র ওপর এই কাজগুলোর কথা বলে। প্রতিটির পেছনে একটি বা দুটি system call আছে:
| কাজ | মানে | Linux system call | কমান্ড | Windows API (মোটামুটি) |
|---|---|---|---|---|
| Search (খোঁজা) | একটি নামের inode বের করা | পথ খোঁজার অংশ হিসেবে, যেমন stat, open | ls name | GetFileAttributes |
| Create (তৈরি) | নতুন entry যোগ | open(O_CREAT), mkdir, link, symlink | touch, mkdir, ln | CreateFile, CreateDirectory |
| Delete (মোছা) | entry সরানো | unlink, rmdir | rm, rmdir | DeleteFile, RemoveDirectory |
| List (তালিকা) | সব entry পড়া | getdents64 (libc-তে readdir) | ls | FindFirstFile/FindNextFile |
| Rename (নাম বদল) | entry-র নাম বা জায়গা বদল | rename, renameat2 | mv | MoveFileEx |
| Traverse (পুরো গাছ ঘোরা) | প্রতিটি sub-directory-তে ঢুকে সব দেখা | openat + getdents64 বারবার | find, du, tar | পুনরাবৃত্ত FindFirstFile |
একটি খুঁটিনাটি: rmdir শুধু খালি directory মোছে (. আর .. ছাড়া কিছু না থাকলে)। rm -r আসলে user-space-এ ভেতরের সবকিছু একে একে unlink করে, শেষে rmdir. পুরো গাছ একবারে মোছার কোনো system call kernel-এ নেই।
২ · Directory-র পাঁচ রকম গঠন — single-level থেকে general graph
Directory কেমন আকৃতির হবে, তা নিয়ে OS-এর ইতিহাসে কয়েকটি ধাপ এসেছে। বিশ্ববিদ্যালয় আর চাকরির পরীক্ষায় এই পাঁচটির তুলনা প্রায়ই আসে।
১. Single-level
পুরো ডিস্কে একটিই directory, সব ফাইল সেখানে। খুব সহজ, খোঁজাও সহজ। কিন্তু সব নাম আলাদা হতে হবে — দুজন user-ই report.txt রাখতে পারবেন না, আর হাজার ফাইলে গুছিয়ে রাখার উপায় নেই। প্রথম দিকের ছোট সিস্টেমে, আর আজও কিছু ক্যামেরা বা embedded যন্ত্রের সমতল ফাইল-তালিকায় এমন দেখা যায়।
২. Two-level (MFD/UFD)
ওপরে একটি Master File Directory (MFD), যেখানে প্রতিটি user-এর নামের পাশে তাঁর নিজস্ব User File Directory (UFD)-র ঠিকানা। দুই user একই নাম রাখতে পারেন, একে অপরের ফাইল থেকে আলাদাও থাকেন। কিন্তু ভাগাভাগি কঠিন, আর এক user-এর ভেতরে আবার ভাগ করা যায় না। সাধারণ প্রোগ্রাম (compiler ইত্যাদি) রাখতে একটি বিশেষ “system user” আর search path লাগে।
সুবিধা: user আলাদা · অসুবিধা: ভাগাভাগি আর গোছানো কঠিন৩. Tree-structured
যেকোনো গভীরতার গাছ — আজকের সব OS-এর ভিত্তি। প্রতিটি ফাইলের একটিই পূর্ণ ঠিকানা (absolute path), আর প্রতিটি process-এর নিজস্ব current working directory, যেখান থেকে relative path হিসাব হয়। এক process cd করলে অন্যটির cwd বদলায় না — তাই shell-এর cd একটি builtin, আলাদা প্রোগ্রাম নয়।
৪. Acyclic graph
গাছ, কিন্তু একটি ফাইল বা sub-directory একাধিক directory থেকে দেখা যায় — link দিয়ে। দুই সহকর্মী একই প্রজেক্ট-ফোল্ডার নিজের নিজের জায়গা থেকে খুলতে পারেন। শর্ত একটাই: কোনো চক্র (cycle) থাকবে না।
সুবিধা: ভাগাভাগি · অসুবিধা: aliasing, ঝুলে থাকা link, মোছার হিসাব৫. General graph
Link দিয়ে cycle-ও তৈরি করা যায় — একটি directory তার নিজের পূর্বপুরুষকে ধরে রাখতে পারে। সবচেয়ে নমনীয়, কিন্তু গাছ ঘোরা অসীম লুপে পড়তে পারে, আর কোন ফাইল আর কারও কাজে লাগে না তা বুঝতে garbage collection লাগে।
সুবিধা: পূর্ণ নমনীয়তা · অসুবিধা: লুপ, GC-র খরচAcyclic graph-এর তিনটি সমস্যা
- Aliasing: একই ফাইলের একাধিক নাম।
duবা backup প্রোগ্রাম সাবধান না হলে একই ফাইল দুবার গুনবে বা দুবার কপি করবে। - Dangling pointer: link যদি শুধু একটি path মনে রাখে (symlink), আর আসল ফাইল মুছে যায় — link এখন এমন কিছুকে দেখায় যা নেই। একে বলে dangling link. আরও খারাপ: পরে একই নামে অন্য কোনো ফাইল তৈরি হলে link চুপচাপ সেটিকে দেখাবে।
- মোছার হিসাব: একটি নাম মুছলে ফাইলটি কি সত্যিই মুছবে? সমাধান reference count: inode গোনে তাকে কতগুলো নাম দেখায় (link count). একটি নাম মুছলে গণনা এক কমে; শূন্য হলে (আর কোনো process ফাইলটি খোলা না রাখলে) তবেই ডেটা মুক্ত হয়। Unix এই পথই নিয়েছে —
rmআসলেunlink, অর্থাৎ “একটি নাম সরানো”.
General graph: কেন reference count আর যথেষ্ট নয়
ধরুন /a/b একটি directory, আর তার ভেতরে loop নামে একটি hard link আছে যা আবার /a-কে দেখায়। এবার / থেকে a নামটি মুছে দিলেন। এখন a আর b একে অপরকে ধরে রেখেছে — দুজনের link count শূন্য নয়, অথচ মূল / থেকে কোনো পথে আর তাদের কাছে পৌঁছানো যায় না। Reference count দিয়ে এই জায়গা কখনো ফেরত আসবে না।
যুক্তিটি সাধারণভাবে বলা যায়: reference count একটি বস্তু মুক্ত করে তখনই, যখন তার দিকে আসা সব তীর চলে যায়। একটি cycle-এর প্রতিটি সদস্যের দিকে অন্তত একটি তীর cycle-এর ভেতর থেকেই আসে, তাই বাইরের সব নাম মুছলেও কোনো সদস্যের গণনা শূন্য হয় না। এমন জায়গা উদ্ধারের একমাত্র উপায় garbage collection: / থেকে পৌঁছানো যায় এমন সবকিছুতে দাগ দেওয়া (mark), তারপর দাগহীন সবকিছু মুক্ত করা (sweep). ডিস্কের লক্ষ লক্ষ inode-এ এটি চালানো অত্যন্ত ধীর। আরেকটি সমস্যা: cycle থাকলে ..-র মানে অস্পষ্ট — একাধিক মা থাকলে কোনটি?
(১) সাধারণ user directory-র hard link বানাতে পারেন না — link() directory-তে EPERM দেয়; শুধু mkdir নিজে . আর .. বানায়। তাই hard link-এর গ্রাফ সবসময় acyclic থাকে। (২) Cycle শুধু symlink দিয়ে সম্ভব, আর symlink link count বাড়ায় না — মুছলে শুধু ঝুলে থাকে, জায়গা আটকে রাখে না। (৩) গাছ-ঘোরা টুল ডিফল্টভাবে symlink অনুসরণ করে না (find-এর -P, du); find -L দিলে GNU find প্রতিটি পূর্বপুরুষ directory-র (device, inode) জোড়া মনে রাখে, একই জোড়া আবার এলে “File system loop detected” বলে থামে। du-ও (device, inode) মনে রেখে একাধিক নামের ফাইল একবারই গোনে।
৩ · Hard link আর symbolic link — একই ফাইলের দুই রকম দ্বিতীয় নাম
এখন আসল মজার অংশ। দুই রকম link-এর পার্থক্য বুঝলে আপনি backup, deployment, package manager আর container-এর অনেক রহস্য বুঝবেন।
🔗 Hard link
Hard link হলো একই inode-এর দিকে আরেকটি directory entry. কোনটি “আসল” আর কোনটি “link” — এমন কোনো পার্থক্যই নেই; দুটি নামই সমান। inode-এর link count বাড়ে। একই file system-এর ভেতরেই শুধু সম্ভব, কারণ inode নম্বরের মানে শুধু নিজের file system-এ।
ln target name
↪️ Symbolic (soft) link
Symlink একটি আলাদা, ছোট ফাইল — নিজের inode — যার ডেটা হলো একটি path লেখা। খোলার সময় kernel সেই path ধরে আবার খোঁজে। Directory, অন্য file system, এমনকি না-থাকা ফাইলকেও দেখাতে পারে।
ln -s target name · readlink name
| পরিস্থিতি | Hard link | Symbolic link |
|---|---|---|
| নিজের inode? | না — লক্ষ্যের inode-ই | হ্যাঁ, আলাদা (ধরন l) |
| link count | বাড়ে | লক্ষ্যের গণনা বদলায় না |
| আসল নামটি মুছলে | ডেটা টিকে থাকে | ঝুলে যায় (dangling), No such file |
| লক্ষ্যের নাম বদলালে বা সরালে (একই FS) | কিছুই হয় না | ভেঙে যায় |
| link-টিকে নিজেকেই অন্য directory-তে সরালে | কিছুই হয় না | relative path থাকলে ভাঙতে পারে; absolute হলে না |
| অন্য file system-এ | অসম্ভব — EXDEV (“Invalid cross-device link”) | সম্ভব |
| Directory-কে | নিষেধ (cycle এড়াতে) | সম্ভব |
| permission | inode-এর, তাই সব নামের জন্য এক | link-এর নিজের permission সাধারণত অর্থহীন (lrwxrwxrwx); লক্ষ্যের permission খাটে |
নিচের ডায়াগ্রামে একটি ফাইল, তার একটি hard link আর একটি symlink — তারপর মোছা আর নতুন করে তৈরি। inode নম্বর কাল্পনিক; “পরে” চেপে প্রতিটি ঘর খেয়াল করুন।
| কমান্ড | |
| directory-র entry | |
| inode 501 (ডেটা “hi”) | |
| inode 502 (symlink) | |
| inode 503 (ডেটা “new”) | |
| cat hard.txt | |
| cat soft.txt |
- একটি ফাইল, একটি নাম।
original.txtতৈরি হলো। kernel একটি নতুন inode (501) দিল, ডেটা “hi”, আর directory-তে entry বসালoriginal.txt → 501. link count ১. - Hard link — নতুন inode নয়।
lnশুধু directory-তে আরেকটি entry লিখল,hard.txt → 501, আর inode 501-এর link count হলো ২. এখন দুটি নাম সমান — কে “আগে” তৈরি, inode তা জানেই না। - Symlink — আলাদা inode.
ln -sনতুন inode 502 বানাল, ধরন symlink, যার ভেতরে শুধু ১২ বাইটের লেখা “original.txt”. inode 501-এর link count বদলায়নি — ২-ই আছে।cat soft.txtলিখলে kernel ঐ লেখাটি পড়ে আবার খোঁজে, পৌঁছায় 501-এ। rm original.txt— একটি নাম গেল।rmআসলেunlink: entry মুছল, 501-এর link count হলো ১. ডেটা অক্ষত,hard.txtদিয়ে এখনো “hi” পড়া যায়। কিন্তুsoft.txt-এর ভেতরের লেখা “original.txt” নামে আর কিছু নেই — symlink এখন dangling.- একই নামে নতুন ফাইল।
original.txtআবার তৈরি হলো — কিন্তু এটি নতুন inode 503, ডেটা “new”. symlink শুধু নাম মনে রাখে, তাই এখন চুপচাপ 503 দেখায়:cat soft.txtদেয় “new”. আর hard link মনে রাখে inode, তাইhard.txtএখনো পুরোনো “hi”. এটিই দুই link-এর মূল দর্শন: নাম বনাম পরিচয়। - শেষ নামও গেল — inode মুক্ত।
rm hard.txt-এ 501-এর link count ০. কোনো process ফাইলটি খোলা না রাখলে kernel এখন inode আর তার ডেটা-ব্লক ফাঁকা হিসেবে চিহ্নিত করে। (কেউ খোলা রাখলে মুক্তি হয় শেষclose-এর পর — log ফাইল মুছেও ডিস্ক না খালি হওয়ার কারণ এটাই।)
এবার আসল Linux-এ একই পরীক্ষা। inode নম্বর প্রতি রানে আলাদা হবে, তাই script নম্বরের বদলে সম্পর্ক ছাপে — “একই inode” নাকি “আলাদা”. শেষে দুটি সীমাও দেখা যাবে: symlink-এর লুপ আর directory-র hard link.
rm -rf original.txt hard.txt soft.txt loopA loopB dir1 dirlink
echo "hi" > original.txt
ln original.txt hard.txt # hard link: a second name for the same inode
ln -s original.txt soft.txt # symlink: a tiny file that stores a path
ls -li --time-style=+%F original.txt hard.txt soft.txt
ino() { stat -c %i "$1"; } # stat does not follow a symlink
[ "$(ino original.txt)" = "$(ino hard.txt)" ] && echo "hard.txt: SAME inode, link count $(stat -c %h hard.txt)"
[ "$(ino original.txt)" != "$(ino soft.txt)" ] && echo "soft.txt: OWN inode, stores '$(readlink soft.txt)' ($(stat -c %s soft.txt) bytes)"
echo "--- rm original.txt ---"
rm original.txt
echo "hard.txt: $(cat hard.txt) (link count now $(stat -c %h hard.txt))"
cat soft.txt 2>&1 || echo "soft.txt is dangling (cat exit $?)"
echo "--- create a NEW original.txt ---"
echo "new" > original.txt
echo "soft.txt: $(cat soft.txt) hard.txt: $(cat hard.txt)"
echo "--- limits ---"
ln -s loopB loopA; ln -s loopA loopB
cat loopA 2>&1 || true
mkdir dir1; ln dir1 dirlink 2>&1 || true
echo "dir1 link count: $(stat -c %h dir1)"আউটপুট (এক রানের; inode নম্বর আর তারিখ প্রতি রানে আলাদা):
4705992 -rw-r--r-- 2 wandbox wandbox 3 2026-10-25 hard.txt
4705992 -rw-r--r-- 2 wandbox wandbox 3 2026-10-25 original.txt
4705993 lrwxrwxrwx 1 wandbox wandbox 12 2026-10-25 soft.txt -> original.txt
hard.txt: SAME inode, link count 2
soft.txt: OWN inode, stores 'original.txt' (12 bytes)
--- rm original.txt ---
hard.txt: hi (link count now 1)
cat: soft.txt: No such file or directory
soft.txt is dangling (cat exit 1)
--- create a NEW original.txt ---
soft.txt: new hard.txt: hi
--- limits ---
cat: loopA: Too many levels of symbolic links
ln: dir1: hard link not allowed for directory
dir1 link count: 2
(১) ls -li-এর প্রথম কলাম inode নম্বর: hard.txt আর original.txt একই (এই রানে 4705992; আপনার রানে অন্য সংখ্যা আসবে, কিন্তু দুটি মিলবে), তৃতীয় কলামে link count 2. soft.txt-এর inode আলাদা, ধরন l, link count 1, আর আকার ১২ বাইট — “original.txt” লেখাটির ঠিক ১২টি অক্ষর; symlink-এর “ডেটা” মানে শুধু এই path. (ls নাম বর্ণানুক্রমে সাজায়, তাই hard.txt আগে।)
(২) rm-এর পর hard link দিয়ে এখনো “hi” পড়া যায়, link count 1. symlink-এর cat ব্যর্থ — stepper-এর ধাপ ৪. (৩) একই নামে নতুন ফাইল আসতেই symlink “new” দেখায়, hard link পুরোনো “hi” — ধাপ ৫. (৪) দুটি symlink একে অপরকে দেখালে kernel সীমা পার হয়ে ELOOP দেয়। directory-র hard link-এ coreutils-এর ln নিজেই আগে থামায়; সরাসরি link() ডাকলেও kernel EPERM দিত। নতুন খালি dir1-এর link count 2 — মা-directory-তে তার নাম আর তার নিজের ..
নাম বদল, সরানো আর অন্য file system
একই file system-এর ভেতরে mv আসলে একটি rename() system call: পুরোনো entry সরিয়ে নতুন entry বসানো, inode আর ডেটা একটুও নড়ে না — তাই ২০ GB-র ভিডিও ফাইলও চোখের পলকে “সরে”. POSIX বলে rename() atomic: অন্য কেউ হয় পুরোনো নামটি দেখবেন, নয়তো নতুনটি — মাঝামাঝি কিছু নয়।
কিন্তু /home থেকে USB stick-এ (/media/usb) সরালে rename() EXDEV ফেরত দেয়, কারণ inode নম্বর অন্য file system-এ অর্থহীন। তখন mv নিজে থেকে পুরো ডেটা কপি করে, তারপর পুরোনোটি মোছে। ফলাফল: সময় লাগে, কাজটি আর atomic নয় (মাঝপথে বিদ্যুৎ গেলে দুই জায়গায় অর্ধেক থাকতে পারে), আর ফাইলের অন্য hard link-গুলোর সাথে সম্পর্ক ছিন্ন হয় — নতুন জায়গায় এটি এক-নামের নতুন ফাইল। Symlink কপি হয় symlink হিসেবেই, ভেতরের path না বদলে; relative path হলে নতুন জায়গায় ভেঙে যেতে পারে।
Symlink loop আর ELOOP: loopA → loopB আর loopB → loopA হলে খোঁজা কখনো শেষ হতো না। তাই kernel একটি path খোঁজার সময় symlink অনুসরণের সংখ্যা গোনে; Linux-এ সীমা ৪০, পার হলে ELOOP (“Too many levels of symbolic links”). এটি cycle ধরার বুদ্ধিমান পদ্ধতি নয়, শুধু একটি গণনার সীমা — ৪১টি বৈধ symlink-এর শিকলও একই error দেবে।
৪ · Windows আর macOS-এ link — shortcut, junction আর alias
“Windows-এ তো shortcut আছে, ওটাই কি symlink?” — না, আর পার্থক্যটা গুরুত্বপূর্ণ। Windows-এ চার রকম জিনিস আছে, আর এদের আচরণ সংস্করণ ও সেটিংসভেদে একটু বদলায়; নিচের সারাংশ সাধারণ ক্ষেত্রের জন্য:
| জিনিস | কে বোঝে | কী দেখাতে পারে | তৈরি |
|---|---|---|---|
Shortcut (.lnk) | শুধু Explorer/shell — file system-এর কাছে এটি একটি সাধারণ ফাইল | যেকোনো ফাইল, ফোল্ডার, এমনকি নেটওয়ার্ক | ডান-ক্লিক → Create shortcut |
| NTFS hard link | file system | শুধু ফাইল, একই volume-এ | mklink /H new target |
| Junction | file system (reparse point) | শুধু directory, স্থানীয় volume-এর absolute path | mklink /J new target |
| Symbolic link | file system (reparse point, Vista থেকে) | ফাইল বা directory, relative বা অন্য volume/নেটওয়ার্ক | mklink বা mklink /D; সাধারণত admin অধিকার বা Developer Mode লাগে |
একটি C প্রোগ্রাম CreateFile দিয়ে .lnk খুললে shortcut-এর লক্ষ্য নয়, ঐ .lnk ফাইলের বাইনারি ডেটাই পাবে — kernel এটিকে link বলে চেনে না। পরিচিত একটি junction-এর উদাহরণ: Vista থেকে পুরোনো প্রোগ্রামের সুবিধার জন্য C:\Documents and Settings একটি junction, যা C:\Users-কে দেখায়। macOS-এ symlink আর hard link দুই-ই আছে (BSD ঐতিহ্য), আর Finder-এর “alias” Windows shortcut-এর মতো একটি আলাদা ফাইল — তবে সেটি ফাইলের ভেতরের পরিচয়ও মনে রাখে, তাই লক্ষ্য সরালেও অনেক সময় খুঁজে পায়।
PS> cmd /c mklink /H copy2.txt notes.txt
Hardlink created for copy2.txt <<===>> notes.txt
PS> cmd /c mklink /J proj D:\work\project
Junction created for proj <<===>> D:\work\project
PS> fsutil hardlink list notes.txt
\Users\rahim\notes.txt
\Users\rahim\copy2.txt৫ · Directory-র ভেতরের বাস্তবায়ন — linear list, hash table আর B-tree
একটি directory-তে নাম খোঁজার গতি নির্ভর করে entry-গুলো কীভাবে সাজানো তার ওপর। ছোট directory-তে কোনো পার্থক্য চোখে পড়ে না; কিন্তু একটি mail server বা ছবির সার্ভারে এক directory-তে লাখো ফাইল থাকতে পারে।
| পদ্ধতি | নাম খোঁজা | যোগ / মোছা | সাজানো তালিকা | কোথায় |
|---|---|---|---|---|
| Linear list | O(n) — শুরু থেকে একে একে | যোগ সস্তা, কিন্তু আগে নামটি আছে কি না খুঁজতে O(n) | আলাদা করে সাজাতে হয় | ext2, dir_index ছাড়া ext3/ext4-এর ছোট directory, FAT |
| Hash table | গড়ে O(1) | গড়ে O(1); collision আর আকার বাড়ানো সামলাতে হয় | নেই — hash-এর ক্রম এলোমেলো | ext3/ext4-এর htree (hash-কে চাবি ধরে একটি অগভীর গাছ) |
| B-tree / B+ tree | O(log n), প্রতিটি node একটি ডিস্ক-ব্লক | O(log n), গাছ নিজে ভারসাম্য রাখে | নামের ক্রমে বিনামূল্যে | NTFS-এর directory index, XFS-এর বড় directory, Btrfs, APFS |
ext4-এর htree আসলে দুটির মিশ্রণ: নামের hash হিসাব করে, hash-কে চাবি ধরে একটি অগভীর B-tree-ধাঁচের index-এ খোঁজে, আর শেষ ধাপের leaf block-গুলো আগের মতোই সাধারণ linear entry. ফল: index-এর গভীরতা সাধারণত ১ বা ২ স্তর (বড় directory-র জন্য large_dir feature-এ ৩), তাই এক লাখ ফাইলেও কয়েকটি ব্লক পড়লেই নাম মেলে। একটি পার্শ্বফল: ext4-এ getdents64 entry দেয় hash-এর ক্রমে, তাই ls -f (না-সাজানো) এলোমেলো দেখায়; সাধারণ ls নিজেই নামগুলো সাজিয়ে দেখায়।
একটি directory-তে ১০,০০,০০০ (দশ লাখ) entry, প্রতিটি ৪ KiB ব্লকে গড়ে ২০০টি entry ধরে নিই (ছোট নাম)। মোট leaf block = ১০,০০,০০০ ÷ ২০০ = ৫,০০০.
- Linear list: আছে এমন নাম খুঁজতে গড়ে অর্ধেক পড়তে হয় — ২,৫০০ ব্লক; নেই এমন নাম (যেমন নতুন ফাইল তৈরির আগে যাচাই) — পুরো ৫,০০০ ব্লক।
- Hash-ভিত্তিক গাছ (htree-ধাঁচ), প্রতিটি index ব্লকে ~৫০০ চাবি: এক স্তরে ৫০০ leaf চেনা যায়, দুই স্তরে ৫০০ × ৫০০ = ২,৫০,০০০. ৫,০০০ leaf-এর জন্য দুই স্তর যথেষ্ট — মোট পড়া = ২ index ব্লক + ১ leaf = ৩ ব্লক.
- অনুপাত: গড়ে ২,৫০০ ÷ ৩ ≈ ৮৩৩ গুণ কম ডিস্ক-ব্লক। (সংখ্যাগুলো ধারণার জন্য; আসল entry-সংখ্যা নামের দৈর্ঘ্যের ওপর নির্ভর করে, আর dentry cache-এ থাকলে ডিস্কই লাগে না।)
৬ · Path-name resolution — kernel কীভাবে একটি path খোঁজে
প্রতিটি open, stat, execve-এর শুরুতে kernel-কে একটি লেখা path থেকে একটি inode-এ পৌঁছাতে হয়। ক্লাসিক Unix-এ এই function-এর নাম ছিল namei (“name to inode”); Linux-এ কাজটি করে fs/namei.c-এর link_path_walk আর সঙ্গী function-গুলো। নিয়মগুলো:
- Path
/দিয়ে শুরু হলে খোঁজা শুরু process-এর root directory থেকে, নইলে তার cwd থেকে (দুটোই process-এর নিজস্ব,chrootআর container এ কারণেই কাজ করে)। - Path-কে
/দিয়ে component-এ ভাঙা; একাধিক/একটি হিসেবে ধরা। - প্রতিটি মাঝের directory-তে execute (x) বা “search” permission লাগে; শুধু read (r) থাকলে নামের তালিকা দেখা যায়, কিন্তু ভেতরে ঢোকা যায় না (খুঁটিনাটি পর্ব ৪৭-এ)।
.মানে একই জায়গা,..মানে মা-directory; root-এর..root নিজেই।- কোনো component symlink হলে তার ভেতরের path পড়ে বাকি path-এর আগে বসিয়ে খোঁজা চলতে থাকে (শেষ component-এ অনুসরণ হবে কি না, তা system call আর flag-এর ওপর নির্ভর করে, যেমন
lstatবাO_NOFOLLOW). - কোনো component একটি mount point হলে খোঁজা ঐ জায়গায় mount করা file system-এর root-এ লাফ দেয় (অংশ ৭)।
এত কাজ প্রতিবার ডিস্কে করলে কম্পিউটার অচল হতো। তাই Linux রাখে dentry cache: (মা-directory, নাম) → inode-এর একটি বড় hash table, RAM-এ। এমনকি “এই নামে কিছু নেই” ফলটিও মনে রাখা হয় (negative dentry) — PATH-এর প্রতিটি ফোল্ডারে একটি কমান্ড খোঁজার সময় এটি খুব কাজে লাগে।
| এখন খোঁজা হচ্ছে | |
| কোন directory-তে | |
| permission যাচাই | |
| dentry cache | |
| হাতে এখন |
- শুরুর বিন্দু। Path
/দিয়ে শুরু, তাই kernel process-এর root directory নেয় (task-এরfs->root). ext4-এ root directory-র inode নম্বর সবসময় ২ — ০ “কিছু নেই” বোঝায়, আর ১ খারাপ ব্লকের তালিকার জন্য সংরক্ষিত। বাকি path:home/rahim/docs/notes.txt, চারটি component. - “home” খোঁজা। আগে
/-এ x permission যাচাই (সবার আছে)। তারপর dentry cache-এ (inode 2, “home”) খোঁজা — hit, সাথে সাথে inode 131. ডিস্ক ছোঁয়াই লাগেনি। - “rahim” খোঁজা।
/home-এ x যাচাই ✓, cache hit → inode 2050. কোনো component mount point হলে এখানেই অন্য file system-এ লাফ হতো; এখানে তা নয়। - “docs” — cache miss.
/home/rahim-এর permissiondrwx------: শুধু মালিক ঢুকতে পারেন। process-টি rahim-এর, তাই x ✓ (অন্য user হলে এখানেইEACCES). Cache-এ নেই, তাই ext4-এরlookupডাকা হলো — htree দিয়ে directory ব্লক পড়ে inode 2101 পাওয়া গেল, আর নতুন dentry cache-এ বসল। পরের বার এটি hit. - শেষ component “notes.txt”.
docs-এ x ✓, cache hit → inode 2207, একটি regular file. এটি শেষ component, তাই এখনopen-এর অনুরোধ অনুযায়ী ফাইলটির নিজের permission দেখা হয় —O_RDONLY, তাই r ✓. মোট চারটি directory-তে x আর একটি ফাইলে r যাচাই হলো। - ফল — একটি file descriptor. kernel একটি
struct fileবানায় (offset ০, mode পড়া, আর inode-এর file_operations — অংশ ৮), process-এর fd টেবিলের প্রথম ফাঁকা ঘরে বসায় (পর্ব ৪১), আর ফেরত দেয় ৩. এরপরread(3, …)আর path খোঁজে না।
এবার user-space থেকে একই কাজ নিজ হাতে: প্রোগ্রামটি দুটি directory আর একটি symlink বানায়, তারপর প্রতিটি component fstatat(…, AT_SYMLINK_NOFOLLOW) দিয়ে পরীক্ষা করে (এটিই lstat-এর আচরণ — symlink নিজেকে দেখায়), আর openat দিয়ে এক ধাপ এগোয়, ঠিক kernel-এর মতো এক directory থেকে পরেরটিতে।
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static ino_t walk(const char *path) { /* resolve one component at a time */
char buf[256], tgt[256];
struct stat st;
int n = 0, dfd = open(".", O_PATH | O_DIRECTORY);
printf("walk %s\n", path);
snprintf(buf, sizeof buf, "%s", path);
for (char *c = strtok(buf, "/"); c; c = strtok(NULL, "/"), n++) {
if (fstatat(dfd, c, &st, AT_SYMLINK_NOFOLLOW) != 0) { perror(c); return 0; }
ssize_t k = readlinkat(dfd, c, tgt, sizeof tgt - 1); /* -1 if not a symlink */
tgt[k > 0 ? k : 0] = '\0';
printf(" %d. %-6s %s%s\n", n + 1, c, S_ISLNK(st.st_mode) ? "symlink, follow -> " :
S_ISDIR(st.st_mode) ? "directory" : "regular file", tgt);
int next = openat(dfd, c, O_PATH); /* O_PATH without O_NOFOLLOW follows links */
close(dfd);
dfd = next;
}
fstat(dfd, &st);
close(dfd);
printf(" components looked up: %d\n", n);
return st.st_ino;
}
int main(void) {
mkdir("d1", 0755); mkdir("d1/d2", 0755); unlink("lnk");
close(open("d1/d2/f.txt", O_CREAT | O_WRONLY, 0644));
if (symlink("d1/d2", "lnk") != 0) perror("symlink");
ino_t a = walk("d1/d2/f.txt"), b = walk("lnk/f.txt");
printf("same file at the end? %s\n", a == b ? "yes, same inode" : "no");
return 0;
}আউটপুট:
walk d1/d2/f.txt
1. d1 directory
2. d2 directory
3. f.txt regular file
components looked up: 3
walk lnk/f.txt
1. lnk symlink, follow -> d1/d2
2. f.txt regular file
components looked up: 2
same file at the end? yes, same inode
(১) প্রথম path-এ তিনটি component, প্রতিটির জন্য একটি fstatat আর একটি openat — kernel-এর namei-ও ঠিক এভাবে এক directory থেকে পরেরটিতে যায়, প্রতি ধাপে permission দেখে। (২) দ্বিতীয় path-এ লেখা আছে মাত্র দুটি component, কিন্তু lnk একটি symlink: AT_SYMLINK_NOFOLLOW (অর্থাৎ lstat-এর আচরণ) থাকায় আমরা link-টিকেই দেখলাম, আর readlinkat তার ভেতরের “d1/d2” পড়ল। তারপর openat(…, O_PATH) link অনুসরণ করল — kernel ভেতরে ভেতরে d1 আর d2 খুঁজে নিল। (৩) দুই পথে শেষ inode একই: একই ফাইলে পৌঁছানোর দুটি নাম — aliasing. (৪) সাধারণ ফাইল বা directory-তে readlinkat −1 দেয় (EINVAL), তাই সেখানে লক্ষ্যের লেখা খালি। O_PATH fd শুধু “জায়গা” চিহ্নিত করে, পড়ার অনুমতি লাগে না — container runtime-এর মতো প্রোগ্রাম নিরাপদে path খুঁজতে এই ধরনের fd ব্যবহার করে।
শেষে আরেকটি সূক্ষ্ম বিষয়, যা অভিজ্ঞদেরও ধোঁকা দেয়: kernel-এর .. “physical” — symlink অনুসরণের পর আসল জায়গার মা-directory. ধরুন ~/proj একটি symlink যা /etc দেখায়। ls ~/proj/.. দেখাবে /-এর ভেতর, আপনার home নয়! অথচ bash-এর cd ~/proj/.. আপনাকে home-এ ফেরত আনে, কারণ shell নিজে “logical” path মনে রাখে (cd -P দিলে physical). নিচের ছোট simulator একটি কাল্পনিক file system-এ namei-র সব নিয়ম — symlink, .., mount আর ELOOP — একসাথে দেখায়:
// A tiny namei: inode table, symlinks, "..", and one mounted USB file system
const I = {
2: { e: { home: 10, etc: 30, mnt: 40 } }, 10: { e: { rahim: 11 } }, // "/" and /home
11: { e: { docs: 12, latest: 14, proj: 15, loop: 16 } }, 12: { e: { 'notes.txt': 13 } },
14: { to: 'docs/notes.txt' }, 15: { to: '/etc' }, 16: { to: 'loop' }, // symlinks
30: { e: { hosts: 31 } }, 40: { e: { 'old.txt': 41 } }, // /etc and /mnt
900: { e: { 'photo.jpg': 901 } }, // root of the USB fs
}; // inodes not listed (13, 31, 41, 901) are regular files
const mounts = { 40: 900 }; // as if: mount /dev/sdb1 /mnt
let follows = 0;
function walk(path, stack) {
stack = path.startsWith('/') ? [2] : stack.slice();
const parts = path.split('/').filter(Boolean);
for (let i = 0; i < parts.length; i++) {
const name = parts[i], dir = I[stack[stack.length - 1]];
if (!dir || !dir.e) return 'ENOTDIR';
if (name === '.') continue;
if (name === '..') { if (stack.length > 1) stack.pop(); continue; }
let ino = dir.e[name];
if (ino === undefined) return 'ENOENT';
if (mounts[ino]) ino = mounts[ino]; // cross into the mounted fs
if (I[ino] && I[ino].to) { // a symlink: splice its path in
if (++follows > 40) return 'ELOOP'; // Linux gives up after 40
return walk([I[ino].to, ...parts.slice(i + 1)].join('/'), stack);
}
stack.push(ino);
}
return 'inode ' + stack[stack.length - 1];
}
for (const p of ['/home/rahim/docs/notes.txt', '/home/rahim/latest', '/home/rahim/proj/hosts',
'/home/rahim/proj/..', '/mnt/photo.jpg', '/mnt/old.txt', '/mnt/..', '/home/rahim/loop',
'/home/rahim/docs/notes.txt/x', '//home/./rahim/../rahim/docs']) {
follows = 0;
console.log(p.padEnd(30), '->', walk(p, [2]).padEnd(10), 'symlinks followed:', follows);
}আউটপুট:
/home/rahim/docs/notes.txt -> inode 13 symlinks followed: 0
/home/rahim/latest -> inode 13 symlinks followed: 1
/home/rahim/proj/hosts -> inode 31 symlinks followed: 1
/home/rahim/proj/.. -> inode 2 symlinks followed: 1
/mnt/photo.jpg -> inode 901 symlinks followed: 0
/mnt/old.txt -> ENOENT symlinks followed: 0
/mnt/.. -> inode 2 symlinks followed: 0
/home/rahim/loop -> ELOOP symlinks followed: 41
/home/rahim/docs/notes.txt/x -> ENOTDIR symlinks followed: 0
//home/./rahim/../rahim/docs -> inode 12 symlinks followed: 0
লাইন ধরে: (১) সরাসরি path আর latest symlink — দুটোই inode 13, দ্বিতীয়টিতে ১টি symlink অনুসরণ (relative লক্ষ্য docs/notes.txt হিসাব হলো link-টি যে directory-তে আছে, /home/rahim থেকে)। (২) proj → /etc (absolute), তাই proj/hosts = /etc/hosts, আর proj/.. = inode 2, অর্থাৎ / — /home/rahim নয়; kernel-এর physical ... (৩) /mnt/photo.jpg USB-র file system থেকে (inode 901), আর /mnt/old.txt এখন ENOENT — mount-এর নিচে চাপা পড়েছে, মোছেনি। /mnt/.. আবার root fs-এর /. (৪) loop নিজেকে দেখায়: ৪০টি অনুসরণের পর ৪১তম-তে ELOOP. (৫) একটি সাধারণ ফাইলের “ভেতরে” x খুঁজলে ENOTDIR. (৬) একাধিক /, . আর .. মিলেও শেষে সঠিক docs (inode 12). আসল kernel-এ ঠিক এই নিয়মগুলোই, সাথে প্রতি ধাপে permission আর dentry cache.
৭ · Mounting — অনেক file system, একটিই গাছ
আপনার ল্যাপটপে হয়তো একটি SSD, একটি USB stick, আর kernel-এর তৈরি কয়েকটি “কাল্পনিক” file system (/proc, /sys) আছে। Windows প্রতিটি volume-কে একটি আলাদা অক্ষর দেয় — C:, D:, E:. Unix-এর দর্শন ভিন্ন: একটিই গাছ, যার শিকড় /, আর প্রতিটি নতুন file system গাছের কোনো একটি directory-তে “জুড়ে দেওয়া” হয়। এই জুড়ে দেওয়ার নাম mount, আর ঐ directory হলো mount point.
অফিসের একটি বড় ফাইল ক্যাবিনেট ভাবুন, যার একটি ড্রয়ারে লেবেল “শাখা অফিস”. শাখা অফিস থেকে পুরো একটি ছোট ক্যাবিনেট এলে সেটি ঐ ড্রয়ারের জায়গায় বসিয়ে দেওয়া হলো। এখন “শাখা অফিস” ড্রয়ার খুললেই নতুন ক্যাবিনেটের ফাইল — আর ড্রয়ারে আগে যা ছিল, তা নতুন ক্যাবিনেটের নিচে চাপা পড়ে, মোছে না। ক্যাবিনেট সরিয়ে নিলে পুরোনো জিনিস আবার দেখা যায়।
Kernel প্রতিটি mount-এর রেকর্ড রাখে একটি mount table-এ (Linux-এ প্রতিটি mount namespace-এর নিজস্ব; container এ কারণেই আলাদা দৃশ্য পায় — পর্ব ৫১)। নিচে একটি USB stick-কে /mnt-এ mount করার পুরো গল্প:
| ঘটনা | |
| mount table | |
| /mnt-এ ঢুকলে পৌঁছায় | |
| ls /mnt | |
| kernel-এর কাজ |
- শুরু। mount table-এ একটিই সারি: SSD-র partition
/-এ, ext4./mntএকটি সাধারণ directory (inode 40), যার ভেতরেold.txtআছে। - USB ঢোকানো — শুধু ডিভাইস। USB driver যন্ত্রটি চিনল, udev
/dev/sdb1নামের device node বানাল। কিন্তু ব্লকগুলো এখনো ফাইল হিসেবে দেখা যায় না — কেউ বলেনি কোন file system আর কোথায় বসবে। (ডেস্কটপ Linux-এ udisks নামের সার্ভিস এখানেই নিজে থেকে mount করে দেয়, সাধারণত/media/<user>/…বা/run/media/<user>/…-এ।) mount— জোড়া লাগল। kernel vfat-এর driver দিয়ে stick-এর boot sector/superblock পড়ল, একটিstruct super_blockআর একটিstruct mountবানাল, mount table-এ নতুন সারি যোগ করল, আর/mnt-এর dentry-তে চিহ্ন দিল “এখানে কিছু mount করা”.old.txtমোছেনি — শুধু চাপা পড়েছে।- Path খোঁজা mount পার হয়।
/mnt/photo.jpgখুঁজতে গিয়ে namei/mnt-এ পৌঁছে চিহ্নটি দেখে, তারপর mount table থেকে USB-র root dentry-তে লাফ দেয়, আর সেখানেphoto.jpgখোঁজে। এখান থেকে..করলে আবার/-এ — mount-এর মা-directory. - Busy — umount নিষেধ। একটি shell-এর cwd
/mnt-এ (বা কোনো প্রোগ্রাম সেখানে ফাইল খোলা রেখেছে)। kernelEBUSYদেয়: “target is busy”. কে আটকে রেখেছে দেখতেfuser -vm /mntবাlsof /mnt. - নিরাপদে umount. shell সরে গেল,
umountসফল: cache-এ থাকা লেখা (dirty page) ডিস্কে লেখা হলো, superblock ছাড়া হলো, সারিটি mount table থেকে গেল।ls /mntআবার পুরোনোold.txtদেখায়। “Safely remove” না করে stick টেনে খুললে এই লেখাগুলো হারানোর ঝুঁকি থাকে।
আরও কয়েক রকম mount
- Bind mount:
mount --bind /srv/data /home/rahim/data— নতুন file system নয়, একটি বিদ্যমান directory-কে আরেক জায়গায় আবার দেখানো। symlink-এর মতো শোনায়, কিন্তু kernel-এর mount স্তরে হয়, তাই..,chrootআর container-এর ভেতরেও ঠিক থাকে। Docker-এর-v /host/dir:/app/dataআসলে একটি bind mount. - Automount: path প্রথমবার ছোঁয়ার মুহূর্তে mount, অনেকক্ষণ ব্যবহার না হলে খুলে ফেলা — autofs বা systemd-এর
.automountunit. অফিসের নেটওয়ার্ক-শেয়ার (NFS) বা কম-ব্যবহৃত ডিস্কের জন্য উপযোগী। - কাল্পনিক file system: procfs (
/proc), sysfs (/sys), tmpfs (RAM-এ থাকা ফাইল, যেমন/dev/shm, অনেক distro-য়/tmp),cgroup2— এদের পেছনে কোনো ডিস্ক নেই, kernel পড়ার মুহূর্তে উত্তর বানায়। - Windows-এ: drive letter-ই মূল পদ্ধতি, তবে NTFS একটি volume-কে অন্য volume-এর একটি খালি ফোল্ডারেও mount করতে পারে (Disk Management-এ “Mount in the following empty NTFS folder”, বা
mountvol) — Unix-এর mount point-এর কাছাকাছি।
এবার এই অনলাইন রানারের container নিজেই কী দেখে, দেখি। /proc/mounts হলো এই process-এর mount namespace-এর mount table, আর findmnt সেটি গাছ বা কলাম আকারে সুন্দর করে দেখায় (না থাকলে script জানাবে):
echo "== first 6 lines of /proc/mounts (device mount-point type options ...) =="
head -n 6 /proc/mounts | cut -c1-90
echo "== mounts per file-system type =="
awk '{print $3}' /proc/mounts | sort | uniq -c | sort -rn
echo "== which file system holds these paths? =="
for d in / /proc /sys /dev /tmp "$PWD"; do
printf '%-22s %s\n' "$d" "$(stat -f -c %T "$d")"
done
echo "total mounts visible to this process: $(wc -l < /proc/self/mounts)"
if command -v findmnt >/dev/null; then findmnt -n -o TARGET,FSTYPE,SOURCE /; else echo "findmnt not installed"; fiআউটপুট:
== first 6 lines of /proc/mounts (device mount-point type options ...) ==
/dev/vda2 /home/wandbox ext4 rw,relatime 0 0
/dev/vda2 /run/podman-init ext4 ro,relatime 0 0
/dev/vdb1 /opt/wandbox ext4 ro,noatime 0 0
/dev/vda2 /etc/resolv.conf ext4 rw,relatime 0 0
/dev/vda2 /etc/hosts ext4 rw,relatime 0 0
shm /dev/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=64000k,uid=1000,gid=1000,inode64 0
== mounts per file-system type ==
10 devtmpfs
7 proc
7 ext4
6 tmpfs
1 sysfs
1 overlay
1 mqueue
1 devpts
1 cgroup2
== which file system holds these paths? ==
/ overlayfs
/proc proc
/sys sysfs
/dev tmpfs
/tmp overlayfs
/home/wandbox ext2/ext3
total mounts visible to this process: 35
/ overlay overlay
এক রানের আউটপুট (প্রথম ছয় লাইনের ক্রম রানভেদে বদলাতে দেখা গেছে, আর অন্য সার্ভারে সংখ্যাগুলো আলাদা হতে পারে)। কয়েকটি শিক্ষা:
/হলো overlay: container image-এর শুধু-পড়ার স্তরগুলোর ওপর একটি লেখার স্তর মিলিয়ে একটি গাছ — Docker আর Podman-এর স্বাভাবিক root (পর্ব ৫১)।- ফাইল-স্তরের bind mount:
/etc/hosts,/etc/resolv.confআর/home/wandboxএকই host ডিস্ক/dev/vda2থেকে — container runtime host-এর ফাইলগুলো এক এক করে ভেতরে bind mount করেছে।/opt/wandbox(সম্ভবত compiler-গুলো) আলাদা ডিস্ক থেকেro, অর্থাৎ শুধু-পড়ার mount. - ১০টি devtmpfs:
/dev/null,/dev/zero-এর মতো device file-গুলো আলাদা আলাদা করে host থেকে আনা; আর একাধিকprocআরtmpfsসম্ভবত/proc-এর স্পর্শকাতর অংশ ঢাকতে বা শুধু-পড়ার করতে — container runtime-গুলো সাধারণত এমন করে। এখানে mount ব্যবহার হচ্ছে নিরাপত্তার পর্দা হিসেবে। ext2/ext3?stat -ffile system চেনে superblock-এর magic number দিয়ে, আর ext2, ext3, ext4-এর magic একই (0xEF53) — তাই আলাদা করতে পারে না;/proc/mountsসঠিকভাবেext4বলছে।- ৩৫টি mount একটি ছোট container-এ; একটি সাধারণ ডেস্কটপ Linux-এও
findmntকয়েক ডজন দেখায় — বেশিরভাগই কাল্পনিক file system.findmntএখানে আছে, আর root-এর উৎস ও ধরন দুটোইoverlay.
৮ · VFS — একই read() কীভাবে ext4, NFS, procfs আর FUSE-এ চলে
আপনার প্রোগ্রাম read(fd, buf, 100) লেখে। fd-টি হতে পারে SSD-র ext4 ফাইল, অফিসের সার্ভারের NFS ফাইল, /proc/cpuinfo (যার কোনো ডিস্কই নেই), অথবা FUSE দিয়ে বানানো Google Drive-এর একটি ফাইল। প্রোগ্রাম কোনো পার্থক্য জানে না। এই জাদুর নাম VFS — virtual file system স্তর।
বাংলাদেশে এক ব্যাংকের ATM কার্ড দিয়ে অনেক সময় অন্য ব্যাংকের বুথ থেকেও টাকা তোলা যায়। বুথ জানে না আপনার ব্যাংকের ভেতরের হিসাব কীভাবে চলে; সে শুধু একটি সাধারণ নিয়মে অনুরোধ পাঠায়, আর আপনার ব্যাংক নিজের পদ্ধতিতে উত্তর দেয়। VFS হলো সেই সাধারণ নিয়ম; ext4, NFS, procfs হলো আলাদা আলাদা ব্যাংক।
VFS কয়েকটি সাধারণ বস্তু সংজ্ঞায়িত করে, আর প্রতিটির সাথে একটি operation table — function pointer-এর একটি struct, যা প্রতিটি file system নিজের function দিয়ে ভরে (C-তে object-oriented নকশা):
| বস্তু | কী বোঝায় | কতগুলো | operation table |
|---|---|---|---|
| superblock | একটি mount করা file system — আকার, block size, root, ফাঁকা জায়গা | প্রতি file system একটি | super_operations: inode বানানো/লেখা, sync_fs, statfs |
| inode | একটি ফাইল বা directory-র মেটাডেটা | প্রতি ফাইল একটি (নাম যত-ই থাক) | inode_operations: lookup, create, link, unlink, symlink, mkdir, rename |
| dentry | path-এর একটি component — (মা, নাম) → inode; শুধু RAM-এ | প্রতিটি নামে একটি (hard link-এ একাধিক dentry, একটি inode) | dentry_operations: নাম তুলনা, পুনঃযাচাই (NFS-এর জন্য জরুরি) |
| file | একটি খোলা ফাইল — offset, mode (পর্ব ৪১-এর open file description) | প্রতিটি open()-এ একটি | file_operations: read_iter, write_iter, mmap, iterate_shared, fsync |
struct file_operations { /* one table per kind of file */
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
int (*iterate_shared)(struct file *, struct dir_context *); /* readdir */
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
/* ... many more ... */
};
const struct file_operations ext4_file_operations = { /* ext4 fills it in */
.llseek = ext4_llseek,
.read_iter = ext4_file_read_iter,
.write_iter = ext4_file_write_iter,
.mmap = ext4_file_mmap,
.open = ext4_file_open,
.fsync = ext4_sync_file,
};
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
if (!(file->f_mode & FMODE_READ))
return -EBADF;
/* (permission and range checks omitted) */
if (file->f_op->read)
return file->f_op->read(file, buf, count, pos); /* jump into the fs */
if (file->f_op->read_iter)
return new_sync_read(file, buf, count, pos);
return -EINVAL;
}অর্থাৎ read() system call → vfs_read() → file->f_op->read_iter — আর কোন function সেখানে বসানো, তা ঠিক হয়েছিল open()-এর সময়, ফাইলটি কোন file system-এর তা দেখে। এভাবে:
- ext4: page cache দেখে, না থাকলে ডিস্কের ব্লক পড়ার অনুরোধ (পর্ব ৩৯-৪০-এর I/O পথ)।
- NFS: নেটওয়ার্কে সার্ভারকে RPC পাঠায় (পর্ব ৫৩)।
- procfs: kernel-এর ভেতরের তথ্য দিয়ে সেই মুহূর্তে লেখা তৈরি করে — এ জন্যই
ls -l /proc/cpuinfoআকার ০ দেখায়, অথচcatকরলে অনেক লাইন আসে। - FUSE: kernel-এর FUSE module অনুরোধটি
/dev/fuseদিয়ে একটি সাধারণ user-space প্রোগ্রামের কাছে পাঠায় (যেমন sshfs), উত্তর এলে ফেরত দেয়। ফলে root অধিকার বা kernel কোড ছাড়াই নতুন file system লেখা যায় — বিনিময়ে প্রতিটি অনুরোধে বাড়তি context switch.
Windows-এও একই চিন্তা, ভিন্ন রূপে: I/O Manager একটি অনুরোধকে IRP (I/O request packet) বানিয়ে সঠিক file system driver-এর (NTFS, FAT, ReFS) কাছে পাঠায়, আর filter driver (যেমন antivirus) মাঝখানে বসতে পারে (পর্ব ৫৬)।
৯ · Filesystem Hierarchy Standard — Linux-এর ফোল্ডার-মানচিত্র
প্রতিটি Linux distro-তে /etc-এ configuration আর /home-এ user-দের ফাইল কেন? কারণ একটি সাধারণ চুক্তি আছে — Filesystem Hierarchy Standard (FHS), যার সর্বশেষ বড় সংস্করণ ৩.০ (২০১৫, Linux Foundation). পর্ব ০৩-এ মানচিত্রটি এক নজরে দেখেছেন; এখানে প্রতিটি directory কোন file system বা mount-এর সাথে জড়িত, সেই চোখে দেখি:
| Directory | কী থাকে | mount-এর দিক থেকে |
|---|---|---|
/ | গাছের শিকড় | root file system, বুটের সময় প্রথম mount (পর্ব ০৭) |
/bin, /sbin | মৌলিক কমান্ড | অনেক নতুন distro-তে আসলে /usr/bin-এর symlink (“usr merge”) |
/etc | সিস্টেমের configuration লেখা-ফাইল | root fs-এর অংশ |
/home | user-দের ফাইল | প্রায়ই আলাদা partition — OS নতুন করে বসালেও ডেটা থাকে |
/usr | প্রোগ্রাম, library, ডকুমেন্টেশন (মূলত শুধু-পড়া) | কোনো কোনো সিস্টেমে আলাদা, এমনকি read-only mount |
/var | বদলাতে থাকা ডেটা — log, mail, cache, ডেটাবেস | সার্ভারে প্রায়ই আলাদা, যাতে log ভরে গেলে / না ভরে |
/tmp | অস্থায়ী ফাইল, সবাই লিখতে পারেন (sticky bit) | অনেক distro-য় tmpfs — reboot-এ মুছে যায় |
/proc | process আর kernel-এর তথ্য | procfs, কোনো ডিস্ক নেই |
/sys | ডিভাইস, driver, kernel-এর সেটিং | sysfs |
/dev | device file (/dev/sda, /dev/null) | devtmpfs, udev পরিচালনা করে |
/mnt, /media | অস্থায়ী আর অপসারণযোগ্য mount | খালি mount point |
Windows-এ তুলনীয় জায়গা: C:\Windows (OS), C:\Program Files (প্রোগ্রাম, অনেকটা /usr), C:\Users (/home), C:\ProgramData (কিছুটা /var আর /etc-এর মিশ্রণ), আর configuration-এর বড় অংশ registry-তে (পর্ব ৫৬)। macOS-এ user-দের ফাইল /Users-এ, আর ভেতরে BSD-ধাঁচের /etc, /usr, /var আছে।
আরও গভীরে: Linux ২.৬.৩৮ (২০১১) থেকে path খোঁজা প্রথমে “RCU-walk” পদ্ধতিতে হয় — কোনো lock বা reference count না নিয়ে, sequence counter দিয়ে যাচাই করে dentry cache-এ দৌড়ে যাওয়া (Nick Piggin-এর কাজ); মাঝপথে কিছু বদলালে ধীর “ref-walk”-এ ফিরে আসে। এজন্যই বহু-core সার্ভারে লাখো stat() একসাথে চললেও lock-এর লড়াই কম। Linux ৫.৬-এ আসা openat2() system call RESOLVE_BENEATH, RESOLVE_NO_SYMLINKS, RESOLVE_IN_ROOT flag দিয়ে প্রোগ্রামকে নিশ্চিত হতে দেয় যে খোঁজা একটি directory-র বাইরে যাবে না — container runtime আর web server-এর জন্য symlink-ভিত্তিক বিপদ থেকে সুরক্ষা। Mount-এরও propagation আছে (shared, slave, private), যা ঠিক করে এক namespace-এ নতুন mount অন্যটিতে দেখা যাবে কি না।
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Hard link আর soft link-এর পার্থক্য? — hard link একই inode-এর আরেকটি নাম (link count বাড়ে, একই FS, directory নয়); soft link নিজস্ব inode-এ একটি path রাখে, অন্য FS আর directory-কেও দেখাতে পারে, লক্ষ্য মুছলে ঝুলে যায়।
- Directory-র hard link কেন নিষেধ? — cycle তৈরি হতো: গাছ-ঘোরা অসীম লুপে পড়ত, reference count শূন্য না হওয়ায় জায়গা ফেরত আসত না (GC লাগত), আর
..-র মানে অস্পষ্ট হতো। - Acyclic আর general graph directory-র পার্থক্য? — প্রথমটিতে ভাগাভাগি আছে কিন্তু cycle নেই, তাই reference count যথেষ্ট; দ্বিতীয়টিতে cycle সম্ভব, তাই traversal-এ লুপ ঠেকানো আর garbage collection লাগে।
- Mounting কী? — একটি file system-কে বিদ্যমান গাছের একটি directory-তে (mount point) জুড়ে দেওয়া; সেখানকার আগের জিনিস ঢাকা পড়ে, umount-এ ফিরে আসে।
- VFS-এর প্রধান বস্তুগুলো? — superblock (একটি mount করা FS), inode (একটি ফাইল), dentry (path-এর একটি নাম), file (একটি খোলা instance); প্রতিটির operation table প্রতিটি FS নিজে ভরে।
১০ · AI-এর যুগে file system-এর প্রশ্ন — উত্তর যাচাই
“Linux-এ একটি ফোল্ডার অন্য জায়গায় দেখাতে চাই” — AI খুব দ্রুত ln -s বা mount --bind লিখে দেবে। কিন্তু link আর mount-এর ভুল সাধারণত সাথে সাথে ধরা পড়ে না; কয়েক মাস পরে backup-এ, deploy-এ বা কারও ফাইল মোছার সময় ধরা পড়ে। AI-এর সাধারণ ভুলগুলো:
| AI-এর ভুল | যা ঘটে | কীভাবে ধরবেন |
|---|---|---|
ln -s-এর argument-এর ক্রম উল্টো (লক্ষ্য আগে, link-এর নাম পরে) | ভুল জায়গায় link, বা বিদ্যমান ফোল্ডারের ভেতরে নতুন link | ls -l আর readlink -f দিয়ে দেখুন link আসলে কোথায় দেখায় |
| relative symlink-কে অন্য directory থেকে তৈরি | লক্ষ্যের path link-এর জায়গা থেকে হিসাব হয়, তাই dangling | find . -xtype l দিয়ে ঝুলে থাকা link খুঁজুন |
| “USB-তে hard link করুন” বা “ডিরেক্টরির hard link বানান” | EXDEV বা hard link not allowed for directory | link-এর নিয়ম মনে রাখুন; stat -f -c %T দিয়ে দুই জায়গার FS দেখুন |
permission সমস্যায় chmod -R 777 বা sudo | সবাই লিখতে পারে — নিরাপত্তা-ঝুঁকি; আসল কারণ (মাঝের directory-তে x নেই) থেকেই যায় | namei -l /full/path দিয়ে প্রতিটি component-এর permission দেখুন |
symlink-ওয়ালা ফোল্ডারে rm -rf link/ (শেষে /) | link নয়, লক্ষ্যের ভেতরের সবকিছু মুছে যেতে পারে | আগে ls -ld; link সরাতে rm link (স্ল্যাশ ছাড়া) বা unlink link |
পুরোনো বা ভুল OS-এর তথ্য — /etc/mtab-কে আসল ফাইল ধরা, Windows-এ ln, macOS-এ GNU-র flag | কমান্ড চলে না বা ভুল ব্যাখ্যা | uname -a, man ln, ls -l /etc/mtab (নতুন সিস্টেমে এটি /proc/self/mounts-এর symlink) |
AI-কে প্রশ্ন করার সময় পরিবেশ, উদ্দেশ্য আর সীমা বলে দিন, আর আগে শুধু-দেখার কমান্ড চান:
AI-এর দেওয়া link বা mount-এর পরামর্শ ব্যবহারের আগে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Unix সাধারণ user-কে directory-র hard link বানাতে দেয় না — অথচ macOS-এর Time Machine একসময় ঠিক এটাই করত। কেন, আর কীভাবে নিরাপদ রাখা হতো?
পুরোনো Unix-এ mkdir কোনো system call ছিল না। mkdir কমান্ডটি ছিল একটি setuid-root প্রোগ্রাম, যা mknod দিয়ে একটি ফাঁকা directory inode বানিয়ে তারপর link() দিয়ে . আর .. জুড়ত — অর্থাৎ superuser directory-র hard link বানাতে পারতেন। ভুল করে একটি directory-কে তার নিজের ভেতরে link করলে fsck-এর মতো টুলও বিভ্রান্ত হতো। 4.2BSD (১৯৮৩) mkdir, rmdir আর rename-কে kernel-এর atomic system call বানায়, আর ধীরে ধীরে directory-র hard link কার্যত নিষিদ্ধ হয়। আজ Linux-এ link() directory পেলে root-এর জন্যও EPERM দেয়।
কারণগুলো অংশ ২-এর যুক্তি: cycle হলে reference count দিয়ে জায়গা ফেরত আসে না, find/du/backup অসীম লুপে পড়ে, আর একাধিক মা থাকলে .. কাকে দেখাবে তা অস্পষ্ট।
তবু Apple ২০০৭ সালে Mac OS X 10.5-এ Time Machine আনার সময় HFS+-এ directory hard link চালু করে, শুধু Time Machine-এর জন্য। প্রতি ঘণ্টার backup-এ যে ফোল্ডার বদলায়নি, সেটি নতুন করে কপি না করে আগের backup-এর ফোল্ডারের দিকে একটি hard link — ফলে প্রতিটি snapshot পুরো ডিস্কের মতো দেখায়, অথচ জায়গা লাগে শুধু বদলের। নিরাপত্তার জন্য কয়েকটি কঠোর শর্ত দেওয়া হয়, যেমন দুটি নাম একই মা-directory-তে থাকতে পারবে না, আর একটি directory-কে তার নিজের ভেতরের (বংশধর) কোনো directory-তে link করা যাবে না — অর্থাৎ গ্রাফ acyclic থাকবে, এটি kernel নিজে নিশ্চিত করে। পরে APFS-এ (macOS 11 Big Sur থেকে নতুন Time Machine ডিস্কে) এই কৌশলের বদলে file system snapshot ব্যবহার হয়, যা copy-on-write file system-এর স্বাভাবিক ক্ষমতা (পর্ব ৪৪)।
Linux-এর জগতে একই প্রয়োজন মেটায় rsync --link-dest: বদলায়নি এমন প্রতিটি ফাইলের hard link, directory নতুন করে তৈরি। নিয়ম ভাঙতে হয় না।
মূল উপলব্ধি: নিষেধটি কোনো গাণিতিক অসম্ভবতা নয়, একটি নকশার সিদ্ধান্ত: cycle-মুক্ত গ্রাফ নিশ্চিত করার সবচেয়ে সস্তা উপায় হলো directory-র link-ই বন্ধ করা। বিশেষ প্রয়োজনে কেউ নিয়ম শিথিল করলে, cycle ঠেকানোর দায় নিজেকে নিতে হয়।
প্র ০২rename() atomic — এই একটি নিশ্চয়তা দিয়ে editor, package manager আর ডেটাবেস কীভাবে নিরাপদে ফাইল বদলায়? আর এই কৌশলে কী ভুল হতে পারে?
ধরুন একটি editor config.json সংরক্ষণ করছে। সরাসরি পুরোনো ফাইলের ওপর লিখলে, অর্ধেক লেখার সময় বিদ্যুৎ গেলে (বাংলাদেশে লোডশেডিং পরিচিত ঘটনা) ফাইলটি অর্ধেক পুরোনো, অর্ধেক নতুন — নষ্ট। তাই প্রচলিত কৌশল:
- একই directory-তে নতুন ফাইল লেখা, যেমন
config.json.tmp; fsync()দিয়ে নিশ্চিত হওয়া যে ডেটা সত্যিই ডিস্কে পৌঁছেছে;rename("config.json.tmp", "config.json")— atomic, তাই যেকোনো মুহূর্তে কেউ হয় পুরো পুরোনো, নয় পুরো নতুন ফাইল দেখবেন;- সবচেয়ে সাবধানী প্রোগ্রাম directory-টিও
fsyncকরে, যাতে নাম বদলের রেকর্ডটিও টেকে।
একই directory কেন? অংশ ৩-এ দেখেছেন — অন্য file system-এ rename EXDEV দেয়, তাই tmp ফাইল /tmp-এ (যা প্রায়ই tmpfs) লিখলে কৌশলটি ভেঙে যায়। dpkg, rpm, git, SQLite-এর কিছু মোড — সবাই এই নীতিতে কাজ করে।
ইতিহাসের একটি শিক্ষণীয় ঘটনা: ২০০৯ সালের দিকে ext4 নতুন হলে অনেক user অভিযোগ করেন, crash-এর পর কিছু configuration ফাইল শূন্য বাইটের হয়ে যাচ্ছে। কারণ প্রোগ্রামগুলো ধাপ ২ (fsync) বাদ দিয়ে শুধু লিখে rename করত; ext4-এর delayed allocation ডেটা কয়েক সেকেন্ড পরে লিখত, কিন্তু rename-এর মেটাডেটা আগেই journal-এ চলে যেত। ফল: নতুন নাম আছে, ডেটা নেই। POSIX অনুযায়ী প্রোগ্রামগুলোরই ভুল ছিল, তবু ব্যবহারিক কারণে ext4-এর রক্ষণাবেক্ষণকারী Ted Ts'o একটি heuristic যোগ করেন (auto_da_alloc): পুরোনো ফাইলের ওপর rename হলে আগে ডেটা লেখা শুরু করা। Crash consistency-র এই বিষয় পর্ব ৪৪-এ বিস্তারিত।
আরেকটি সূক্ষ্মতা: rename-এ inode বদলায়, তাই যে ফাইলের hard link ছিল সেটি নতুন ফাইলে আর যুক্ত থাকে না, আর কোনো প্রোগ্রাম পুরোনো ফাইলটি খোলা রাখলে সে পুরোনো inode-ই পড়তে থাকে। Log rotation টুল এ জন্যই প্রোগ্রামকে signal পাঠিয়ে ফাইল আবার খোলায়।
মূল উপলব্ধি: নিরাপদ ফাইল-আপডেট তিনটি আলাদা নিশ্চয়তার ওপর দাঁড়ায় — ডেটা টেকসই (fsync), নাম বদল atomic (rename), আর দুটো একই file system-এ। যেকোনো একটি বাদ দিলে “বেশিরভাগ সময় কাজ করে” এমন কোড পাবেন, যা ঠিক crash-এর দিনেই ব্যর্থ হয়।
প্র ০৩Symlink কেন নিরাপত্তার দিক থেকে ঝুঁকিপূর্ণ, আর আধুনিক OS কীভাবে এর থেকে রক্ষা করে?
Symlink-এর শক্তিই তার বিপদ: নামটি দেখতে এক জায়গার, কিন্তু খোলা হয় অন্য জায়গার ফাইল। সমস্যা হয় যখন বেশি অধিকারের একটি প্রোগ্রাম কম অধিকারের user-দের লেখার জায়গায় কাজ করে — ক্লাসিক উদাহরণ /tmp, যেখানে সবাই ফাইল তৈরি করতে পারেন। পুরোনো দিনে কোনো root-চালিত script যদি অনুমেয় নামে, যেমন /tmp/report.txt-এ লিখত, অন্য কেউ আগে থেকেই ঐ নামে একটি symlink রেখে দিলে script অজান্তে একটি জরুরি সিস্টেম ফাইলের ওপর লিখে ফেলত। এটি “time-of-check to time-of-use” (TOCTOU) ধরনের ত্রুটি: পরীক্ষা আর ব্যবহারের মাঝের মুহূর্তে নামের অর্থ বদলে যায়।
প্রতিরক্ষার স্তরগুলো (সবই কনফিগার আর যাচাই করার মতো):
- Kernel-এর নীতি: Linux ৩.৬ (২০১২) থেকে
fs.protected_symlinksআরfs.protected_hardlinkssysctl; বেশিরভাগ distro এগুলো ডিফল্টভাবে ১ রাখে। তখন sticky bit-ওয়ালা, সবার-লেখার directory-তে অন্য user-এর symlink অনুসরণ করা হয় না, আর নিজের নয় এমন ফাইলের hard link বানানো যায় না। যাচাই:sysctl fs.protected_symlinks. - নিরাপদ API:
mkstemp()অনুমান-অযোগ্য নামে আরO_CREAT | O_EXCLদিয়ে ফাইল তৈরি করে, যা আগে থেকে থাকা symlink-এ ব্যর্থ হয়;O_NOFOLLOWশেষ component-এ symlink হলে খুলতে অস্বীকার করে;openat2()-এরRESOLVE_NO_SYMLINKS/RESOLVE_BENEATHপুরো path-এ নিয়ন্ত্রণ দেয়। - নকশা: প্রতিটি service-এর নিজস্ব private tmp (systemd-এর
PrivateTmp=yes), আর web server-এ “symlink অনুসরণ নিষেধ, বা শুধু একই মালিকের হলে অনুমতি” ধরনের সেটিং। - Windows: ঠিক এই ঝুঁকির কারণে Vista থেকে symlink তৈরি করতে আলাদা অধিকার (SeCreateSymbolicLinkPrivilege) লাগে, যা ডিফল্টভাবে admin-দের থাকে; Windows 10-এর একটি আপডেট থেকে Developer Mode চালু করলে সাধারণ user-ও বানাতে পারেন।
মূল উপলব্ধি: একটি path একটি “প্রতিশ্রুতি”, পরিচয় নয় — খোঁজার মুহূর্তে তার অর্থ ঠিক হয়। অধিকারপ্রাপ্ত কোড যত কম path আর যত বেশি খোলা fd (openat দিয়ে) নিয়ে কাজ করে, তত নিরাপদ।
প্র ০৪VFS-এর ধারণা কোথা থেকে এলো, আর “সব কিছুই ফাইল” abstraction কোথায় ফুটো হয়ে যায়?
প্রথম দিকের Unix-এ একটিই file system ছিল, কোড সরাসরি তার গঠনের সাথে বাঁধা। ১৯৮০-র দশকের মাঝামাঝি Sun Microsystems নেটওয়ার্কে ফাইল ভাগাভাগির জন্য NFS বানানোর সময় সমস্যায় পড়ে: দূরের সার্ভারের ফাইলকে স্থানীয় ফাইলের মতো দেখাতে হলে kernel-এর ফাইল-কোডকে file system-নিরপেক্ষ করতে হবে। Steve Kleiman-এর নকশা “vnode/VFS” (SunOS-এ, ১৯৮৫ সালের দিকে; ১৯৮৬-র USENIX-এ প্রকাশিত প্রবন্ধ) প্রতিটি file system-কে একটি operation table দিয়ে প্লাগ-ইন হিসেবে বসানোর পথ দেখায়। পরে প্রায় সব Unix, আর Linux-ও একই ধারণা নেয়। Bell Labs-এর Plan 9 আরও এগিয়ে যায় — নেটওয়ার্ক, window, process সবই ফাইল, আর প্রতিটি process-এর নিজস্ব namespace; Linux-এর /proc আর mount namespace (২.৪.১৯, ২০০২) এই ধারার উত্তরাধিকার। FUSE ২০০৫ সালে Linux ২.৬.১৪-এ যুক্ত হয়ে user-space file system-এর দরজা খোলে।
কিন্তু abstraction “ফুটো” হয় (leaky abstraction) যেখানে নিচের file system-এর আচরণ সত্যিই ভিন্ন:
- NFS: অনেক NFS সংস্করণ “close-to-open” consistency দেয় — একজন ফাইল বন্ধ করার পর আরেকজন খুললে তবেই নতুন ডেটা নিশ্চিত। স্থানীয় ফাইলের মতো মুহূর্তে মুহূর্তে মিল আশা করলে বাগ। সার্ভার না পেলে
read()মিনিটের পর মিনিট আটকে থাকতে পারে। - procfs: আকার ০, অথচ ডেটা আছে; প্রতিবার পড়লে ভিন্ন উত্তর। ফাইলের আকার দেখে বাফার বানায় এমন কোড এখানে ভুল করে।
- FAT/vfat: permission, owner, symlink, hard link — কিছুই নেই; mount-এর সময় সবার জন্য একটি কাল্পনিক মালিক আর permission বসানো হয়। ছোট-বড় হাতের অক্ষর আলাদা নয়।
- FUSE: প্রতিটি অনুরোধে user-space-এ যাতায়াত, তাই ছোট ছোট অনেক ফাইলে ধীর; আর সার্ভিসটি crash করলে mount “Transport endpoint is not connected” দেখায়।
মূল উপলব্ধি: VFS একটি চুক্তি দেয় — কোন function আছে — কিন্তু প্রতিটি function-এর গতি, consistency আর ব্যর্থতার ধরন নিচের file system-এর ওপর নির্ভর করে। ভালো systems প্রোগ্রামার interface দেখে নিশ্চিন্ত হন না, জানতে চান “এর নিচে কে?” — stat -f আর findmnt এই প্রশ্নের উত্তর দেয়।
প্র ০৫Windows-এ C:, D: আর Unix-এ একটিই গাছ — দুটি নকশা কোথা থেকে এলো, আর কোনটি কখন সুবিধাজনক?
Drive letter-এর শিকড় ১৯৭০-এর দশকের CP/M-এ, যেখানে প্রতিটি floppy drive একটি অক্ষর: A:, B:. MS-DOS (১৯৮১) এই ধারণা নেয়; hard disk এলে তার জায়গা হয় C: — এজন্যই আজও Windows সাধারণত C:-এ থাকে, যদিও floppy আর নেই। প্রতিটি অক্ষরের নিজস্ব গাছ আর নিজস্ব current directory. Unix (১৯৭০-এর দশক, Bell Labs) শুরু থেকেই একটি গাছ রেখেছে, যেখানে /etc/fstab বা বুটের সময় বিভিন্ন ডিস্ক বিভিন্ন ডালে জোড়া হয়।
Drive letter-এর সুবিধা: সাধারণ user-এর কাছে সহজ — “পেনড্রাইভ E:-এ”. কোন ফাইল কোন ডিস্কে, তা নামেই বোঝা যায়, আর এক ডিস্ক থেকে আরেক ডিস্কে সরানো যে কপি (সময়সাপেক্ষ), তা স্পষ্ট। অসুবিধা: মাত্র ২৬টি অক্ষর; ডিস্ক যোগ বা অক্ষর বদলালে path ভেঙে যায়; একটি ভরে যাওয়া ফোল্ডারকে নতুন ডিস্কে সরাতে চাইলে প্রোগ্রামের সব path বদলাতে হয়।
একক গাছের সুবিধা: path-গুলো হার্ডওয়্যার-নিরপেক্ষ — /var/log ভরে গেলে নতুন ডিস্ক এনে /var-এ mount করলেই চলে, কোনো প্রোগ্রাম বদলাতে হয় না। নেটওয়ার্ক file system, RAM-এর tmpfs, kernel-এর procfs — সব একই নিয়মে। আর প্রতি process-এর আলাদা mount namespace থাকায় container নিজের মতো গাছ পায়। অসুবিধা: কোন path কোন ডিস্কে তা নাম দেখে বোঝা যায় না — তাই mv কখনো তাৎক্ষণিক, কখনো ঘণ্টাব্যাপী কপি (অংশ ৩-এর EXDEV).
বাস্তবে দুটো দর্শন কাছাকাছি এসেছে। Windows NT-র ভেতরে আসলে একটি একক object namespace আছে — C: শুধু \Device\HarddiskVolume3-এর মতো একটি নামের প্রতীকী link; NTFS volume mount point দিয়ে একটি ডিস্ক একটি ফোল্ডারে বসানো যায়। আবার WSL-এ Windows-এর C: দেখা যায় /mnt/c-এ — একটি mount point হিসেবে।
মূল উপলব্ধি: ব্যবহারকারীর কাছে কী দেখানো হবে (অক্ষর না গাছ) আর kernel ভেতরে কী রাখে (একটি namespace) — দুটি আলাদা স্তর। ভালো নকশা নিচের স্তরকে নমনীয় রাখে, ওপরের স্তরকে পরিচিত।
অনুশীলন
প্রথমে নিজে চেষ্টা করুন, তারপর উত্তর মিলিয়ে নিন।
-
হিসাব (numerical): একটি directory-তে ৫০,০০০টি entry, প্রতিটি ডিস্ক-ব্লকে ১০০টি entry ধরে। (ক) Linear list-এ আছে এমন একটি নাম খুঁজতে গড়ে আর সবচেয়ে খারাপ ক্ষেত্রে কতগুলো ব্লক পড়তে হবে? (খ) নেই এমন নাম? (গ) প্রতিটি index ব্লকে ৫০০টি চাবি রাখা যায় এমন hash-ভিত্তিক গাছে কতগুলো?
মোট leaf block = ৫০,০০০ ÷ ১০০ = ৫০০.
(ক) আছে এমন নাম: গড়ে অর্ধেক — ৫০০ ÷ ২ = ২৫০ ব্লক (আরও নিখুঁতভাবে (৫০০ + ১) ÷ ২ ≈ ২৫০.৫); সবচেয়ে খারাপ ক্ষেত্রে ৫০০ ব্লক.
(খ) নেই এমন নাম: নিশ্চিত হতে পুরোটাই — ৫০০ ব্লক. প্রতিটি নতুন ফাইল তৈরির আগে এই যাচাই লাগে, তাই linear list-এ বড় directory-তে ফাইল তৈরিও ধীর।
(গ) এক স্তরের index ৫০০টি leaf চেনাতে পারে, আর leaf ঠিক ৫০০টি — তাই ১ index ব্লক + ১ leaf ব্লক = ২ ব্লক, নাম থাকুক বা না থাকুক। অনুপাত গড়ে ২৫০ ÷ ২ = ১২৫ গুণ, নেই এমন নামে ৫০০ ÷ ২ = ২৫০ গুণ কম।
-
আউটপুট অনুমান করুন: একটি খালি directory-তে এই কমান্ডগুলো চালালে শেষ চারটি লাইনে কী ছাপা হবে?
echo A > f; ln f g; ln g h; ln -s f s rm f stat -c %h g cat s 2>&1 | head -c 20; echo echo B > f cat s hf,g,hতিনটি নামই একই inode (link count ৩);sএকটি symlink যার ভেতরে “f”.rm f-এর পর link count ২, তাই প্রথম লাইন2.cat sব্যর্থ — দ্বিতীয় লাইনcat: s: No such file(প্রথম ২০ বাইট)। তারপর নতুনfতৈরি হলো — নতুন inode, ডেটা B.cat s hপ্রথমে symlink অনুসরণ করে নতুনfপড়ে:B, তারপরhপুরোনো inode পড়ে:A. অর্থাৎ শেষ চার লাইন:2,cat: s: No such file,B,A. -
Path হাতে ট্রেস করুন:
/var/wwwএকটি symlink যা/srv/wwwদেখায়। একটি প্রোগ্রামopen("/var/www/../log/./app.log")করল। kernel আসলে কোন ফাইল খুলবে? কয়টি symlink অনুসরণ হলো, আর কোন কোন directory-তে x permission লাগবে?ধাপ:
/→var→wwwএকটি symlink, ভেতরে/srv/www(absolute) — তাই আবার/থেকে:srv→www. এখন বাকি../log/./app.log: kernel-এর..physical, তাই/srv/www-এর মা/srv; তারপরlog,.(একই জায়গা),app.log. খোলা হবে/srv/log/app.log—/var/log/app.logনয়! symlink অনুসরণ ১টি. x permission লাগবে/,/var,/srv,/srv/www,/srv/log-এ (খোঁজার পথে যে directory-গুলোর ভেতরে নাম খোঁজা হয়েছে), তারপরapp.log-এ open-এর মোড অনুযায়ী r বা w. (bash-এcd /var/www/..করলে কিন্তু/var-এ যাবেন — shell-এর logical path.) -
কমান্ড লিখুন: (ক) বর্তমান ফোল্ডারের নিচে সব ঝুলে থাকা (dangling) symlink খুঁজুন। (খ)
/homefile system-এreport.pdf-এর সব hard link (একই inode-এর সব নাম) খুঁজুন। (গ)/dataকোন ডিভাইস থেকে, কোন ধরনের file system হিসেবে mount করা — দেখুন।(ক)
find . -xtype l—-xtype lমানে “symlink অনুসরণ করার পরেও যা symlink থেকে যায়”, অর্থাৎ লক্ষ্য নেই। (GNU find; macOS-এর BSD find-এ এই flag নেই, সেখানেfind . -type l ! -exec test -e {} \; -print)(খ)
find /home -xdev -samefile /home/rahim/report.pdf—-xdevঅন্য file system-এ ঢোকে না (সেখানে একই inode নম্বরের অর্থ ভিন্ন)। বিকল্প:ls -iদিয়ে inode নম্বর নিয়েfind /home -xdev -inum NUMBER. আগেstat -c %hদেখে নিন কতগুলো নাম পাওয়ার কথা।(গ)
findmnt /dataবাfindmnt -T /data(path যেকোনো ফাইল হলেও তার mount দেখায়); বিকল্পdf -T /dataবাgrep ' /data ' /proc/mounts.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি প্রোগ্রাম open("/home/rahim/notes.txt", O_RDONLY) ডাকল। kernel-এর ভেতরের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- System call-এ user mode থেকে kernel mode-এ প্রবেশ
- Path
/দিয়ে শুরু, তাই process-এর root directory থেকে খোঁজা শুরু - প্রতিটি মাঝের directory-তে x permission যাচাই আর dentry cache (না পেলে ডিস্ক) থেকে পরের inode
- শেষ component-এর inode পাওয়া আর তার r permission যাচাই
struct fileতৈরি, inode-এর file_operations জোড়া- fd টেবিলের প্রথম ফাঁকা ঘরের নম্বর ফেরত
একটি ফাইলের একটি hard link আর একটি symbolic link আছে। এরপর ফাইলটির আসল নামটি মুছে ফেলা হলো। কোন কথাটি সঠিক?
- দুটি link-ই এখন ব্যর্থ হবে
- Hard link দিয়ে ডেটা পড়া যাবে, symlink ঝুলে যাবে
- Symlink দিয়ে ডেটা পড়া যাবে, hard link ঝুলে যাবে
- আসল নাম মুছলে kernel সব link-ও মুছে দেয়
ext4-এ একটি directory-র ভেতরে ৩টি sub-directory আর ১০টি সাধারণ ফাইল আছে। ঐ directory-র link count সাধারণত কত?
- ১
- ২
- ৫
- ১৫
Unix-ধাঁচের OS সাধারণ ব্যবহারে directory-র hard link তৈরি করতে দেয় না। প্রধান কারণ কোনটি?
- Cycle তৈরি হতে পারে — traversal, reference count আর “..” তিনটিই ভেঙে পড়ে
- Directory-র hard link অনেক ডিস্ক-জায়গা নেয়
- Directory-র inode-এ link count রাখার জায়গা নেই
- Hard link শুধু root ব্যবহার করতে পারেন
Linux-এ /home একটি ext4 partition আর /media/usb একটি আলাদা vfat USB stick. mv /home/rahim/video.mp4 /media/usb/ চালালে ভেতরে কী ঘটে?
- rename() সফল হয়, শুধু directory entry সরে, ডেটা নড়ে না
- mv ব্যর্থ হয়ে “Invalid cross-device link” দেখায়
- একটি hard link তৈরি হয়, তারপর পুরোনো নাম মোছে
- rename() EXDEV দেয়; mv ডেটা কপি করে তারপর মূল ফাইল মোছে
একটি process /home/rahim/docs/notes.txt পড়ার জন্য খুলতে চায়। কোন permission-গুলো লাগবে?
- প্রতিটি directory-তে r, আর ফাইলে r
- /, /home, /home/rahim আর docs-এ x, আর notes.txt-এ r
- শুধু notes.txt-এ r
- প্রতিটি directory-তে w, আর ফাইলে r
Linux-এর VFS-এ কোন বস্তুটি একটি নির্দিষ্ট open() থেকে আসা “খোলা ফাইল” বোঝায় — যার নিজস্ব offset আর mode আছে?
- struct super_block
- struct inode
- struct dentry
- struct file
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Directory হলো নাম → inode-এর তালিকা; গঠন single-level → two-level → tree → acyclic graph → general graph, আর cycle এড়াতে Unix directory-র hard link দেয় না।
- Hard link = একই inode-এর আরেকটি নাম (link count, একই FS); symlink = নিজের inode-এ একটি path (অন্য FS, directory, ঝুলে যেতে পারে, ৪০-এর পরে ELOOP).
- Directory-র ভেতর: linear list O(n), hash গড়ে O(1), B-tree O(log n) আর সাজানো — ext4-এর htree, NTFS-এর B+ tree.
- namei: root বা cwd থেকে শুরু, প্রতিটি component-এ x যাচাই, dentry cache, symlink আর mount point পার হয়ে শেষ inode.
- Mount একটি file system-কে গাছের একটি ডালে বসায়; VFS-এর superblock, inode, dentry, file আর operation table-এর জন্যই একই system call সব file system-এ চলে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৩ · File system-এর ভেতর — allocation, inode, FAT আর free spaceপরবর্তী পর্বinode-এর ভেতর থেকে ডেটা-ব্লক পর্যন্ত।
- অপারেটিং সিস্টেম · কোর্স হোম৬৪টি পর্বপুরো কোর্সের রোডম্যাপ — ১০টি মডিউল।
- পর্ব ০৩ · Linux টার্মিনাল — shell, কমান্ড আর ফাইলঝালিয়ে নিন
cd,ls, path আর ফাইলের মৌলিক কমান্ড। - DSA · B-treeপাশের ট্র্যাককেন ডিস্কের index-এ B-tree, আর তার insert/split কীভাবে হয়।
- DSA · Hash tableপাশের ট্র্যাকhtree-এর পেছনের hashing আর collision-এর হিসাব।