غواصی در اعماق: malloc، پروسه‌ها و حافظه تخصیص یافته

اینم یه تجربه پراکنده دیگه!

چند شب پیش یکی از دوستان با این سوال به سراغ من اومد:

مشکل اینه که وقتی با malloc یا new یک حافظه تخصیص میدی و با free یا delete حافظه رو برمیگیردونی, اون حافظه شاید بلاک هاش خالی بشه اما توی تسک لیست خالی به عنوان منابع استفاده شده میاره و تا پایان پروسه پاک نمیشه. از دوستانی که آشنایی دارن پیگیری کن ببین جریان چیه. مهمه .

خدا خیرت بده. یه جایی از حافظه نمایی رشد میکه و وقتی خالیش میکنیم (با اینکه از لیست heap سیستم عامل خالی میشه) اما توی لیست پروسه‌ها(مثل نتیجه ps) هنوز به عنوان حافظه استفاده شده هست و حافظه آزاد نمیشه !!!‌ عجیبه . کلی توی اینترنت اینو سوال کردن !!

من که لینوکسم. اما فکر کنم ویندوزم همینه. چون سوالای اینترنت هردوشونه

کلا بخوام سوال رو خلاصه کنم اینجوریه که چرا وقتی حافظه رو به سیستم‌عامل پس میدیم واقعا پس داده نمیشه. من یکم از دانسته‌هام استفاده کردم و یکم هم تحقیقات کردم

پیش‌نیازها

پیش نیازهایی که به نظرم باید بدونیم ایناست:

  • نکته صفر اینکه با توجه به تحقیقات من زیر کلمه کلیدی new از همون malloc استفاده میشه. پس بررسی malloc به تنهایی میتونه جوابی برای ما فراهم کنه.
  • اول اینکه باید بدونیم که وقتی malloc صدا زده میشه چه اتفاقی میفته. کاری که malloc میکنه اینه که heap پروسه رو بهت اختصاص میده. حالا heap توسط سیستم عامل مدیریت میشه و در صورت نیاز به پروسه‌ی شما حافظه بیشتری تخصیص میده.
  • دوم اینکه فضای heap توسط کرنل مدیریت میشه و به اصطلاح نگاشته میشه به یه چیزی به نام Virtual Memory
  • سوم اینکهVirtual Memory نگاشته میشن به چیزی به نام Memory Page که نزدیک به سخت افزار تره
  • چهارم اینکه Memory Page نگاشته میشن بلوک‌های واقعی حافظه. از این مطمئن نیستم اما این نزدکترین لایه به سخت افزاره که من باهاش آشنا هستم.

بررسی مساله

حالا اگه بخوایم مساله رو بررسی کنیم باید به چند‌تا سوال جواب بدیم:

  • اول اینکه malloc چطور عمل میکنه؟ در پیاده‌سازیش ساز و کارش چیه؟
  • دوم اینکه روند افزایش طول Virtual Memory و اضافه شدن Memory Pageها چطوره؟
  • سوم اینکه بعد از پس دادن Memory Pageها سیستم عامل چطور اونها رو از یک پروسه پس میگیره؟

خب جواب سوالا به ترتیب اینا به نظر میرسن:

  • پیاده‌سازی‌های متفاوتی از malloc وجود داره
    • پیاده‌سازی معمولا اون اینطوریه که پس از گرفتن حافظه وقتی free شد معمولا اونها رو به سیستم عامل پس نمیده و نگه میداره که شاید بعدا بخواد دوباره تخصیص بده.
    • دوم اینکه درخواست یک Memory Page جدید توسط دستوراتی مثل mmap و sbrk درخواست یک بلاک بزرگ حافظه میکنه و اون رو بنا به درخواست نرم‌افزار تخصیص میده.
    • بعدش هم اینکه برای اینکه یک Memory Page رو برگردونه به سیستم عامل بایستی تمام حافظه‌هایی که روی اون تخصیص داده شدن برگشت داده بشه. که این هم همیشه اتفاق نمی‌افته.
    • معمولا هم پیاده‌سازی‌ها اینجورین که حافظه‌ای که توسط sbrk افزایش پیدا کرده رو دست نمیزنن(دلیل دقیقش رو نمیدونم) و حافظه‌ای که توسط mmap اضافه شده رو به راحتی برمیگردونن.
  • تقریبا جواب سوال دوم رو هم تا الان دادم. مقدار Virtual Memory توسط glibc کنترل میشه و بوسیله malloc تخصیص داده میشه.
  • جواب سوال سوم هم اینه که کرنل لینوکس همیشه حافظه برگشت داده شده رو برنمیگردونه چون که هزینه داره فقط اون رو علامت میزنه که در صورت نیاز اون رو حذف کنه یا به swap ببره.

