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

Paging — page, frame আর page table

Paging — Pages, Frames, Page Tables, Address Translation and Page Table Entries

Taught in Bangla, this lesson explains why paging solves fragmentation, splitting addresses into page number and offset, translating through a page table with the textbook 32-byte example and fully worked hex address problems, page table size and location, the bits of a page table entry, internal fragmentation and page size trade-offs, shared pages, and checking page size on a real system.

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

  • Paging কেন contiguous allocation-এর external fragmentation দূর করে, আর কোন দাম দিয়ে
  • যেকোনো logical address-কে বাইনারিতে ভেঙে p আর d বের করা, তারপর physical address — দশমিক আর hex দুই রূপেই
  • Page table-এর আকার হিসাব করা, আর কেন বড় টেবিলই পরের পর্বের TLB আর multilevel টেবিলের কারণ
  • Page table entry-র বিটগুলো — valid, present, protection, dirty, accessed, user/supervisor — আর সেগুলো দিয়ে সুরক্ষা ও শেয়ারিং
  • Internal fragmentation আর page size বাছাইয়ের দর-কষাকষি, আর বাস্তব Linux-এ page নিজের চোখে দেখা

১ · কেন paging — টুকরো জায়গার সমস্যার সমাধান

পর্ব ৩১-এ দেখেছেন, প্রতিটি প্রসেসকে যদি RAM-এর একটানা একটি অংশ দিতে হয় (contiguous allocation), তাহলে সময়ের সাথে মেমরিতে ছোট ছোট ফাঁকা জায়গা ছড়িয়ে পড়ে। মোট খালি জায়গা যথেষ্ট, কিন্তু কোনো একটি ফাঁকা অংশই নতুন প্রসেসের জন্য যথেষ্ট বড় নয় — এটিই external fragmentation. Compaction করে সব প্রসেস সরিয়ে ফাঁকা জায়গা জোড়া দেওয়া যায়, কিন্তু তা অনেক সময় নেয়, আর চলমান প্রসেসের ঠিকানা বদলানোর ঝামেলাও আছে।

Paging সমস্যাটিকে একেবারে গোড়া থেকে বদলে দেয়। প্রশ্নটি আর “এতটা একটানা জায়গা কোথায় পাব?” থাকে না। নিয়ম হয়: প্রসেসের ঠিকানা-জগৎকে (logical address-এর জগৎ) সমান মাপের টুকরোয় ভাগ করুন — প্রতিটি টুকরো একটি page. Physical RAM-কেও ঠিক একই মাপের ঘরে ভাগ করুন — প্রতিটি ঘর একটি frame. এবার যেকোনো page যেকোনো খালি frame-এ বসতে পারে; পাশাপাশি থাকার দরকার নেই। কোন page কোন frame-এ আছে, তার হিসাব রাখে প্রসেসের নিজস্ব একটি টেবিল — page table.

একটি বড় লাইব্রেরির কথা ভাবুন, যার সব তাক সমান মাপের খোপে ভাগ করা। একটি বিশাল বিশ্বকোষের ২০টি খণ্ড এসেছে। পুরোনো নিয়মে (contiguous) ২০টি পাশাপাশি খালি খোপ খুঁজতে হতো — হয়তো পুরো লাইব্রেরিতে ২০টি খালি খোপ আছে, কিন্তু ছড়িয়ে-ছিটিয়ে। নতুন নিয়মে লাইব্রেরিয়ান যেকোনো খালি খোপে একটি করে খণ্ড রেখে দেন, আর একটি ক্যাটালগ-কার্ডে লিখে রাখেন: “খণ্ড ১ → তাক ৭-এর খোপ ৩, খণ্ড ২ → তাক ২-এর খোপ ৯…”. পাঠক বলেন “খণ্ড ২-এর ১৪০ নম্বর পৃষ্ঠা”; লাইব্রেরিয়ান কার্ড দেখে খোপে যান আর সেই খণ্ডের ১৪০ নম্বর পৃষ্ঠা খোলেন। খণ্ড = page, খোপ = frame, ক্যাটালগ-কার্ড = page table, পৃষ্ঠা নম্বর = offset.

মূল ধারণা

Paging-এ প্রসেস নিজেকে একটি একটানা ঠিকানা-জগৎ হিসেবে দেখে (০ থেকে শুরু, কোনো ফাঁক নেই), কিন্তু physical RAM-এ তার টুকরোগুলো ছড়ানো। এই দুই দৃষ্টির মাঝখানে প্রতিটি মেমরি-ব্যবহারে অনুবাদ করে hardware — MMU — আর অনুবাদের নিয়ম লিখে রাখে OS, page table-এ। প্রোগ্রামার বা compiler-কে physical ঠিকানা নিয়ে ভাবতেই হয় না।

যেহেতু সব frame সমান মাপের, যেকোনো খালি frame যেকোনো page-এর জন্য সমান ভালো। “কোন ফাঁকে বসাব” — first fit, best fit-এর সেই প্রশ্নই আর ওঠে না; খালি frame-এর একটি তালিকা থাকলেই চলে। তবে paging বিনামূল্যে নয়। তিনটি দাম দিতে হয়, আর এই পর্বের বাকিটা সেই তিন দামেরই হিসাব: (১) প্রতিটি প্রসেসের জন্য একটি page table রাখার মেমরি, (২) প্রতিটি মেমরি-ব্যবহারে অনুবাদের সময়, আর (৩) শেষ page-এর ভেতরে অব্যবহৃত জায়গা — internal fragmentation.

Paging-এর প্রথম বড় বাস্তব প্রয়োগ ধরা হয় যুক্তরাজ্যের University of Manchester-এর Atlas কম্পিউটারকে, যা ১৯৬২ সালে চালু হয়। Tom Kilburn-এর দল একটি “one-level store”-এর ধারণা দেন: ছোট core memory আর বড় ড্রাম মিলে প্রোগ্রামারের চোখে একটিই বড় মেমরি, আর টুকরোগুলো (প্রতিটি ৫১২ word) দরকার মতো ড্রাম থেকে core-এ আসা-যাওয়া করে। আজকের virtual memory-র (পর্ব ৩৫) মূল চিন্তা সেখান থেকেই। পরে IBM System/370 (১৯৭২), DEC VAX (১৯৭৭) আর Intel 80386 (১৯৮৫) paging-কে সাধারণ কম্পিউটারে নিয়ে আসে; 80386-এর বেছে নেওয়া ৪ KiB page আজও x86-এর মূল page size.

২ · ঠিকানার দুই ভাগ — page number আর offset

Paging-এর পুরো হিসাব একটি সরল পর্যবেক্ষণের ওপর দাঁড়িয়ে: page size সবসময় ২-এর ঘাত (৫১২, ১০২৪, ৪০৯৬…)। ধরুন logical address-এর মোট বিট m, অর্থাৎ logical ঠিকানা-জগতের আকার ২m বাইট। আর page size ২n বাইট। তাহলে প্রতিটি logical address দুই ভাগে ভাঙে:

logical address = [ p : উপরের m − n বিট ] [ d : নিচের n বিট ]
p = page number (কোন page), d = page offset (page-এর ভেতরে কত নম্বর বাইট)

কেন এমন? কারণ ২n দিয়ে ভাগ করা বাইনারিতে শুধু n ঘর ডানে সরানো। p = ঠিকানা ÷ ২n-এর ভাগফল, d = ভাগশেষ — আর ২-এর ঘাত হলে ভাগফল ঠিক উপরের বিটগুলো, ভাগশেষ ঠিক নিচের n বিট। Hardware-কে কোনো ভাগ করতে হয় না; তারগুলো দুই দলে ভাগ করে দিলেই হলো। মোট page-এর সংখ্যা ২m−n, আর প্রতিটি page-এ ২nটি বাইট।

যা জানাসূত্রউদাহরণ: m = ৩২, page ৪ KiB
Offset-এর বিটn = log2(page size)৪০৯৬ = ২১২ → n = ১২
Page number-এর বিটm − n৩২ − ১২ = ২০
Logical page-এর সংখ্যা২m−n২২০ = ১০,৪৮,৫৭৬
p আর d (যেকোনো ঠিকানা A)p = ⌊A ÷ page size⌋, d = A mod page sizehex-এ: শেষ ৩টি hex অঙ্ক d, বাকিটা p
Physical frame-এর সংখ্যাRAM-এর আকার ÷ page size৮ GiB ÷ ৪ KiB = ২০,৯৭,১৫২

৪ KiB page-এ offset ১২ বিট, আর ১২ বিট মানে ঠিক ৩টি hex অঙ্ক (প্রতিটি hex অঙ্ক ৪ বিট)। তাই 0x1ABC-এর মতো ঠিকানায় শেষ তিন অঙ্ক ABC হলো d, আর বাকি 1 হলো p — চোখে দেখেই ভাগ করা যায়। কিন্তু page size ১ KiB হলে offset ১০ বিট, যা hex অঙ্কের সীমায় পড়ে না; তখন বাইনারিতে লিখে ভাগ করতে হয়। নিচের stepper-এ ঠিক সেটাই দেখুন।

1 m = 16, page = 1024 = 2^10, n = 102 0x3A7F = 0011 1010 0111 11113 p = top 6 bits, d = low 10 bits4 p = 001110, d = 10 0111 11115 check: 14 x 1024 + 639 = 14975
ঠিকানার অবস্থা
Address
p (page)
d (offset)
Pages in space
  1. মাপগুলো ঠিক করা। ঠিকানা ১৬ বিটের, তাই logical ঠিকানা-জগৎ ২১৬ = ৬৫,৫৩৬ বাইট (m = ১৬)। Page size ১০২৪ = ২১০, তাই offset n = ১০ বিট, আর page number-এর জন্য বাকি ১৬ − ১০ = ৬ বিট — মোট ২৬ = ৬৪টি page.
  2. Hex থেকে বাইনারি। প্রতিটি hex অঙ্ক ঠিক ৪ বিট: 3 → 0011, A → 1010, 7 → 0111, F → 1111. ফলে 0x3A7F = 0011101001111111, দশমিকে ১৪,৯৭৫.
  3. কোথায় কাটবেন। নিচের n = ১০ বিট offset, উপরের ৬ বিট page number. খেয়াল করুন, কাটার জায়গাটি একটি hex অঙ্কের মাঝখানে পড়েছে (A = 1010-এর দুই বিট বাঁয়ে, দুই বিট ডানে) — তাই এখানে hex দেখে সরাসরি ভাগ করা যায় না।
  4. দুই অংশের মান। p = 001110 = ৮ + ৪ + ২ = ১৪. d = 1001111111 = ৫১২ + ১২৭ = ৬৩৯ = 0x27F. অর্থাৎ ঠিকানাটি page ১৪-এর ৬৩৯ নম্বর বাইট।
  5. ভাগ দিয়ে যাচাই। ১৪,৯৭৫ ÷ ১,০২৪ = ১৪, ভাগশেষ ৬৩৯; আর ১৪ × ১,০২৪ + ৬৩৯ = ১৪,৯৭৫. খাতায় ভাগ করে মেলানো যায়, কিন্তু MMU কোনো ভাগ করে না — উপরের ৬টি তার page table-এর দিকে, নিচের ১০টি সরাসরি physical ঠিকানার দিকে যায়।

