ادیولوژیست ( شنوایی شناس ) - توسعه دهنده اندروید
و چنین گفت سیلبرشاتس - فصل 9 مدیریت حافظه- بخش 1

⭕ مقدمه فصل
در فصل ۵ دیدیم که چگونه میتوان CPU را بین مجموعهای از پردازهها (processes) به اشتراک گذاشت. با زمانبندی منسب CPU میتوانیم:
بهره وری CPU (CPU Utilization) را افزایش دهیم
سرعت پاسخگویی سیستم به کاربران را بهتر کنیم
اما برای رسیدن به این بهبود عملکرد، باید چندین پردازه را همزمان در حافظه نگه داریم؛ یعنی لازم است حافظه را نیز بین پردازه ها به اشتراک بگذاریم.
در این فصل، روشهای مختلف مدیریت حافظه (Memory Management) بررسی میشود.
الگوریتمهای مدیریت حافظه از یک روش ابتدایی مثل (bare-machine) تا راهبردهای پیشرفتهای مانند صفحهبندی (Paging) متنوع هستند.
هر روش:
مزایا و معایب خاص خود را دارد
انتخاب آن به عوامل مختلفی بستگی دارد، مخصوصاً طراحی سختافزار سیستم
همانطور که خواهیم دید، بسیاری از این الگوریتمها نیاز به پشتیبانی سختافزاری دارند. به همین دلیل در بسیاری از سیستمها، مدیریت حافظه با همکاری سختافزار و سیستمعامل انجام میشود.
اهداف این فصل
در این فصل یاد میگیرید:
تفاوت بین آدرس منطقی (Logical Address) و آدرس فیزیکی (Physical Address) را توضیح دهیم.
نقش واحد مدیریت حافظه (MMU) در تبدیل آدرسها را درک کنیم.
الگوریتمهای تخصیص پیوسته حافظه مانند:
First Fit ، best Fit ، worst Fit
تفاوت بین
Internal Fragmentation و External Fragmentation
را توضیح دهیم .
در سیستم paging ، تبدیل آدرس منطقی به فیزیکی با استفاده از TLB را انجام دهیم.
روشهای:
صفحهبندی سلسلهمراتبی (Hierarchical Paging)
صفحهبندی هششده (Hashed Paging)
جدول صفحه معکوس (Inverted Page Table)
را توضیح دهیم.
نحوه ترجمه آدرس در معماریهای:
IA-32
x86-64 ،
ARMv8 ،
را شرح دهیم.
⭕ 9.1 پیشزمینه (Background)
حافظه یکی از مهمترین بخشهای یک سیستم کامپیوتری مدرن است.
حافظه شامل:
آرایه بزرگی از بایتهاست
که هر بایت یک آدرس منحصر به فردی دارد
چرخه اجرای دستور (Instruction Execution Cycle)
در اینجا CPU :
دستور را از حافظه میخواند (Fetch)
آن را رمزگشایی میکند (Decode)
ممکن است دادههای دیگه ای رو هم نیاز باشه که مجددا از حافظه بخواند (Operands)
دستور اجرا میشود (Execute)
نتیجه دوباره در حافظه ذخیره میشود (Store)
بنابراین واحد حافظه صرفا جریان و توالی آدرسهای حافظه را میبیند و :
حافظه نمیداند این آدرسها چگونه تولید شدهاند.
نمیداند مربوط به دادهاند یا دستور.
در نتیجه هنگام مطالعه مدیریت حافظه، ما به این کاری نداریم که برنامه چگونه آدرس تولید میکند؛ بلکه فقط به توالی آدرسهایی که برنامه در حال اجرا تولید میکند توجه داریم.
آدرس منطقی vs آدرس فیزیکی
وقتی برنامه میگوید:
متغیر X در آدرس 1000 است این عدد 1000 یک آدرس منطقی (Logical Address) است.
اما در حافظه واقعی ممکن است متغیر در خانه 45872 باشد ، این آدرس واقعی را آدرس فیزیکی (Physical Address) میگویند.
تبدیل این دو به هم توسط سختافزار ( MMU ) انجام میشود.
چرا این جداسازی مهم است؟
چون:
هر برنامه فضای آدرس مخصوص خودش را دارد.
دو برنامه میتوانند هر دو آدرس 1000 داشته باشند ولی در حافظه فیزیکی در محلهای کاملاً متفاوتی قرار میگیرند.
این کار باعث:
امنیت
جداسازی پردازهها
امکان اجرای همزمان چند برنامه
میشود.
نقش سختافزار
بسیاری از روشهای مدیریت حافظه نیاز دارند که:
CPU
MMU
کش
TLB (Translation Lookaside Buffer) -> همون بافر cpu هستش
همگی با سیستمعامل هماهنگ باشند.
بنابراین مدیریت حافظه یک موضوع ترکیبی از سختافزار و نرمافزار است.
⭕ 9.1.1 سختافزار پایه (Basic Hardware)
حافظه اصلی (Main Memory) و رجیسترهای داخل هر هسته پردازنده تنها حافظههای عمومی هستند که CPU میتواند مستقیماً به آنها دسترسی داشته باشد.
دستورهای سیستمی میتوانند آدرسهای حافظه (RAM) را به عنوان ورودی بگیرند،
اما هیچ دستوری وجود ندارد که مستقیماً با آدرس دیسک(حافظه های ثانویه) کار کند.
بنابراین ، هر دستوری که اجرا میشود و هر دادهای که آن دستور استفاده میکند
باید در یکی از این حافظههای با دسترسی مستقیم (رجیستر یا RAM) قرار داشته باشد .
اگر دادهای در حافظه اصلی نباشد، باید قبل از اجرا از دیسک به حافظه منتقل شود .
سرعت رجیستر در مقابل حافظه اصلی
🔹 رجیسترها:
داخل هسته CPU هستند
معمولاً در یک سیکل کلاک قابل دسترسیاند
بعضی CPUها میتوانند در هر سیکل چند عملیات روی رجیستر انجام دهند
منظور از یک سیکل کلاک ، همان واحدِ زمانِ پایهی کارِ CPU است
مثلاً اگر کلاک اسپید پردازنده 3 گیگاهرتز باشد یعنی CPU در هر ثانیه ۳ میلیارد سیکل کلاک خواهد داشت
حافظه اصلی (RAM):
از طریق گذرگاه حافظه (Memory Bus) در دسترس است
دسترسی به آن ممکن است چندین سیکل کلاک طول بکشد
هنگام انتظار برای دسترسی به داده های آن، CPU مجبور به توقف ( Stall ) میشود
مشکل Stall چیست؟
اگر CPU داده مورد نیاز را نداشته باشد:
باید صبر کند
اجرای دستور متوقف میشود
کارایی کاهش پیدا میکند
از آنجا که دسترسی به حافظه بسیار زیاد اتفاق میافتد، این وضعیت قابل قبول نیست.
راهحل : Cache
برای حل مشکل، یک حافظه سریع بین CPU و RAM قرار داده میشود:
✅ Cache Memory
معمولاً داخل خود چیپ CPU است ( ولی مانند رجیسترها درون هسته های پردازشی نیست )
بسیار سریعتر از RAM ( ولی از رجیستر ها کند تر است)
به صورت خودکار توسط سختافزار مدیریت میشود
سیستمعامل در مدیریت مستقیم آن دخالتی ندارد
در پردازندههای Multithreaded Core وقتی حافظه در حالت انتظار باشد، هسته میتواند به یک thread دیگر سوئیچ کند تا زمان تلف نشود.
مسئله مهمتر: حفاظت (Protection)
فقط سرعت مهم نیست — امنیت هم مهم است.
باید مطمئن شویم:
برنامههای کاربر به حافظه سیستمعامل دسترسی نداشته باشند (وارد فضای کرنل مود نشود)
برنامههای کاربران به حافظه یکدیگر دسترسی نداشته باشند
و این حفاظت باید توسط سختافزار انجام شود ، چون برای اینکه سیستمعامل بتواند هر دسترسی حافظه را کنترل کند نیازمند صرف توان پردازشی زیادی خواهیم بود که کارایی سیستم را به شدت کاهش می دهد.
سلسلهمراتب حافظه (Memory Hierarchy)
از سریعترین به کندترین:
رجیستر
کش (L1, L2, L3)
حافظه اصلی (RAM)
دیسک (SSD/HDD)
ظرفیت بیشتر
سرعت کمتر
چرا CPU مستقیم به دیسک دسترسی ندارد؟
دیسک میلیونها برابر کندتر از رجیستر است
معماری CPU برای کار با حافظه سریع طراحی شده
چرا حفاظت سختافزاری ضروری است؟
برای اینکه هر پردازه (process) فضای حافظهی جداگانه داشته باشد، ابتدا باید از این جداسازی مطمئن شویم ؛ وقتی جدا سازی فیزیکی حافظه رو داشته باشیم ، عملا تداخل رو غیر ممکن کردیم.
این فضای حافظهی جداگانه برای هر پردازه باعث میشود پردازهها از هم محافظت شوند و این جداسازی ، شرط اصلی اینه که بتوانیم چند پردازه را همزمان در حافظه بارگذاری کنیم و اجرای همزمان (concurrent execution) اونها رو داشته باشیم.
برای جداسازی حافظه باید :
تعیین کنیم پردازه فقط میتواند چه بازهای از آدرسها را استفاده کند
تضمین کنیم پردازه فقط همان آدرسهای مجاز را قابل دسترسی داشته باشد
راهحل با دو رجیستر: Base و Limit