راه حل مساله

حالا بریم سراغ مساله اصلی(چرا حافظه برگشت داده شده برگشت نخورده) و توجیهی که براش پیدا کردیم:
* مهمترین دلیل این اتفاق به نظرم malloc هست که حافظه رو نگه داشته و برنگردونده
* دومین دلیلش هم میتونه این باشه که کرنل هنوز Memory Page رو برنداشته و فقط علامت زده

حالا راه حل چیه:
* اول اینکه به کرنل و glibc اعتماد کنیم
* دوم اینکه بیایم و این پارامتر مروبط به malloc رو تنظیم کنیم۴
* سوم اینکه بیایم از یه پیاده‌سازی دیگه برای تخصیص حافظه و آزاد سازی اون استفاده کنیم
* چهارم اینکه بیایم خودمون مستقم از mmap و munmap استفاده کنیم

امیدوارم اینا راه گشای دیگران هم باشه

برای مطالعه بیشتر:

۱

۲

۳

۴

همین!

۳ comments

  1. سلام
    وقتی صحبت از حافظه virtual است واحد حافظه Page می باشد و اندازه Page در کمترین حالت ۴K است در نتیجه برای حافظه های کوچیک استفادش مناسب نیست. برای همین از مفهومی به نام heap استفاده می شود. و مدیریت حافظه heap حافظه سطح یوزر تو همان سطح یوزر انجام می شود و نه کرنل.
    چرا حافظه برنمی گرده به خاطر اینه که مدیریت حافظه heap یک حافظه بزرگ که اصطلاحا heap می گن از حافظه virtual میگیره (مضربی از Page). و هر موقع یوزر حافظه بخواد (در صورتی که اندازه حافظه درخواستی کوچیک باشه مثلا ۱۰۰ یا ۱۰۰۰ بایت و …) از حافظه heap حافظه داده میشه. بعد اگه یوزر حافظه رو free کنه مدیریت حافظه داخل خودش این حافظه را به عنوان free در نظر می گیره و چیزی به سیستم عامل بر نمی گرده و خوب منطقی هم هست چون بی معنی است مثلا یک حافظه ۱۰۰ بایتی رو بخایم به سیستم عامل بدیم.

    برای حافظه های بزرگ تر داستان فرق داره و مدیریت حافظه باید حافظه اضافی از Virtual Memory بگیره یک نکته دیگه این است که بین حافظه Virtual با حافظه Physical فرق وجود دارد. وقتی مثلا یک حافظه Virutal آزاد میشه. ممکنه باز ببینیم حافظه هنوز هست ولی در واقع سیستم عامل حافظه فیزیکی که حافظه Virtual به آن اشاره می کرده به عنوان حافظه آزاد در نظر میگیرده. یک مثال دیگه اینه که کاربر مثلا ۱m حافظه می خواد به صورت virtual این حافظه داده میشه ولی به صورت فیزیکی تا موقعی که کاربر کاری با حافظه نکنه سیستم عامل حافظه فیزیکی نمیگیره.

    1. سلام

      ممنون از توضیحات تکمیلیتون. پیشنهاد میکنم اینها رو یه پست تکمیلی کنید توی بلاگتون چون کامنت‌ها معمولا کمتر از یک پست مورد توجه قرار میگیره

      1. قابل نداشت
        این موضوع یک جزئاتی داره که باید یکم در مورشون مطالعه کنم تا مناسب آوردن در وبلاگم بشه.
        فعلا دارم مطلبی دیگه ای مرتبط به حافظه ها می نویسم ولی خیلی کند پیش میره، یکم تنبلم تو نوشتن. تا بعد ببینیم چی میشه 🙂
        شاد باشید

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *