در قسمت قبل با مفهوم paging در مدیریت حافظه آشنا شدیم. در این بخش از این مفهوم برای توضیح و تشریح یکی از مهمترین تکنیکهای مدیریت حافظه در دنیای سیستمعامل، یعنی حافظه مجازی، استفاده خواهیم کرد. اما قبل از صحبت کردن درمورد حافظه مجازی، باید با یکی دیگر از مفاهیم کلیدی اما ساده دنیای سیستمعامل آشنا شویم به نام swapping.
به دلایل متعددی، که بخشی از آنها در پستهای اخیر ذکر شده، هر دستورالعمل یا instruction که پردازنده نیاز دارد اجرا کند، حتما باید داخل حافظه اصلی کامپیوتر بارگذاری شده باشد. این یعنی پردازنده نمیتواند instruction هایی که داخل دیسک (مثلا حافظه HDD یا SSD) ذخیره شدهاند را مستقیما اجرا کند. این مسئله یک مشکل خیلی بدیهی بوجود میآرود و آن عدم وجود حافظه کافی برای اجرا چندین پروسه همزمان است. همچنین در برخی موقعیتها ممکن است پروسههایی وجود داشته باشند، که هنوز تمام ( terminate) نشدهاند اما فعلا به دلایلی غیرفعال هستند. مثلا منتظر رسیدن ورودی از سمت کاربر یا شبکه هستند یا هر چیز دیگری.
در این شرایط، سیستمعامل برای استفاده بهینهتر از حافظه اصلی، دادههای این پروسههای غیرفعال را به دیسک یا swap space منتقل میکند (اصطلاحا swap out)، و از آن حافظه برای پروسههای فعالی که حافظه کافی ندارند استفاده میکند (اصطلاحا swap in).
حافظه مجازی به بیان خیلی ساده جدا سازی کامل تصویری است که برنامهها و پروسهها از حافظه واقعی و فیزیکی سیستم دارند به طوری که سیستمعامل بتواند بخشهایی از دادههای پروسهها را در دیسک نگه دارد.
در موارد زیادی دیده میشود که بخشهایی از برنامه به ندرت استفاده شدهاند. مثلا بخشهایی از برنامه برای جلوگیری از خطاهای احتمالی است، یا قابلیتهایی که کاربر ممکن است استفاده نکند. همچنین بعضی اوقات، آرایهها و متغیرهایی که در برنامه تعریف شدهاند بسیار بزرگتر از مقدار نیاز کاربر هست. در این گونه موارد، سیستمعامل سعی میکند با استراتژیهای مختلف، بخشی از برنامه که احتمال استفاده از آن کم است را وارد حافظه اصلی نکند، تا از حافظه استفاده بهصرفهتری کند. تصویر زیر تا حدی نمایانگر این شیوه هست.
در این تکنیک، حافظه logical الزاما با حافظه فیزیکی تطابق ندارد. و ممکن است آدرسهایی از حافظه logical به دیسک اشاره کند. در این موارد سیستمعامل موظف است که سریعا داده را از دیسک به حافظه اصلی منتقل کند. در ادامه بیشتر به این روند میپردازیم.
همچنین چیزی داریم به نام virtual address space که آدرسهای ممکن و قابل استفاده برای هر برنامه خاص است. این فضای آدرسدهی، به این دلیل که سیستمعامل به طور پویا دادهها را از حافظه به دیسک منتقل میکند، میتواند بسیار بیشتر از مقدار اصلی حافظه کامپیوتر باشد. یعنی مثلا وقتی کل حافظه اصلی ۸ گیگابایت است، هر برنامه فضایی به وسعت ۴ گیگابایت دارد. در روش سنتی، اگر ۲ برنامه با فضای آدرسدهی ۴ گیگابایت اجرا کنیم، کل حافظه پر میشود. اما سیستمعاملهای امروزی که از حافظه مجازی پشتیبانی میکنند میتوانند تعداد زیادی برنامه را با فضای آدرسدهی خیلی بزرگ مدیریت کنند طوری که هیچکدام به مشکل برنخورند. همانطور که در تصویر بالاتر نشان داده شده است، حافظه مجازی بزرگتر است از حافظه اصلی (فیزیکی) و مقادیری از دادههای روی حافظه مجازی، داخل دیسک یا همان backing store هستند.
به علاوه، از آنجایی که حافظه مجازی، نگاه برنامه به حافظه را بهشدت انتزاعیتر و سادهتر میکند، سیستمعامل میتواند دادههای یکسانی که چند برنامه به صورت اشتراک استفاده میکنند را در یک حافظه واحد نگهدارد. چیزی که مشابه آن را در paging هم دیدیم.
یکی از مرسومترین روشهای استفاده شده در پیادهسازی حافظه مجازی، paging-on-demand است. وقتی یک برنامه اجرا میشود، احتمالا تمام آپشنهای آن برنامه مورد نیاز نیست. پس لازم نیست که کل برنامه وارد حافظه شود. ابتدا سیستمعامل حافظه مجازی برنامه را تخصیص میدهد طوری که از نگاه خود برنامه، همه چیز در حافظه است. اما در واقعیت بخشهایی از برنامه در دیسک نشستهاند و هنوز وارد حافظه نشدهاند. اجرا برنامه ادامه مییابد با حداقل page هایی از برنامه که نیاز دارند در حافظه باشند. هر وقت برنامه به بخشی از دادهها یا حافظه نیاز داشت که داخل page های بارگذاری شده در حافظه نبود، سیستمعامل موظف است سریعا این page را از دیسک به حافظه منتقل کند. اصطلاحا به این شرایط page fault گفته میشود. یعنی برنامه page ای را بخواهد که هنوز وارد حافظه مجازی نشده.
page هایی که هنوز وارد حافظه نشدهاند، در page-table موجود هستند، اما با یک بیت، نشان داده میشود که این page در حافظه نیست.
همانطور که در تصویر نشان داده شده، از نگاه برنامه، تمامی دادهها روی حافظه هستند ( حافظه logical) اما در واقعیت اینطور نیست. و در page-table مشخص است که کدام یک از این page ها آماده برای استفاده هستند یا نه.
هر وقت یک page-fault رخ میدهد، چند مرحله باید طی شود، تا page مورد نظر از دیسک به حافظه منتقل شود. این مراحل بهطور مختصر در شکل زیر نمایش داده شدهاند.
از تصویر بالا واضح است که هزینه پردازشی هر page-fault کم نیست و به ازاء هر page-fault باید همه این ۶ مرحله طی شود. لذا بهتر است تا حد امکان از رخ دادن page-fault جلوگیری شود.
در شدیدترین حالت، میتوان برنامه را بدون هیچ page بارگذاری شده داخل حافظه اجرا کرد. در این حالت احتمالا در ابتدای برنامه، به ازای هر instruction چند page-fault رخ دهد که بوضوح موجب کند شدن روند اجرا میشود. این روند در ادامه بهبود پیدا میکند، چون هر چه جلوتر میرود page های بیشتری از برنامه داخل حافظه بارگذاری شدهاند و احتمالا page-fault کمتری رخ خواهد داد. به این روش اصطلاحا pure demand paging گفته میشود. یعنی تا وقتی به یک page نیاز نداریم، آن را داخل حافظه بارگذاری نکنیم.