این حفاظت معمولاً با دو رجیستر انجام میشود: Base و Limit (در شکل 9.1).
Base: کوچکترین آدرس فیزیکیِ مجاز (پایین ترین خونه مجاز)
Limit: اندازه / طول بازه مجاز
مثال:
اگر
base = 300040و
limit = 120900
پس برنامه میتواند از 300040 تا 420939 (شامل هر دو) را به صورت قانونی دسترسی داشته باشد.
سختافزار CPU طوری تنظیم میشود که:
در حالت user mode هر آدرسی که برنامه تولید/درخواست میکند را با رجیسترهای
baseوlimitبررسی کند.
اگر برنامه ای در user mode تلاش کند به حافظهی سیستمعامل (operating-system memory)یا حافظهی پردازهی دیگری دست بزند، یک trap (تله/استثنا) به سیستمعامل رخ میدهد.
تفاوت Trap و Interrupt : این آقایون دو تاشونم یه جور پیام به سییستم عامل هستن منتهی با این تفاوت که Interrupt توسط سخت افزار تولید میشه و میتونه maskable یا unMaskable باشه ولی Trap توسط پردازه در حال اجرا ایجاد میشه و بلافاصله پس از اجرا تاثیر خودش رو در روند کاری سیستم نشون میده.
سیستمعامل این تلاش را به عنوان یک خطای کشنده (fatal error) تلقی میکند. (تصویر 9.2)
نتیجه: هیچ برنامهی User Mode (چه تصادفی چه عمدی) نمیتواند کد یا دادههای سیستمعاملی یا داده های دیگر برنامه ها را تغییر دهد.