KB নাকি KiB? মেমরির হিসাবে ১ KiB = ১০২৪ বাইট, ১ MiB = ১০২৪ KiB, ১ GiB = ১০২৪ MiB (২-এর ঘাত)। অনেক বই আর পরীক্ষার প্রশ্নে “4 KB page” লেখা থাকে, কিন্তু অর্থ সবসময় ৪০৯৬ বাইট। ডিস্ক বা নেটওয়ার্কের বিজ্ঞাপনে KB/MB প্রায়ই ১০০০-এর ঘাত। Page-এর অঙ্কে সবসময় ১০২৪ ধরে হিসাব করুন।

৩ · Page table দিয়ে অনুবাদ — পাঠ্যবইয়ের ৩২ বাইটের উদাহরণ

অনুবাদের নিয়ম এক লাইনের। p দিয়ে page table-এর p নম্বর ঘর দেখুন; সেখানে লেখা আছে frame number f. Physical address-এ offset বদলায় না — একটি page পুরোটাই একটি frame-এ বসে, তাই page-এর ভেতরের ৬৩৯ নম্বর বাইট frame-এর ভেতরেও ৬৩৯ নম্বর বাইট।

physical address = f × page size + d
বাইনারিতে: f-এর বিটগুলোর ঠিক পাশে d-এর n বিট জুড়ে দেওয়া

অপারেটিং সিস্টেমের প্রায় প্রতিটি পাঠ্যবইয়ে (যেমন Silberschatz, Galvin ও Gagne-এর Operating System Concepts) একটি ছোট উদাহরণ আছে, যা পরীক্ষায়ও প্রায়ই আসে। Physical মেমরি মাত্র ৩২ বাইট, page size ৪ বাইট — তাই ৩২ ÷ ৪ = ৮টি frame (০–৭)। একটি প্রসেসের logical মেমরি ১৬ বাইট (m = ৪), যেখানে a থেকে p পর্যন্ত ১৬টি অক্ষর রাখা — অর্থাৎ ৪টি page (০–৩), n = ২. Page table: page ০ → frame ৫, page ১ → frame ৬, page ২ → frame ১, page ৩ → frame ২.

৩২ বাইটের মেমরি, ৪ বাইটের page

Logical memory → page table → physical memory

PageLogical ঠিকানাভেতরের অক্ষরPage table: framePhysical ঠিকানা
০০–৩a b c d৫২০–২৩
১৪–৭e f g h৬২৪–২৭
২৮–১১i j k l১৪–৭
৩১২–১৫m n o p২৮–১১
ছবি ৩২.১ — চারটি page চারটি ছড়ানো frame-এ (১, ২, ৫, ৬); frame ০, ৩, ৪, ৭ অন্য প্রসেসের জন্য খালি বা ব্যস্ত। প্রসেসের চোখে ঠিকানা ০ থেকে ১৫ একটানা।

এবার কয়েকটি ঠিকানা অনুবাদ করি (হিসাব নিচের simulator-এ আবার যাচাই হবে):

  • Logical ০ (অক্ষর a): p = ০ ÷ ৪ = ০, d = ০. Page ০ → frame ৫, তাই physical = ৫ × ৪ + ০ = ২০.
  • Logical ৩ (d): p = ০, d = ৩ → ৫ × ৪ + ৩ = ২৩.
  • Logical ৪ (e): p = ১, d = ০ → ৬ × ৪ + ০ = ২৪.
  • Logical ১৩ (n): p = ৩, d = ১ → ২ × ৪ + ১ = ৯. খেয়াল করুন, logical ঠিকানা বড়, physical ঠিকানা ছোট — ক্রম রাখার কোনো বাধ্যবাধকতা নেই।

বাইনারিতে দেখলে কাজটি আরও যান্ত্রিক। Logical ১৩ = 11 01 (৪ বিট: উপরের ২ বিট p = ৩, নিচের ২ বিট d = ১)। Frame ২ = 010 (physical ঠিকানা ৫ বিটের, কারণ ৩২ = ২৫; তাই frame number ৩ বিট)। এখন frame-এর বিটের পাশে d জুড়ে দিন: 010 01 = ৯. এই “জুড়ে দেওয়া”-ই hardware করে — গুণ বা যোগের সার্কিট লাগে না।

Logical আর physical-এর বিট-সংখ্যা আলাদা হতে পারে। Logical বিট নির্ভর করে প্রসেস কত বড় ঠিকানা-জগৎ দেখবে তার ওপর; physical বিট নির্ভর করে কত RAM লাগানো যায় তার ওপর। এখানে logical ৪ বিট, physical ৫ বিট — দুটো offset একই (২ বিট), পার্থক্য শুধু page number (২ বিট) আর frame number-এ (৩ বিট)। বাস্তবে উল্টোটাও হয়: ৩২-বিটের প্রসেস ৬৪ GiB RAM-এর মেশিনে চলে, আর x86-64-এ virtual ঠিকানা সাধারণত ৪৮ বিট, physical ঠিকানা CPU ভেদে আরও কম বা বেশি।

৪ · Hex ঠিকানার অনুবাদ — পরীক্ষার ধাঁচে পূর্ণ সমাধান

বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় সবচেয়ে সাধারণ প্রশ্নটি এমন: “Page size ৪ KiB. একটি প্রসেসের page table দেওয়া আছে। নিচের logical address-গুলোর physical address বের করুন; কোনোটি অবৈধ হলে বলুন।” ধরুন টেবিলটি এই (৫টি entry, অর্থাৎ table length ৫):

Page p০১২৩৪
Frame f৫৯—0x1A (২৬)২
Valid বিটvvi (invalid)vv

৪ KiB = ২১২, তাই d = শেষ ৩টি hex অঙ্ক, আর f × ৪০৯৬ মানে hex-এ f-এর পরে তিনটি শূন্য বসানো। ফলে hex-এ উত্তর হয় শুধু “f-এর hex, তারপর d-এর তিন অঙ্ক”:

LogicalpdPage tablePhysical
0x1ABC১0xABC (২৭৪৮)frame ৯0x9ABC = ৩৯,৬১২
0x3004৩0x004frame 0x1A0x1A004 = ১,০৬,৫০০
0x0123০0x123frame ৫0x5123 = ২০,৭৭১
0x2FFF২0xFFFinvalidtrap → OS
0x5000৫0x000p ≥ table length ৫trap → OS
দশমিক ৪১০০১৪frame ৯৯ × ৪০৯৬ + ৪ = ৩৬,৮৬৮

দশমিক ঠিকানা দিলে (শেষ সারি) ভাগ করুন: ৪১০০ ÷ ৪০৯৬ = ১, ভাগশেষ ৪. দুটি অবৈধ ঠিকানার পার্থক্যও খেয়াল করুন: 0x2FFF-এর page টেবিলে আছে কিন্তু invalid চিহ্নিত, আর 0x5000-এর page নম্বর টেবিলের দৈর্ঘ্যই ছাড়িয়ে গেছে। দুই ক্ষেত্রেই MMU নিজে কিছু ঠিক করে না — CPU একটি exception তোলে, নিয়ন্ত্রণ যায় kernel-এর handler-এ, আর OS ঠিক করে কী হবে। ঠিকানাটি সত্যিই অবৈধ হলে Linux প্রসেসকে SIGSEGV পাঠায় — পরিচিত “Segmentation fault”. (Page বৈধ কিন্তু এখন ডিস্কে আছে — এমন page fault-এর গল্প পর্ব ৩৫-এ।)

নিচের stepper-এ MMU-র ভেতরে একেকটি ঠিকানা কীভাবে চলে, আর প্রতিটিতে কতবার মেমরিতে যেতে হয়, তা দেখুন:

- PTBR -> table, PTLR = 50 [0] frame 0x5   valid1 [1] frame 0x9   valid2 [2] ---         invalid3 [3] frame 0x1A  valid4 [4] frame 0x2   valid
MMU-র হিসাব
Logical
p, d
PTE
Physical
Memory accesses
  1. ঠিকানা ভাগ। CPU একটি নির্দেশে 0x1ABC ঠিকানা থেকে পড়তে চাইল। MMU উপরের বিট আর নিচের ১২ বিট আলাদা করল: p = ১, d = 0xABC. এখনো মেমরিতে যাওয়া হয়নি।
  2. প্রথম মেমরি-access: PTE পড়া। Page table নিজেও RAM-এ থাকে; তার শুরুর ঠিকানা থাকে একটি register-এ — PTBR. প্রতিটি entry ৪ বাইট হলে entry ১ আছে PTBR + ১ × ৪ ঠিকানায়। MMU সেটি পড়ল: valid, frame 0x9. আগে p < PTLR (১ < ৫) যাচাই হয়েছে।
  3. দ্বিতীয় মেমরি-access: আসল ডেটা। Frame ৯-এর পাশে d জুড়ে physical ঠিকানা 0x9ABC. এবার সেখান থেকে ডেটা আনা হলো। একটি লোড-নির্দেশে মোট দুইবার মেমরিতে যেতে হলো — এটিই paging-এর দ্বিতীয় দাম।
  4. দ্বিতীয় ঠিকানা 0x3004. p = ৩, d = 0x004. PTE ৩: valid, frame 0x1A. Physical = 0x1A004 (দশমিকে ২৬ × ৪০৯৬ + ৪ = ১,০৬,৫০০)। আবার দুইবার মেমরি।
  5. তৃতীয় ঠিকানা 0x2FFF — invalid. PTE ২ পড়া হলো (একবার মেমরি), কিন্তু valid বিট ০. MMU থেমে যায়, CPU exception তোলে; kernel-এর handler দেখে ঠিকানাটি প্রসেসের বৈধ এলাকার বাইরে, তাই Linux-এ প্রসেস পায় SIGSEGV. ডেটার access কখনো ঘটে না।
  6. চতুর্থ ঠিকানা 0x5000 — টেবিলের বাইরে। p = ৫, কিন্তু টেবিলে মাত্র ৫টি entry (০–৪); PTLR (page-table length register)-এর সাথে তুলনায় ৫ ≥ ৫, তাই PTE পড়ার আগেই trap. এভাবে ছোট প্রসেসের জন্য পুরো ২২০ entry-র টেবিল রাখতে হয় না। (x86-64-এ PTLR নেই; সেখানে কাজটি করে multilevel টেবিলের not-present entry — পর্ব ৩৩।)

নিজের বইয়ের অনুশীলনী যাচাই করতে নিচের translator-এ page size, page table আর ঠিকানার তালিকা বদলে নিন। ঠিকানা দশমিক সংখ্যা বা "0x…" আকারের hex লেখা — দুটোই চলে; টেবিলে null মানে invalid. প্রথম কলটি উপরের ৩২ বাইটের উদাহরণ, দ্বিতীয়টি এই অংশের hex প্রশ্ন।

// Paging address translator: logical address -> (p, d) -> frame -> physical
function translate(PAGE, table, addrs) {
  const n = Math.log2(PAGE);                          // offset bits
  const hex = x => "0x" + x.toString(16).toUpperCase();
  console.log("page size " + PAGE + " = 2^" + n + ", table length " + table.length);
  for (const a of addrs) {
    const la = typeof a === "number" ? a : parseInt(a, 16);
    const p = Math.floor(la / PAGE), d = la % PAGE;   // = la >> n, la & (PAGE - 1)
    let out = String(a).padEnd(7) + "-> p=" + p + " d=" + d + " (" + hex(d) + ")";
    if (p >= table.length) out += "  p >= length -> trap";
    else if (table[p] === null) out += "  invalid -> trap";
    else {
      const pa = table[p] * PAGE + d;
      out += "  frame " + table[p] + " -> " + pa + " (" + hex(pa) + ")";
    }
    console.log(out);
  }
}
// textbook: 32-byte memory, 4-byte pages, page table 0->5, 1->6, 2->1, 3->2
translate(4, [5, 6, 1, 2], [0, 3, 4, 13, 15]);
// exam style: 4 KiB pages, page 2 invalid, 5 entries (PTLR = 5)
translate(4096, [5, 9, null, 0x1A, 2],
          ["0x1ABC", "0x3004", "0x0123", "0x2FFF", "0x5000", 4100]);

আউটপুট (নির্ধারিত, প্রতিবার একই):

page size 4 = 2^2, table length 4
0      -> p=0 d=0 (0x0)  frame 5 -> 20 (0x14)
3      -> p=0 d=3 (0x3)  frame 5 -> 23 (0x17)
4      -> p=1 d=0 (0x0)  frame 6 -> 24 (0x18)
13     -> p=3 d=1 (0x1)  frame 2 -> 9 (0x9)
15     -> p=3 d=3 (0x3)  frame 2 -> 11 (0xB)
page size 4096 = 2^12, table length 5
0x1ABC -> p=1 d=2748 (0xABC)  frame 9 -> 39612 (0x9ABC)
0x3004 -> p=3 d=4 (0x4)  frame 26 -> 106500 (0x1A004)
0x0123 -> p=0 d=291 (0x123)  frame 5 -> 20771 (0x5123)
0x2FFF -> p=2 d=4095 (0xFFF)  invalid -> trap
0x5000 -> p=5 d=0 (0x0)  p >= length -> trap
4100   -> p=1 d=4 (0x4)  frame 9 -> 36868 (0x9004)

প্রথম ছয় লাইন ছবি ৩২.১-এর হিসাব: ০ → ২০, ৩ → ২৩, ৪ → ২৪, ১৩ → ৯, আর ১৫ (অক্ষর p) → ১১. পরের অংশ ঠিক উপরের টেবিলের উত্তর — 0x9ABC, 0x1A004, 0x5123, দুটি trap, আর দশমিক ৪১০০ → ৩৬,৮৬৮. কোডে Math.floor(la / PAGE) আর la % PAGE খাতার ভাগের মতো; মন্তব্যে লেখা la >> n আর la & (PAGE - 1) হলো hardware-এর পথ — ডানে সরানো আর নিচের বিটগুলো ছেঁকে নেওয়া। Page size ২-এর ঘাত না হলে এই দ্বিতীয় পথ কাজ করত না; সেজন্যই page size সবসময় ২-এর ঘাত।

৫ · Page table কত বড়, কোথায় থাকে, আর কত সময় নেয়

পূর্ণ সমাধান: ৩২-বিট ঠিকানা, ৪ KiB page

প্রশ্ন: “Logical address ৩২ বিট, page size ৪ KiB, প্রতিটি page table entry ৪ বাইট। (ক) কতগুলো page? (খ) Page table-এ কতগুলো entry? (গ) একটি প্রসেসের page table কত বড়?”

  1. Page size ৪ KiB = ২১২ বাইট → offset n = ১২ বিট।
  2. Page number = ৩২ − ১২ = ২০ বিট → মোট page = ২২০ = ১০,৪৮,৫৭৬ (প্রায় ১০ লাখ)।
  3. একতলা (single-level) টেবিলে প্রতিটি সম্ভাব্য page-এর জন্য একটি entry, তাই entry = ২২০টি।
  4. আকার = ২২০ × ৪ বাইট = ২২২ বাইট = ৪ MiB, প্রতিটি প্রসেসের জন্য।

৪ MiB ছোট শোনায়, কিন্তু তিনটি সমস্যা আছে। প্রথমত, টেবিলটি নিজেই একটানা ৪ MiB জায়গা চায় — যে সমস্যা দূর করতে paging এসেছিল, সেটিই টেবিলের জন্য ফিরে এলো। দ্বিতীয়ত, ১০০০টি প্রসেস চললে শুধু টেবিলেই প্রায় ৪০০০ MiB. তৃতীয়ত, বেশিরভাগ প্রসেস তার ৪ GiB ঠিকানা-জগতের সামান্য অংশই ব্যবহার করে, তাই টেবিলের বেশিরভাগ entry invalid — খালি ঘরের জন্য মেমরি খরচ। ৬৪-বিট সিস্টেমে ছবিটি আরও ভয়ংকর: ৪৮-বিট virtual ঠিকানা আর ৪ KiB page-এ ২৩৬টি entry, প্রতিটি ৮ বাইট হলে একতলা টেবিল হতো ৫১২ GiB. এই কারণেই বাস্তব সিস্টেম multilevel page table ব্যবহার করে, যেখানে অব্যবহৃত অংশের টেবিল তৈরিই হয় না — বিস্তারিত পর্ব ৩৩-এ। এখানে শুধু মূল চিন্তাটি মনে রাখুন: টেবিলকেও page-এ ভাগ করা, আর ঠিকানা-জগতের খালি অংশের জন্য কোনো টেবিল না রাখা।

PTBR আর context switch

প্রতিটি প্রসেসের নিজস্ব page table, তাই একই logical ঠিকানা 0x1000 দুই প্রসেসে দুই আলাদা frame-এ যায় — এভাবেই একটি প্রসেস আরেকটির মেমরি দেখতে পায় না। চলতি প্রসেসের টেবিল কোথায়, তা MMU জানে PTBR থেকে। x86-এ এই register-এর নাম CR3; ARM64-এ TTBR0_EL1 (user অংশের জন্য) আর TTBR1_EL1 (kernel অংশের জন্য); RISC-V-তে satp. প্রসেসের PCB-তে টেবিলের ঠিকানা লেখা থাকে, আর context switch-এ (পর্ব ৯) kernel নতুন প্রসেসের মানটি এই register-এ বসায়। পুরো টেবিল কপি করতে হয় না — একটি register বদলালেই পুরো ঠিকানা-জগৎ বদলে যায়। PTBR বদলানো একটি privileged নির্দেশ; শুধু kernel mode-এ চলে, নইলে যেকোনো প্রোগ্রাম অন্যের মেমরিতে ঢুকে পড়ত।

দুইবার মেমরি, আর TLB-র প্রবেশ

Stepper-এ দেখেছেন, প্রতিটি লোড বা স্টোরে আগে PTE, তারপর ডেটা — দুইবার মেমরি। মেমরি-access ১০০ ns ধরলে প্রতিটি ব্যবহার ২০০ ns — অর্থাৎ paging একাই প্রোগ্রামকে দ্বিগুণ ধীর করত। কেউ এটি মেনে নিত না। সমাধান হলো CPU-র ভেতরে একটি ছোট, খুব দ্রুত cache, যা সাম্প্রতিক অনুবাদগুলো (p → f) মনে রাখে: TLB. অনুবাদ TLB-তে পাওয়া গেলে (TLB hit) PTE পড়তে মেমরিতে যেতে হয় না। Locality-র কারণে প্রোগ্রাম একই কয়েকটি page বারবার ছোঁয়, তাই hit ratio সাধারণত খুব বেশি।

TLB hit ratio α, মেমরি-access সময় tm, আর TLB খোঁজার সময় tTLB হলে effective access time:

EAT = α (tTLB + tm) + (১ − α)(tTLB + ২ tm)

শুধু একটি স্বাদ: tTLB নগণ্য ধরে, tm = ১০০ ns আর α = ০.৮০ হলে EAT = ০.৮০ × ১০০ + ০.২০ × ২০০ = ১২০ ns (২০% ধীর); α = ০.৯৯ হলে ১০১ ns — প্রায় paging ছাড়া যতটা। TLB-র গঠন, ASID আর এই সূত্রের পূর্ণ numerical সমস্যা পর্ব ৩৩-এ।

OS-এর খাতা: frame table আর খালি frame-এর তালিকা

Page table প্রসেসের দিক থেকে হিসাব (“আমার page কোথায়?”)। OS-এর দরকার উল্টো দিকের হিসাবও (“RAM-এর কোন frame কার, কোনটি খালি?”)। এই কাজে OS রাখে একটি frame table — প্রতিটি physical frame-এর জন্য একটি ঘর: খালি নাকি বরাদ্দ, বরাদ্দ হলে কোন প্রসেসের কোন page. আর দ্রুত বরাদ্দের জন্য একটি free-frame list.

ধরুন একটি নতুন প্রসেসের ৪টি page লাগবে, আর খালি তালিকায় আছে frame ১৪, ১৩, ১৮, ২০, ১৫. OS তালিকা থেকে একে একে নেয়: page ০ → ১৪, page ১ → ১৩, page ২ → ১৮, page ৩ → ২০; প্রতিটি বসানোর সাথে সাথে প্রসেসের page table-এ entry লেখে আর frame table-এ মালিকানা চিহ্নিত করে। তালিকায় থাকে শুধু ১৫. লক্ষ করুন, “কোন ফাঁকে বসাব” নিয়ে কোনো খোঁজাখুঁজি নেই — তালিকার মাথা থেকে নিলেই হয়। n page-এর প্রসেসের জন্য শুধু n টি খালি frame থাকলেই যথেষ্ট, পাশাপাশি হোক বা না হোক।

OS-কে প্রসেসের টেবিলও মনে রাখতে হয়। একটি system call-এ প্রসেস যখন একটি buffer-এর logical ঠিকানা দেয় (যেমন read(fd, buf, n)), kernel-কে জানতে হয় সেই ঠিকানা কোন frame-এ পড়েছে আর আদৌ বৈধ কি না। আর dispatcher প্রতিটি context switch-এ পরের প্রসেসের টেবিল hardware-এ বসায়। তাই পাঠ্যবইয়ের ভাষায় OS প্রতিটি প্রসেসের page table-এর একটি কপি (বাস্তবে তার ঠিকানা, PCB-তে) রাখে — আর এই বাড়তি কাজ context switch-এর সময় কিছুটা বাড়ায়।

৬ · Page table entry-র ভেতরে — বিট ধরে ধরে

একটি page table entry (PTE) শুধু frame number নয়। Frame number-এর পাশে কয়েকটি ছোট বিট থাকে, যা hardware প্রতিটি access-এ পড়ে বা লেখে। পাঠ্যবইয়ের সাধারণ নামগুলো আর x86-64-এর আসল নাম পাশাপাশি:

বিট (পাঠ্যবই)x86-64 PTE-তেকে বসায় / কে পড়েকাজ
Valid/invalidআলাদা বিট নেই; P = ০ দিয়ে বোঝানোOS বসায়, MMU পড়েPage কি প্রসেসের বৈধ ঠিকানা-জগতের অংশ?
Presentবিট ০ (P)OS বসায়, MMU পড়েPage এখন RAM-এ আছে কি না; না থাকলে page fault (পর্ব ৩৫)
Protection (R/W/X)বিট ১ (R/W), বিট ৬৩ (NX/XD)OS বসায়, MMU যাচাই করেলেখা যাবে কি না; নির্দেশ হিসেবে চালানো যাবে কি না
User/supervisorবিট ২ (U/S)OS বসায়User mode-এর কোড এই page ছুঁতে পারবে, নাকি শুধু kernel
Accessed / referencedবিট ৫ (A)MMU বসায়, OS পড়ে ও মোছেসম্প্রতি ব্যবহার হয়েছে কি না — page replacement-এর তথ্য (পর্ব ৩৬)
Dirty / modifiedবিট ৬ (D)MMU বসায় (লেখার সময়), OS পড়েPage বদলেছে কি না; বদলালে সরানোর আগে ডিস্কে লিখতে হবে
Frame numberবিট ১২ থেকে উপরেOS বসায়Physical frame-এর নম্বর

x86-64-এ আরও কয়েকটি বিট আছে — cache নিয়ন্ত্রণের (PWT, PCD, PAT) আর G (global: context switch-এও TLB থেকে মুছবে না, kernel-এর page-এর জন্য)। ARM64-এ নাম ও বিন্যাস ভিন্ন — যেমন access flag (AF), user আর privileged অংশের জন্য আলাদা execute-never বিট (UXN, PXN), access permission (AP) — কিন্তু ধারণাগুলো একই। Frame number-এর জন্য ১২ বিট ছেড়ে শুরু কেন? কারণ frame-এর শুরুর ঠিকানা সবসময় ৪০৯৬-এর গুণিতক, তাই তার নিচের ১২ বিট সবসময় শূন্য — সেগুলো সংরক্ষণ করার দরকার নেই, ঐ জায়গাতেই flag-বিটগুলো রাখা হয়।

Valid আর present এক নয়। পাঠ্যবইয়ের “valid” মানে ঠিকানাটি প্রসেসের বৈধ এলাকা কি না। “Present” মানে page এখন RAM-এ আছে কি না। Virtual memory-তে (পর্ব ৩৫) একটি বৈধ page ডিস্কে সরানো থাকতে পারে — তখন present = ০, কিন্তু access অবৈধ নয়; OS page-টি এনে দিয়ে নির্দেশটি আবার চালায়। x86-এ hardware দুটিকে আলাদা করে না (দুই ক্ষেত্রেই P = ০ আর page fault), পার্থক্য করে OS নিজের হিসাব (Linux-এ প্রসেসের মেমরি-এলাকার তালিকা) দেখে।

Valid বিট দিয়ে সুরক্ষা — পাঠ্যবইয়ের ১৪-বিট উদাহরণ

একটি সিস্টেমে ঠিকানা ১৪ বিট (০ থেকে ১৬,৩৮৩), page size ২ KiB (২০৪৮), তাই মোট ১৬,৩৮৪ ÷ ২০৪৮ = ৮টি page (০–৭)। একটি প্রোগ্রাম ব্যবহার করে শুধু ঠিকানা ০ থেকে ১০,৪৬৮. ১০,৪৬৮ ÷ ২০৪৮ = ৫ (ভাগশেষসহ), তাই page ০–৫ valid, page ৬ আর ৭ invalid. প্রোগ্রাম ১৪,০০০ ঠিকানায় গেলে (page ৬) trap — ভালো। কিন্তু ১১,০০০ ঠিকানায় গেলে? সেটি page ৫-এর ভেতরে (১০,২৪০–১২,২৮৭), যা valid — তাই hardware বাধা দেবে না, যদিও প্রোগ্রামের আসল সীমা ১০,৪৬৮. সুরক্ষা page-এর মাপে কাজ করে, বাইটের মাপে নয়। ১০,৪৬৯ থেকে ১২,২৮৭ পর্যন্ত জায়গাটি একই সাথে সুরক্ষার ফাঁক আর internal fragmentation.

এই page-মাপের সুরক্ষা নিজের চোখে দেখা যায়। নিচের C প্রোগ্রাম mmap দিয়ে দুটি page নেয়, দুটিতেই লেখে, তারপর mprotect দিয়ে শুধু প্রথম page-কে read-only করে — অর্থাৎ kernel সেই page-এর PTE-তে R/W বিট মুছে দেয়। এরপর প্রথম page-এ লেখার চেষ্টা MMU আটকায়, kernel প্রসেসকে SIGSEGV পাঠায়। প্রোগ্রামটি নিজের signal handler দিয়ে সেটি ধরে, sigsetjmp/siglongjmp দিয়ে লেখার জায়গা থেকে বেরিয়ে আসে, আর ফল ছাপে:

#define _GNU_SOURCE
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>

static sigjmp_buf back;
static void *volatile fault_at;
static void on_segv(int sig, siginfo_t *info, void *ctx) {
    (void)sig; (void)ctx;
    fault_at = info->si_addr;                  /* address that faulted */
    siglongjmp(back, 1);                       /* leave the bad write */
}

int main(void) {
    long ps = sysconf(_SC_PAGESIZE);
    volatile char *mem = mmap(NULL, 2 * ps, PROT_READ | PROT_WRITE,
                              MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (mem == MAP_FAILED) { perror("mmap"); return 1; }
    struct sigaction sa = {0};
    sa.sa_sigaction = on_segv;  sa.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &sa, NULL);
    printf("page size %ld, start page-aligned: %s\n", ps,
           (unsigned long)mem % ps ? "no" : "yes");
    mem[0] = 'A'; mem[ps] = 'B';                  /* both pages writable */
    mprotect((void *)mem, ps, PROT_READ);         /* page 0: read-only */
    printf("read page 0: %c\n", mem[0]);
    if (sigsetjmp(back, 1) == 0) { mem[10] = 'X'; puts("write succeeded?!"); }
    else printf("write blocked: SIGSEGV at offset %ld\n",
                (long)((char *)fault_at - (char *)mem));
    mem[ps + 10] = 'Y';                           /* page 1 unaffected */
    printf("page 1: %c%c, page 0 mem[10] = %d\n", mem[ps], mem[ps + 10], mem[10]);
    return 0;
}

আসল আউটপুট:

page size 4096, start page-aligned: yes
read page 0: A
write blocked: SIGSEGV at offset 10
page 1: BY, page 0 mem[10] = 0

প্রথম লাইন: সার্ভারে page ৪০৯৬ বাইট, আর mmap-এর দেওয়া ঠিকানা ৪০৯৬-এর গুণিতক — kernel সবসময় পুরো page দেয়, page-এর মাঝখান থেকে নয়। mprotect-এর পরও পড়া চলে (A), কারণ শুধু লেখার অনুমতি মোছা হয়েছে। লেখার চেষ্টা আটকেছে ঠিক offset ১০-এ — si_addr দেখায় MMU কোন ঠিকানায় থেমেছিল, আর সেটি আমাদের লেখার ঠিকানাই। পাশের page ১-এ লেখা দিব্যি চলেছে (BY): অনুমতি page ধরে ধরে, একই mmap-এর দুই page-এর দুই নিয়ম। শেষে mem[10] এখনো ০, অর্থাৎ 'X' কখনো লেখা হয়নি — নির্দেশটি মাঝপথে বাতিল হয়েছিল। sigaction-এর SA_SIGINFO না দিলে handler si_addr পেত না। সতর্কতা: SIGSEGV ধরে প্রোগ্রাম চালিয়ে যাওয়া এখানে শুধু পরীক্ষার জন্য; সাধারণ প্রোগ্রামে segmentation fault মানে বাগ, যা ঢাকা নয়, ঠিক করা উচিত। Java-র মতো runtime আর কিছু database অবশ্য ইচ্ছা করে এই কৌশল ব্যবহার করে (যেমন guard page বা null-check-এর জন্য)। Windows-এ একই কাজ হয় VirtualAlloc আর VirtualProtect দিয়ে, আর ভুল access-এ আসে STATUS_ACCESS_VIOLATION exception, যা structured exception handling দিয়ে ধরা যায়।

৭ · Internal fragmentation আর page size-এর দর-কষাকষি

Paging external fragmentation দূর করে, কিন্তু internal fragmentation আনে: প্রসেসের আকার page size-এর ঠিক গুণিতক না হলে শেষ frame-এর একটি অংশ অব্যবহৃত থাকে। পাঠ্যবইয়ের পরিচিত হিসাব:

  1. Page size ২,০৪৮ বাইট, প্রসেস ৭২,৭৬৬ বাইট।
  2. ৭২,৭৬৬ ÷ ২,০৪৮ = ৩৫, ভাগশেষ ১,০৮৬ → ৩৫টি পূর্ণ page আর একটি page-এ ১,০৮৬ বাইট।
  3. মোট frame লাগবে ৩৬টি। শেষ frame-এ অব্যবহৃত ২,০৪৮ − ১,০৮৬ = ৯৬২ বাইট.
  4. সবচেয়ে খারাপ ক্ষেত্র: প্রসেস n page আর ১ বাইট — তখন প্রায় পুরো একটি frame (২,০৪৭ বাইট) নষ্ট।

প্রসেসের আকার এলোমেলো হলে শেষ page-এর ব্যবহৃত অংশ ০ থেকে পুরো page-এর মধ্যে সমানভাবে ছড়ানো ধরে নেওয়া যায়, তাই গড়ে প্রতি প্রসেসে (বা প্রতি মেমরি-এলাকায়) আধা page নষ্ট. এটি ছোট page-এর পক্ষে যুক্তি। কিন্তু ছোট page মানে বেশি page, বড় টেবিল, আর TLB-তে কম জায়গা ধরা। নিচের টেবিল (৩২-বিট ঠিকানা, ৪ বাইটের entry, একতলা টেবিল) দর-কষাকষিটি দেখায়:

Page sizeEntry-র সংখ্যাটেবিলের আকারগড় internal fragmentation (প্রতি এলাকা)৬৪-entry TLB কতটা ধরে
৫১২ বাইট৮৩,৮৮,৬০৮৩২ MiB২৫৬ বাইট৩২ KiB
৪ KiB১০,৪৮,৫৭৬৪ MiB২ KiB২৫৬ KiB
১৬ KiB২,৬২,১৪৪১ MiB৮ KiB১ MiB
৬৪ KiB৬৫,৫৩৬২৫৬ KiB৩২ KiB৪ MiB
২ MiB২,০৪৮৮ KiB১ MiB১২৮ MiB

শেষ কলামটির নাম TLB reach — TLB-র entry-সংখ্যা × page size. বড় page-এ একই TLB অনেক বেশি মেমরি ঢেকে রাখে, তাই বড় ডেটা নিয়ে কাজ করা প্রোগ্রামে TLB miss কমে। আরেকটি দিক I/O: ডিস্ক থেকে page আনার সময় (পর্ব ৩৫) প্রতিটি পড়ার একটি নির্দিষ্ট খরচ (HDD-তে seek আর rotation, SSD-তেও প্রতিটি অনুরোধের নিজস্ব খরচ) আছে; বড় page-এ সেই খরচ বেশি ডেটায় ভাগ হয়, তাই প্রতি বাইটে I/O সস্তা। কিন্তু উল্টো দিকও আছে: বড় page-এ এমন ডেটাও আসে যা প্রোগ্রাম চায়নি, আর মেমরিতে “কাজের” অংশ কম সূক্ষ্মভাবে ধরা যায়। ছোট page প্রোগ্রামের locality আরও নিখুঁতভাবে অনুসরণ করে।

