এই পর্বে যা শিখবেন
- Page replacement-এর পুরো ধাপ, victim-এর write-back আর dirty bit কীভাবে I/O কমায় — সংখ্যাসহ
- Address trace থেকে reference string বানানো, আর fault গোনার নিয়ম
- FIFO, OPT আর LRU-র পূর্ণ trace — পরীক্ষার খাতায় যেভাবে টেবিল আঁকতে হয়; Belady's anomaly-র হিসাব
- OPT কেন সর্বোত্তম (প্রমাণের রূপরেখা), আর stack property দিয়ে কেন LRU ও OPT-এ anomaly হয় না
- LRU-র বাস্তবায়ন আর তার কাছাকাছি নীতি — additional-reference-bits, second chance, clock, enhanced second chance, LFU/MFU, page buffering; Linux, Windows আর অ্যাপ্লিকেশন-cache-এ এদের রূপ
১ · RAM ভরা — তবু নতুন page লাগবে
পর্ব ৩৫-এ দেখেছেন, demand paging-এ একটি প্রোগ্রামের সব page শুরুতেই RAM-এ আনা হয় না; যেটি প্রথম ছোঁয়া হয়, তখনই আসে। Page RAM-এ না থাকলে CPU একটি page fault দেয়, kernel ডিস্ক থেকে page এনে একটি ফাঁকা frame-এ বসায়, page table হালনাগাদ করে, আর থেমে যাওয়া instruction আবার চালায়। কিন্তু একটি প্রশ্ন সেখানে খোলা ছিল: ফাঁকা frame না থাকলে?
Multiprogramming-এ RAM প্রায় সবসময় ভরা থাকে (খালি RAM মানে অপচয়), তাই নতুন page আনতে হলে কাউকে সরাতে হয়। কাকে — সেই সিদ্ধান্তই page replacement. ভুল সিদ্ধান্ত মানে এমন page সরানো, যা একটু পরেই আবার লাগবে; তখন আরেকটি page fault, আরেকবার ডিস্কে যাওয়া।
অফিসে আপনার টেবিলে একসাথে মাত্র তিনটি ফাইল রাখার জায়গা; বাকি সব ফাইল পাশের ঘরের ফাইল ক্যাবিনেটে। নতুন একটি ফাইল লাগলে টেবিলের একটি ফাইল ক্যাবিনেটে ফেরত পাঠাতে হয়। কোনটি পাঠাবেন? যেটি সকালে প্রথম এনেছিলেন? যেটি অনেকক্ষণ খোলেননি? নাকি যেটি আজ আর লাগবেই না — যদি তা জানতেন? আর যে ফাইলে আপনি নতুন কিছু লিখেছেন, সেটি ফেরত পাঠানোর আগে ক্যাবিনেটের কপিটিও হালনাগাদ করতে হবে; যেটি শুধু পড়েছেন, সেটি সোজা রেখে দিলেই হয়। টেবিল = RAM-এর frame, ক্যাবিনেট = ডিস্ক, ফাইল = page.
Page fault-এ replacement-সহ পুরো ধাপ
- ডিস্কে (swap বা ফাইলে) চাওয়া page-টি কোথায় আছে খুঁজে বের করা।
- একটি ফাঁকা frame খোঁজা। থাকলে সেটিই নেওয়া।
- ফাঁকা না থাকলে replacement algorithm দিয়ে একটি victim frame বাছা।
- Victim page-টি dirty হলে আগে ডিস্কে লেখা; তারপর তার page table entry-কে “নেই” (invalid) চিহ্ন দেওয়া, আর TLB-তে তার পুরোনো অনুবাদ থাকলে মুছে দেওয়া (multicore-এ অন্য core-এর TLB-ও — TLB shootdown).
- চাওয়া page ডিস্ক থেকে এই frame-এ পড়া; page table আর frame-এর হিসাব হালনাগাদ করা।
- যে instruction-এ fault হয়েছিল, সেটি আবার চালানো।
Dirty bit — অর্ধেক I/O বাঁচানোর কৌশল
উপরের ধাপ ৪ খরচের মূল জায়গা। Victim RAM-এ আসার পর না বদলালে (যেমন প্রোগ্রামের code) ডিস্কের কপি হুবহু একই — লেখার দরকার নেই। কিন্তু OS কীভাবে জানবে বদলেছে কি না? প্রতিটি page table entry-তে hardware একটি modify bit বা dirty bit রাখে: page-এ কোনো byte লেখা হলেই CPU নিজে সেটি ১ করে দেয়। x86-এ এটি PTE-র D (Dirty) বিট; পাশেই থাকে A (Accessed) বিট, যার কথা অংশ ৭-এ আসবে।
- Dirty bit না থাকলে: প্রতিটি replacement-এ দুটি ডিস্ক-transfer — victim লেখা, তারপর নতুন page পড়া।
- Dirty bit থাকলে: victim পরিষ্কার হলে একটিই transfer — শুধু পড়া। Fault-এর সেবার সময় প্রায় অর্ধেক।
ছোট হিসাব: ধরুন পরিষ্কার victim হলে fault সামলাতে লাগে ৮ ms, dirty হলে (লেখা + পড়া) ২০ ms, আর ৭০% ক্ষেত্রে victim dirty. গড় সময় = ০.৩ × ৮ + ০.৭ × ২০ = ২.৪ + ১৪ = ১৬.৪ ms. Dirty bit না থাকলে প্রতিটিতেই ২০ ms লাগত। তাই কিছু নীতি (অংশ ৭-এর enhanced second chance) পরিষ্কার page-কেই আগে সরাতে চায়।
কেন fault-এর সংখ্যা নিয়ে এত মাথাব্যথা? পর্ব ৩৫-এর effective access time-এর সূত্র মনে করুন: EAT = (১ − p) × memory access + p × fault-এর সময়. Memory access ২০০ ns আর fault ৮ ms হলে, প্রতি ১,০০০টি access-এ মাত্র একটি fault (p = ০.০০১) হলেও EAT ≈ ০.৯৯৯ × ২০০ + ০.০০১ × ৮০,০০,০০০ ≈ ৮,২০০ ns — অর্থাৎ মেমরি প্রায় ৪১ গুণ ধীর। তাই replacement নীতির লক্ষ্য: সবচেয়ে কম page-fault rate.
দুটি আলাদা প্রশ্ন: demand paging চালাতে OS-কে দুটি algorithm ঠিক করতে হয় — প্রতিটি প্রসেসকে কতগুলো frame দেবে (frame allocation), আর frame ভরা থাকলে কাকে সরাবে (page replacement). প্রথমটি, সঙ্গে global বনাম local replacement আর thrashing, আসবে পর্ব ৩৭-এ। এই পর্বে ধরে নিচ্ছি একটি প্রসেস, নির্দিষ্ট সংখ্যক frame.
২ · Reference string — ঠিকানার স্রোত থেকে page-এর ধারা
কোনো algorithm কতটা ভালো, মাপতে হলে একটি প্রোগ্রাম মেমরির কোন কোন জায়গা কোন ক্রমে ছোঁয়, সেই তালিকা লাগে — address trace. বাস্তবে এটি লাখ লাখ ঠিকানার লম্বা তালিকা। কিন্তু replacement-এর প্রশ্নে পুরো ঠিকানা লাগে না, শুধু page নম্বর লাগে; আর একই page পরপর কয়েকবার ছোঁয়া হলে প্রথমটির পর বাকিগুলো কখনো fault দিতে পারে না। এই দুই পর্যবেক্ষণে ছোট করা তালিকাটিই reference string.
উদাহরণ: page-এর আকার ১০০ byte (হিসাব সহজ রাখতে)। একটি প্রোগ্রাম এই ঠিকানাগুলো ছোঁয়:
0100, 0432, 0101, 0612, 0102, 0103, 0104, 0101, 0611, 0102, 0103,
0104, 0101, 0610, 0102, 0103, 0104, 0101, 0609, 0102, 0105
- Page নম্বর = ঠিকানা ÷ page-এর আকার (ভাগফলের পূর্ণ অংশ)। ০১০০ → page ১, ০৪৩২ → page ৪, ০৬১২ → page ৬… ফলে ২১টি page নম্বর: 1, 4, 1, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1, 1.
- পরপর একই page-কে একটি ধরা। page ১ পরপর চারবার ছোঁয়া হলে প্রথমবারের পর সে RAM-এ আছেই। ফল: 1, 4, 1, 6, 1, 6, 1, 6, 1, 6, 1 — ১১টি reference.
দুটি সীমা চোখে পড়ে। frame ৩ বা বেশি হলে fault হবে মাত্র ৩টি — প্রতিটি আলাদা page-এর প্রথম আসা (এগুলোকে compulsory বা cold-start fault বলে, কোনো algorithm এদের এড়াতে পারে না)। আর frame মাত্র ১টি হলে প্রতিটি reference-ই fault — ১১টি। মাঝের সংখ্যা algorithm-নির্ভর। সাধারণত frame বাড়লে fault কমে — ব্যতিক্রম অংশ ৩-এ।
Fault গোনার নিয়ম আগে ঠিক করুন। এই পর্বে (আর বেশিরভাগ পাঠ্যবইয়ে) frame শুরুতে খালি থাকে, আর খালি frame ভরার fault-ও গোনা হয়। কোনো প্রশ্নে “initial fault বাদ দিয়ে” বলা থাকলে প্রথম কয়েকটি বাদ দিতে হয় — উত্তর বদলায়। খাতার টেবিলে নতুন page victim-এর জায়গাতেই বসান — মিলিয়ে দেখা সহজ হয়।
এই পর্বজুড়ে দুটি string বারবার আসবে। প্রথমটি Silberschatz-এর পাঠ্যবইয়ের পরিচিত ২০টি reference: 7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1 (ছয়টি আলাদা page: 0, 1, 2, 3, 4, 7). দ্বিতীয়টি Belady's anomaly-র ১২টি reference: 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5.
৩ · FIFO — যে আগে এসেছে, সে আগে যাবে
সবচেয়ে সহজ নীতি: FIFO (first-in, first-out). প্রতিটি page RAM-এ আসার সময় একটি queue-র শেষে বসে; সরাতে হলে queue-র মাথার page — যে সবচেয়ে আগে এসেছিল। বাস্তবায়নে কোনো hardware লাগে না, প্রতি reference-এ কোনো কাজও নেই; শুধু fault-এর সময় queue-তে একটি যোগ আর একটি বাদ।
ঠিক এই দুর্বলতা পাঠ্যবইয়ের string-এ দেখা যায় (পুরো trace অংশ ৫-এর stepper-এ)। Page 0 এই string-এর সবচেয়ে ব্যস্ত page — ২০টির মধ্যে ৬ বার আসে। FIFO ধাপ ১০-এ (page 3 আনার সময়) 0-কে সরায়, কারণ সে তখন queue-র মাথায়; আর পরের ধাপেই, ধাপ ১১-এ, 0 আবার লাগে — আরেকটি fault. FIFO-র মোট fault ১৫টি, তিন নীতির মধ্যে সবচেয়ে বেশি।
Belady's anomaly — বেশি frame, বেশি fault
স্বাভাবিক বুদ্ধি বলে, একটি প্রসেসকে বেশি frame দিলে fault কমবে বা অন্তত বাড়বে না। ১৯৬৯ সালে László Bélády, R. A. Nelson আর G. S. Shedler (IBM) দেখান, FIFO-তে এটি সবসময় সত্য নয়। এটিই Belady's anomaly. নিচে ১২টি reference-এর string-টি ৩ আর ৪ frame-এ পাশাপাশি চলছে।
1 ref 12 ref 23 ref 34 ref 45 ref 16 ref 27 ref 58 ref 19 ref 210 ref 311 ref 412 ref 5
| ৩ frame | |
| ৪ frame |
- ধাপ ১: page ১. ৩ frame: fault (ফাঁকা জায়গা); ৪ frame: fault (ফাঁকা জায়গা)। মোট ১ বনাম ১।
- ধাপ ২: page ২. ৩ frame: fault (ফাঁকা জায়গা); ৪ frame: fault (ফাঁকা জায়গা)। মোট ২ বনাম ২।
- ধাপ ৩: page ৩. ৩ frame: fault (ফাঁকা জায়গা); ৪ frame: fault (ফাঁকা জায়গা)। মোট ৩ বনাম ৩।
- ধাপ ৪: page ৪. ৩ frame: fault, ১ সরল; ৪ frame: fault (ফাঁকা জায়গা)। মোট ৪ বনাম ৪।
- ধাপ ৫: page ১. ৩ frame: fault, ২ সরল; ৪ frame: hit. মোট ৫ বনাম ৪।
- ধাপ ৬: page ২. ৩ frame: fault, ৩ সরল; ৪ frame: hit. মোট ৬ বনাম ৪।
- ধাপ ৭: page ৫. ৩ frame: fault, ৪ সরল; ৪ frame: fault, ১ সরল। মোট ৭ বনাম ৫। page ১ আছে ৩-frame-এ, কিন্তু ৪-frame-এ নেই — inclusion ভাঙল।
- ধাপ ৮: page ১. ৩ frame: hit; ৪ frame: fault, ২ সরল। মোট ৭ বনাম ৬। page ২ আছে ৩-frame-এ, কিন্তু ৪-frame-এ নেই — inclusion ভাঙল।
- ধাপ ৯: page ২. ৩ frame: hit; ৪ frame: fault, ৩ সরল। মোট ৭ বনাম ৭।
- ধাপ ১০: page ৩. ৩ frame: fault, ১ সরল; ৪ frame: fault, ৪ সরল। মোট ৮ বনাম ৮।
- ধাপ ১১: page ৪. ৩ frame: fault, ২ সরল; ৪ frame: fault, ৫ সরল। মোট ৯ বনাম ৯। page ৫ আছে ৩-frame-এ, কিন্তু ৪-frame-এ নেই — inclusion ভাঙল।
- ধাপ ১২: page ৫. ৩ frame: hit; ৪ frame: fault, ১ সরল। মোট ৯ বনাম ১০।
ফল: ৩ frame-এ ৯টি fault, ৪ frame-এ ১০টি. ধাপ ৪ পর্যন্ত ৪-frame মেমরি ৩-frame মেমরির সব page ধরে রেখেছিল, তাই ধাপ ৫ আর ৬-এ সে hit পেল, ৩-frame পেল না — এ পর্যন্ত প্রত্যাশিত। কিন্তু সেই hit-গুলোর কারণে ৪-frame-এর queue-তে 1 আর 2 “পুরোনো” থেকে গেল (FIFO hit-এ queue বদলায় না), আর ধাপ ৭-এ page 5 আসতেই 1 বিদায় নিল — অথচ ৩-frame মেমরি তখন 1-কে রেখে দিয়েছে। বড় মেমরির page-সেট আর ছোটটিকে ধারণ করে না — অংশ ৬-এ এটিই মূল চাবি।
নিচের finder একই string-এ frame ১ থেকে ৭ পর্যন্ত FIFO আর LRU চালায়, আর যেখানে frame বাড়ানোয় fault বেড়েছে, সেখানে ANOMALY লেখে।
// Belady's anomaly finder: page faults for 1..7 frames, FIFO vs LRU.
function faults(refs, n, policy) {
const mem = []; // mem[0] is always the next victim
let f = 0;
for (const p of refs) {
const i = mem.indexOf(p);
if (i >= 0) { // hit
if (policy === "LRU") { mem.splice(i, 1); mem.push(p); } // move to most-recent end
continue; // FIFO ignores hits
}
f++;
if (mem.length === n) mem.shift(); // evict oldest load / least recent use
mem.push(p);
}
return f;
}
function report(title, refs) {
console.log(title + ": " + refs.join(","));
for (const policy of ["FIFO", "LRU"]) {
const c = [];
for (let n = 1; n <= 7; n++) c.push(faults(refs, n, policy));
const bad = [];
for (let n = 2; n <= 7; n++) if (c[n - 1] > c[n - 2]) bad.push((n - 1) + "->" + n + " frames");
console.log(" " + policy.padEnd(4) + " frames 1..7: " + c.join(" ") +
(bad.length ? " ANOMALY at " + bad.join(", ") : " never increases"));
}
}
report("Belady string", [1,2,3,4,1,2,5,1,2,3,4,5]);
report("Classic string", [7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1]);আউটপুট (নির্ধারিত, প্রতিবার একই):
Belady string: 1,2,3,4,1,2,5,1,2,3,4,5
FIFO frames 1..7: 12 12 9 10 5 5 5 ANOMALY at 3->4 frames
LRU frames 1..7: 12 12 10 8 5 5 5 never increases
Classic string: 7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
FIFO frames 1..7: 20 15 15 10 9 6 6 never increases
LRU frames 1..7: 20 17 12 8 7 6 6 never increases
প্রথম string-এ FIFO-র সারি 12 12 9 10 5 5 5: ৩ থেকে ৪ frame-এ fault ৯ থেকে বেড়ে ১০ — anomaly. ৫টি বা বেশি frame-এ fault ৫টি, যা আলাদা page-এর সংখ্যা (১ থেকে ৫), অর্থাৎ শুধু compulsory fault. একই string-এ LRU-র সারি 12 12 10 8 5 5 5 কখনো বাড়ে না। পাঠ্যবইয়ের string-এ FIFO-ও এবার ঠিকঠাক আচরণ করে (২ আর ৩ frame-এ সমান ১৫, বাড়েনি) — anomaly কিছু বিশেষ string-এই দেখা দেয়, সব string-এ নয়। mem[0] সবসময় পরের victim: FIFO-তে hit হলে কিছু বদলায় না, LRU-তে hit-এর page তালিকার শেষে (সবচেয়ে নতুন) চলে যায় — দুই নীতির পুরো পার্থক্য ওই একটি লাইনে।
৪ · OPT (MIN) — ভবিষ্যৎ জানলে কী করতাম
১৯৬৬ সালে IBM-এর গবেষক László Bélády IBM Systems Journal-এ “A study of replacement algorithms for a virtual-storage computer” প্রকাশ করেন — IBM-এর পরীক্ষামূলক M44/44X virtual memory সিস্টেমে কাজের সূত্রে। সেখানে তিনি এমন একটি নীতি বর্ণনা করেন, যা যেকোনো reference string-এ সম্ভাব্য সবচেয়ে কম fault দেয়; তিনি একে MIN বলেছিলেন, পাঠ্যবইয়ে একে OPT বা optimal page replacement বলা হয়। এর সর্বোত্তমতার আনুষ্ঠানিক প্রমাণ আসে পরে, ১৯৭০-এর দশকের শুরুতে — Mattson, Gecsei, Slutz ও Traiger (১৯৭০) এবং Aho, Denning ও Ullman (১৯৭১)-এর কাজে।
OPT: সেই page সরানো, যেটি সবচেয়ে দেরিতে আবার লাগবে — অথবা আর কখনো লাগবে না। একাধিক page আর কখনো না লাগলে তাদের যেকোনোটি সরানো যায়; fault-সংখ্যা বদলায় না।
পাঠ্যবইয়ের string-এ ধাপ ৪-এ (page 2 এল, frame-এ 7, 0, 1) সামনের দিকে তাকান: 0 আবার লাগবে ধাপ ৫-এ, 1 লাগবে ধাপ ১৪-এ, 7 লাগবে ধাপ ১৮-এ — সবচেয়ে দূরে, তাই 7 যায়। ধাপ ৮-এ (page 4; frame-এ 2, 0, 3) 2 লাগবে ধাপ ৯-এ, 3 ধাপ ১০-এ, 0 ধাপ ১১-এ — তাই 0 যায়। এভাবে চালালে মোট fault ৯টি; এর মধ্যে ৬টি compulsory (ছয়টি আলাদা page), অর্থাৎ replacement-এর কারণে মাত্র ৩টি। এই string-এ ৩ frame দিয়ে ৯-এর কম fault অসম্ভব।
কেন OPT সর্বোত্তম — প্রমাণের রূপরেখা
দাবি: একই frame-সংখ্যায়, যেকোনো reference string-এ OPT-এর fault যেকোনো algorithm A-এর fault-এর চেয়ে বেশি নয়। প্রমাণের ধারণা (exchange argument): ধরুন A প্রথম i টি সিদ্ধান্তে OPT-এর সাথে মেলে, কিন্তু (i+1)-তম fault-এ আলাদা — OPT সরায় p (যার পরের ব্যবহার সবচেয়ে দূরে), A সরায় q. আমরা একটি নতুন algorithm A′ বানাই, যে এই fault-এ p সরায়, আর পরে যতটা সম্ভব A-কে নকল করে। এখন A′-এর মেমরি A-এর মতোই, শুধু A-র কাছে p, A′-এর কাছে q. q-এর পরের ব্যবহার p-এর আগে (কারণ p সবচেয়ে দূরে)। যতক্ষণ p বা q কেউ চাওয়া হয় না, দুজনের ফল হুবহু এক। q চাওয়া হলে A fault দেয়, A′ hit পায় — A′ একটি fault এগিয়ে গেল। ধরুন তখন A কোনো x সরাল; x = p হলে দুজনের মেমরি এখন এক, শেষ। না হলে এখন পার্থক্য শুধু “A-র কাছে p, A′-এর কাছে x”; পরে x বা p প্রথম চাওয়ার সময় A′ এক fault খরচ করে অন্যটি সরিয়ে আবার মিলে যায় — সঞ্চিত এক fault দিয়ে শোধ, তাই A′ কখনো A-র চেয়ে বেশি fault দেয় না। অর্থাৎ A′ ≤ A, আর A′ এখন OPT-এর সাথে i+1 টি সিদ্ধান্তে মেলে। এভাবে বারবার করলে OPT ≤ A. ∎
এটি পর্ব ১৭-এর SJF-এর আত্মীয়: দুজনেই “ভবিষ্যৎ জানলে সেরা”, আর দুজনেরই একই সমস্যা — বাস্তবে ভবিষ্যৎ জানা যায় না। কোন ঠিকানা কখন ছোঁয়া হবে, তা নির্ভর করে input আর ব্যবহারকারীর ওপর।
তাহলে OPT কী কাজে লাগে? মাপকাঠি হিসেবে। একটি প্রোগ্রামের address trace আগে রেকর্ড করে রাখা হলে (অফলাইনে) পুরো ভবিষ্যৎ জানা থাকে; সেই trace-এ OPT চালিয়ে জানা যায় সম্ভাব্য সেরা ফল কত। নতুন নীতি OPT-এর চেয়ে কয়েক শতাংশ বেশি fault দিলে উন্নতির জায়গা কম; বহু গুণ বেশি দিলে চিন্তার বিষয়।
৫ · LRU — অতীত দেখে ভবিষ্যতের অনুমান
ভবিষ্যৎ না জানলে সবচেয়ে ভালো আন্দাজ কী? পর্ব ৪-এর locality of reference: যে page সম্প্রতি ব্যবহৃত হয়েছে, সেটি শিগগির আবার লাগার সম্ভাবনা বেশি (temporal locality); যে page অনেকক্ষণ ছোঁয়া হয়নি, সেটি হয়তো আর লাগবে না। তাই LRU (least recently used) নীতি: যে page সবচেয়ে বেশি সময় ধরে ব্যবহৃত হয়নি, সেটি সরানো। OPT সামনে তাকায় — “কে সবচেয়ে দেরিতে লাগবে”; LRU পেছনে তাকায় — “কে সবচেয়ে আগে লেগেছিল”। সময়ের দিক উল্টে দেওয়া OPT.
একটি সুন্দর প্রতিসাম্য আছে: কোনো string S-এ LRU-র fault-সংখ্যা, উল্টো string-এ (শেষ থেকে শুরু) LRU-র fault-সংখ্যার সমান; OPT-এর বেলায়ও তাই। এই পর্বের simulator দিয়ে পাঠ্যবইয়ের string উল্টে চালালে ৩ frame-এ LRU আবার ১২, OPT আবার ৯ দেয়। (খেয়াল করুন: “উল্টো string-এ LRU = মূল string-এ OPT” — এমন দাবি ভুল; এখানে ১২ ≠ ৯.)
নিচের stepper-এ তিন নীতি একই string-এ পাশাপাশি চলছে — পরীক্ষার খাতায় যেভাবে টেবিল আঁকতে হয়, প্রতিটি কলাম একেকটি ধাপ। প্রতিটি fault-এ কেন ওই victim, তা ডান দিকের বাক্সে লেখা; ক্যাপশনের “মোট fault” FIFO / LRU / OPT ক্রমে।
1 ref 7 next: 0 1 2 0 3 0 …2 ref 0 next: 1 2 0 3 0 4 …3 ref 1 next: 2 0 3 0 4 2 …4 ref 2 next: 0 3 0 4 2 3 …5 ref 0 next: 3 0 4 2 3 0 …6 ref 3 next: 0 4 2 3 0 3 …7 ref 0 next: 4 2 3 0 3 2 …8 ref 4 next: 2 3 0 3 2 1 …9 ref 2 next: 3 0 3 2 1 2 …10 ref 3 next: 0 3 2 1 2 0 …11 ref 0 next: 3 2 1 2 0 1 …12 ref 3 next: 2 1 2 0 1 7 …13 ref 2 next: 1 2 0 1 7 0 …14 ref 1 next: 2 0 1 7 0 115 ref 2 next: 0 1 7 0 116 ref 0 next: 1 7 0 117 ref 1 next: 7 0 118 ref 7 next: 0 119 ref 0 next: 120 ref 1 next: -
| FIFO | |
| LRU | |
| OPT |
- ধাপ ১: page ৭. তিন নীতিতেই fault, ফাঁকা frame-এ বসল। মোট fault ১ / ১ / ১।
- ধাপ ২: page ০. তিন নীতিতেই fault, ফাঁকা frame-এ বসল। মোট fault ২ / ২ / ২।
- ধাপ ৩: page ১. তিন নীতিতেই fault, ফাঁকা frame-এ বসল। মোট fault ৩ / ৩ / ৩।
- ধাপ ৪: page ২. তিন নীতিতেই ৭ সরল। মোট fault ৪ / ৪ / ৪।
- ধাপ ৫: page ০. তিন নীতিতেই hit. মোট fault ৪ / ৪ / ৪।
- ধাপ ৬: page ৩. FIFO: ০ সরল; LRU: ১ সরল; OPT: ১ সরল। মোট fault ৫ / ৫ / ৫।
- ধাপ ৭: page ০. FIFO: ১ সরল; LRU: hit; OPT: hit. মোট fault ৬ / ৫ / ৫।
- ধাপ ৮: page ৪. FIFO: ২ সরল; LRU: ২ সরল; OPT: ০ সরল। মোট fault ৭ / ৬ / ৬।
- ধাপ ৯: page ২. FIFO: ৩ সরল; LRU: ৩ সরল; OPT: hit. মোট fault ৮ / ৭ / ৬।
- ধাপ ১০: page ৩. FIFO: ০ সরল; LRU: ০ সরল; OPT: hit. মোট fault ৯ / ৮ / ৬।
- ধাপ ১১: page ০. তিন নীতিতেই ৪ সরল। মোট fault ১০ / ৯ / ৭।
- ধাপ ১২: page ৩. তিন নীতিতেই hit. মোট fault ১০ / ৯ / ৭।
- ধাপ ১৩: page ২. তিন নীতিতেই hit. মোট fault ১০ / ৯ / ৭।
- ধাপ ১৪: page ১. FIFO: ২ সরল; LRU: ০ সরল; OPT: ৩ সরল। মোট fault ১১ / ১০ / ৮।
- ধাপ ১৫: page ২. FIFO: ৩ সরল; LRU: hit; OPT: hit. মোট fault ১২ / ১০ / ৮।
- ধাপ ১৬: page ০. FIFO: hit; LRU: ৩ সরল; OPT: hit. মোট fault ১২ / ১১ / ৮।
- ধাপ ১৭: page ১. তিন নীতিতেই hit. মোট fault ১২ / ১১ / ৮।
- ধাপ ১৮: page ৭. FIFO: ০ সরল; LRU: ২ সরল; OPT: ২ সরল। মোট fault ১৩ / ১২ / ৯।
- ধাপ ১৯: page ০. FIFO: ১ সরল; LRU: hit; OPT: hit. মোট fault ১৪ / ১২ / ৯।
- ধাপ ২০: page ১. FIFO: ২ সরল; LRU: hit; OPT: hit. মোট fault ১৫ / ১২ / ৯।
LRU-র ১২টি fault, OPT-এর ৯-এর কাছাকাছি, FIFO-র ১৫-এর চেয়ে অনেক ভালো। LRU কোথায় OPT-এর কাছে হারে? ধাপ ৯-এ (page 2) LRU 3-কে সরায়, কারণ 3 শেষ ব্যবহৃত হয়েছিল ধাপ ৬-এ — কিন্তু 3 আবার লাগে ঠিক পরের ধাপে। অতীত দিয়ে ভবিষ্যৎ আন্দাজ করলে এমন ভুল হবেই; locality যত জোরালো, ভুল তত কম।
নিচের simulator একই কাজ চারটি নীতিতে করে — FIFO, OPT, LRU আর Clock (Clock অংশ ৭-এ) — আর খাতার মতো টেবিল ছাপে: প্রতিটি কলাম একটি reference, প্রতিটি সারি একটি frame, শেষ সারিতে F মানে fault, ফাঁকা মানে hit. refs আর N বদলে নিজের বইয়ের অঙ্ক যাচাই করতে পারেন — তবে আগে হাতে করুন।
// Page-replacement simulator: FIFO, OPT, LRU and Clock on one reference string.
const refs = [7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1];
const N = 3; // number of frames
const policies = {
FIFO: (s) => s.queue.shift(), // frame loaded longest ago
OPT: (s, t, fr) => { let best = 0, far = -1; // page used farthest in the future
fr.forEach((p, i) => { let nx = refs.indexOf(p, t + 1); if (nx < 0) nx = Infinity;
if (nx > far) { far = nx; best = i; } }); return best; },
LRU: (s, t, fr) => fr.reduce((b, p, i) => s.last[p] < s.last[fr[b]] ? i : b, 0),
Clock: (s) => { while (s.bit[s.hand]) { s.bit[s.hand] = 0; s.hand = (s.hand + 1) % N; }
const v = s.hand; s.hand = (s.hand + 1) % N; return v; } // give second chances
};
for (const [name, victim] of Object.entries(policies)) {
const fr = Array(N).fill(null), s = { queue: [], last: {}, bit: Array(N).fill(0), hand: 0 };
const rows = fr.map(() => []), mark = [];
let faults = 0;
refs.forEach((p, t) => {
let i = fr.indexOf(p);
if (i >= 0) mark.push(" "); // hit
else {
faults++; mark.push("F");
i = fr.indexOf(null); // use a free frame if there is one
if (i < 0) i = victim(s, t, fr);
fr[i] = p; s.queue.push(i);
}
s.last[p] = t; s.bit[i] = 1; // every reference sets the reference bit
fr.forEach((q, k) => rows[k].push(q === null ? "." : q));
});
console.log(name + " (" + faults + " faults)");
console.log("ref " + refs.join(" "));
rows.forEach((r, k) => console.log("frame" + k + " " + r.join(" ")));
console.log("fault " + mark.join(" ") + "\n");
}আউটপুট (নির্ধারিত, প্রতিবার একই):
FIFO (15 faults)
ref 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
frame0 7 7 7 2 2 2 2 4 4 4 0 0 0 0 0 0 0 7 7 7
frame1 . 0 0 0 0 3 3 3 2 2 2 2 2 1 1 1 1 1 0 0
frame2 . . 1 1 1 1 0 0 0 3 3 3 3 3 2 2 2 2 2 1
fault F F F F F F F F F F F F F F F
OPT (9 faults)
ref 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
frame0 7 7 7 2 2 2 2 2 2 2 2 2 2 2 2 2 2 7 7 7
frame1 . 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0
frame2 . . 1 1 1 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1
fault F F F F F F F F F
LRU (12 faults)
ref 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
frame0 7 7 7 2 2 2 2 4 4 4 0 0 0 1 1 1 1 1 1 1
frame1 . 0 0 0 0 0 0 0 0 3 3 3 3 3 3 0 0 0 0 0
frame2 . . 1 1 1 3 3 3 2 2 2 2 2 2 2 2 2 7 7 7
fault F F F F F F F F F F F F
Clock (14 faults)
ref 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1
frame0 7 7 7 2 2 2 2 4 4 4 4 3 3 3 3 0 0 0 0 0
frame1 . 0 0 0 0 0 0 0 2 2 2 2 2 1 1 1 1 7 7 7
frame2 . . 1 1 1 3 3 3 3 3 0 0 0 0 2 2 2 2 2 1
fault F F F F F F F F F F F F F F
চারটি টেবিল চারটি নীতির পুরো trace: FIFO ১৫, OPT ৯, LRU ১২, Clock ১৪টি fault. টেবিলগুলো stepper-এর সাথে কলাম ধরে মেলে — যেমন LRU-র ধাপ ৯-এর কলামে frame2-তে 3 সরে 2 এসেছে। চারটি নীতি শুধু “victim কোন frame” ফাংশনে আলাদা। s.last[p] = t প্রতিটি reference-এ সময় লিখে রাখে (LRU-র জন্য), আর s.bit[i] = 1 প্রতিটি reference-এ reference bit তোলে (Clock-এর জন্য), যেমন hardware করে। OPT-এর ফাংশন refs.indexOf(p, t + 1) দিয়ে সামনের string দেখে — বাস্তব OS-এর পক্ষে ঠিক এই লাইনটিই অসম্ভব।
LRU বাস্তবায়নের দুই উপায় — আর তাদের দাম
Counter (সময়ের ছাপ)
CPU-তে একটি logical clock বা counter, প্রতিটি memory reference-এ বাড়ে। প্রতিটি page table entry-তে একটি “শেষ ব্যবহারের সময়” ঘর; প্রতিটি reference-এ hardware clock-এর মান সেখানে লিখে দেয়। Replacement-এ সব entry খুঁজে সবচেয়ে ছোট মান — O(frame-সংখ্যা) খোঁজা।
দাম: প্রতি access-এ একটি বাড়তি memory writeStack (doubly linked list)
Page নম্বরগুলোর একটি তালিকা; কোনো page ব্যবহৃত হলে তাকে তালিকার মাথায় তুলে আনা হয় (সবচেয়ে বেশি ছয়টি pointer বদল)। নিচের page সবসময় LRU — victim খুঁজতে O(1). এটিই LRU stack, অংশ ৬-এর প্রমাণের ভিত্তি।
দাম: প্রতি access-এ তালিকা বদলসমস্যাটা “প্রতি access-এ”। CPU সেকেন্ডে শত কোটি memory access করে; প্রতিটিতে software দিয়ে (interrupt-এ) হিসাব রাখলে মেমরি দশ গুণ বা বেশি ধীর হতো। তাই পুরো LRU প্রায় কোনো বাস্তব OS-এর page replacement-এ নেই; আছে তার সস্তা অনুকরণ (অংশ ৭)। ছোট, software-নিয়ন্ত্রিত cache-এ (যেখানে প্রতিটি access এমনিতেই software-এর ভেতর দিয়ে যায়) অবশ্য সত্যিকারের LRU সহজ — নিচের C প্রোগ্রামটি counter-পদ্ধতি দেখায়।
#include <stdio.h>
#define MAXF 8
/* LRU with a "counter" per frame: every access stores the current time;
the victim is the frame with the smallest (oldest) time stamp. */
int lru_faults(const int *refs, int len, int nframes) {
int page[MAXF], stamp[MAXF], used = 0, faults = 0;
for (int t = 0; t < len; t++) {
int i = -1;
for (int k = 0; k < used; k++) if (page[k] == refs[t]) i = k;
if (i < 0) { /* page fault */
faults++;
if (used < nframes) i = used++; /* free frame */
else { /* search all stamps: O(frames) */
i = 0;
for (int k = 1; k < used; k++) if (stamp[k] < stamp[i]) i = k;
}
page[i] = refs[t];
}
stamp[i] = t; /* the "clock register" copy */
}
return faults;
}
int main(void) {
int belady[] = {1,2,3,4,1,2,5,1,2,3,4,5};
int classic[] = {7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1};
for (int n = 3; n <= 4; n++)
printf("Belady string, %d frames: %d faults\n", n, lru_faults(belady, 12, n));
printf("Classic string, 3 frames: %d faults\n", lru_faults(classic, 20, 3));
return 0;
}আউটপুট:
Belady string, 3 frames: 10 faults
Belady string, 4 frames: 8 faults
Classic string, 3 frames: 12 faults
প্রতিটি reference-এ stamp[i] = t — এটি সেই “hardware clock-এর মান PTE-তে লেখা”, যা বাস্তব hardware প্রতিটি access-এ করতে পারে না। Victim খুঁজতে সব stamp দেখতে হয় (ভেতরের for). ফল তিনটি: Belady string-এ ৩ frame-এ ১০, ৪ frame-এ ৮ — frame বাড়ায় fault কমেছে, FIFO-র মতো বাড়েনি; আর পাঠ্যবইয়ের string-এ ১২, JS simulator-এর LRU-র সাথে হুবহু এক।
৬ · Stack algorithm — কেন LRU আর OPT-এ anomaly হয় না
Belady's anomaly কেন FIFO-তে হয়, কিন্তু LRU-তে কখনো নয় — পরীক্ষার প্রিয় তাত্ত্বিক প্রশ্ন। উত্তর ১৯৭০ সালের গবেষণাপত্রে: R. L. Mattson, J. Gecsei, D. R. Slutz আর I. L. Traiger, “Evaluation techniques for storage hierarchies” (IBM Systems Journal).
ধরুন Mt(n) = n টি frame থাকলে, string-এর প্রথম t টি reference-এর পর মেমরিতে থাকা page-এর সেট। একটি algorithm stack algorithm, যদি প্রতিটি string, প্রতিটি t আর প্রতিটি n-এর জন্য Mt(n) ⊆ Mt(n + 1) — অর্থাৎ ছোট মেমরিতে যা আছে, বড় মেমরিতেও তা আছে। এটিই inclusion property.
উপপাদ্য ১: stack algorithm-এ frame বাড়ালে fault কখনো বাড়ে না। প্রমাণ: t-তম reference-এ page x চাওয়া হলো। n frame-এ এটি hit হলে x ∈ Mt−1(n) ⊆ Mt−1(n+1), তাই n+1 frame-এও hit. অর্থাৎ n frame-এর প্রতিটি hit, n+1 frame-এও hit; তাই n+1 frame-এর hit-সংখ্যা ≥ n frame-এর, আর fault ≤. ∎
উপপাদ্য ২: LRU একটি stack algorithm. প্রমাণ: LRU-র নিয়মে n frame-এর মেমরিতে সবসময় থাকে সবচেয়ে সম্প্রতি ব্যবহৃত n টি আলাদা page (প্রথম n টি আলাদা page আসার পর থেকে; তার আগে সব page-ই থাকে)। এই সেট নির্ভর করে শুধু string-এর ওপর, n-এর ওপর নয় — একটি একক তালিকা (LRU stack) সব page-কে শেষ ব্যবহারের ক্রমে সাজায়, আর n frame মানে তালিকার মাথার n টি। মাথার n টি অবশ্যই মাথার n+1 টির অংশ। ∎
OPT-ও stack algorithm: Mattson ও সহকর্মীরা দেখান, যে algorithm প্রতিটি মুহূর্তে page-গুলোকে এমন একটি অগ্রাধিকার-ক্রমে সাজায় যা frame-সংখ্যার ওপর নির্ভর করে না, আর সবসময় সবচেয়ে কম অগ্রাধিকারের page সরায়, সেটি stack algorithm. LRU-র অগ্রাধিকার “শেষ ব্যবহারের সময়”, OPT-এর “পরের ব্যবহারের সময়” (যত কাছে, তত বেশি অগ্রাধিকার) — কোনোটিই n-এর ওপর নির্ভর করে না। FIFO-র অগ্রাধিকার “কখন RAM-এ এসেছিল” — আর কখন এসেছিল তা নির্ভর করে কবে fault হয়েছিল, অর্থাৎ n-এর ওপর। তাই FIFO stack algorithm নয়।
FIFO-র বেলায় inclusion ভাঙার প্রমাণ আমরা Belady stepper-এই দেখেছি: ধাপ ৭-এর পর ৩-frame-এর সেট {1, 2, 5}, ৪-frame-এর সেট {2, 3, 4, 5} — 1 ছোট মেমরিতে আছে, বড়টিতে নেই। ঠিক পরের ধাপে 1 চাওয়া হলো: ৩ frame-এ hit, ৪ frame-এ fault.
একবার চালিয়ে সব মেমরি-আকারের উত্তর — stack distance
Mattson-এর গবেষণার ব্যবহারিক রত্ন এটি। LRU stack একবার পুরো string-এর ওপর চালান; প্রতিটি reference-এ দেখুন চাওয়া page stack-এর কত গভীরে ছিল — একে বলে stack distance (প্রথমবার এলে ∞)। তাহলে n frame-এর LRU-তে fault = যেসব reference-এর stack distance > n. একটি পাস, আর সব n-এর উত্তর একসাথে — cache-এর আকার ঠিক করার জন্য এটি আজও ব্যবহৃত হয়। Belady string-এ:
| ধাপ | Page | LRU stack-এর অবস্থা (মাথা → তলা), reference-এর পর | Stack distance | ৩ frame | ৪ frame |
|---|---|---|---|---|---|
| ১ | 1 | 1 | ∞ | F | F |
| ২ | 2 | 2 1 | ∞ | F | F |
| ৩ | 3 | 3 2 1 | ∞ | F | F |
| ৪ | 4 | 4 3 2 1 | ∞ | F | F |
| ৫ | 1 | 1 4 3 2 | ৪ | F | hit |
| ৬ | 2 | 2 1 4 3 | ৪ | F | hit |
| ৭ | 5 | 5 2 1 4 3 | ∞ | F | F |
| ৮ | 1 | 1 5 2 4 3 | ৩ | hit | hit |
| ৯ | 2 | 2 1 5 4 3 | ৩ | hit | hit |
| ১০ | 3 | 3 2 1 5 4 | ৫ | F | F |
| ১১ | 4 | 4 3 2 1 5 | ৫ | F | F |
| ১২ | 5 | 5 4 3 2 1 | ৫ | F | F |
| মোট: ৩ frame-এ ১০টি fault (৫টি ∞ + distance ৪, ৪, ৫, ৫, ৫), ৪ frame-এ ৮টি (৫টি ∞ + তিনটি ৫) — C প্রোগ্রামের ফলের সাথে মেলে | |||||
৭ · LRU-র কাছাকাছি — reference bit, second chance আর clock
পুরো LRU দামি, কিন্তু hardware একটি সস্তা সাহায্য দেয়: প্রতিটি page table entry-তে একটি reference bit (x86-এ A বা Accessed বিট)। Page-টি পড়া বা লেখা হলেই MMU নিজে সেটি ১ করে; শূন্য করে শুধু OS. এক বিটে ক্রম জানা যায় না, শুধু “শেষবার শূন্য করার পর ব্যবহৃত কি না”।
Additional-reference-bits — ৮ বিটের ইতিহাস
প্রতিটি page-এর জন্য memory-তে একটি ৮ বিটের byte রাখুন। নিয়মিত বিরতিতে (ধরুন প্রতি ১০০ ms-এ একটি timer interrupt-এ) OS প্রতিটি page-এর reference bit ওই byte-এর সবচেয়ে বাঁ দিকে (high-order) ঢুকিয়ে দেয়, বাকি বিটগুলো এক ঘর ডানে সরিয়ে দেয় (shift register), সবচেয়ে ডানের বিট ফেলে দেয়, তারপর reference bit শূন্য করে। ফলে byte-টি শেষ ৮টি বিরতির ব্যবহারের ইতিহাস।
00000000— শেষ ৮টি বিরতিতে একবারও ব্যবহৃত হয়নি।11111111— প্রতিটি বিরতিতে ব্যবহৃত।11000100বনাম01110111: unsigned সংখ্যা হিসেবে প্রথমটি ১৯৬, দ্বিতীয়টি ১১৯. প্রথমটি শেষ দুই বিরতিতে ব্যবহৃত, দ্বিতীয়টি সর্বশেষ বিরতিতে নয় — তাই সবচেয়ে ছোট সংখ্যার page-টিই আনুমানিক LRU, এখানে দ্বিতীয়টি সরবে।
সমান হলে তাদের মধ্যে FIFO. বেশি বিট মানে লম্বা ইতিহাস, কিন্তু বেশি কাজ; ইতিহাস-byte না রেখে শুধু reference bit দেখলে নিচের second chance.
Second chance আর clock
মূলে FIFO, শুধু একটি দয়া যোগ করা: queue-র মাথার page-এর reference bit ১ হলে তাকে সরানো হয় না — বিট শূন্য করে তাকে আবার queue-র শেষে পাঠানো হয়, যেন সে এইমাত্র এসেছে; এটিই তার “দ্বিতীয় সুযোগ”। বিট ০ হলে সে বিদায়। যে page এত ঘন ঘন ব্যবহৃত হয় যে প্রতি ঘুরতি পর্যন্ত বিট আবার ১ হয়ে যায়, সে কখনো সরে না।
Queue থেকে বের করে আবার ঢোকানোর ঝামেলা এড়াতে বাস্তবায়নটি হয় একটি circular queue-তে: frame-গুলো একটি ঘড়ির মুখের মতো বৃত্তে সাজানো, আর একটি pointer — ঘড়ির কাঁটা বা hand — পরের প্রার্থী দেখায়। Fault হলে hand এগোয়: বিট ১ পেলে শূন্য করে পরের frame-এ যায়, বিট ০ পেলে সেই page সরিয়ে সেখানে নতুন page বসায় আর এক ঘর এগিয়ে থামে। এই রূপটিই clock algorithm; Multics প্রকল্পের F. J. Corbató ১৯৬৮ সালের একটি গবেষণাপত্রে এর বর্ণনা দেন বলে সাধারণত কৃতিত্ব দেওয়া হয়।
পাড়ার বাড়ির দারোয়ান রাতে একটি নির্দিষ্ট ক্রমে ফ্ল্যাটগুলো ঘুরে দেখেন। প্রতিটি দরজায় একটি ছোট কাগজ গোঁজা থাকে; বাসিন্দা দিনে একবারও বের হলে কাগজটি পড়ে যায় (বিট ১)। দারোয়ান এসে দেখেন — কাগজ পড়ে আছে? “আজ এরা সক্রিয় ছিলেন” — নতুন কাগজ গুঁজে (বিট ০) পরের দরজায় যান। কাগজ জায়গামতো? তাহলে এই বাসা অনেক দিন ফাঁকা — এখানেই নতুন ভাড়াটে উঠবে।
1 ref 72 ref 03 ref 14 ref 25 ref 06 ref 37 ref 08 ref 49 ref 210 ref 311 ref 0… refs 12-20: 3 2 1 2 0 1 7 0 1
| Frame ০ ১ ২ | |
| Reference bit | |
| ফল |
- ধাপ ১: page ৭. fault, ফাঁকা frame-এ বসল, R বিট ১. Hand ০ নম্বর frame-এই। মোট fault ১।
- ধাপ ২: page ০. fault, ফাঁকা frame-এ বসল, R বিট ১. Hand ০ নম্বর frame-এই। মোট fault ২।
- ধাপ ৩: page ১. fault, ফাঁকা frame-এ বসল, R বিট ১. Hand ০ নম্বর frame-এই। মোট fault ৩।
- ধাপ ৪: page ২. fault. Hand ৩টি frame-এ বিট ১ পেয়ে শূন্য করল (page ৭, ০, ১-এর দ্বিতীয় সুযোগ), তারপর ০ নম্বর frame-এ বিট ০ — page ৭ সরল, ২ বসল; hand এখন ১-এ। মোট fault ৪।
- ধাপ ৫: page ০. hit — hardware তার R বিট ১ করে, hand নড়ে না। মোট fault ৪।
- ধাপ ৬: page ৩. fault. Page ০-এর বিট ১, তাই সে দ্বিতীয় সুযোগ পেল (বিট ০); পরের, ২ নম্বর frame-এ বিট ০ — page ১ সরল, ৩ বসল; hand এখন ০-এ। মোট fault ৫।
- ধাপ ৭: page ০. hit — hardware তার R বিট ১ করে, hand নড়ে না। মোট fault ৫।
- ধাপ ৮: page ৪. fault. Hand ৩টি frame-এ বিট ১ পেয়ে শূন্য করল (page ২, ০, ৩-এর দ্বিতীয় সুযোগ), তারপর ০ নম্বর frame-এ বিট ০ — page ২ সরল, ৪ বসল; hand এখন ১-এ। মোট fault ৬।
- ধাপ ৯: page ২. fault. Hand-এর নিচেই (১ নম্বর frame) বিট ০ — ঘোরাঘুরি ছাড়াই page ০ সরল, ২ বসল; hand এখন ২-এ। মোট fault ৭।
- ধাপ ১০: page ৩. hit — hardware তার R বিট ১ করে, hand নড়ে না। মোট fault ৭।
- ধাপ ১১: page ০. fault. Hand ৩টি frame-এ বিট ১ পেয়ে শূন্য করল (page ৩, ৪, ২-এর দ্বিতীয় সুযোগ), তারপর ২ নম্বর frame-এ বিট ০ — page ৩ সরল, ০ বসল; hand এখন ০-এ। মোট fault ৮।
- বাকি ৯টি reference. একই নিয়মে চললে শেষে frame-এ থাকে ০ ৭ ১, আর মোট fault ১৪টি — FIFO-র ১৫-এর চেয়ে কম, LRU-র ১২-এর চেয়ে বেশি। C প্রোগ্রামটি প্রতিটি ধাপ ছাপে; মিলিয়ে দেখুন।
ধাপ ৪ আর ৮ লক্ষ করুন: তিনটি বিটই ১ থাকায় hand পুরো এক পাক ঘুরে সবাইকে শূন্য করে, তারপর যেখান থেকে শুরু করেছিল সেখানেই victim পায় — সব বিট ১ হলে clock হুবহু FIFO হয়ে যায়। এ কারণে clock-ও stack algorithm নয়; Belady string-এ এই পর্বের simulator-এর নিয়মে clock-ও ৩ frame-এ ৯, ৪ frame-এ ১০টি fault দেয় — FIFO-র মতোই anomaly. নিচের C প্রোগ্রাম একই নিয়মে প্রতিটি ধাপ ছাপে — JS simulator-এর Clock টেবিলের সাথে মিলিয়ে দেখুন।
#include <stdio.h>
#define N 3 /* frames */
int refs[] = {7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1};
int frame[N] = {-1, -1, -1}; /* -1 = empty */
int bit[N]; /* reference bits */
int hand = 0; /* the clock hand */
int main(void) {
int len = sizeof refs / sizeof refs[0], faults = 0;
for (int t = 0; t < len; t++) {
int p = refs[t], i = -1;
for (int k = 0; k < N; k++) if (frame[k] == p) i = k;
char mark = ' ';
if (i < 0) { /* page fault */
faults++; mark = 'F';
for (int k = 0; k < N && i < 0; k++) if (frame[k] == -1) i = k;
if (i < 0) { /* no free frame: run the clock */
while (bit[hand]) { bit[hand] = 0; hand = (hand + 1) % N; }
i = hand; hand = (hand + 1) % N;
}
frame[i] = p;
}
bit[i] = 1; /* the hardware sets R on every access */
printf("ref %d %c frames", p, mark);
for (int k = 0; k < N; k++) {
if (frame[k] < 0) printf(" .");
else printf(" %2d", frame[k]);
}
printf(" R=%d%d%d hand->%d\n", bit[0], bit[1], bit[2], hand);
}
printf("Clock, %d frames: %d page faults\n", N, faults);
return 0;
}আউটপুট:
ref 7 F frames 7 . . R=100 hand->0
ref 0 F frames 7 0 . R=110 hand->0
ref 1 F frames 7 0 1 R=111 hand->0
ref 2 F frames 2 0 1 R=100 hand->1
ref 0 frames 2 0 1 R=110 hand->1
ref 3 F frames 2 0 3 R=101 hand->0
ref 0 frames 2 0 3 R=111 hand->0
ref 4 F frames 4 0 3 R=100 hand->1
ref 2 F frames 4 2 3 R=110 hand->2
ref 3 frames 4 2 3 R=111 hand->2
ref 0 F frames 4 2 0 R=001 hand->0
ref 3 F frames 3 2 0 R=101 hand->1
ref 2 frames 3 2 0 R=111 hand->1
ref 1 F frames 3 1 0 R=010 hand->2
ref 2 F frames 3 1 2 R=011 hand->0
ref 0 F frames 0 1 2 R=111 hand->1
ref 1 frames 0 1 2 R=111 hand->1
ref 7 F frames 0 7 2 R=010 hand->2
ref 0 frames 0 7 2 R=110 hand->2
ref 1 F frames 0 7 1 R=111 hand->0
Clock, 3 frames: 14 page faults
প্রতিটি লাইনে frame-এর page, তিনটি reference bit (frame ০, ১, ২) আর ধাপের শেষে hand কোথায়। চতুর্থ লাইনে (page 2) while লুপ তিনটি বিটই শূন্য করে frame ০-তে ফেরে, 7 সরায়, hand যায় ১-এ — তাই R=100: নতুন page 2-এর বিট ১, বাকিরা এইমাত্র শূন্য হয়েছে। ধাপ ১১-এর লাইনে (ref 0 F) আবার পুরো পাক, R=001. শেষ লাইন: ১৪টি fault, আর frame-এর কলাম JS টেবিলের Clock অংশের সাথে হুবহু এক। এখানে নতুন page-এর বিট ১ ধরা হয়; কিছু বই ০ ধরে, তাতে সংখ্যা বদলাতে পারে — পরীক্ষায় প্রশ্নের নিয়মটি আগে পড়ে নিন।
Enhanced second chance — reference আর modify বিট একসাথে
অংশ ১-এর dirty bit মনে করুন: dirty page সরাতে দুটি I/O, পরিষ্কারটিতে একটি। তাই (reference, modify) জোড়া দিয়ে চারটি শ্রেণি করা হয়:
| (R, M) | মানে | সরানোর জন্য |
|---|---|---|
| (০, ০) | সম্প্রতি ব্যবহৃত নয়, বদলানোও নয় | সবচেয়ে ভালো victim |
| (০, ১) | সম্প্রতি ব্যবহৃত নয়, কিন্তু dirty | খারাপ নয়, তবে আগে লিখতে হবে |
| (১, ০) | সম্প্রতি ব্যবহৃত, পরিষ্কার | হয়তো শিগগির আবার লাগবে |
| (১, ১) | সম্প্রতি ব্যবহৃত আর dirty | সবচেয়ে খারাপ victim |
Clock-এর মতোই hand ঘোরে, কিন্তু প্রথমে সবচেয়ে নিচের যে শ্রেণিতে কেউ আছে, সেই শ্রেণির প্রথম page সরায়; দরকারে বৃত্তটি কয়েকবার ঘুরতে হতে পারে। ফল: সম্প্রতি অব্যবহৃত আর পরিষ্কার page-কে অগ্রাধিকার — I/O কম। Silberschatz-এর পাঠ্যবই এর একটি রূপ পুরোনো Macintosh-এর virtual memory-তে ব্যবহারের কথা বলে; Tanenbaum-এর বইয়ে প্রায় একই ধারণা NRU (not recently used) নামে আছে।
৮ · গণনা-ভিত্তিক নীতি আর page buffering
LFU আর MFU
প্রতিটি page কতবার ব্যবহৃত হয়েছে, একটি counter-এ গুনে রাখুন। তারপর দুই বিপরীত যুক্তি:
- LFU (least frequently used): সবচেয়ে কম ব্যবহৃত page সরানো — যুক্তি, যে বেশি ব্যবহৃত, সে গুরুত্বপূর্ণ। সমস্যা: যে page প্রোগ্রামের শুরুর দিকে খুব ব্যবহৃত হয়েছিল কিন্তু এখন আর লাগে না, তার বড় count তাকে চিরকাল রেখে দেয়। প্রতিকার: নিয়মিত সব count এক বিট ডানে সরিয়ে (অর্ধেক করে) পুরোনো ইতিহাস ক্ষয় করা।
- MFU (most frequently used): সবচেয়ে বেশি ব্যবহৃত page সরানো — যুক্তি, ছোট count-এর page হয়তো এইমাত্র এসেছে, তাকে এখনো ব্যবহারের সুযোগ দেওয়া উচিত।
দুটিই বাস্তবে কম ব্যবহৃত — LRU-র মতোই দামি, অথচ OPT-এর ভালো অনুকরণ নয়। এই পর্বের simulator-এ count = RAM-এ আসার পর থেকে ব্যবহারের সংখ্যা, আর সমান হলে সবচেয়ে ছোট নম্বরের frame — এই নিয়মে পাঠ্যবইয়ের string-এ ৩ frame-এ LFU ১৪টি fault দেয় (অনুশীলন ৪-এ নিজে যোগ করে দেখবেন), যা LRU-র ১২-এর চেয়ে খারাপ। তবে অ্যাপ্লিকেশন-cache-এ, যেখানে কিছু key সত্যিই চিরকাল জনপ্রিয় (একটি ওয়েবসাইটের হোমপেজ), ক্ষয়সহ LFU ভালো কাজ করে — অংশ ৯-এ Redis.
Page buffering — হাতে কয়েকটি ফাঁকা frame রাখা
উপরের যেকোনো algorithm-এর পাশে OS কয়েকটি কৌশল যোগ করে, যাতে fault-এর সময় অপেক্ষা কম হয়:
- ফাঁকা frame-এর পুল (free-frame pool): সবসময় কয়েকটি frame ফাঁকা রাখা। Fault হলে নতুন page সোজা একটি ফাঁকা frame-এ পড়া হয় — victim লেখার অপেক্ষা ছাড়াই। Victim পরে লেখা হয়ে পুলে যোগ হয়।
- Dirty page আগেভাগে লেখা: ডিস্ক অলস থাকলে বদলানো page-গুলো লিখে তাদের dirty bit শূন্য করা — পরে victim হলে আর লিখতে হবে না।
- পুলের frame-এ কে ছিল মনে রাখা: ফাঁকা পুলে যাওয়া frame-এর পুরোনো ডেটা তখনো মোছা হয়নি। সেই page আবার লাগলে, frame-টি অন্য কাউকে দেওয়ার আগেই, ডিস্কে না গিয়ে সোজা ফেরত — একটি “soft” fault. ফলে ভুল victim বাছাইয়ের ক্ষতি অনেকটা মুছে যায়।
শেষ কৌশলটির বিখ্যাত উদাহরণ DEC-এর VAX/VMS: পাঠ্যবইয়ের বর্ণনা অনুযায়ী VAX hardware-এ reference bit ছিল না, তাই VMS সাধারণ FIFO চালাত, আর সরানো page-গুলোকে ফাঁকা ও modified তালিকায় কিছুক্ষণ রেখে দিত — ভুল করে সরানো ব্যস্ত page দ্রুত ফেরত পাওয়া যেত (ভাবনার প্রশ্ন ৫)। আজকের Windows-এর standby আর modified list এই একই ধারণার উত্তরসূরি।
৯ · বাস্তব সিস্টেমে — Linux, Windows আর অ্যাপ্লিকেশন-cache
Linux: active/inactive list থেকে MGLRU
Linux বহু বছর ধরে (২.৬-এর যুগ থেকে) পুরো মেমরিকে কয়েকটি LRU তালিকায় রাখে — প্রতিটি ধরনের জন্য একটি active আর একটি inactive list; ২.৬.২৮ থেকে ফাইলের page (page cache) আর anonymous memory (heap, stack) আলাদা তালিকায়। নতুন page সাধারণত inactive list-এ ঢোকে; দ্বিতীয়বার ব্যবহৃত হলে active-এ ওঠে। মেমরি কম পড়লে kswapd নামের kernel thread inactive list-এর তলা থেকে page দেখে: hardware-এর accessed বিট ১ হলে তাকে আরেক সুযোগ দেয় (second chance-এর ছায়া), না হলে ফেরত নেয় — পরিষ্কার ফাইল-page সোজা ফেলে দিয়ে, dirty বা anonymous page লিখে বা swap করে।
দুই তালিকার কারণ একটি পরিচিত দুর্বলতা: একটি বড় ফাইল একবার পড়লে (ধরুন backup নেওয়া বা ভিডিও কপি করা) সাধারণ LRU সেই একবার-ব্যবহৃত page দিয়ে পুরো মেমরি ভরে ফেলে, আর সত্যিই ব্যস্ত page সরিয়ে দেয়। দুই তালিকায় একবার-ব্যবহৃত page inactive-এই থেকে যায় আর সেখান থেকেই বিদায় নেয় — একে scan resistance বলে।
Linux ৬.১ (ডিসেম্বর ২০২২) থেকে একটি বিকল্প নকশা kernel-এ এসেছে: MGLRU (multi-gen LRU), মূলত Google-এর Yu Zhao-র কাজ। দুই তালিকার বদলে page-গুলো কয়েকটি “প্রজন্মে” (generation) ভাগ হয়; বয়স বাড়ানোর জন্য kernel প্রসেসের page table ধরে ধরে accessed বিট পড়ে, আর সবচেয়ে পুরোনো প্রজন্ম থেকে ফেরত নেয়। Google ChromeOS আর Android-এর পরীক্ষায় kswapd-এর CPU খরচ আর low-memory kill কমার কথা জানিয়েছিল; বিভিন্ন workload-এ ফল ভিন্ন হতে পারে। এটি CONFIG_LRU_GEN দিয়ে compile হয়, আর অনেক distro-তে চালু থাকলেও সব জায়গায় ডিফল্ট নয় — নিজের মেশিনে দেখে নিন:
$ grep -E '^(Active|Inactive)' /proc/meminfo
Active: 5123456 kB
Inactive: 2345678 kB
Active(anon): 1987654 kB
Inactive(anon): 456789 kB
Active(file): 3135802 kB
Inactive(file): 1888889 kB
$ cat /sys/kernel/mm/lru_gen/enabled # file absent = kernel built without MGLRU
0x00070x0007 মানে MGLRU চালু (বিটগুলো তার আলাদা অংশ নির্দেশ করে), 0x0000 মানে বন্ধ — তখন পুরোনো active/inactive নীতি চলে। পড়তে সাধারণ ব্যবহারকারীই যথেষ্ট; বদলাতে root লাগে — তার আগে kernel ডকুমেন্টেশনের multigen_lru অংশ পড়ে নিন। Linux-এর reclaim, page cache আর swap-এর বাস্তব দিক পর্ব ৩৮-এ।
Windows: working set আর trimming
Windows প্রতিটি প্রসেসের RAM-এ থাকা page-গুলোকে তার working set বলে (পর্ব ৩৭-এর তাত্ত্বিক working set-এর সাথে নামের মিল, অর্থ পুরো এক নয়)। ফাঁকা RAM কমে গেলে memory manager-এর একটি অংশ (working set manager) প্রসেসগুলোর working set থেকে কম ব্যবহৃত page ছেঁটে ফেলে — working-set trimming (accessed বিট দেখে বয়স হিসাব করে)। ছাঁটা page সঙ্গে সঙ্গে হারায় না: পরিষ্কার হলে যায় standby list-এ, dirty হলে modified list-এ (পরে modified page writer লেখে)। প্রসেস আবার সেটি চাইলে ডিস্ক ছাড়াই ফেরত পায় — অংশ ৮-এর page buffering. Task Manager-এর “Standby” মেমরি আসলে এই তালিকা।
OS-এর বাইরে — একই প্রশ্ন সবখানে
- CPU cache: set-associative cache-এ একটি set-এর কয়েকটি way-এর মধ্যে কোনটি সরবে, তা hardware ঠিক করে প্রতি clock cycle-এ — পুরো LRU সেখানে খুব দামি, তাই অনেক নকশা কয়েকটি বিট দিয়ে pseudo-LRU (যেমন tree-PLRU) ব্যবহার করে।
- Database-এর buffer pool: PostgreSQL তার shared buffers-এ clock-sweep চালায় — প্রতিটি buffer-এ একটি ছোট usage count, hand ঘোরার সময় কমে; MySQL-এর InnoDB একটি LRU তালিকা রাখে যার মাঝখানে নতুন page ঢোকে (midpoint insertion), যাতে বড় table scan পুরো pool ধুয়ে না দেয়। বিস্তারিত DBMS কোর্সের storage internals পর্বে।
- Redis:
maxmemoryছাড়ালেallkeys-lruনীতিতে key সরায় — কিন্তু সত্যিকারের LRU তালিকা রাখে না; কয়েকটি key নমুনা হিসেবে তুলে (ডিফল্টmaxmemory-samples 5) তাদের মধ্যে সবচেয়ে পুরোনোটি সরায় — LRU-র সস্তা অনুকরণ। ৪.০ থেকে ক্ষয়সহ LFU নীতিও আছে। - নিজের কোডে: ওয়েব অ্যাপের ছোট cache-এ সত্যিকারের LRU সহজ — hash table + doubly linked list, প্রতিটি get/put O(1). ইন্টারভিউর “LRU cache লিখুন” প্রশ্নটি ঠিক এটিই।
আরও গভীরে: LRU-র scan-দুর্বলতা আর LFU-র পুরোনো-ইতিহাস-দুর্বলতা মেলাতে গবেষকেরা কয়েকটি নীতি প্রস্তাব করেছেন — LRU-K (O'Neil ও সহকর্মী, ১৯৯৩; শেষ K-তম ব্যবহারের সময় দেখে), 2Q, আর IBM-এর Megiddo ও Modha-র ARC (২০০৩), যা recency আর frequency-র দুই তালিকার আকার চলার সময় নিজে নিজে মানিয়ে নেয়; ZFS-এর ARC cache এর ওপর ভিত্তি করে। আর বিপুল RAM-এ (শত শত GB) hand-এর এক পাক ঘোরাই সময়সাপেক্ষ — তাই MGLRU page table ধরে হাঁটে, যাতে ঘন অংশে একসাথে অনেক accessed বিট পড়া যায়।
১০ · AI-এর যুগে page replacement
Page replacement-এর অঙ্ক AI সঙ্গে সঙ্গে করে দেয় — আর প্রায়ই ভুল করে। বারবার দেখা ভুলগুলো:
| AI-এর ভুল | কেন ভুল | কীভাবে যাচাই করবেন |
|---|---|---|
| Fault গোনায় এক-দুটি ভুল, বিশেষ করে লম্বা string-এ | ভাষা-মডেল ধাপে ধাপে হিসাব রাখে না, “দেখতে ঠিক” সংখ্যা লেখে | এই পর্বের simulator-এ string বসিয়ে মেলান; খাতায় টেবিল আঁকুন |
| FIFO-তে hit-এর পর queue-র ক্রম বদলে ফেলা | এটি LRU-র নিয়ম; FIFO hit-এ কিছু বদলায় না | যেকোনো hit-এর পরের ধাপে queue-র মাথা মিলিয়ে দেখুন |
| “LRU-তেও Belady's anomaly হতে পারে” বা “Belady's anomaly সব algorithm-এ হয়” | LRU ও OPT stack algorithm, এদের হয় না | অংশ ৬-এর প্রমাণ; finder-এ LRU-র সারি দেখুন |
| “Clock = LRU” | Clock এক বিটের অনুকরণ; সব বিট ১ হলে FIFO | Simulator-এ Clock ১৪ বনাম LRU ১২ |
| “উল্টো string-এ LRU = মূল string-এ OPT” | সঠিক দাবি: LRU(S) = LRU(উল্টো S), OPT(S) = OPT(উল্টো S) | Simulator-এ string উল্টে চালান |
| “Linux পুরো LRU চালায়” বা MGLRU-কে সব distro-র ডিফল্ট বলা | Linux LRU-র অনুকরণ চালায়; MGLRU kernel config আর distro-নির্ভর | /sys/kernel/mm/lru_gen/enabled আর kernel ডকুমেন্টেশন দেখুন |
Production সার্ভারে “sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' দিয়ে মেমরি খালি করুন” | এটি কাজের cache ফেলে দেয়, পরে সব আবার ডিস্ক থেকে পড়তে হয় — সমস্যা লুকায়, সমাধান করে না | আগে MemAvailable আর কে মেমরি নিচ্ছে তা মাপুন (পর্ব ৩৮) |
অঙ্ক দেওয়ার সময় নিয়মগুলো স্পষ্ট লিখে দিন, আর যাচাইযোগ্য টেবিল চান:
AI-এর উত্তর মানার আগে — চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Belady's anomaly কী? কোন algorithm-এ হয়? — frame বাড়ালে fault বাড়ার ঘটনা; FIFO-তে হয় (1,2,3,4,1,2,5,1,2,3,4,5: ৩ frame ৯, ৪ frame ১০), stack algorithm (LRU, OPT)-এ হয় না।
- OPT কেন বাস্তবায়ন করা যায় না, তবু কেন শেখানো হয়? — ভবিষ্যৎ reference জানতে হয়; সম্ভাব্য সেরা ফল হিসেবে অন্য algorithm মাপার মাপকাঠি।
- Dirty (modify) bit-এর কাজ? — page বদলেছে কি না জানায়; পরিষ্কার victim লিখতে হয় না, তাই replacement-এ I/O অর্ধেক।
- LRU কীভাবে বাস্তবায়ন করবেন, আর OS-এ কেন সরাসরি নেই? — counter বা stack (O(1) cache-এ hash map + doubly linked list); প্রতি memory access-এ হালনাগাদ hardware-এ দামি, তাই reference bit-ভিত্তিক clock.
- Second chance আর clock-এর সম্পর্ক? — একই algorithm; clock হলো circular queue আর hand দিয়ে second chance-এর বাস্তবায়ন, সব বিট ১ হলে FIFO-র মতো।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১১৯৬০-এর দশকে IBM-এ Bélády কেন একটি “অসম্ভব” algorithm নিয়ে কাজ করলেন? একটি বাস্তবায়ন-অযোগ্য নীতি গবেষণায় এত গুরুত্বপূর্ণ কেন?
১৯৬০-এর দশকের মাঝামাঝি virtual memory নতুন আর বিতর্কিত ধারণা। ব্রিটেনের Atlas কম্পিউটার (১৯৬২) প্রথম paging দেখিয়েছিল, কিন্তু অনেক প্রকৌশলী সন্দেহ করতেন — ড্রাম বা ডিস্ক থেকে বারবার page আনার খরচে প্রোগ্রাম এত ধীর হয়ে যাবে যে লাভের চেয়ে ক্ষতি বেশি। IBM-এর M44/44X ছিল এই প্রশ্নের উত্তর খোঁজার একটি পরীক্ষামূলক যন্ত্র: একটি IBM 7044-কে বদলে প্রতিটি ব্যবহারকারীকে নিজের “ভার্চুয়াল” মেশিন দেওয়া। সেখানে বাস্তব প্রোগ্রাম চালিয়ে মাপা হচ্ছিল, কোন replacement নীতিতে কত fault হয়।
কিন্তু মাপ নিলেই একটি প্রশ্ন ওঠে: “ভালো” মানে কত ভালো? একটি নীতি যদি ১,০০০টি fault দেয়, সেটি কি চমৎকার, নাকি খারাপ? তুলনার একটি স্থির বিন্দু লাগে। Bélády-র ১৯৬৬ সালের গবেষণাপত্রের গুরুত্বপূর্ণ অবদান ঠিক এটি — তিনি এমন একটি নীতি বর্ণনা করলেন (MIN), যা কোনো string-এ সম্ভাব্য সর্বনিম্ন fault দেয়। এটি চালাতে ভবিষ্যৎ লাগে, কিন্তু রেকর্ড করা trace-এ ভবিষ্যৎ জানা থাকে। ফলে প্রতিটি বাস্তব নীতি সম্পর্কে প্রশ্ন করা যায়: “এটি সেরা সম্ভাব্যের চেয়ে কত গুণ খারাপ?” সেই গবেষণায় random, FIFO আর ব্যবহারের ইতিহাসভিত্তিক বিভিন্ন নীতি তুলনা করা হয়েছিল, আর দেখা গিয়েছিল ইতিহাস কাজে লাগানো নীতিগুলো সাধারণত ভালো — locality-র একটি প্রাথমিক পরীক্ষামূলক প্রমাণ।
এই চিন্তার ধারা আজও জীবন্ত। CPU cache, CDN, database buffer — যেকোনো নতুন cache-নীতির গবেষণাপত্রে সাধারণত Bélády-র MIN-এর সাথে তুলনা থাকে। সাম্প্রতিক কিছু গবেষণা (যেমন machine learning দিয়ে cache নীতি শেখানো) আরও এক ধাপ এগিয়ে MIN-কেই “শিক্ষক” বানায় — অতীতের trace-এ MIN কী করত দেখে মডেল শেখে, তারপর ভবিষ্যৎ না জেনেও তার অনুকরণ করার চেষ্টা করে। পর্ব ১৭-এর SJF-ও একই ভূমিকা পালন করে: বাস্তবে চালানো যায় না, কিন্তু scheduler-এর গুণ মাপার মানদণ্ড।
মূল উপলব্ধি: একটি অসম্ভব কিন্তু সর্বোত্তম algorithm বাস্তব নকশার জন্য সীমারেখা আঁকে — কতটা উন্নতি এখনো সম্ভব, আর কোথায় চেষ্টা থামানো উচিত।
প্র ০২Belady's anomaly কি শুধু পাঠ্যবইয়ের কৌতূহল, নাকি বাস্তব নকশার জন্যও শিক্ষা আছে?
সরাসরি ক্ষতির দিক থেকে anomaly বিরল। এই পর্বের finder-এ পাঠ্যবইয়ের ২০টি reference-এর string-এ FIFO কোনো anomaly দেখায়নি; Belady string-টি ইচ্ছা করে বানানো। বাস্তব প্রোগ্রামে FIFO-তে frame বাড়ালে প্রায় সবসময় fault কমে। তাছাড়া আধুনিক OS খাঁটি FIFO চালায় না। তাহলে ১৯৬৯ সালের আবিষ্কারটি এত বিখ্যাত কেন?
কারণ এটি একটি মৌলিক অনুমান ভেঙে দেয়: “বেশি resource মানে কম খারাপ ফল” — যা প্রায় সবাই স্বতঃসিদ্ধ ধরে নিত। সেই অনুমানের ওপর ভর করে memory কেনার পরিকল্পনা, প্রসেসকে frame দেওয়ার নীতি (পর্ব ৩৭) তৈরি হয়। Anomaly দেখায়, কোনো নীতির জন্য এটি প্রমাণ করতে হয়, ধরে নেওয়া যায় না। আর প্রমাণের চেষ্টাতেই Mattson ও সহকর্মীরা ১৯৭০ সালে stack algorithm-এর ধারণায় পৌঁছান, যা দিয়ে একটি trace একবার চালিয়েই সব মেমরি-আকারের fault-সংখ্যা পাওয়া যায় — cache-এর আকার ঠিক করার সবচেয়ে কার্যকর পদ্ধতিগুলোর একটি, যা আজও storage আর CDN-এর ক্ষমতা-পরিকল্পনায় ব্যবহৃত হয়।
দ্বিতীয় শিক্ষা: এই পর্বের simulator দেখিয়েছে, clock-ও Belady string-এ anomaly দেখায় (৩ frame ৯, ৪ frame ১০), কারণ সব বিট ১ হলে সে FIFO. অর্থাৎ যে বাস্তব নীতিগুলো FIFO-র ওপর দাঁড়ানো, তারা তত্ত্বে anomaly-মুক্ত নয়। পরের গবেষণায় দেখানো হয়েছে, FIFO-র anomaly কতটা বড় হতে পারে তারও কোনো ছোট নির্দিষ্ট সীমা নেই — বিশেষভাবে বানানো string-এ পার্থক্য ইচ্ছামতো বাড়ানো যায়। আর একটি সাধারণ প্রকৌশল-শিক্ষা আছে: যেকোনো সিস্টেমে “resource বাড়ালে ফল খারাপ হলো” দেখলে সেটিকে মাপার ভুল বলে উড়িয়ে না দিয়ে নীতিটির গঠন পরীক্ষা করা উচিত — thread pool বাড়ালে বেশি lock contention, cache বাড়ালে বেশি সময় scan — এমন বাস্তব “anomaly” প্রায়ই দেখা যায়।
মূল উপলব্ধি: Belady's anomaly-র আসল মূল্য একটি ভুল স্বতঃসিদ্ধ ভাঙায় আর তার প্রতিকার হিসেবে stack algorithm-এর তত্ত্ব জন্ম দেওয়ায় — monotonicity প্রমাণ করতে হয়, ধরে নেওয়া যায় না।
প্র ০৩LRU যদি এত ভালো হয়, তবে Linux কেন সরাসরি LRU চালায় না, আর দুই তালিকা থেকে MGLRU-তে কেন গেল?
প্রথম কারণ খরচ, যা অংশ ৫-এ দেখেছেন: প্রতিটি memory access-এ একটি তালিকা বা timestamp হালনাগাদ করার hardware সাধারণ CPU-তে নেই, আর software দিয়ে করা অসম্ভব রকম ধীর। Hardware দেয় শুধু accessed বিট। তাই Linux-কে এক বিট থেকে recency আন্দাজ করতে হয় — আর সেটি করে কতক্ষণ পরপর, কোন page-এর বিট দেখা হবে, তা ঠিক করে।
দ্বিতীয় কারণ, খাঁটি LRU-ও আদর্শ নয়। ধরুন একটি সার্ভারে একটি database চলছে, আর রাতে একটি backup প্রোগ্রাম ১০০ GB ফাইল একবার পড়ে। LRU-র চোখে সেই প্রতিটি page “সবচেয়ে সম্প্রতি ব্যবহৃত” — ফলে database-এর গরম page সরিয়ে একবার-ব্যবহৃত backup-এর page দিয়ে মেমরি ভরে যায়, আর সকালে database ধীর। Linux-এর active/inactive নকশা এর উত্তর: প্রথমবার আসা page inactive-এ থাকে, দ্বিতীয়বার ব্যবহৃত হলে তবেই active-এ ওঠে। এটি 2Q-জাতীয় নীতির একটি রূপ, যা frequency-র একটুখানি বিবেচনা যোগ করে।
কিন্তু দুই তালিকারও সীমা ছিল। কতটা active থেকে inactive-এ নামাতে হবে, anonymous আর file-এর মধ্যে কতটা ভারসাম্য — এসব heuristic জটিল হয়ে উঠছিল; আর বিপুল RAM-এর মেশিনে তালিকা ধরে ধরে প্রতিটি page-এর বিট দেখা (প্রতিটির জন্য reverse mapping দিয়ে page table খুঁজে) CPU খরচ বাড়াত। ফোনের মতো কম RAM-এর যন্ত্রে ভুল victim মানে অ্যাপ বন্ধ হয়ে যাওয়া — ব্যবহারকারীর কাছে খুব চোখে পড়ার মতো। MGLRU কয়েকটি প্রজন্ম রেখে বয়স আরও সূক্ষ্মভাবে মাপে, আর page table ধরে হাঁটে, যা ঘন অংশে অনেক বিট একসাথে পড়তে দেয়। Google-এর প্রকাশিত পরীক্ষায় ChromeOS আর Android-এ ভালো ফল দেখানো হয়েছিল, তবে সব workload-এ লাভ সমান নয় — তাই এটি config-এর বিকল্প, জোর করে চাপানো নয়।
মূল উপলব্ধি: বাস্তব replacement নীতি তিনটি জিনিসের আপস — hardware যা দেয় (এক বিট), workload-এর ধরন (scan, লুপ, জনপ্রিয় key), আর আন্দাজ করার নিজের খরচ; “LRU” সেখানে লক্ষ্য, পদ্ধতি নয়।
প্র ০৪Database নিজের buffer pool রাখে, অথচ OS-এরও page cache আছে। কেন দুটি স্তর, আর database কেন OS-এর replacement নীতির ওপর ভরসা করে না?
OS-এর page replacement সাধারণ-উদ্দেশ্যের: সে জানে না কোন page একটি index-এর মূল (root), যা প্রতিটি query-তে লাগবে, আর কোনটি একটি বড় table scan-এর অংশ, যা আর কখনো লাগবে না। Database জানে। Query planner আগেই বলতে পারে “এই query পুরো table একবার পড়বে” — তাই PostgreSQL বড় sequential scan-এর জন্য একটি ছোট ring buffer ব্যবহার করে, যাতে scan পুরো shared buffers ধুয়ে না দেয়; InnoDB নতুন page-কে LRU তালিকার মাঝখানে ঢোকায়, যাতে একবার-পড়া page মাথায় না ওঠে। এটি অ্যাপ্লিকেশন-জ্ঞান দিয়ে replacement — OS যা পারে না।
দ্বিতীয় কারণ সঠিকতা। Database-কে নিয়ন্ত্রণ করতে হয় কোন dirty page কখন ডিস্কে যাবে — write-ahead logging-এর নিয়মে, একটি page-এর বদলের log আগে ডিস্কে না পৌঁছালে page নিজে লেখা যাবে না (DBMS কোর্সের recovery পর্ব)। OS-এর page cache যখন খুশি dirty page লিখে দিলে এই নিয়ম ভাঙতে পারে। তাই database নিজের buffer pool-এ লেখার সময় নিজে ঠিক করে।
কিন্তু দুই স্তরের দাম আছে — “double caching”: একই page database-এর buffer-এ আর OS-এর page cache-এ, দুই জায়গায় RAM খায়। সমাধান ভিন্ন ভিন্ন: InnoDB সাধারণত O_DIRECT দিয়ে OS-এর cache এড়িয়ে চলতে পারে (innodb_flush_method), আর বেশিরভাগ RAM নিজের buffer pool-কে দেয়। PostgreSQL ঐতিহ্যগতভাবে উল্টো পথে — shared_buffers মাঝারি রেখে বাকিটা OS-এর page cache-এর ওপর ভরসা করে, যদিও direct I/O নিয়ে কাজ চলছে। দুই নকশাই কাজ করে; দুটির পেছনেই এই পর্বের প্রশ্ন — কে কাকে সরাবে, আর কে বেশি জানে।
এখানে stack algorithm-এর ধারণাও কাজে লাগে: database প্রশাসক জানতে চান buffer pool দ্বিগুণ করলে hit ratio কতটা বাড়বে। কিছু database আর গবেষণা-টুল stack distance-এর ধারণা দিয়ে এর অনুমান দেয় — একবার trace চালিয়ে সব আকারের উত্তর।
মূল উপলব্ধি: যে ভবিষ্যৎ সবচেয়ে ভালো জানে, replacement-এর সিদ্ধান্ত তার হাতে থাকা উচিত — OS-এর সাধারণ অনুমানের চেয়ে database-এর query-জ্ঞান OPT-এর বেশি কাছে।
প্র ০৫Hardware-এ reference bit না থাকলে OS কী করবে? VAX/VMS-এর গল্প আর আধুনিক ARM থেকে কী শেখা যায়?
DEC-এর VAX (১৯৭৭) ছিল তার যুগের সবচেয়ে সফল minicomputer-গুলোর একটি, আর তার virtual memory hardware-এ একটি পরিচিত অনুপস্থিতি ছিল — page table entry-তে reference bit নেই। VMS-এর নকশাকারেরা তাই অন্য পথ নেন। প্রতিটি প্রসেস নিজের সীমিত “resident set”-এর মধ্যে সাধারণ FIFO চালাত। কিন্তু FIFO-র সরানো page সঙ্গে সঙ্গে হারাত না: পরিষ্কার হলে যেত একটি free page list-এ, dirty হলে modified page list-এ — যেখানে সে কিছুক্ষণ অপেক্ষা করত। এর মধ্যে প্রসেস আবার সেটি চাইলে (অর্থাৎ FIFO ভুল victim বেছেছিল) page-টি ডিস্ক ছাড়াই তালিকা থেকে ফেরত আসত। কার্যত তালিকাগুলোই ছিল “second chance” — reference bit-এর বদলে সময় দিয়ে মাপা: ব্যস্ত page তালিকা থেকে দ্রুত ফেরত আসে, অব্যবহৃত page তালিকার শেষে পৌঁছে সত্যিই হারায়।
আরেকটি কৌশলও সম্ভব, যা বিভিন্ন সিস্টেম ব্যবহার করেছে: software দিয়ে reference bit নকল করা। OS একটি page-এর entry ইচ্ছা করে “নেই” (invalid) চিহ্ন দেয়, যদিও page আসলে RAM-এ আছে। পরে প্রসেস সেটি ছুঁলে একটি সস্তা fault হয়; OS তখন লিখে রাখে “ব্যবহৃত হয়েছে”, entry আবার valid করে, আর চলতে দেয়। প্রতি page-এ বিরতিপ্রতি বড়জোর একটি fault — বিট পাওয়ার খরচ সামান্য।
ARM-এর ইতিহাস এই কৌশলটির আধুনিক রূপ দেখায়। ARMv8-এর page table entry-তে একটি Access Flag (AF) আছে, কিন্তু মূল নকশায় hardware নিজে সেটি ১ করত না — AF শূন্য থাকা page ছুঁলে একটি fault হতো, আর OS বিট তুলত। ARMv8.1 থেকে hardware-এর নিজে AF আর dirty অবস্থা হালনাগাদ করার একটি ঐচ্ছিক সুবিধা এসেছে, যা সব chip-এ থাকে না; Linux দুই রূপই সামলায়। অর্থাৎ “hardware বিট দেয়” — এটি x86-এ সত্য, সব জায়গায় স্বতঃসিদ্ধ নয়।
মূল উপলব্ধি: Replacement নীতি hardware-এর সাথে মিলিয়ে নকশা করতে হয়; একটি বিট না থাকলে সময় (page buffering) বা ইচ্ছাকৃত fault দিয়ে তার তথ্য কেনা যায় — দাম দিয়ে।
অনুশীলন
হাতে হিসাব, আউটপুট অনুমান, কোড আর ব্যাখ্যা — আগে নিজে চেষ্টা করুন, তারপর উত্তর মেলান।
-
হাতে হিসাব: Reference string 1, 2, 3, 4, 2, 1, 5, 6, 2, 1, 2, 3, 7, 6, 3, 2, 1, 2, 3, 6. frame শুরুতে খালি। ৩টি আর ৪টি frame-এ FIFO, LRU আর OPT-এ কতগুলো page fault হবে? ৩ frame-এর জন্য টেবিল আঁকুন।
উত্তর (simulator দিয়ে যাচাই করা): ৩ frame — FIFO ১৬, LRU ১৫, OPT ১১. ৪ frame — FIFO ১৪, LRU ১০, OPT ৮. এখানে FIFO-তেও frame বাড়ায় fault কমেছে — anomaly নেই।
ref 1 2 3 4 2 1 5 6 2 1 2 3 7 6 3 2 1 2 3 6 FIFO 1 1 1 4 4 4 4 6 6 6 6 3 3 3 3 2 2 2 2 6 . 2 2 2 2 1 1 1 2 2 2 2 7 7 7 7 1 1 1 1 . . 3 3 3 3 5 5 5 1 1 1 1 6 6 6 6 6 3 3 fault F F F F F F F F F F F F F F F F (16) LRU 1 1 1 4 4 4 5 5 5 1 1 1 7 7 7 2 2 2 2 2 . 2 2 2 2 2 2 6 6 6 6 3 3 3 3 3 3 3 3 3 . . 3 3 3 1 1 1 2 2 2 2 2 6 6 6 1 1 1 6 fault F F F F F F F F F F F F F F F (15) OPT 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 6 . 2 2 2 2 2 2 2 2 2 2 2 7 7 7 2 2 2 2 2 . . 3 4 4 4 5 6 6 6 6 6 6 6 6 6 1 1 1 1 fault F F F F F F F F F F F (11)যাচাই: replacement simulator-এ
refsএই string দিয়ে বদলেN= 3 ও 4 চালান। OPT-এর শেষ fault-এ (page 6) frame-এ 3, 2, 1 — কারও আর পরের ব্যবহার নেই, তাই যেকোনোটি সরানো চলে; টেবিলে প্রথমটি (3) সরেছে, সংখ্যা একই থাকে। -
আউটপুট অনুমান: Replacement simulator-এ শুধু
const N = 3;বদলেconst N = 4;করলে চার নীতির শিরোনাম-লাইনে (যেমনFIFO (… faults)) কী কী সংখ্যা ছাপা হবে? চালানোর আগে অন্তত FIFO আর LRU হাতে বের করুন।FIFO (10 faults),OPT (8 faults),LRU (8 faults),Clock (9 faults). ৪ frame-এ LRU আর OPT সমান — frame বাড়লে ভুল victim-এর সুযোগ কমে, তাই নীতির পার্থক্যও কমে। সবগুলোই ৩ frame-এর চেয়ে কম বা সমান: FIFO ১৫ → ১০, OPT ৯ → ৮, LRU ১২ → ৮, Clock ১৪ → ৯ — এই string-এ কোনো anomaly নেই। (finder-এর পাঠ্যবই-string সারিতেও FIFO-র ৪ frame-এ ১০, LRU-র ৮ ছাপা আছে।) -
হাতে trace — enhanced second chance: চারটি frame একটি বৃত্তে, hand এখন A-তে। (R, M): A (১, ১), B (০, ১), C (১, ০), D (০, ১)। ক্রম A → B → C → D → A. প্রথমে সবচেয়ে নিচের যে শ্রেণিতে কেউ আছে, hand-এর ক্রমে সেই শ্রেণির প্রথম page সরে। কোন page সরবে, আর কেন D নয়? আর সাধারণ clock (শুধু R বিট) হলে?
কারও (০, ০) নেই। পরের শ্রেণি (০, ১)-এ আছে B আর D; hand A থেকে ঘুরলে প্রথমে B পড়ে, তাই B সরবে (আগে ডিস্কে লিখে, কারণ M = ১)। D-ও একই শ্রেণির, কিন্তু ঘোরার ক্রমে পরে। C (১, ০) পরিষ্কার হলেও সম্প্রতি ব্যবহৃত, তাই শ্রেণি উঁচু; A (১, ১) সবচেয়ে খারাপ প্রার্থী।
সাধারণ clock হলে: A-এর R = ১ → শূন্য করে এগোয়; B-এর R = ০ → B সরে. এখানে দুই নীতির উত্তর এক হলো, কিন্তু কারণ আলাদা — clock dirty-কে আলাদা করে দেখে না। কিন্তু যদি C (০, ০) হতো, enhanced নীতি C বাছত (পরিষ্কার, একটি I/O), আর সাধারণ clock তখনো B বাছত — hand-এর ক্রমে প্রথম R = ০ সে-ই, যদিও dirty (দুটি I/O).
-
কোড লিখুন: Replacement simulator-এ LFU নীতি যোগ করুন: count = page RAM-এ আসার পর থেকে কতবার ব্যবহৃত হয়েছে (আসার সময় ১), victim = সবচেয়ে ছোট count, সমান হলে ছোট নম্বরের frame. পাঠ্যবইয়ের string-এ ৩ frame-এ কতগুলো fault?
তিনটি জায়গায় বদল:
s-এcnt: {}যোগ; hit-এs.cnt[p]++, নতুন page বসানোর সময়s.cnt[p] = 1; আরpolicies-এ একটি লাইন:LFU: (s, t, fr) => fr.reduce((b, p, i) => s.cnt[p] < s.cnt[fr[b]] ? i : b, 0)চালালে শিরোনাম
LFU (14 faults)— LRU-র ১২-এর চেয়ে খারাপ। টেবিলে দেখবেন, page 0 frame1-এ প্রথম থেকে শেষ পর্যন্ত বসে থাকে (তার count সবার বড় হয়ে যায়), যা এখানে কাজে লাগে; কিন্তু ধাপ ১০-এ আসা page 3-ও (ধাপ ১২-এর hit-এর পর count ২) frame0-তে আটকে যায়, যদিও শেষ আটটি reference-এ সে আর আসে না। ফলে শেষ দিকের সব নতুন page একমাত্র frame2-এ পালা করে ঢোকে-বেরোয় — পুরোনো বড় count-এর এই “আটকে থাকা”-ই LFU-র পরিচিত দুর্বলতা, যার প্রতিকার count-এর ক্ষয় (aging).
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমফাঁকা frame না থাকলে page fault সামলানোর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ডিস্কে চাওয়া page-টির অবস্থান খুঁজে বের করা
- Replacement algorithm দিয়ে একটি victim frame বাছা
- Victim dirty হলে ডিস্কে লেখা, তার page table entry invalid করা
- চাওয়া page ওই frame-এ পড়া আর page table হালনাগাদ করা
- Fault হওয়া instruction আবার চালানো
Reference string 7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1, ৩টি frame (শুরুতে খালি)। FIFO, OPT আর LRU-তে page fault কতগুলো?
- FIFO ১২, OPT ৯, LRU ১৫
- FIFO ১৫, OPT ৯, LRU ১২
- FIFO ১৫, OPT ৬, LRU ১২
- FIFO ১৪, OPT ১০, LRU ১২
FIFO-তে reference string 1,2,3,4,1,2,5,1,2,3,4,5-এ ৩টি আর ৪টি frame দিয়ে fault-সংখ্যা কত, আর এই ঘটনার নাম কী?
- ৩ frame-এ ১০, ৪ frame-এ ৯ — thrashing
- ৩ frame-এ ৯, ৪ frame-এ ৯ — thrashing
- ৩ frame-এ ৯, ৪ frame-এ ১০ — Belady's anomaly
- ৩ frame-এ ১০, ৪ frame-এ ৮ — Belady's anomaly
কোন page replacement algorithm-গুলো কখনো Belady's anomaly দেখায় না, আর কেন?
- FIFO আর LRU, কারণ দুটিই queue ব্যবহার করে
- Clock আর OPT, কারণ দুটিই reference bit দেখে
- সব algorithm, যথেষ্ট বড় reference string-এ
- LRU আর OPT, কারণ এরা stack algorithm (inclusion property মানে)
Victim page-এর modify (dirty) bit ০ হলে replacement-এ কী লাভ হয়?
- Victim-কে ডিস্কে লিখতে হয় না, তাই I/O প্রায় অর্ধেক হয়
- Page-টি সম্প্রতি ব্যবহৃত হয়নি, তাই সে নিশ্চিত LRU
- নতুন page ডিস্ক থেকে পড়তে হয় না
- TLB হালনাগাদ করার দরকার পড়ে না
Clock (second chance) algorithm-এ fault হলে hand-এর নিচের frame-এর reference bit ১ পাওয়া গেলে কী হয়?
- ওই page সঙ্গে সঙ্গে সরানো হয়
- বিট ০ করে hand পরের frame-এ যায়
- ওই page-এর ব্যবহার-count বাড়িয়ে পাশ কাটানো হয়
- Hand থেমে যায় আর OS অন্য algorithm চালায়
৮ বিটের additional-reference-bits পদ্ধতিতে চারটি page-এর ইতিহাস-byte: P (11000100), Q (01110111), R (10000000), S (11111111). কোন page সরবে?
- P
- R
- Q
- S
OPT (Bélády-র MIN) page replacement সম্পর্কে কোন কথাটি সঠিক?
- যে page সবচেয়ে আগে ব্যবহৃত হয়েছিল, সেটি সরায়
- Linux আর Windows এটি সরাসরি ব্যবহার করে
- এতে Belady's anomaly হতে পারে
- সম্ভাব্য সর্বনিম্ন fault দেয়, কিন্তু ভবিষ্যৎ জানতে হয় বলে শুধু মাপকাঠি হিসেবে ব্যবহৃত হয়
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Replacement: ফাঁকা frame না থাকলে victim বাছা, dirty হলে লেখা, তারপর নতুন page পড়া। Dirty bit পরিষ্কার victim-এ I/O অর্ধেক করে। লক্ষ্য — সর্বনিম্ন page-fault rate.
- Reference string: ঠিকানা ÷ page-আকার, পরপর একই page একটি। পাঠ্যবইয়ের string, ৩ frame: FIFO ১৫, OPT ৯, LRU ১২ (Clock ১৪)।
- Belady's anomaly: FIFO-তে 1,2,3,4,1,2,5,1,2,3,4,5 — ৩ frame ৯, ৪ frame ১০. Stack algorithm (LRU, OPT) inclusion property মানে, তাই কখনো anomaly নয় (Mattson ও সহকর্মী, ১৯৭০)।
- OPT সবচেয়ে দেরিতে লাগবে এমন page সরায় — সর্বোত্তম (exchange argument), কিন্তু ভবিষ্যৎ লাগে, তাই মাপকাঠি। LRU counter বা stack দিয়ে হয়, কিন্তু প্রতি access-এ hardware খরচ — তাই reference bit দিয়ে অনুকরণ।
- অনুকরণ ও বাস্তবতা: additional-reference-bits, second chance/clock (সব বিট ১ হলে FIFO), enhanced (R, M), LFU/MFU, page buffering; Linux active/inactive ও MGLRU, Windows working-set trimming ও standby list, database buffer pool, Redis-এর নমুনা-LRU.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৭ · Frame allocation, thrashing আর working setপরবর্তী পর্বকোন প্রসেস কত frame পাবে, আর কম পেলে কী হয়।
- অপারেটিং সিস্টেম · কোর্স হোম৬৪টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- পর্ব ৩৫ · Virtual memory — demand paging, page fault আর copy-on-writeপেছনে ফিরেPage fault কীভাবে ঘটে আর effective access time-এর হিসাব।
- পর্ব ৬০ · Numerical সমস্যা — scheduling, page replacement, Banker's আর diskপরীক্ষার প্রস্তুতিPage replacement-সহ আরও অনুশীলনী।
- DBMS · Storage internalsডেটাবেসDatabase-এর buffer pool আর তার replacement নীতি।
- DSA · Hash tableডেটা স্ট্রাকচারO(1) LRU cache বানানোর প্রথম অর্ধেক।