
مدیریت حافظه
در این فصل، با نحوه پیادهسازی حافظه مجازی (Virtual Memory) در ویندوز و چگونگی مدیریت بخشی از آن که در حافظه فیزیکی (Physical Memory) نگهداری میشود، آشنا خواهید شد. همچنین ساختار داخلی و اجزای تشکیلدهنده «مدیر حافظه» (Memory Manager)، از جمله ساختارهای داده کلیدی و الگوریتمهای آن را بررسی خواهیم کرد. پیش از بررسی این مکانیسمها، نگاهی به سرویسهای پایهای ارائهشده توسط مدیر حافظه و مفاهیم کلیدی مانند تفاوت حافظه رزرو شده (Reserved) و واگذار شده (Committed) و همچنین حافظه اشتراکی (Shared Memory) خواهیم داشت.
بهطور پیشفرض، اندازه فضای مجازی یک فرآیند (Process) در نسخه ۳۲ بیتی ویندوز، ۲ گیگابایت است. اگر فایل اجرایی (Image) بهطور خاص به عنوان «آگاه از فضای آدرسدهی بزرگ» (Large Address Space-Aware) علامتگذاری شده باشد و سیستم با گزینه خاصی بوت شود (که در بخش «چیدمان فضای آدرسدهی x86» در ادامه این فصل توضیح داده شده است)، یک فرآیند ۳۲ بیتی میتواند در ویندوز ۳۲ بیتی تا ۳ گیگابایت و در ویندوز ۶۴ بیتی تا ۴ گیگابایت رشد کند. اندازه فضای آدرسدهی مجازی فرآیند در نسخههای ۶۴ بیتی ویندوز ۸ و سرور ۲۰۱۲ برابر با ۸۱۹۲ گیگابایت (۸ ترابایت) است و در ویندوز ۸.۱ (و نسخههای جدیدتر) و سرور ۲۰۱۲ R2، این مقدار به ۱۲۸ ترابایت میرسد.
همانطور که در فصل ۲ («معماری سیستم»، بهویژه در جدول ۲-۲) مشاهده کردید، حداکثر مقدار حافظه فیزیکی که ویندوز در حال حاضر پشتیبانی میکند، بسته به نسخه و ویرایش مورد استفاده، بین ۲ گیگابایت تا ۲۴ ترابایت متغیر است. از آنجا که فضای آدرسدهی مجازی ممکن است بزرگتر یا کوچکتر از حافظه فیزیکی موجود در دستگاه باشد، مدیر حافظه دو وظیفه اصلی بر عهده دارد:
۱. ترجمه یا نگاشت (Mapping): تبدیل فضای آدرسدهی مجازی یک فرآیند به حافظه فیزیکی؛ به گونهای که وقتی یک «رشته» (Thread) در بافت آن فرآیند، اقدام به خواندن یا نوشتن در فضای آدرس مجازی میکند، به آدرس فیزیکی صحیح ارجاع داده شود. (به آن بخش از فضای آدرس مجازی یک فرآیند که در حافظه فیزیکی مقیم است، «مجموعه کاری» یا Working Set گفته میشود).
۲. صفحهبندی (Paging): انتقال بخشی از محتوای حافظه به دیسک در زمانی که حافظه بیش از حد اشغال شده باشد (یعنی زمانی که رشتههای در حال اجرا سعی میکنند بیش از حافظه فیزیکی موجود استفاده کنند) و بازگرداندن آن محتوا به حافظه فیزیکی در صورت نیاز.
علاوه بر مدیریت حافظه مجازی، مدیر حافظه مجموعهای از سرویسهای هستهای را ارائه میدهد که زیرسیستمهای مختلف محیط ویندوز بر پایه آنها بنا شدهاند. این سرویسها شامل «فایلهای نگاشتشده در حافظه» (که در داخل سیستم Section Objects نامیده میشوند)، حافظه «کپی در هنگام نوشتن» (Copy-on-write) و پشتیبانی از برنامههایی است که از فضاهای آدرسدهی بزرگ و پراکنده (Sparse) استفاده میکنند. همچنین، مدیر حافظه راهکاری را برای فرآیندها فراهم میکند تا بتوانند مقادیر بیشتری از حافظه فیزیکی را (نسبت به آنچه در یک لحظه در فضای آدرس مجازی قابل نگاشت است) تخصیص داده و استفاده کنند؛ برای مثال در سیستمهای ۳۲ بیتی با بیش از ۳ گیگابایت حافظه فیزیکی.
مدیر حافظه (Memory Manager) بخشی از «اجراییِ ویندوز» (Windows Executive) است و به همین دلیل در فایل Ntoskrnl.exe قرار دارد. این بخش بزرگترین مؤلفه در Executive محسوب میشود که نشاندهنده اهمیت و پیچیدگی بالای آن است. لازم به ذکر است که هیچ بخشی از مدیر حافظه در لایه انتزاع سختافزار (HAL) وجود ندارد. مدیر حافظه از اجزای زیر تشکیل شده است:
مجموعهای از سرویسهای سیستمیِ اجرایی: برای تخصیص، آزادسازی و مدیریت حافظه مجازی که بیشتر آنها از طریق APIهای ویندوز یا رابطهای درایورِ دستگاه در حالت هسته (Kernel-mode) در دسترس هستند.
مدیریتکننده تله (Trap Handler) برای خطاهای دسترسی و عدم اعتبار ترجمه: جهت رفع استثناهای مدیریت حافظه که توسط سختافزار شناسایی میشوند و همچنین مقیم کردن صفحات مجازی در حافظه فیزیکی به نیابت از یک فرآیند.
شش روتین کلیدی سطح بالا: هر یک از این روتینها در یکی از شش رشته (Thread) مختلفِ حالت هسته در فرآیند سیستم (System Process) اجرا میشوند:
مدیر مجموعه تعادل (KeBalanceSetManager، اولویت ۱۷): این بخش یک روتین داخلی به نام «مدیر مجموعه کاری» (MmWorkingSetManager) را یک بار در ثانیه و همچنین زمانی که حافظه آزاد به کمتر از حد معینی میرسد، فراخوانی میکند. مدیر مجموعه کاری، سیاستهای کلی مدیریت حافظه مانند هرس کردن (Trimming) مجموعههای کاری، تعیین عمر صفحات (Aging) و نوشتن صفحات تغییریافته را هدایت میکند.
تعویضکننده فرآیند/پشته (KeSwapProcessOrStack، اولویت ۲۳): این بخش وظیفه ورود و خروج (Inswapping/Outswapping) پشتههای رشتههای هسته و خودِ فرآیندها را بر عهده دارد. مدیر مجموعه تعادل و کد زمانبندی رشتهها در هسته، در صورت نیاز به عملیات جابهجایی، این رشته را بیدار میکنند.
نویسنده صفحات تغییریافته (MiModifiedPageWriter، اولویت ۱۸): این بخش صفحات «کثیف» (Dirty - صفحاتی که محتوای آنها تغییر کرده) را از لیست صفحات تغییریافته به فایلهای صفحهبندی (Paging Files) مناسب منتقل میکند. این رشته زمانی بیدار میشود که نیاز به کاهش حجم لیست صفحات تغییریافته باشد.
نویسنده صفحات نگاشتشده (MiMappedPageWriter، اولویت ۱۸): محتوای صفحات تغییریافته در فایلهای نگاشتشده (Mapped Files) را روی دیسک یا فضای ذخیرهسازی راه دور مینویسد. این رشته زمانی بیدار میشود که حجم لیست تغییریافتهها باید کاهش یابد یا صفحاتی بیش از ۵ دقیقه در این لیست مانده باشند. وجود این رشته دومِ نویسنده ضروری است، زیرا ممکن است باعث ایجاد «خطای صفحه» (Page Fault) شود که خود نیازمند صفحات آزاد است. اگر تنها یک رشته نویسنده وجود داشت و حافظه آزاد تمام میشد، سیستم در انتظارِ صفحه آزاد دچار بنبست (Deadlock) میشد.
رشته ارجاعزدایی قطعه (MiDereferenceSegmentThread، اولویت ۱۹): مسئول کاهش حافظه پنهان (Cache) و همچنین افزایش یا کاهش حجم فایل صفحهبندی (Page File) است. برای مثال، اگر فضای آدرس مجازی برای رشد «استخر صفحهبندی شده» (Paged Pool) وجود نداشته باشد، این رشته حافظه پنهان صفحات را هرس میکند تا فضای اشغال شده توسط آن برای استفاده مجدد آزاد شود.
رشته صفر کردن صفحه (MiZeroPageThread، اولویت ۰): وظیفه این رشته صفر کردن محتوای صفحات در «لیست آزاد» است تا مجموعهای از صفحات صفرشده برای پاسخگویی به خطاهای صفحه (که نیاز به صفحه خالی دارند) آماده باشد. در برخی موارد، صفر کردن حافظه توسط تابع سریعتری به نام MiZeroInParallel انجام میشود.
مدیریت حافظه در قطعات مجزایی به نام «صفحه» (Page) انجام میشود؛ زیرا واحد مدیریت حافظه در سختافزار (MMU)، ترجمه آدرسهای مجازی به فیزیکی را در سطحِ دانهبندی یک صفحه انجام میدهد. از این رو، صفحه کوچکترین واحد حفاظتی (Protection) در سطح سختافزار محسوب میشود. پردازندههایی که ویندوز بر روی آنها اجرا میشود، از دو اندازه صفحه پشتیبانی میکنند: کوچک و بزرگ. اندازههای واقعی این صفحات بر اساس معماری پردازنده متفاوت است که در جدول ۵-۱ فهرست شدهاند.