ছোট page-এর পক্ষে

কম internal fragmentation; প্রোগ্রাম যতটুকু ছোঁয় ঠিক ততটুকু মেমরিতে আনা যায়; সুরক্ষার সীমা সূক্ষ্ম।

৪ KiB-এর মতো

বড় page-এর পক্ষে

ছোট page table; বেশি TLB reach; প্রতি বাইটে সস্তা I/O; টেবিল হাঁটার ধাপ কম।

১৬ KiB, ২ MiB, ১ GiB

ঐতিহাসিকভাবে page size বেড়েছে: Atlas-এ ৫১২ word, VAX-এ ৫১২ বাইট, তারপর ৪ KiB প্রায় সার্বজনীন, আর এখন কিছু প্ল্যাটফর্ম ১৬ KiB-এ যাচ্ছে। কারণ RAM প্রতি দশকে বহুগুণ বেড়েছে, কিন্তু TLB-র আকার সেই হারে বাড়েনি, আর আধা page নষ্ট হওয়া বড় RAM-এ তুচ্ছ। সমাধান হিসেবে আধুনিক CPU একসাথে একাধিক page size দেয়: x86-64-এ ৪ KiB-এর পাশাপাশি huge page — ২ MiB আর ১ GiB. Database আর virtual machine-এর মতো বড় মেমরির কাজে huge page ব্যবহার হয়; Linux-এ hugetlbfs আর transparent huge page (THP) এর দুই পথ, যার বিস্তারিত পর্ব ৩৮-এ।

৮ · Shared page — এক frame, অনেক প্রসেস

Paging-এর একটি বড় উপহার হলো শেয়ার করা সহজ। দুই প্রসেসের page table-এ দুটি entry যদি একই frame number রাখে, তাহলে দুই প্রসেস একই physical মেমরি দেখে — যদিও তাদের logical ঠিকানা আলাদা হতে পারে। কপি করতে হয় না, শুধু টেবিলে একই নম্বর লিখলেই হয়।

সবচেয়ে বড় প্রয়োগ প্রোগ্রামের কোড। একটি অফিসের ৪০ জন কর্মী একসাথে একই text editor চালাচ্ছেন, editor-এর কোড ১৫০ KiB আর প্রত্যেকের নিজস্ব ডেটা ৫০ KiB. শেয়ার না করলে মোট ৪০ × (১৫০ + ৫০) = ৮,০০০ KiB. কোড শেয়ার করলে কোডের একটিই কপি: ১৫০ + ৪০ × ৫০ = ২,১৫০ KiB — প্রায় চার ভাগের এক ভাগ। আজকের Linux-এ প্রতিটি প্রোগ্রাম C library (glibc-এর libc.so.6) ব্যবহার করে; কয়েকশো প্রসেস চললেও libc-এর কোডের page-গুলো RAM-এ সাধারণত একবারই থাকে, প্রতিটি প্রসেসের টেবিল সেই একই frame-এ নির্দেশ করে।

শর্ত একটাই: শেয়ার করা কোড হতে হবে reentrant (pure code) — চলার সময় নিজেকে কখনো বদলায় না। প্রতিটি প্রসেসের নিজস্ব অবস্থা (ভ্যারিয়েবল, stack) থাকে তার নিজের ডেটা page-এ। OS এটি নিশ্চিত করে protection বিট দিয়ে: শেয়ার করা কোডের page read-only আর executable, তাই কোনো প্রসেস ভুল করে লিখতে গেলে trap. নিচের stepper-এ দুই প্রসেস একই editor চালাচ্ছে:

P1 page 0 → 3কোড, R-X
P1 page 1 → 4কোড, R-X
P1 page 2 → 7ডেটা, RW-
frame 3editor কোড (১)
frame 4editor কোড (২)
frame 7P1-এর ডেটা
frame 2P2-এর ডেটা
P2 page 0 → 3কোড, R-X
P2 page 1 → 4কোড, R-X
P2 page 2 → 2ডেটা, RW-
  1. P1 editor খুলল। OS editor-এর কোড ডিস্ক থেকে frame ৩ আর ৪-এ আনল, P1-এর ডেটার জন্য দিল frame ৭. P1-এর টেবিল: page ০ → ৩, page ১ → ৪ (দুটিই read + execute, লেখা নিষেধ), page ২ → ৭ (read + write). মোট ৩টি frame ব্যস্ত।
  2. P2 একই editor খুলল। OS দেখল কোডটি ইতিমধ্যে frame ৩ আর ৪-এ আছে (একই ফাইলের একই অংশ)। তাই আবার ডিস্ক থেকে পড়ল না; P2-এর টেবিলেও লিখল page ০ → ৩, page ১ → ৪, একই R-X অনুমতিতে। একটি নতুন frame-ও খরচ হয়নি।
  3. ডেটা আলাদা। P2-এর নিজস্ব ডেটা (খোলা ফাইলের লেখা, cursor-এর অবস্থান) যায় নতুন frame ২-এ, read + write. এখন মোট ৪টি frame; শেয়ার না করলে লাগত ৬টি। দুই প্রসেস একই কোড চালায়, কিন্তু একজনের ডেটা আরেকজন দেখতে পায় না।
  4. কেউ কোডে লিখতে চাইলে। P1-এর একটি বাগ page ০-তে (frame ৩) লেখার চেষ্টা করল। PTE-তে লেখার অনুমতি নেই, তাই MMU আটকায় আর trap — Linux-এ P1 পায় SIGSEGV. P2 নিরাপদ থাকে; শেয়ার করা কোড কোনো প্রসেস নষ্ট করতে পারে না।
  5. বড় ছবিতে। ৪০ জন একসাথে চালালে কোডের frame এই দুটিই থাকে, শুধু ডেটার frame বাড়ে — সেই ১৫০ + ৪০ × ৫০ = ২,১৫০ KiB-এর হিসাব। লক্ষ করুন, page number দুই প্রসেসে একই (০, ১) হওয়া জরুরি নয়; শুধু frame এক। (আধুনিক shared library position-independent code ব্যবহার করে, যাতে ভিন্ন ঠিকানায় বসলেও কাজ করে।)

শেয়ারিং শুধু কোডে সীমিত নয়। দুই প্রসেস ইচ্ছা করে একই ডেটা page ভাগ করে নিতে পারে — shared memory দিয়ে IPC (পর্ব ১২) ঠিক এভাবেই কাজ করে। আর fork()-এর পর parent আর child প্রথমে সব page শেয়ার করে, শুধু-পড়ার অনুমতিতে; কেউ লিখলে তখনই কপি হয় — copy-on-write, যার পূর্ণ গল্প পর্ব ৩৫-এ। Windows-এও DLL-এর কোড এভাবে প্রসেসগুলোর মধ্যে শেয়ার হয়, আর macOS-এ সিস্টেমের library-গুলো একটি বড় “shared cache” হিসেবে সব প্রসেসে ভাগ হয়।

৯ · বাস্তব সিস্টেমে paging — page size দেখুন নিজের চোখে

প্ল্যাটফর্মমূল page sizeবড় page
x86-64 (Linux, Windows, Intel Mac-এর macOS)৪ KiB২ MiB, ১ GiB
বেশিরভাগ ARM64 Linux আর Android ফোন৪ KiBgranule অনুযায়ী (যেমন ২ MiB)
Apple silicon-এর macOS১৬ KiB—
কিছু নতুন Android ডিভাইস১৬ KiB (Android 15 থেকে সমর্থন)—
কিছু ARM64 আর POWER সার্ভার Linux৬৪ KiB (কনফিগারেশন অনুযায়ী)—

ARM64 আর্কিটেকচার নিজেই তিনটি page size (granule) সমর্থন করে — ৪, ১৬ আর ৬৪ KiB — আর কোনটি ব্যবহার হবে, তা ঠিক করে OS. তাই একই CPU-তে ভিন্ন OS ভিন্ন page size নিতে পারে। Google Android 15 থেকে ১৬ KiB page-এর ডিভাইস সমর্থন শুরু করেছে এবং app-নির্মাতাদের ১৬ KiB-এর সাথে মানিয়ে নিতে বলেছে; কোন ফোন কোনটি ব্যবহার করে তা নির্মাতা আর মডেলভেদে আলাদা। শিক্ষা হলো: কোডে কখনো ৪০৯৬ লিখে রাখবেন না — চলার সময় জিজ্ঞেস করুন। C-তে sysconf(_SC_PAGESIZE) (উপরের প্রোগ্রামের মতো), shell-এ getconf PAGESIZE.

নিচের script page size দেখায়, তারপর /proc-এর /proc/self/maps পড়ে — এখানে self মানে যে প্রসেস ফাইলটি পড়ছে, অর্থাৎ awk নিজে। প্রতিটি লাইন একটি মেমরি-এলাকা (শুরু-শেষ ঠিকানা hex-এ, অনুমতি, ফাইল)। Script প্রতিটি এলাকার শুরু আর শেষ page size দিয়ে ভাগ যায় কি না (% ps == 0) যাচাই করে, আর মোট কত page তা যোগ করে। শেষে /proc/self/smaps থেকে প্রতিটি এলাকার kernel page size গোনে:

echo "page size: $(getconf PAGESIZE) bytes"
awk -v ps="$(getconf PAGESIZE)" '
function hex(s,   i, n) {                  # hex string -> number, any awk
  n = 0; s = tolower(s)
  for (i = 1; i <= length(s); i++) n = n * 16 + index("0123456789abcdef", substr(s, i, 1)) - 1
  return n
}
{ split($1, r, "-"); a = hex(r[1]); b = hex(r[2]); pages = (b - a) / ps
  total += pages; n++
  if (a % ps == 0 && b % ps == 0) ok++
  if (n == 1 || $6 == "[heap]" || $6 == "[stack]")
    printf "%s  %s  %5d pages  %s\n", r[1], $2, pages, $6
}
END { printf "regions: %d, page-aligned: %d, total %d pages = %d KiB\n", n, ok, total, total * ps / 1024 }
' /proc/self/maps
echo "KernelPageSize in grep's own smaps:"
grep KernelPageSize /proc/self/smaps | sort | uniq -c

একবার চালানোর আসল ফল (ঠিকানা আর সংখ্যা প্রতিবার ও প্রতি মেশিনে আলাদা হবে, কারণ ASLR):

page size: 4096 bytes
5a606eebb000  r--p      2 pages  /usr/bin/mawk
5a607eb55000  rw-p     33 pages  [heap]
7ffe6c517000  rw-p     33 pages  [stack]
regions: 29, page-aligned: 29, total 1025 pages = 4100 KiB
KernelPageSize in grep's own smaps:
     29 KernelPageSize:        4 kB

