در این مطلب سعی میکنم صرفا مفاهیم اولیهای که برای مدیریت حافظه توسط سیستمعامل بکار گرفته میشود را در حد سواد خودم تشریح کنم. در بخشهای بعدی احتمالا بیشتر به تکنیکهای مختلف مدیریت حافظه میپردازم.
حافظههای داخلی پردازنده، و حافظه اصلی خود کامپیوتر، تنها ظرفیتهایی هستند که پردازنده مستقیما دسترسی به آنها دارد. پردازنده برای اجرا هر دستور (instruction) نیاز به دادههایی دارد (مثلا ورودیهای آن instruction) و همه این این دادهها به علاوه خود instruction باید یا در حافظه اصلی و یا در حافظههای داخلی خود پردازنده قرار داشته باشند. پردازنده دسترسی مستقیم به دیسک ندارد و این قید ما را ملزم میکند که قبل از اجرا هر دستور، تمامی دادههای مرتبط و مورد نیاز آن را در حافظه بارگذاری کنیم و از آنجایی که حافظه محدود است، و دادهها زیاد، مدیریت حافظه به بخشی حیاتی از طراحی سیستمعامل تبدیل میشود.
توضیح مختصری بر انواع حافظهها
حافظههایی که ما با آنها سر و کار داریم از چند نوع مختلف میتوانند باشد. معمولا منظور ما از حافظه، حافظه volatile است. یعنی حافظهای که فقط هنگام اتصال برق به سیستم اطلاعات را در خود ذخیره میکند، و بعد از قطعی برق، تضمینی برای بقای دادهها وجود ندارد. دو مثال اصلی از این نوع حافظههای volatile رجیسترهای پردازنده و RAM هستند.
حافظههای non-volatile هم حافظههایی هستند که بعد از قطعی برق و خاموشی سیستم، دادهها را برای مدت طولانی (حتی چند ده سال) در خود نگهداری میکند. تصویر زیر حرفهای خوبی برای گفتن دارد:
تعامل پردازنده با حافظهها
پردازنده تنها به ۳ حافظه بالایی در تصویر بالا دسترسی مستقیم دارد. یعنی حافظههای register، cache و main memory. که دو مورد اول حافظههای داخلی خود پردازنده هستند. حافظه register، که نزدیکترین حافظه به خود پردازنده است، کمترین ظرفیت را دارد، چیزی در حدود ۵۱۲ بایت، یا کمتر. دادههای مورد نیاز برای اجرا هر دستورالعمل، الزاما باید در این حافظه بارگذاری شده باشند قبل از شروع اجرا.
پردازنده برای دسترسی به دادههای داخل registerهای خود، زمان بسیار کمی را صرف میکند. در حد یک یا دو CPU clock. اما برای دسترسی به دادههای داخل حافظه اصلی، زمان بسیار بیشتری را باید صرف کند، که این زمان ممکن است به چند صد CPU clock نیز برسد. و از آنجایی که پردازنده مکررا نیاز دارد به دادههای داخل حافظه اصلی دسترسی داشته باشد، سرعت اجرا برنامهها به طرز قابل توجهی افت میکند.
راهحل این مشکل، حافظه میانی cache بود. این حافظه داخل خود پردازنده تعبیه شده و بسیار به هسته پردازنده نزدیک است. این قرابت زیاد، موجب میشود که سرعت آن به طرز چشمگیری افزایش پیدا کند. نمودار زیر این مسئله را به خوبی ترسیم میکند:
همانطور که میبینید سرعت خواندن داده از حافظههای کش، به چند هزار میلیارد بیت بر ثانیه میرسد در حالی که این سرعت برای حافظه RAM چند ده میلیارد بیت بر ثانیه است. وجود حافظه کش باعث میشود پردازنده در غالب موارد، نیاز نداشته باشد دادههای مورد نظر خود را مستقیما از حافظه بردارد. و این بهبود قابلتوجهی به سرعت پردازش میدهد.
logical address و physical address
دو نوع آدرسدهی در دنیای سیستمعامل داریم. نوع اول که بدیهیترین نوع آن است، آدرسدهی فیزیکی است. یعنی عینا آدرس آن خانه از حافظه که داده مورد نظر داخل آن قرار دارد را داشته باشیم. مثلا وقتی میگوییم آدرس فیزیکی این داده 0x412f51ِ است، یعنی دقیقا در خانه 0x412f51ام از حافظه این داده ذخیره شده است.
اما آیا برنامهها به این آدرس دسترسی دارند؟ به دلایلی بهتر است که برنامهها مستقیما به آدرس فیزیکی سیستم دسترسی نداشته باشند. از طرفی مجرد کردن (abstraction) برنامهها از دنگ و فنگهای سختافزاری و مشکلات سطحپایین یک مزیت محسوب میشود، از طرف دیگر دادن آدرس فیزیکی، ناخواسته دادههای از سیستم را به برنامه میدهد که لزومی ندارد بداند. مثلا حداقل ظرفیت حافظه را از آدرس فیزیکی میتوان تخمین زد. به علاوه دادن آدرس فیزیکی به برنامهها، دسترسی آنها را به دادههای دیگری که در بخشهای دیگر حافظه ذخیره شدهاند هموارتر میکند. لذا آدرس منطقی یا logical address به داد ما میرسد.
این نوع آدرس، توسط خود پردازنده تولید شده و در واقعیت وجود ندارد. مثلا وقتی پردازنده حافظه 0x100a را به یک برنامه میدهد، بدین معنا نیست که واقعا خانه 0x100a ام از حافظه را در اختیار برنامه گذاشته. این آدرس به عبارتی یک آدرس مجازی است که توسط سختافزاری به نام MMU (memory management unit) تبدیل به آدرس فیزیکی میشود. اینکار تا حدی باعث میشود که برنامهها از یکدیگر در بعضی از سطوح ایزولهتر شوند. به علاوه، حافظه منطقی، در مدیریت حافظه وقتی که حافظه درخواستی از سمت برنامهها بیشتر از حافظه موجود کامپیوتر است شدیدا مفید واقع میشود.
base register و limit register
برای مدیریت حافظه، هر پروسه، ۲ رجیستر مهم دارد:
مثلا وقتی رجیستر بِیس یک برنامه ۵۲۰ باشد، و رجیستر limit آن ۸۰ باشد، یعنی برنامه مجاز به استفاده حافظه از خانه ۵۲۰ تا ۵۲۰+۸۰ است. یعنی از ۵۲۰ تا ۶۰۰ محدوده مجاز برای استفاده توسط این برنامه است. در تصویر زیر رابطه این دو رجیستر را میتوان مشاهده کرد:
Dynamic loading
در ابتدا گفته شد که برای اجرا هر برنامه، لازم هست که کل آن پروسه در حافظه بارگذاری شود و سپس اجرا پروسه شروع شود. اینکار در پیادهسازی نسبتا راحت است اما ممکن است حجم زیادی از حافظه بیمورد اشغال شود. برای مثال فرض کنید، برنامه ویرایش متنی را اجرا میکنید. این برنامه احتمالا قابلیتهای فراوانی دارد و شما در هر لحظه از همه آنها استفاده نمیکنید. به همین علت شاید بیهوده باشد که همه برنامه در حافظه ذخیره شوند. احتمالا بهتر است که بخشی از برنامه که در لحظه اجرا استفاده میشود در حافظه ذخیره شود و باقی برنامه در دیسک بماند. هر موقع نیاز شد از آنها استفاده کنیم، آن بخش از برنامه را سریعا از دیسک به حافظه منتقل میکنیم. البته که واضح است این کار سرعت پردازش را تا حدی کند میکند اما استفاده از حافظه را به مراتب بهینهتر میکند.
در مطالب بعدی احتمالا از تکنیکهای مختلف مدیریت حافظه مثل paging و حافظه مجازی صحبت کنیم.