مزیت اصلی صفحات بزرگ (Large Pages)، سرعت بالای ترجمه آدرس برای مراجعاتی است که به دادههای درون آن صفحه صورت میگیرد. این مزیت به این دلیل است که با اولین مراجعه به هر بایت در یک صفحه بزرگ، بافر ترجمه سختافزاری یا همان TLB ، اطلاعات لازم برای ترجمه آدرس تمامی بایتهای دیگر آن صفحه بزرگ را در حافظه پنهان (Cache) خود ذخیره میکند.
در مقابل، اگر از صفحات کوچک استفاده شود، برای پوشش همان محدوده از آدرسهای مجازی، به ورودیهای بیشتری در TLB نیاز است؛ این امر باعث میشود با ترجمه آدرسهای مجازی جدید، ورودیهای قبلی سریعتر جایگزین (Recycle) شوند. در نتیجه، هنگام مراجعه به آدرسهای مجازی خارج از محدوده یک صفحه کوچک (که ترجمه آن قبلاً کش نشده)، سیستم مجبور است مجدداً به ساختارهای «جدول صفحه» (Page Table) مراجعه کند. از آنجا که TLB یک حافظه پنهان بسیار کوچک است، صفحات بزرگ از این منبع محدود استفاده بهینهتری میکنند.
ویندوز برای بهرهگیری از این مزیت در سیستمهایی با بیش از ۲ گیگابایت رم، موارد زیر را با صفحات بزرگ نگاشت میکند:
فایلهای اصلی سیستمعامل (Ntoskrnl.exe و Hal.dll).
دادههای حیاتی سیستمعامل (مانند بخش اولیه یا Non-paged Pool و ساختارهای دادهای که وضعیت هر صفحه از حافظه فیزیکی را توصیف میکنند).
درخواستهای فضای I/O (فراخوانی MmMapIoSpace توسط درایورها)، مشروط بر اینکه طول و تراز (Alignment) درخواست به اندازه کافی بزرگ باشد.
علاوه بر این، اپلیکیشنها نیز میتوانند فایلهای اجرایی، حافظه خصوصی و بخشهای متصل به فایل صفحهبندی خود را با صفحات بزرگ نگاشت کنند (به پرچم MEM_LARGE_PAGES در توابع VirtualAlloc و امثال آن مراجعه کنید). همچنین میتوانید با افزودن مقدار LargePageDrivers در ریجستری (مسیر HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management)، درایورهای خاصی را برای استفاده از صفحات بزرگ تعیین کنید.
تخصیص صفحات بزرگ ممکن است پس از مدتی که از کارکرد سیستم گذشت، با شکست مواجه شود. دلیل آن این است که هر صفحه بزرگ باید از تعدادی صفحات کوچک متوالی در حافظه فیزیکی تشکیل شده باشد (جدول ۵-۱ را ببینید). علاوه بر این، این صفحات باید دقیقاً از مرز (Boundary) یک صفحه بزرگ شروع شوند. برای مثال در یک سیستم x64، صفحات فیزیکی ۰ تا ۵۱۱ میتوانند یک صفحه بزرگ بسازند، اما صفحات ۱۰ تا ۵۲۱ خیر. با فعالیت سیستم، حافظه فیزیکی آزاد دچار «قطعهقطعه شدن» (Fragmentation) میشود که مشکلی برای صفحات کوچک ایجاد نمیکند، اما مانع از تخصیص صفحات بزرگ میشود.
نکات مهم دیگر:
این نوع حافظه همیشه غیرقابل صفحهبندی (Non-pageable) است، زیرا سیستم فایل صفحهبندی از صفحات بزرگ پشتیبانی نمیکند. به همین دلیل، فراخواننده باید دارای مجوز SeLockMemoryPrivilege باشد.
این تخصیصها جزئی از «مجموعه کاری» (Working Set) فرآیند محسوب نمیشوند و مشمول محدودیتهای Job برای استفاده از حافظه مجازی نیستند.
در ویندوز ۱۰ (نسخه ۱۶۰۷) و سرور ۲۰۱۶، صفحات بزرگ میتوانند به صورت صفحات عظیم (Huge Pages) با اندازه ۱ گیگابایت نگاشت شوند.
استفاده از صفحات بزرگ یک جنبه منفی دارد: هر صفحه (چه بزرگ، چه کوچک) باید دارای یک سطح حفاظتی واحد برای کل صفحه باشد. اگر یک صفحه بزرگ شامل هر دو بخش «کد فقطخواندنی» و «دادههای خواندنی/نوشتنی» باشد، کل صفحه باید به صورت خواندنی/نوشتنی علامتگذاری شود. این یعنی کد سیستمعامل قابل تغییر خواهد بود! در این حالت، یک درایور مخرب یا دارای باگ میتواند کدهای حساس سیستمعامل را بدون بروز خطای دسترسی (Access Violation) تغییر دهد.
اگر از صفحات کوچک استفاده شود، بخشهای فقطخواندنی به درستی محافظت میشوند و در صورت تلاش برای تغییر آنها، سیستم بلافاصله با یک صفحه آبی (Crash) متوقف شده و دستور خاطی را شناسایی میکند. این امر اگرچه کارایی ترجمه آدرس را کمی کاهش میدهد، اما از تخریب تدریجی و سختتشخیصِ دادههای سیستم جلوگیری میکند.
نکته: اگر مشکوک به تخریب کدهای هسته (Kernel) هستید، ابزار Driver Verifier را فعال کنید؛ این ابزار استفاده از صفحات بزرگ را غیرفعال میکند تا امنیت و عیبیابی دقیقتر میسر شود.
ویندوز نیز مانند اکثر سیستمعاملهای مدرن، مکانیزمی را برای اشتراکگذاری حافظه میان فرآیندها و سیستمعامل فراهم میکند. حافظه اشتراکی (Shared Memory) را میتوان به عنوان حافظهای تعریف کرد که برای بیش از یک فرآیند قابل مشاهده است و یا در فضای آدرسدهی مجازیِ بیش از یک فرآیند حضور دارد.
برای مثال، اگر دو فرآیند از یک DLL یکسان استفاده کنند، منطقی است که صفحاتِ کد مربوط به آن DLL تنها یکبار در حافظه فیزیکی بارگذاری شوند و همان صفحات بین تمام فرآیندهایی که آن DLL را نگاشت (Map) کردهاند، به اشتراک گذاشته شود؛ همانطور که در شکل ۵-۲ نشان داده شده است.
هر فرآیند همچنان نواحی حافظه اختصاصی (Private) خود را برای ذخیره دادههای شخصی حفظ میکند، اما صفحات کد DLL و دادههای تغییرنیافته میتوانند بدون هیچ مشکلی به اشتراک گذاشته شوند. همانطور که در ادامه توضیح خواهیم داد، این نوع اشتراکگذاری بهطور خودکار رخ میدهد؛ زیرا صفحات کد در فایلهای اجرایی (مانند EXE، DLL و موارد دیگری نظیر محافظهای صفحه نمایش یا SCR که در واقع همان DLL با نامی متفاوت هستند) به صورت «فقط-اجرا» (Execute-only) و صفحات قابلنوشتن به صورت «کپی در هنگام نوشتن» (Copy-on-write) نگاشت میشوند..
شکل ۵-۲ دو فرآیند را نشان میدهد که بر اساس فایلهای اجرایی متفاوتی هستند، اما یک DLL مشترک را که تنها یک بار در حافظه فیزیکی بارگذاری شده، به اشتراک میگذارند. در این حالت، کد خودِ فایلهای اجرایی (EXE) به اشتراک گذاشته نمیشود چون دو فرآیند متفاوت هستند. اما اگر دو یا چند فرآیند یک فایل واحد (مثلاً Notepad.exe) را اجرا کنند، کد EXE نیز بین آنها به اشتراک گذاشته خواهد شد.
ابتداییترین ساختارها در مدیر حافظه که برای پیادهسازی حافظه اشتراکی استفاده میشوند، اشیاء بخش (Section Objects) نام دارند که در Windows API با نام File-mapping objects شناخته میشوند.
این ساختار بنیادین برای نگاشت آدرسهای مجازی به کار میرود؛ خواه این آدرسها در حافظه اصلی باشند، خواه در فایل صفحهبندی (Page File) یا هر فایل دیگری که اپلیکیشن بخواهد مانند حافظه رم به آن دسترسی داشته باشد. یک «بخش» میتواند توسط یک یا چند فرآیند باز شود؛ به عبارت دیگر، اشیاء بخش لزوماً به معنای حافظه اشتراکی نیستند (ممکن است فقط توسط یک فرآیند استفاده شوند).
یک Section Objects میتواند به دو صورت متصل شود: ۱. به یک فایل باز روی دیسک: که به آن «فایل نگاشتشده» (Mapped File) میگویند. ۲. به حافظه واگذار شده (Committed Memory): برای ایجاد حافظه اشتراکی. به این نوع، «بخشهای متکی به فایل صفحهبندی» (Page-file-backed sections) گفته میشود؛ زیرا در صورت نیازِ سیستم به حافظه فیزیکی، این صفحات (برخلاف فایلهای معمولی) در فایل صفحهبندی نوشته میشوند. (از آنجا که ویندوز میتواند بدون فایل صفحهبندی هم اجرا شود، این بخشها ممکن است در واقع فقط توسط حافظه فیزیکی پشتیبانی شوند). مانند هر صفحه خالی دیگری که در حالت کاربر (User-mode) قابل مشاهده میشود، صفحات اشتراکی واگذار شده نیز در اولین دسترسی همیشه با صفر پر میشوند تا اطمینان حاصل شود که هیچ داده حساسی نشت نمیکند.
برای ایجاد یک Section Objects ، از توابع CreateFileMapping یا نسخههای مشابه آن استفاده میشود. در این توابع، دستگیره (Handle) فایلی که قبلاً باز شده مشخص میگردد (یا برای بخشهای متکی به فایل صفحهبندی، مقدار INVALID_HANDLE_VALUE داده میشود). فرآیندهای دیگر میتوانند از طریق نام، ارثبری دستگیره یا تکثیر دستگیره (DuplicateHandle) به این بخشها دسترسی پیدا کنند. درایورهای دستگاه نیز میتوانند با توابعی نظیر ZwOpenSection و ZwMapViewOfSection با این اشیاء کار کنند.
یک Section Objects میتواند به فایلهایی ارجاع دهد که بسیار بزرگتر از فضای آدرسدهی یک فرآیند هستند. برای دسترسی به چنین بخشهای بزرگی، فرآیند تنها قسمتی را که نیاز دارد نگاشت میکند که به آن نمای بخش (View of the section) میگویند. این کار با توابع خانواده MapViewOfFile انجام میشود. استفاده از «نماها» به فرآیندها اجازه میدهد در فضای آدرسدهی خود صرفهجویی کنند، زیرا فقط بخشهای مورد نیاز در هر لحظه وارد حافظه میشوند.
اپلیکیشنهای ویندوز از فایلهای نگاشتشده برای انجام راحتِ عملیات ورودی/خروجی (I/O) استفاده میکنند، بهطوری که فایل به سادگی به صورت دادههای موجود در حافظه ظاهر میشود. اما اپلیکیشنها تنها مصرفکنندگان این اشیاء نیستند:
بارگذار تصویر (Image Loader): از اشیاء بخش برای نگاشت فایلهای EXE، DLL و درایورها به حافظه استفاده میکند.
مدیر حافظه پنهان (Cache Manager): از آنها برای دسترسی به دادهها در فایلهای کششده استفاده میکند.
قابلیت جلوگیری از اجرای دادهها (Data Execution Prevention یا DEP) یا همان حفاظت از صفحه به روش «عدم اجرا» (NX)، باعث میشود تا هرگونه تلاش برای انتقال کنترل به دستورالعملی در یک صفحه که با علامت «عدم اجرا» مشخص شده است، منجر به خطای دسترسی (Access Fault) شود. این ویژگی میتواند از سوءاستفاده برخی بدافزارها از باگهای سیستم از طریق اجرای کدهای قرار داده شده در صفحات داده (مانند پشته یا Stack) جلوگیری کند.
DEP همچنین میتواند برنامههایی را که ضعیف نوشته شدهاند و مجوزهای صفحاتی را که قصد اجرای کد از آنها دارند به درستی تنظیم نمیکنند، شناسایی کند.
اگر در حالت هسته (Kernel mode) تلاشی برای اجرای کد در صفحه «عدم اجرا» صورت گیرد، سیستم با کد خطای ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY (0xFC) دچار فروپاشی (Crash) میشود.
اگر این اتفاق در حالت کاربر (User mode) رخ دهد، یک استثنای STATUS_ACCESS_VIOLATION (0xC0000005) به رشتهای که قصد ارجاع غیرقانونی داشته، ارسال میشود.
اگر فرآیندی حافظهای تخصیص دهد که نیاز به قابلیت اجرا داشته باشد، باید صراحتاً آن صفحات را با تعیین پرچمهای PAGE_EXECUTE ،PAGE_EXECUTE_READ ،PAGE_EXECUTE_READWRITE یا PAGE_EXECUTE_WRITECOPY در توابع تخصیص حافظه مشخص کند.
سیستمهای ۳۲ بیتی (x86): در سیستمهایی که از DEP پشتیبانی میکنند، بیت شماره ۶۳ در «ورودی جدول صفحه» (PTE) برای علامتگذاری صفحه به عنوان غیرقابلاجرا استفاده میشود. بنابراین، ویژگی DEP تنها زمانی در دسترس است که پردازنده در حالت PAE (Physical Address Extension) کار کند؛ در غیر این صورت، ورودیهای جدول صفحه تنها ۳۲ بیت عرض خواهند داشت و فضایی برای این بیت وجود ندارد.
سیستمهای ARM: در این سیستمها، DEP همیشه روی حالت AlwaysOn (همیشه روشن) تنظیم شده است.
ویندوزهای ۶۴ بیتی: حفاظت از اجرا همیشه برای تمام فرآیندهای ۶۴ بیتی و درایورهای دستگاه اعمال میشود و تنها با تنظیم گزینه nx در تنظیمات BCD به حالت AlwaysOff قابل غیرفعال شدن است. در ویندوز ۶۴ بیتی، این حفاظت بر روی پشتههای رشته (در هر دو حالت کاربر و هسته)، صفحات حالت کاربر که صراحتاً به عنوان قابلاجرا علامتگذاری نشدهاند، «استخر صفحهبندی شده هسته» (Kernel Paged Pool) و «استخر نشست هسته» (Kernel Session Pool) اعمال میشود.
در ویندوز ۳۲ بیتی، حفاظت از اجرا فقط روی پشتههای رشته و صفحات حالت کاربر اعمال میشود و شامل استخرهای حافظه هسته (Paged Pool و Session Pool) نمیشود.
نحوه اعمال DEP برای فرآیندهای ۳۲ بیتی به مقدار گزینه nx در دادههای پیکربندی بوت (BCD) بستگی دارد. برای تغییر این تنظیمات، میتوان از تب Data Execution Prevention در کادر محاورهای Performance Options استفاده کرد (شکل ۵-۳). تنظیمات انجام شده در این بخش، مقدار گزینه nx را در BCD تغییر میدهد.
برنامههای ۳۲ بیتی که از این حفاظت مستثنی شدهاند، در ریجستری و در مسیر زیر فهرست میشوند: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers در اینجا، نام مقدار (Value Name) همان مسیر کامل فایل اجرایی و دادهی آن (Data) برابر با DisableNXShowUI قرار داده میشود.