এখানে awk আসলে mawk — container-এ সেটিই আছে, তাই hex রূপান্তরের ফাংশনটি নিজে লেখা হয়েছে (strtonum শুধু GNU awk-এ)। প্রতিটি শুরুর ঠিকানার শেষ তিন hex অঙ্ক 000 — ৪০৯৬ = 0x1000-এর গুণিতক hex-এ ঠিক এমনই দেখায়, অংশ ২-এর “শেষ তিন অঙ্ক = offset” নিয়মের উল্টো পিঠ। ২৯টি এলাকার ২৯টিই page-aligned: kernel মেমরি দেয় আর অনুমতি বসায় শুধু পুরো page ধরে। প্রথম এলাকা mawk প্রোগ্রামের ফাইলের প্রথম ২টি page (শুধু-পড়া), [heap] আর [stack] এখানে ৩৩টি করে page. মোট ১০২৫টি page = ৪১০০ KiB হলো awk-এর ঠিকানা-জগতে বসানো এলাকা — এর সবটা RAM-এ নেই; কোনটি সত্যিই frame পেয়েছে, তা virtual memory-র বিষয় (পর্ব ৩৫)। ঠিকানাগুলো ১২ hex অঙ্কের (৪৮ বিটের মধ্যে) — x86-64-এর user ঠিকানা-জগৎ। শেষ লাইন: grep-এর নিজের ২৯টি এলাকার প্রতিটির kernel page size ৪ kB, কোনো huge page নেই। Apple silicon-এর Linux বা ১৬ KiB-এর Android-এ একই script চালালে প্রথম লাইনে ১৬৩৮৪ আসত, আর % ps যাচাই তখনও সব এলাকাকে aligned দেখাত — script page size hard-code করে না বলেই।

অন্য OS-এ একই প্রশ্নের উত্তর অন্য পথে — /proc শুধু Linux-এর জিনিস:

getconf PAGESIZE                    # 4096 on x86-64; 16384 on a 16 KiB-page kernel
grep -i huge /proc/meminfo          # HugePages_Total, Hugepagesize: 2048 kB ...
cat /sys/kernel/mm/transparent_hugepage/enabled   # e.g. always [madvise] never
$ getconf PAGESIZE
16384
$ sysctl hw.pagesize
hw.pagesize: 16384
PS> [Environment]::SystemPageSize
4096

Windows-এ C প্রোগ্রাম GetSystemInfo দিয়ে dwPageSize (৪০৯৬) পায়। একই কাঠামোয় আরেকটি মান আছে — dwAllocationGranularity, সাধারণত ৬৪ KiB: VirtualAlloc নতুন এলাকা বসানোর শুরুর ঠিকানা এর গুণিতকে রাখে, যদিও সুরক্ষা আর বরাদ্দ চলে ৪ KiB page ধরে। দুটিকে গুলিয়ে ফেলা একটি পরিচিত ভুল।

আরও গভীরে: Linux kernel প্রতিটি physical frame-এর জন্য একটি struct page রাখে — এটিই বাস্তবের frame table (সাধারণত প্রতিটি ৬৪ বাইট, তাই ১৬ GiB RAM-এর ৪১,৯৪,৩০৪টি frame-এর জন্য প্রায় ২৫৬ MiB, RAM-এর প্রায় ১.৬%). খালি frame-এর তালিকা একটি সাধারণ লিংকড লিস্ট নয়, বরং buddy allocator (পর্ব ৩১), যা ২-এর ঘাতের মাপে একটানা frame-এর দল রাখে — কারণ huge page আর কিছু device-এর DMA-র জন্য একটানা physical মেমরি এখনো লাগে। x86-64-এ page table hardware নিজেই হাঁটে (page walk), আর MIPS-এর মতো কিছু পুরোনো RISC CPU-তে TLB miss হলে OS-এর software handler টেবিল খুঁজত — page table-এর গঠন তখন পুরোপুরি OS-এর ইচ্ছামতো। ১৬ KiB page-এ যাওয়া সহজ নয়: Asahi Linux (Apple silicon-এ Linux) আর Android-এর অভিজ্ঞতা দেখিয়েছে, যেসব প্রোগ্রাম বা library ৪০৯৬ ধরে নিয়ে লেখা, সেগুলো ভাঙতে পারে।

১০ · AI-এর যুগে paging-এর অঙ্ক আর কোড

Paging-এর অঙ্ক দেখতে সহজ, তাই AI-কে দিলে দ্রুত উত্তর আসে। কিন্তু কয়েকটি ভুল বারবার দেখা যায় — বেশিরভাগই একক আর বিটের গোলমাল:

AI-এর ভুলকেন ভুলকীভাবে যাচাই করবেন
৪ KB-কে ৪০০০ বাইট ধরাPage size ২-এর ঘাত, ৪০৯৬n = log2(page size) পূর্ণসংখ্যা কি না দেখুন
Offset-এর বিট আর page-এর সংখ্যা গুলিয়ে ফেলাn বিট offset; page-এর সংখ্যা ২m−np-বিট + d-বিট = m কি না মেলান
Page table-এর আকার বিটে বলে বাইট লেখা, বা entry-র আকার বাদ দেওয়াআকার = entry-সংখ্যা × entry-র বাইটএককসহ প্রতিটি ধাপ লিখতে বলুন
“Paging-এ কোনো fragmentation নেই”External নেই, internal আছে৭২,৭৬৬ বাইটের উদাহরণটি AI-কে দিন
“Page size সবসময় ৪ KiB”, আর কোডে 4096 লিখে দেওয়াApple silicon-এ ১৬ KiB, কিছু Android আর সার্ভারে ভিন্নgetconf PAGESIZE চালিয়ে দেখুন
Huge page চালু করতে চোখ বুজে sudo-তে /proc/sys/vm বা /sys/kernel/mm-এ লেখার কমান্ডপুরো সিস্টেমের আচরণ বদলায়, কখনো ধীরও করেআগে kernel ডকুমেন্টেশন পড়ুন, test মেশিনে মাপুন

অঙ্ক দেওয়ার সময় একক আর নিয়ম স্পষ্ট লিখে দিন, আর প্রতিটি ধাপ যাচাইযোগ্য আকারে চান:

নিচের paging সমস্যাটি ধাপে ধাপে সমাধান করো। ১ KiB = ১০২৪ বাইট ধরবে। Page size ৪ KiB, logical address ৩২ বিট, প্রতিটি page table entry ৪ বাইট। Page table: page 0 → frame 5, page 1 → frame 9, page 2 → invalid, page 3 → frame 0x1A, page 4 → frame 2 (table length 5). ১. Offset আর page number-এর বিট-সংখ্যা, মোট page আর single-level page table-এর আকার বের করো, প্রতিটি ধাপে একক লিখে। ২. 0x1ABC, 0x3004, 0x2FFF, 0x5000 — প্রতিটির p, d (hex-এ), frame আর physical address দাও; অবৈধ হলে কোন কারণে (invalid নাকি table length ছাড়িয়েছে) তা বলো। ৩. শেষে প্রতিটি উত্তর f × 4096 + d দিয়ে দশমিকে যাচাই করে দেখাও। কোথাও অনুমান করলে স্পষ্ট বলো।

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

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

  • Paging কোন fragmentation দূর করে, কোনটি করে না? — External দূর করে; internal থাকে, গড়ে প্রতি প্রসেসে আধা page.
  • ৩২-বিট ঠিকানা, ৪ KiB page, ৪ বাইটের entry — page table কত বড়? — ২২০ entry × ৪ বাইট = ৪ MiB.
  • Logical থেকে physical address কীভাবে? — p = উপরের বিট, d = নিচের n বিট; page table থেকে f; physical = f × page size + d.
  • TLB ছাড়া paging-এ একটি মেমরি-ব্যবহারে কতবার মেমরিতে যেতে হয়? — দুইবার: একবার PTE, একবার ডেটা।
  • Dirty বিট কেন লাগে? — বদলানো page সরানোর আগে ডিস্কে লিখতে হয়; বদলায়নি এমন page লেখা ছাড়াই ফেলে দেওয়া যায়।

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

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

প্র ০১১৯৬০-এর দশকের শুরুতে Atlas-এর নকশাকারেরা paging উদ্ভাবন করলেন কেন? সেই প্রেক্ষাপট আজকের নকশা বুঝতে কীভাবে সাহায্য করে?

১৯৫০-এর দশকের শেষে কম্পিউটারের দ্রুত মেমরি (magnetic core) ছিল খুব দামি আর ছোট; বড় ডেটা থাকত ধীর ড্রামে। প্রোগ্রামারদের নিজেদেরই ঠিক করতে হতো কোন অংশ কখন ড্রাম থেকে core-এ আনবেন — একে বলা হতো overlay. এটি ছিল ক্লান্তিকর, ভুলপ্রবণ, আর প্রতিটি মেশিনের জন্য আলাদা। Manchester-এর Atlas দল (Tom Kilburn ও সহকর্মীরা) প্রশ্ন করলেন: এই কাজ যদি মেশিন নিজেই করে? তাঁদের উত্তর “one-level store”: প্রোগ্রামার একটি বড় ঠিকানা-জগৎ দেখবেন, আর hardware ও supervisor প্রোগ্রাম মিলে ঠিক করবে কোন টুকরো এখন core-এ। টুকরোগুলো সমান মাপের হলে core-এর যেকোনো খালি জায়গায় বসানো যায় — এখান থেকেই page আর page frame.

Atlas-এর একটি বৈশিষ্ট্য আজও শিক্ষণীয়। প্রতিটি core frame-এর সাথে একটি register ছিল, যেখানে লেখা থাকত সেই frame-এ এখন কোন page. ঠিকানা এলে সব register একসাথে তুলনা করা হতো — একরকম associative খোঁজ। অর্থাৎ টেবিলটি ছিল frame-ভিত্তিক, page-ভিত্তিক নয় — আজকের ভাষায় inverted page table আর TLB-র পূর্বপুরুষ (পর্ব ৩৩)। Page না পেলে supervisor ড্রাম থেকে এনে দিত, আর প্রায় অব্যবহৃত একটি page সরাতে একটি “learning” algorithm ব্যবহার করত — page replacement-এর সূচনা (পর্ব ৩৬)।

এই ইতিহাস দেখায়, paging মূলত দুই সমস্যার এক সমাধান: মেমরি ভাগাভাগির fragmentation আর প্রোগ্রামারের বোঝা। প্রথম কাজটি (সমান টুকরো, যেকোনো জায়গায়) এই পর্বের বিষয়; দ্বিতীয়টি (ডিস্কে রাখা, দরকারে আনা) virtual memory. পরে IBM-এর System/360 Model 67 আর Multics-এর মতো সিস্টেম, তারপর ১৯৭০-এর দশকের IBM System/370 আর VAX এটিকে ব্যবসায়িক কম্পিউটারের স্বাভাবিক অংশ বানায়। আজ ফোন থেকে সার্ভার পর্যন্ত প্রায় প্রতিটি সাধারণ কম্পিউটারের MMU এই ষাট বছরের পুরোনো ধারণার উত্তরসূরি।

মূল উপলব্ধি: ভালো abstraction একটি ঝামেলার কাজ মানুষের হাত থেকে মেশিনে সরায়; Atlas প্রোগ্রামারকে physical মেমরির হিসাব থেকে মুক্তি দিয়েছিল — আর সেই মুক্তির দাম (টেবিল, অনুবাদের সময়) কমানোর চেষ্টাই পরের সব উদ্ভাবনের গল্প।

