কেস স্টাডি: একটি মেমরি অ্যালোকেটর সিমুলেটর বানানো
এই পাঠে যা শিখবেন
- একটি বাস্তব
MemoryAllocatorক্লাস — যা allocate ও free দুটোই সমর্থন করে, তিনটি স্ট্র্যাটেজি pluggable ভাবে - কীভাবে free() করার পর তৈরি হওয়া একাধিক হোল প্রতিটি স্ট্র্যাটেজিতে ভিন্নভাবে ব্যবহৃত হয়
- external fragmentation পরিমাপ করার একটি সহজ, সঠিক পদ্ধতি
- কেন ওয়ার্স্ট-ফিট তত্ত্বে "বড় হোল রেখে দেয়" বলা হলেও বাস্তবে প্রায়ই সবচেয়ে বেশি ফ্র্যাগমেন্টেশন তৈরি করে
১ · MemoryAllocator ক্লাস
L28-এ আমরা ফার্স্ট-ফিট, বেস্ট-ফিট ও ওয়ার্স্ট-ফিটকে আলাদা ফাংশন হিসেবে দেখেছিলাম, শুধু allocate অপারেশনের ওপর।
এখানে সেগুলোকে একটি পূর্ণাঙ্গ ক্লাসে একত্রিত করা হলো — যেটি allocate() এবং free()
দুটোই সমর্থন করে, এবং প্রতিটি হোলকে (start, size) আকারে ট্র্যাক করে (ঠিক L28-এর মতোই), সবসময়
ঠিকানা-ক্রম অনুযায়ী সাজানো রাখে।
class MemoryAllocator:
def __init__(self, total_size, strategy):
self.total_size = total_size
self.strategy = strategy # "first_fit" | "best_fit" | "worst_fit"
self.free_holes = [(0, total_size)] # (start, size), ঠিকানা-ক্রমে সাজানো
self.occupied = {} # name -> (start, size)
def _choose_hole(self, size):
candidates = [h for h in self.free_holes if h[1] >= size]
if not candidates:
return None
if self.strategy == "first_fit":
for h in self.free_holes: # ঠিকানা-ক্রমে প্রথম যথেষ্ট বড় হোল
if h[1] >= size:
return h
elif self.strategy == "best_fit":
return min(candidates, key=lambda h: (h[1], h[0])) # সবচেয়ে ছোট, কিন্তু যথেষ্ট
elif self.strategy == "worst_fit":
return max(candidates, key=lambda h: (h[1], -h[0])) # সবচেয়ে বড়
raise ValueError("অজানা strategy")
def allocate(self, name, size):
hole = self._choose_hole(size)
if hole is None:
return None
start, hole_size = hole
self.free_holes.remove(hole)
self.occupied[name] = (start, size)
remainder = hole_size - size
if remainder > 0:
self.free_holes.append((start + size, remainder))
self.free_holes.sort(key=lambda h: h[0])
return start
def free(self, name):
start, size = self.occupied.pop(name)
self.free_holes.append((start, size))
self.free_holes.sort(key=lambda h: h[0])
def total_free(self):
return sum(size for _, size in self.free_holes)
def largest_hole(self):
return max((size for _, size in self.free_holes), default=0)
def fragmentation_report(self):
total = self.total_free()
largest = self.largest_hole()
return {
"holes": sorted(self.free_holes),
"hole_count": len(self.free_holes),
"total_free": total,
"largest_hole": largest,
"wasted_fragmented": total - largest, # ফ্রি, কিন্তু সবচেয়ে বড় একক অনুরোধের চেয়ে ছোট টুকরোয় ছড়ানো
}
# একই allocate/free সিকোয়েন্স -- মাঝে ইচ্ছাকৃতভাবে "filler" ব্লক (F1, F2) রাখা হয়েছে
# যাতে A, B, C ফ্রি করার পর তিনটি পৃথক, ভিন্ন-সাইজের হোল তৈরি হয় (কোনো merge ছাড়াই)।
ops = [
("alloc", "A", 300), ("alloc", "F1", 50), ("alloc", "B", 500),
("alloc", "F2", 50), ("alloc", "C", 200),
("free", "A", None), ("free", "B", None), ("free", "C", None),
("alloc", "D", 150), ("alloc", "E", 80),
]
for strategy in ["first_fit", "best_fit", "worst_fit"]:
allocator = MemoryAllocator(1200, strategy)
print(f"\n=== স্ট্র্যাটেজি: {strategy} ===")
for op, name, size in ops:
if op == "alloc":
start = allocator.allocate(name, size)
if name in ("D", "E"):
print(f" {name}({size}) বরাদ্দ -> ঠিকানা {start}-তে শুরু হওয়া হোলে")
else:
allocator.free(name)
report = allocator.fragmentation_report()
print(f" ফ্রি হোল লেআউট: {report['holes']}")
print(f" মোট ফ্রি: {report['total_free']}, সবচেয়ে বড় হোল: {report['largest_hole']}, "
f"ফ্র্যাগমেন্টেড/অপচয়: {report['wasted_fragmented']}")
L28-এ আমরা তত্ত্বে বলেছিলাম ওয়ার্স্ট-ফিট "ইচ্ছাকৃতভাবে একটি বড় অবশিষ্ট রেখে দেয়, যা পরে কাজে লাগতে পারে" — কিন্তু এই কোড চালিয়ে দেখা যায় বাস্তবে এর উল্টো ঘটনাও ঘটতে পারে: বারবার সবচেয়ে বড় হোলটি বেছে নেওয়ায়, একটি সিস্টেমে কখনোই একটিও বড়, ব্যবহারযোগ্য হোল অবশিষ্ট থাকে না — এটাই কেন ওয়ার্স্ট-ফিট ব্যবহারিকভাবে প্রায় ব্যবহৃত হয় না।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ তিনটি স্ট্র্যাটেজিতেই মোট ফ্রি মেমরি (৮৭০) একই কেন, কিন্তু "সবচেয়ে বড় হোল" আলাদা?
মোট ফ্রি মেমরি নির্ভর করে শুধু কতগুলো ব্লক বরাদ্দ করা হয়েছে তার ওপর (মোট মেমরি − মোট বরাদ্দকৃত), যা তিনটি স্ট্র্যাটেজিতেই একই সিকোয়েন্স চালানোর কারণে অভিন্ন। কিন্তু কোন হোল ব্যবহার করে বরাদ্দ করা হয়েছে তা স্ট্র্যাটেজি-নির্ভর — তাই সেই ফ্রি মেমরিটুকু কীভাবে ছোট-ছোট টুকরোয় ভাগ হয়ে আছে (এবং তাই সবচেয়ে বড় টুকরোটি কত বড়) তা প্রতিটি স্ট্র্যাটেজিতে ভিন্ন হয়।
প্র ০২ "wasted_fragmented = total_free − largest_hole" — এই মেট্রিকটি ঠিক কী বোঝায়?
এটি বোঝায়, যদি ভবিষ্যতে একটি নতুন বড় অনুরোধ আসে যেটি শুধু সবচেয়ে বড় একক হোলেই ফিট করতে পারে (কনটিগুয়াস অ্যালোকেশন, L28), তাহলে বাকি ফ্রি মেমরি — যদিও প্রযুক্তিগতভাবে "ফ্রি" — সেই অনুরোধের জন্য ব্যবহারযোগ্য নয়, যতক্ষণ না হোলগুলো একত্র (coalesce) করা হয়। এটাই এক্সটার্নাল ফ্র্যাগমেন্টেশনের বাস্তব পরিণতি (L28)।
প্র ০৩ এই সিমুলেটরে কোনো coalescing (পাশাপাশি দুটি ফ্রি হোলকে একটি বড় হোলে একত্র করা) বাস্তবায়ন করা হয়নি। এটি যোগ করলে ফলাফল কীভাবে বদলাতে পারত?
Coalescing থাকলে, যদি free() করার পর নতুন ফ্রি হওয়া ব্লকের ঠিক পাশে (আগে বা পরে) আরেকটি ফ্রি হোল থাকে, তারা স্বয়ংক্রিয়ভাবে একটি বড় হোলে একত্র হয়ে যেত — এতে "সবচেয়ে বড় হোল" সাধারণত বড় হতো এবং তিনটি স্ট্র্যাটেজির মধ্যে চূড়ান্ত ফ্র্যাগমেন্টেশনের পার্থক্য কিছুটা কমে যেতে পারত, যদিও সম্পূর্ণ দূর হতো না — কারণ ফ্র্যাগমেন্টেশনের মূল কারণ (অ-সংলগ্ন খালি ব্লক তৈরি হওয়া) এখনও থেকেই যায়।
অনুশীলন
-
পরীক্ষা করুন: কোড সেলে
ops-এর একদম শেষে("alloc", "F", 350)যোগ করে আবার রান করুন। কোন স্ট্র্যাটেজিতে এই বরাদ্দ ব্যর্থ হয় (None রিটার্ন করে)?first_fit ও best_fit-এ শেষ পর্যন্ত সবচেয়ে বড় হোল ৫০০ (অক্ষত), তাই ৩৫০-এর অনুরোধ সফল হবে। কিন্তু worst_fit-এ শেষে সবচেয়ে বড় হোল মাত্র ৩০০, যা ৩৫০-এর অনুরোধের চেয়ে ছোট — তাই worst_fit-এ এই বরাদ্দ ব্যর্থ হবে (
allocate()Noneরিটার্ন করবে), যদিও মোট ফ্রি মেমরি (তিনটিতেই সমান) ৩৫০-এর চেয়ে ঢের বেশি — এক্সটার্নাল ফ্র্যাগমেন্টেশনের প্রভাব হাতেনাতে দেখা গেল। -
চিন্তা করুন:
_choose_hole-এ best_fit-এর টাই-ব্রেকিং নিয়ম(h[1], h[0])ব্যবহার করে — অর্থাৎ সমান সাইজের একাধিক হোল থাকলে ঠিকানা অনুযায়ী ছোটটি (প্রথমটি) বেছে নেয়। এই নিয়ম না থাকলে কী সমস্যা হতে পারত?টাই-ব্রেকিং নিয়ম ছাড়া, একই সাইজের একাধিক হোল থাকলে
min()ফাংশন কোনটি বেছে নেবে তা তালিকায় তাদের ক্রমের ওপর নির্ভর করত (অনির্ধারিত/সামঞ্জস্যহীন আচরণ, বিশেষত যদি ভবিষ্যতে কোডে হোল-তালিকা তৈরির ক্রম বদলে যায়) — একটি স্পষ্ট টাই-ব্রেকিং নিয়ম নিশ্চিত করে ফলাফল সবসময় পুনরুৎপাদনযোগ্য (reproducible) থাকে, যা একটি সিমুলেটরের জন্য গুরুত্বপূর্ণ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ: চূড়ান্ত প্রকল্প — একটি সম্পূর্ণ OS কার্নেল সিমুলেশন L56 · শেষ পাঠ এই কোর্সের সবকটি মডিউল একত্রিত করে একটি TinyKernel তৈরি করুন — কোর্সের চূড়ান্ত সমন্বয়।
- কন্টিগুয়াস মেমরি অ্যালোকেশন L28 এই পাঠে ব্যবহৃত ফার্স্ট-ফিট/বেস্ট-ফিট/ওয়ার্স্ট-ফিটের ভিত্তি তত্ত্ব একবার ঝালিয়ে নিন।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ M13-এর সবকটি কেস স্টাডি ও M14-এর চূড়ান্ত প্রকল্প এক জায়গায় দেখুন।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks ও Operating Systems — সব এক জায়গায়।