چرا فقط سیستمعامل میتواند Base و Limit را تغییر دهد؟
base و limit فقط توسط سیستمعامل و با دستور privileged instruction قابل مقدار دهی و تغییر اند
و چون privileged instructions فقط در kernel mode اجرا میشوند ، کاربرهای user Mode نمیتوانند محتویات این رجیسترها را دستکاری کنند.
سیستمعامل در kernel mode دسترسی نامحدود دارد
در kernel mode سیستمعامل دسترسی کامل به حافظهی پردازه های سیستمی و حافظهی پردازههای عادی دارد؛ بنابراین میتواند کارهای لازم را انجام دهد مثل:
بارگذاری برنامه ها در حافظه
در صورت وقوع خطا حذف/بررسی پردازه از حافظه
دستکاری پارامترهای فراخوانیهای سیستمی (system calls)
انجام I/O از/به حافظهی کاربر
و خدمات دیگر
⭕ 9.1.2 مقیدسازی آدرس (Address Binding)
معمولاً یک برنامه به صورت یک فایل اجرایی دودویی روی دیسک(حافظه ثانویه) است. برای اجرا باید:
برنامه از دیسک به حافظه (RAM) آورده شود
در قالب یک (process context) قرار بگیرد
تا CPU بتواند آن را اجرا کند .
و وقتی کار پردازش پردازه تمام شد، حافظهاش برای پردازههای دیگر آزاد میشود.
برنامه میتواند در هر جای RAM قرار بگیرد
بیشتر سیستمها اجازه میدهند پردازه کاربر در هر بخشی از حافظهی فیزیکی باشد.
پس اگر فضای آدرس سیستم از 00000 شروع شود، الزاماً اولین آدرسِ پردازهی کاربر هم 00000 نیست.
برنامه در مراحل مختلف ، آدرس دهی های متفاوتی خواهد داشت
اغلب قبل از اجرا، برنامه چند مرحله را طی میکند (شکل 9.3).