প্র ০২৪ KiB page প্রায় চল্লিশ বছর টিকে আছে, অথচ RAM বেড়েছে হাজার গুণ। তাহলে Apple আর Google-এর মতো কোম্পানি এখন ১৬ KiB-এর দিকে যাচ্ছে কেন, আর তাতে কী ভাঙে?

১৯৮৫ সালে Intel 80386 যখন ৪ KiB page বেছে নেয়, একটি সাধারণ PC-তে RAM ছিল কয়েকশো KiB থেকে কয়েক MiB. তখন ৪ KiB ছিল ভারসাম্যপূর্ণ — টেবিল ছোট, internal fragmentation কম। আজ ফোনেও ৮–১২ GiB RAM; কিন্তু TLB-র entry-সংখ্যা সেই হারে বাড়েনি, কারণ TLB-কে প্রতিটি নির্দেশের সাথে তাল মিলিয়ে অত্যন্ত দ্রুত হতে হয়, আর বড় associative টেবিল ধীর ও বিদ্যুৎ-খরচে। ফলে TLB reach (entry × page size) মোট RAM-এর তুলনায় ক্রমেই ছোট হয়েছে, আর বড় প্রোগ্রামে TLB miss আর page walk সময়ের একটি উল্লেখযোগ্য অংশ খেয়ে ফেলে।

১৬ KiB page-এ একই TLB চার গুণ মেমরি ঢাকে, page table চার ভাগের এক ভাগ, আর page fault-এ প্রতিবার বেশি ডেটা আসে। Apple silicon-এর macOS শুরু থেকেই ১৬ KiB ব্যবহার করে। Google জানিয়েছে Android 15 থেকে ১৬ KiB page-এর ডিভাইস সমর্থিত, আর তাদের প্রকাশিত পরীক্ষায় app খোলার সময় আর ব্যাটারির মতো মাপকাঠিতে উন্নতি দেখা গেছে — তবে এই সংখ্যা ডিভাইস আর কাজভেদে আলাদা, তাই সাধারণীকরণে সতর্ক থাকা উচিত। দাম হলো বেশি internal fragmentation: প্রতিটি ছোট মেমরি-এলাকায় গড় অপচয় ২ KiB থেকে বেড়ে ৮ KiB, আর অনেক ছোট প্রসেসের সিস্টেমে মোট মেমরি-ব্যবহার কিছুটা বাড়ে।

আসল কঠিন অংশ সফটওয়্যারের সামঞ্জস্য। বহু প্রোগ্রাম আর library ৪০৯৬ সংখ্যাটি কোডে লিখে রেখেছে, বা ধরে নিয়েছে mmap-এর ঠিকানা ৪ KiB-এর গুণিতক হলেই যথেষ্ট। Executable ফাইলের (ELF) segment-গুলো যদি শুধু ৪ KiB-এর সীমায় সাজানো থাকে, ১৬ KiB-এর kernel তাদের ঠিকমতো বসাতে পারে না — এজন্য Android-এর native library-কে ১৬ KiB alignment দিয়ে নতুন করে build করতে বলা হয়েছে। Apple silicon-এ Linux চালানো Asahi Linux প্রকল্পও এমন সমস্যায় পড়েছিল, যেখানে কিছু memory allocator (যেমন ৪ KiB page ধরে build করা jemalloc) আর তার ওপর নির্ভর করা প্রোগ্রাম চলত না। এটি একটি পুরোনো শিক্ষার পুনরাবৃত্তি: আর্কিটেকচারের একটি “স্থির” সংখ্যা দীর্ঘদিন বদলায়নি বলে সবাই সেটিকে চিরস্থায়ী ধরে নেয়।

মূল উপলব্ধি: Page size একটি trade-off, যার সেরা মান হার্ডওয়্যারের অনুপাত (RAM বনাম TLB) বদলালে বদলায় — আর যে কোড এই সংখ্যা চলার সময় জিজ্ঞেস করে, সেই কোডই পরিবর্তনে টিকে থাকে।

প্র ০৩Paging external fragmentation দূর করে — তাহলে Linux-কে আজও “memory compaction” করতে হয় কেন? Physical মেমরিতে একটানা জায়গা কি আর কখনো লাগে না?

প্রসেসের দিক থেকে উত্তর সত্যি: ৪ KiB page দিয়ে মেমরি চাইলে যেকোনো খালি frame চলে, তাই external fragmentation তার কোনো সমস্যা করে না। কিন্তু পুরো সিস্টেমের দিক থেকে কয়েকটি জায়গায় এখনো একটানা physical মেমরি লাগে। প্রথম, huge page: ২ MiB-এর একটি huge page মানে ৫১২টি পাশাপাশি ৪ KiB frame, ২ MiB-এর সীমায় শুরু। মেমরি অনেকক্ষণ চললে খালি frame ছড়িয়ে পড়ে — মোট খালি অনেক, কিন্তু ৫১২টি পাশাপাশি খালি পাওয়া কঠিন। পর্ব ৩১-এর সমস্যাটি এখানে আবার ফিরে আসে, শুধু টুকরোর মাপ বড়।

দ্বিতীয়, device আর DMA: কিছু পুরোনো বা সাধারণ device CPU-র MMU দিয়ে যায় না, physical ঠিকানা সরাসরি ব্যবহার করে, তাই তাদের buffer একটানা হতে হয়। (আধুনিক সিস্টেমে IOMMU device-এর জন্যও paging-এর মতো অনুবাদ করে এই সমস্যা কমায়।) তৃতীয়, kernel-এর নিজের কিছু বড় বরাদ্দ, যা দ্রুততার জন্য physical-ভাবে একটানা রাখা হয়।

এজন্য Linux-এর buddy allocator frame-গুলো ২-এর ঘাতের দলে রাখে, আর মেমরি টুকরো হয়ে গেলে compaction চালায়: সরানো যায় এমন page (যেমন সাধারণ প্রসেসের page) অন্য frame-এ কপি করে, page table-এ নতুন frame number লিখে দেয় — প্রসেস টেরই পায় না, কারণ তার logical ঠিকানা বদলায়নি। Paging এখানে compaction-কে contiguous allocation-এর চেয়ে অনেক সহজ করে: শুধু টেবিলের একটি ঘর বদলালেই page “সরে” যায়। কিন্তু kernel-এর কিছু page সরানো যায় না, তাই Linux বরাদ্দের সময়ই সরানো-যায় আর না-যায় page আলাদা এলাকায় রাখার চেষ্টা করে; device-এর জন্য CMA (contiguous memory allocator) নামে একটি সংরক্ষিত এলাকাও রাখা যায়।

মূল উপলব্ধি: Paging fragmentation-কে শেষ করেনি, বরং এক স্তর থেকে আরেক স্তরে সরিয়েছে — প্রসেসের চোখ থেকে লুকিয়েছে, আর kernel-কে সেটি সামলানোর সহজ হাতিয়ার (টেবিলে নম্বর বদল) দিয়েছে।

প্র ০৪Page table কি hardware হাঁটবে, নাকি OS-এর software? x86-এর hardware page walk আর MIPS-এর software-managed TLB-র নকশা থেকে কী শেখা যায়?

TLB-তে অনুবাদ না পেলে কাউকে মেমরিতে page table খুঁজে দেখতে হয়। x86 (80386 থেকে) আর ARM-এর পথ hardware page walk: CPU-র ভেতরের একটি সার্কিট PTBR (CR3) থেকে শুরু করে টেবিলের নির্দিষ্ট গঠন ধরে entry পড়ে, TLB-তে বসায়, আর নির্দেশ চলতে থাকে — OS জানতেও পারে না। এর সুবিধা গতি: কোনো exception নেই, pipeline প্রায় থামে না, আর আধুনিক CPU একাধিক walk একসাথে করে, টেবিলের উপরের স্তরের entry-ও আলাদা cache-এ রাখে। দাম হলো কঠোরতা: page table-এর বিন্যাস hardware ঠিক করে দেয়, OS সেটি বদলাতে পারে না।

১৯৮০-এর দশকের MIPS (আর পরে আরও কিছু RISC নকশা) উল্টো পথ নেয়। TLB miss হলে hardware শুধু একটি exception তোলে; OS-এর একটি ছোট, খুব সাবধানে লেখা handler নিজের ইচ্ছামতো ডেটা স্ট্রাকচারে (linear table, hash table — যা খুশি) খুঁজে TLB-তে entry লিখে দেয়। RISC দর্শনের সাথে এটি মেলে: hardware সরল রাখা, জটিলতা software-এ সরিয়ে দেওয়া। OS-গবেষকদের কাছে এটি স্বাধীনতা — নতুন ধরনের টেবিল পরীক্ষা করা যায় hardware না বদলেই। কিন্তু প্রতিটি miss-এ exception, handler-এ ঢোকা-বের হওয়া, আর cache-এ handler-এর কোড রাখার খরচ আছে; বড় মেমরির কাজে miss বেশি হলে এই খরচ চোখে পড়ে।

বাজারে শেষ পর্যন্ত hardware walk প্রায় সর্বত্র জিতেছে — x86-64, ARM64, RISC-V সবই এটি ব্যবহার করে — কারণ মেমরি বড় হওয়ায় miss বেড়েছে, আর hardware সেখানে অনেক দ্রুত। তবু software-এর নমনীয়তার শিক্ষা হারায়নি: virtual machine-এ hypervisor-কে অতিথি OS-এর টেবিল নিয়ন্ত্রণ করতে হয়, আর আগে এটি “shadow page table” নামের software কৌশলে হতো, যতদিন না CPU-তে দুই স্তরের hardware অনুবাদ (Intel EPT, AMD NPT) এলো (পর্ব ৫০)।

মূল উপলব্ধি: কোনো কাজ hardware-এ রাখলে গতি পাওয়া যায়, software-এ রাখলে নমনীয়তা; ঘন ঘন ঘটা, স্থির নিয়মের কাজ শেষ পর্যন্ত hardware-এ চলে যায়।

প্র ০৫Shared page মেমরি বাঁচায় — তাহলে কিছু cloud প্ল্যাটফর্ম ভিন্ন গ্রাহকের VM-এর মধ্যে একই রকম page জোড়া লাগানো (deduplication) কেন বন্ধ করে দিয়েছিল?

Shared page-এর মেমরি-সাশ্রয় এত আকর্ষণীয় যে এটি কোড ছাড়িয়ে ডেটাতেও ব্যবহার হয়েছে। Linux-এর KSM (kernel samepage merging) আর VMware-এর transparent page sharing পটভূমিতে মেমরি খুঁজে দেখে, দুটি page-এর বিষয়বস্তু হুবহু এক হলে একটি frame রেখে দুই টেবিলেই সেটি বসায় (copy-on-write চিহ্ন দিয়ে, যাতে কেউ লিখলে আবার আলাদা হয়)। একই OS চালানো অনেক VM-এ হাজার হাজার page এক, তাই সাশ্রয় বিশাল।