در نسخههای کلاینت ویندوز (اعم از ۶۴ بیتی و ۳۲ بیتی)، حفاظت از اجرا برای فرآیندهای ۳۲ بیتی بهطور پیشفرض به گونهای پیکربندی شده است که تنها بر فایلهای اجرایی اصلیِ سیستمعامل ویندوز اعمال شود؛ به عبارت دیگر، گزینه nx در BCD روی حالت OptIn تنظیم شده است. هدف از این کار، جلوگیری از خرابی برنامههای ۳۲ بیتی (مانند برنامههای فشردهشده یا خود-استخراجگر) است که ممکن است برای اجرا به صفحاتی متکی باشند که صراحتاً به عنوان «قابل اجرا» علامتگذاری نشدهاند. در مقابل، در سیستمهای سرور ویندوز، این حفاظت بهطور پیشفرض برای تمامی برنامههای ۳۲ بیتی اعمال میشود و گزینه nx روی حالت OptOut قرار دارد.
حتی اگر DEP را بهصورت اجباری فعال کنید، همچنان روشهای دیگری وجود دارد که برنامهها از طریق آنها میتوانند DEP را برای فایلهای اجرایی خود غیرفعال کنند. برای مثال، صرفنظر از اینکه کدام گزینه حفاظتی فعال باشد، «بارگذار تصویر» (Image Loader) امضای فایل اجرایی را با مکانیسمهای شناختهشدهی قفلگذاری (مانند SafeDisc و SecuROM) مطابقت میدهد و در صورت شناسایی، جهت حفظ سازگاری با نرمافزارهای قدیمیِ دارای قفل (مانند بازیهای کامپیوتری)، حفاظت از اجرا را غیرفعال میکند.
در زمان مقداردهی اولیه سیستم، مدیر حافظه دو «استخر حافظه» (Memory Pool) یا «هیپ» (Heap) با اندازه پویا ایجاد میکند که اکثر مؤلفههای حالت هسته (Kernel-mode) برای تخصیص حافظه سیستم از آنها استفاده میکنند:
استخر غیرصفحهبندی شده (Non-paged pool): این استخر شامل محدودههایی از آدرسهای مجازی سیستم است که تضمین میشود در تمام لحظات در حافظه فیزیکی مقیم باشند. بنابراین، در هر زمانی میتوان بدون بروز «خطای صفحه» (Page Fault) به آنها دسترسی داشت؛ این بدان معناست که از هر سطح IRQL (سطح درخواست وقفه) میتوان به این حافظه دسترسی پیدا کرد. یکی از دلایل نیاز به این استخر این است که خطاهای صفحه در سطح DPC/dispatch یا بالاتر قابل رفع نیستند. در نتیجه، هر کد یا دادهای که ممکن است در سطح DPC/dispatch یا بالاتر اجرا یا فراخوانی شود، باید در حافظه غیرقابلصفحهبندی باشد.
استخر صفحهبندی شده (Paged pool): این بخش ناحیهای از حافظه مجازی در فضای سیستم است که میتواند به دیسک منتقل (Page out) یا از آن بازگردانده (Page in) شود. درایورهای دستگاه که نیازی به دسترسی به حافظه در سطح DPC/dispatch یا بالاتر ندارند، میتوانند از این استخر استفاده کنند. این حافظه از بافت (Context) هر فرآیندی قابل دسترسی است.
هر دوی این استخرها در بخش «سیستمیِ» فضای آدرسدهی قرار دارند و در فضای آدرس مجازیِ تمام فرآیندها نگاشت میشوند. بخش اجرایی ویندوز (Executive) روتینهایی را برای تخصیص و آزادسازی حافظه از این استخرها فراهم میکند. برای اطلاعات بیشتر درباره این روتینها، میتوانید به توابعی که با ExAllocatePool ،ExAllocatePoolWithTag و ExFreePool شروع میشوند در مستندات WDK مراجعه کنید.
سیستمها با چهار استخر صفحهبندی شده (که برای تشکیل کل استخر صفحهبندی شده سیستم با هم ترکیب میشوند) و دو استخر غیرصفحهبندی شده شروع به کار میکنند. بسته به تعداد گرههای NUMA در سیستم، ممکن است استخرهای بیشتری (تا ۶۴ عدد) ایجاد شود. داشتن بیش از یک استخر صفحهبندی شده، تداخل و مسدود شدن کدهای سیستم را هنگام فراخوانیهای همزمان روتینهای استخر کاهش میدهد. علاوه بر این، استخرهای مختلف در محدودههای آدرس مجازی متفاوتی نگاشت میشوند که با گرههای مختلف NUMA در سیستم مطابقت دارند. ساختارهای دادهای مختلف (مانند لیستهای Look-aside برای صفحات بزرگ) که تخصیصهای استخر را توصیف میکنند نیز در گرههای مختلف NUMA توزیع میشوند.
علاوه بر استخرهای صفحهبندی شده و غیرصفحهبندی شده، چند استخر دیگر با ویژگیها یا کاربردهای خاص وجود دارد:
استخر فضای نشست (Session space pool): برای دادههایی استفاده میشود که بین تمام فرآیندهای یک «نشست» (Session) مشترک هستند.
استخر ویژه (Special pool): تخصیصها در این استخر توسط صفحاتی با علامت «عدم دسترسی» (No access) محصور میشوند. این کار به شناسایی و ایزوله کردن مشکلاتی در کد کمک میکند که به حافظه، قبل یا بعد از محدوده تخصیصیافتهی خود دستاندازی میکنند.
بیشتر برنامهها بلوکهایی را تخصیص میدهند که کوچکتر از حداقل واحد ۶۴ کیلوبایتیِ قابل تخصیص توسط توابع سطح صفحه (مانند VirtualAlloc) هستند. اختصاص دادن چنین فضای بزرگی برای موارد نسبتاً کوچک، از نظر مصرف حافظه و کارایی بهینه نیست. برای حل این مشکل، ویندوز مؤلفهای به نام مدیر هیپ را ارائه میدهد. این مؤلفه، تخصیصهای کوچک را درون نواحی بزرگتری که قبلاً توسط توابع سطح صفحه رزرو شدهاند، مدیریت میکند. واحد دانهبندی (Granularity) تخصیص در مدیر هیپ بسیار کوچک است: ۸ بایت در سیستمهای ۳۲ بیتی و ۱۶ بایت در سیستمهای ۶۴ بیتی. مدیر هیپ برای بهینهسازی مصرف حافظه و افزایش کارایی در مواجهه با این تخصیصهای کوچک طراحی شده است.
مدیر هیپ در دو مکان وجود دارد: Ntdll.dll و Ntoskrnl.exe. رابطهای برنامهنویسی زیرسیستم (مانند Windows Heap APIs) توابع موجود در Ntdll.dll را فراخوانی میکنند، در حالی که اجزای مختلف اجرایی (Executive) و درایورهای دستگاه، توابع موجود در Ntoskrnl.exe را صدا میزنند. رابطهای بومی آن (با پیشوند Rtl) فقط برای استفاده در اجزای داخلی ویندوز یا درایورهای حالت هسته در دسترس هستند. رابطهای مستند شدهی Windows API برای هیپ (با پیشوند Heap)، در واقع درخواستها را به توابع بومی در Ntdll.dll ارجاع میدهند. علاوه بر این، رابطهای قدیمی (با پیشوند Local یا Global) جهت پشتیبانی از برنامههای قدیمی ویندوز ارائه شدهاند؛ اینها نیز در داخل سیستم، مدیر هیپ را فراخوانی کرده و از برخی رابطهای تخصصی آن برای بازسازی رفتارهای قدیمی استفاده میکنند.
رایجترین توابع هیپ در ویندوز عبارتند از:
* HeapCreate یا HeapDestroy: به ترتیب برای ایجاد یا حذف یک هیپ استفاده میشوند. در زمان ایجاد، میتوان اندازه اولیه رزرو شده و واگذار شده (Committed) را مشخص کرد.
* HeapAlloc: یک بلوک از هیپ تخصیص میدهد. این تابع به RtlAllocateHeap در Ntdll.dll ارجاع داده میشود.
* HeapFree: بلوکی را که قبلاً با HeapAlloc تخصیص یافته بود، آزاد میکند.
* HeapReAlloc: اندازه یک تخصیص موجود را تغییر میدهد (بزرگ یا کوچک میکند). این تابع به RtlReAllocateHeap در Ntdll.dll ارجاع داده میشود.
* HeapLock و HeapUnlock: انحصار متقابل (Mutual Exclusion) را برای عملیاتهای روی هیپ کنترل میکنند.
* HeapWalk: ورودیها و نواحی موجود در یک هیپ را پیمایش و فهرست میکند.
هر فرآیند حداقل یک هیپ دارد که به آن «هیپ پیشفرض فرآیند» (Default Process Heap) گفته میشود. این هیپ در زمان آغاز به کار فرآیند ایجاد شده و در طول تمام مدت حیات آن هرگز حذف نمیشود. اندازه پیشفرض آن ۱ مگابایت است، اما میتوانید با استفاده از پرچم /HEAP در زمان لینک کردن فایل اجرایی، اندازه اولیه بزرگتری برای آن تعیین کنید. البته این مقدار صرفاً رزرو اولیه است و در صورت نیاز، هیپ بهطور خودکار منبسط میشود. همچنین میتوانید اندازه اولیه «واگذار شده» (Committed) را نیز در فایل اجرایی مشخص کنید.
هیپ پیشفرض میتواند بهطور صریح توسط برنامه یا بهطور ضمنی توسط برخی توابع داخلی ویندوز استفاده شود. یک اپلیکیشن میتواند با فراخوانی تابع GetProcessHeap آدرس هیپ پیشفرض خود را استعلام کند. همچنین فرآیندها میتوانند با تابع HeapCreate هیپهای اختصاصی (Private) اضافی ایجاد کنند. زمانی که فرآیند دیگر به یک هیپ اختصاصی نیاز نداشته باشد، میتواند با فراخوانی HeapDestroy فضای آدرس مجازی آن را بازپس بگیرد. آرایهای از تمام هیپهای موجود در هر فرآیند نگهداری میشود که یک رشته (Thread) میتواند با تابع GetProcessHeaps آنها را بررسی کند.
تا پیش از ویندوز ۱۰ و سرور ۲۰۱۶، تنها یک نوع هیپ وجود داشت که ما آن را «NT heap» مینامیم. هیپ NT توسط یک لایه اختیاری در بخش جلویی (Front-end) تقویت میشود که در صورت استفاده، شامل «هیپ با قطعهقطعه شدنِ کم» یا همان LFH (Low-Fragmentation Heap) است.
ویندوز ۱۰ نوع جدیدی از هیپ را با نام «Segment heap» معرفی کرد. این دو نوع هیپ دارای عناصر مشترکی هستند، اما ساختار و نحوه پیادهسازی آنها با یکدیگر متفاوت است. بهطور پیشفرض، تمام اپلیکیشنهای UWP و برخی از فرآیندهای سیستمی از Segment heap استفاده میکنند، در حالی که سایر فرآیندها از همان NT heap بهره میبرند. این تنظیمات را میتوان در ریجستری تغییر داد.
Telegram: @CaKeegan
Bale: @CaKeegan
Gmail : amidgm2020@gmail.com