Program : هنوز در حافظه ثانویه هستیم .
Process :در حافظه اصلی هستیم.
در این مراحل، آدرسها میتوانند به شکلهای متفاوت نمایش داده شوند:
در سورس کد برنامه، آدرسها معمولاً نمادین (symbolic) هستند مثل نام یک متغیر:
String userNameکامپایلر این آدرسهای نمادین را به آدرسهای قابل جابهجایی (relocatable) تبدیل میکند، مثل:
«32 بایت بعد از ابتدای فضای حافظه این ماژول»
سپس لینکر/لودر (در Section 2.5) آدرسهای قابل جابهجایی را به آدرسهای مطلق (absolute) تبدیل میکند مثل: خونه شماره 6354 از حافظه.
Data Binding رو میتونیم توی این مراحل انجام بدیم :
Compile time (زمان کامپایل)
اگر از قبل بدانیم پردازه در حافظه دقیقاً کجا قرار میگیرد، آدرس دهی مطلق (absolute) رو خواهیم داشت و اگر بعداً محل قرار گیری پردازه داخل حافظه عوض شود، باید دوباره کامپایل کنیم.
Load time (زمان بارگذاری)
اگر در زمان کامپایل محل دقیق را ندانیم، کامپایلر کد قابل جابهجایی (relocatable) تولید میکند و بایند نهایی تا زمان بارگذاری عقب میافتد.
اگر آدرس شروع تغییر کند، فقط باید کد کاربر را دوباره با آن مقدار بارگذاری کنیم.
Execution time (زمان اجرا/Run time)
اگر بتوانیم در طول اجرای پردازه آن را از یک بخش حافظه به بخش دیگر منتقل کنیم، mapping باید در run time انجام شود.
این کار نیاز به سختافزار ویژه دارد (توضیحات در Section 9.1.3 ).
بسیاری از سیستمعاملها از این قابلیت پشتیبانی میکنند
علی یوسفی - اردیبهشت ماه 1405 (در میانه خدمت مقدس سربازی 🤕)
مطلبی دیگر از این انتشارات
الگوریتم های بازگشتی Recursive
مطلبی دیگر از این انتشارات
تاریخچه زبان جاوا - قسمت 4
مطلبی دیگر از این انتشارات
و چنین گفت سیلبرشاتس - فصل 1 بخش 1