সমস্যা হলো শেয়ার করা মানে একটি অদৃশ্য যোগাযোগের পথও তৈরি করা। দুই প্রসেস বা দুই VM একই physical frame ভাগ করলে CPU-র cache-ও সেটি ভাগ করে। গবেষকেরা (যেমন ২০১৪ সালে প্রকাশিত Flush+Reload কৌশলে) দেখিয়েছেন, একটি প্রসেস শেয়ার করা page-এর একটি অংশ cache থেকে সরিয়ে দিয়ে আবার পড়তে কত সময় লাগে মেপে বুঝতে পারে অন্য প্রসেস সেটি সম্প্রতি ব্যবহার করেছে কি না — আর এভাবে ক্রিপ্টোগ্রাফির লাইব্রেরির ভেতরের গোপন তথ্যের আভাস পাওয়া সম্ভব। Deduplication-এ আরেকটি সূক্ষ্মতা: copy-on-write ভাঙতে লেখার সময় বেশি লাগে, তাই সময় মেপে বোঝা যায় অন্য কোথাও একই বিষয়বস্তুর page আছে কি না। এজন্য VMware ২০১৪–১৫ সালের দিকে ডিফল্টভাবে ভিন্ন VM-এর মধ্যে page sharing বন্ধ করে, আর অনেক cloud প্ল্যাটফর্ম ভিন্ন গ্রাহকের মধ্যে এমন জোড়া লাগানো এড়িয়ে চলে।

প্রতিরক্ষার দিক থেকে শিক্ষাগুলো স্পষ্ট: ভিন্ন বিশ্বাসের এলাকার (ভিন্ন গ্রাহক, sandbox-এর ভেতর আর বাইরে) মধ্যে physical page শেয়ার না করা; ক্রিপ্টোগ্রাফির কোড এমনভাবে লেখা যাতে সময় গোপন তথ্যের ওপর নির্ভর না করে (constant-time); আর hardware-স্তরে ECC মেমরি ও আধুনিক DRAM-সুরক্ষা ব্যবহার করা, কারণ page table নিজেও মেমরিতে থাকে আর তার বিট নষ্ট হলে সুরক্ষার পুরো ভিত্তিই নড়ে যায় (Rowhammer-এর মতো hardware ত্রুটির গবেষণা এটিই দেখিয়েছে)। Hardware-স্তরের side-channel (যেমন Spectre) আর মেমরি-সুরক্ষার অন্য প্রতিরক্ষা পর্ব ৪৮-এ।

মূল উপলব্ধি: যা শেয়ার করা যায়, তা দিয়ে তথ্যও চুইয়ে পড়তে পারে; মেমরি-সাশ্রয় আর বিচ্ছিন্নতার (isolation) মধ্যে ভারসাম্য ঠিক হয় কারা পরস্পরকে বিশ্বাস করে তার ওপর।

অনুশীলন

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

  1. হাতে হিসাব (বিট): একটি logical ঠিকানা-জগতে ৬৪টি page, প্রতিটি ১০২৪ বাইট; physical মেমরিতে ৩২টি frame. (ক) Logical address কত বিট? (খ) Physical address কত বিট? (গ) Page table-এ কতগুলো entry, আর প্রতিটি entry-তে frame number-এর জন্য কমপক্ষে কত বিট?

    (ক) ৬৪ × ১০২৪ = ২৬ × ২১০ = ২১৬, তাই ১৬ বিট (৬ বিট p + ১০ বিট d).

    (খ) ৩২ × ১০২৪ = ২৫ × ২১০ = ২১৫, তাই ১৫ বিট (৫ বিট f + একই ১০ বিট d).

    (গ) প্রতিটি logical page-এর জন্য একটি entry: ৬৪টি. প্রতিটিতে ৩২টি frame-এর যেকোনোটি লিখতে ৫ বিট; বাস্তবে valid আর protection বিটের জন্য আরও কয়েকটি বিট যোগ হয়। লক্ষ করুন, offset দুই দিকেই সমান — page আর frame একই মাপের বলে।

  2. হাতে অনুবাদ: Page size ১০২৪ বাইট; page table: page ০ → frame ৩, page ১ → frame ৭, page ২ → invalid, page ৩ → frame ১ (table length ৪)। দশমিক logical address ১০২৩, ২০৪৯, ৩১০০ আর ৫০০০-এর physical address বের করুন।

    ১০২৩: p = ০, d = ১০২৩ → ৩ × ১০২৪ + ১০২৩ = ৪০৯৫ (page-এর একেবারে শেষ বাইট, frame ৩-এর শেষ বাইটে)।

    ২০৪৯: p = ২, d = ১ → page ২ invalid → trap.

    ৩১০০: ৩১০০ ÷ ১০২৪ = ৩, ভাগশেষ ৩১০০ − ৩০৭২ = ২৮ → frame ১ → ১ × ১০২৪ + ২৮ = ১০৫২.

    ৫০০০: p = ৪ (৪০৯৬–৫১১৯), কিন্তু টেবিলে মাত্র ৪টি entry (০–৩) → table length ছাড়িয়েছে → trap.

    যাচাই: translator-এর শেষ লাইন বদলে translate(1024, [3, 7, null, 1], [1023, 2049, 3100, 5000]); চালালে একই চার ফল আসে।

  3. আউটপুট অনুমান: page_protect.c থেকে শুধু mprotect(...)-এর লাইনটি মুছে দিলে প্রোগ্রাম কী ছাপবে? প্রতিটি লাইন লিখুন।

    দুটি page-ই লেখার অনুমতিতে থাকে, তাই কোনো SIGSEGV হয় না; sigsetjmp প্রথমবার ০ ফেরত দেয়, লেখা সফল হয়, আর handler কখনো চলে না:

    page size 4096, start page-aligned: yes
    read page 0: A
    write succeeded?!
    page 1: BY, page 0 mem[10] = 88

    শেষ সংখ্যা ৮৮, কারণ 'X'-এর ASCII মান ৮৮ — এবার সত্যিই লেখা হয়েছে। (১৬ KiB page-এর মেশিনে প্রথম লাইনে ১৬৩৮৪ আসবে।) এই তুলনাটিই দেখায় যে আগের আউটপুটের “blocked” লাইন কোনো C-এর নিয়মের ফল নয়, শুধু PTE-র একটি বিটের ফল।

  4. কমান্ড ও হিসাব: (ক) Linux, macOS আর Windows-এ page size দেখার একটি করে কমান্ড লিখুন। (খ) ৮ GiB RAM আর ৪ KiB page-এ কতগুলো frame? Linux প্রতিটি frame-এর জন্য ৬৪ বাইটের একটি struct page রাখলে সেই খাতাতেই কত মেমরি যায়?

    (ক) Linux: getconf PAGESIZE; macOS: getconf PAGESIZE বা sysctl hw.pagesize; Windows PowerShell: [Environment]::SystemPageSize.

    (খ) ৮ GiB ÷ ৪ KiB = ২৩৩ ÷ ২১২ = ২২১ = ২০,৯৭,১৫২টি frame. খাতা: ২২১ × ৬৪ = ২২৭ বাইট = ১২৮ MiB, অর্থাৎ RAM-এর ১/৬৪ ≈ ১.৬%. ১৬ KiB page হলে frame চার ভাগের এক ভাগ, খাতাও ৩২ MiB — বড় page-এর আরেকটি লুকানো লাভ।

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

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

ক্রমTLB ছাড়া একটি paging সিস্টেমে একটি লোড-নির্দেশের ঠিকানা অনুবাদের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. CPU একটি logical address তৈরি করে
  2. MMU ঠিকানাকে page number p আর offset d-এ ভাগ করে
  3. p-কে page-table length-এর সাথে তুলনা করে
  4. PTBR + p × entry-র আকার ঠিকানা থেকে PTE পড়ে
  5. Valid আর protection বিট যাচাই করে
  6. f × page size + d ঠিকানা থেকে ডেটা আনে

Logical address ৩২ বিট আর page size ৪ KiB হলে page number আর offset-এর বিট-সংখ্যা কত?

  • p = ২২, d = ১০
  • p = ২০, d = ১২
  • p = ১২, d = ২০
  • p = ৩০, d = ২

৩২-বিট logical address, ৪ KiB page আর প্রতিটি page table entry ৪ বাইট হলে একটি প্রসেসের single-level page table-এর আকার কত?

  • ১ MiB
  • ৪ KiB
  • ৪ MiB
  • ৪ GiB

Page size ৪ KiB. একটি প্রসেসের page table-এ page ৩ → frame 0x1A. Logical address 0x3004-এর physical address কত?

  • 0x1A004
  • 0x301E
  • 0x1A04
  • 0x3004

Paging সম্পর্কে কোন বক্তব্যটি সঠিক?

  • Paging সব ধরনের fragmentation দূর করে
  • Paging external fragmentation আনে, internal দূর করে
  • প্রসেসের page-গুলো পাশাপাশি frame-এ রাখতে হয়
  • External fragmentation থাকে না, কিন্তু internal fragmentation থাকে

TLB নেই এমন একটি paging সিস্টেমে মেমরি-access ১০০ ns. একটি লোড-নির্দেশের ডেটা আনতে মোট কত সময় লাগবে (অন্য খরচ বাদে)?

  • ১০০ ns
  • ২০০ ns
  • ৩০০ ns
  • ১২০ ns

একটি page table entry-র dirty (modified) বিটের কাজ কী?

  • Page এখন RAM-এ আছে কি না জানানো
  • Page সম্প্রতি পড়া বা লেখা হয়েছে কি না জানানো
  • Page-এ লেখা হয়েছে কি না জানানো, যাতে সরানোর আগে ডিস্কে লেখা দরকার কি না বোঝা যায়
  • Page-এ লেখার অনুমতি আছে কি না ঠিক করা

শব্দকোষ

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

মনে রাখুন

  1. Page আর frame সমান মাপের; যেকোনো page যেকোনো খালি frame-এ — external fragmentation নেই, internal আছে (গড়ে আধা page).
  2. ঠিকানা ভাগ: ২m ঠিকানা-জগৎ, ২n page → p = উপরের m − n বিট, d = নিচের n বিট; physical = f × page size + d.
  3. টেবিলের আকার: ৩২-বিট, ৪ KiB, ৪ বাইট entry → ২২০ entry, ৪ MiB প্রতি প্রসেস; ৬৪-বিটে একতলা টেবিল অসম্ভব — তাই multilevel (পর্ব ৩৩)।
  4. PTBR (x86-এ CR3) টেবিল দেখায়; TLB ছাড়া প্রতি access-এ দুইবার মেমরি; EAT = α(tTLB + tm) + (১ − α)(tTLB + ২tm).
  5. PTE-র বিট — valid/present, R/W/X, U/S, accessed, dirty — দিয়ে page-মাপের সুরক্ষা আর shared page (reentrant কোড, libc); বাস্তবে ৪ KiB বা ১৬ KiB, চলার সময় getconf PAGESIZE/sysconf দিয়ে জানুন।

পরের পর্বে: প্রতিটি access-এ দুইবার মেমরি আর বিশাল টেবিল — paging-এর এই দুই দাম কমানোর উপায়। পর্ব ৩৩-এ TLB-র ভেতর, EAT-এর পূর্ণ অঙ্ক, আর multilevel, hashed ও inverted page table.

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

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