مدیریت صحیح حافظه در سیستم های امبدد

مدیریت_صحیح_حافظه_در_سیستم‌های_امبدد
275 بازدید
۱۴۰۴-۰۳-۰۵
13 دقیقه
  • نویسنده: Zeus ‌
  • درباره نویسنده: زئوس هستم ساکن المپ

چند روز پیش توی دورهمی سوم سیسوگ (sug3) یه ارائه در مورد مقوله مدیریت حافظه داشتم ولی خوب به دلیل کمبود تایم نشد اونظور که دوست داشتم به چالش ها وباید و نباید های مدیریت حافظه مخصوصاً در سیستم‌های امبدد بپردازم، برای همین در این مقاله سعی می‌کنم تا جای ممکن مبحث رو بازش کنم و اونچه در مورد مدیریت حافظه میدونم رو باشما به اشتراک بذارم برای نوشتن ایم مطلب هم از منبع ۱ و منبع ۲ و منبع ۳ و البته دو سه تای دیگه که لینکشون رو الان پیدا نکردم. فرقی نمی‌کنه از حافظهٔ استاتیک استفاده می‌کنید، یا یه پشتهٔ ساده دارین یا حافظه رو به‌صورت داینامیک روی هیپ مدیریت می‌کنید؛ در هر حال باید با دقت و وسواس جلو رفت مخصوصاً اگر میخوایم دچار باگ های عجیب و غریب نشیم.
تو دنیای امبدد، بی‌توجهی و سرسری گرفتن ریسک‌های مدیریت حافظه، می‌تونه خیلی گرون تموم بشه (خیلی گرون)!

تقریباً هر برنامه‌ای با RAM سر و کار داره، اما این که این حافظه چطوری بین بخش‌های مختلف برنامه  تقسیم بشه، داستان‌های زیادی داره که توی این مقاله قراره به چندتاییش بپردازیم.
توی این مطلب، قراره روش‌های مختلف مدیریت حافظه رو یه دور مرور کنیم تا توی پروژه‌های بعدی، انتخاب‌‌های بهتری داشته باشیم.

از تخصیص کامل حافظه به‌صورت استاتیک گرفته، تا استفاده از یه یا چندتا پشته و همین‌طور هیپ(heap)، همه رو بررسی می‌کنیم.
همچنین قراره ببینیم که پیاده‌سازی هیپ چطور ممکنه باعث تکه‌تکه شدن حافظه (Fragmentation) بشه و چه تأثیری روی عملکرد سیستم‌ها داره.

تخصیص استاتیک حافظه؛ خیال راحت از همون اول!

اگه کل حافظه رو به‌صورت استاتیک تخصیص بدیم، می‌تونیم دقیقاً موقع کامپایل مشخص کنیم که هر بایت از RAM قراره کجا و چطوری مصرف بشه. تو دنیای امبدد، این یه مزیت خیلی بزرگه! چون دیگه خبری از باگ‌های مرموز حافظه مثل memory Leak ، خطاهای تخصیص، یا اشاره‌گرهای سرگردان (Dangling Pointers) نیست.

خیلی از کامپایلرهای پردازنده‌های ۸ بیتی مثل خانوادهٔ 8051 یا PIC، دقیقاً برای همین کار طراحی شدن: یعنی همه چیز رو استاتیک مدیریت می‌کنن.
توی این مدل، داده‌ها یا global هستن، یا static (داخل فایل یا تابع)، یا local به یک تابع.

  • داده‌های global و static همیشه تو یه جای ثابت از حافظه قرار می‌گیرن، چون باید در طول عمر برنامه در دسترس بمونن.
  • داده‌های local هم یه مدل جالب دارن: برای هر تابع، یه بلوک مشخص از حافظه کنار گذاشته میشه. یعنی مثلاً اگه یه تابع متغیر محلی x داشته باشه، هر بار که این تابع صدا زده میشه، x دقیقاً تو همون جای همیشگی حافظه قرار می‌گیره.
    وقتی تابع اجرا نمی‌شه، اون فضا آزاد می‌مونه.

این شیوه بیشتر توی C Compiler هایی استفاده میشه که سخت‌افزارشون پشتیبانی مناسبی برای استک نداره.

شکل زیر ساختار حافظه رو در این حالت نشون میده: بدون هیپ، بدون استک، فقط دیتاهای global و یک بلاک استاتیک برای هر تابع.

ساختار حافظه بدون هیپ، بدون استک، فقط دیتاهای global و یک بلاک استاتیک برای هر تابع

ساختار حافظه بدون هیپ، بدون استک، فقط دیتاهای global و یک بلاک استاتیک برای هر تابع

این روش یه محدودیت مهم داره: دیگه خبری از تابع بازگشتی (Recursion) یا صدا زدن تابع در تابع نیست!
مثلاً یه روال وقفه (Interrupt Handler) نمی‌تونه تابعی رو صدا بزنه که ممکنه همزمان از مسیر اصلی برنامه هم صدا زده بشه. در عوض، خیال برنامه‌نویس از بابت مشکلات تخصیص حافظه در زمان اجرا راحت میشه.

خیلی خوب می‌شد اگه همهٔ کامپایلرها یه گزینه داشتن که اجازه می‌داد بدون استفاده از استک، همهٔ حافظه رو به صورت استاتیک تخصیص بدیم.
البته باید قبول کنیم که با این کار، مقداری از انعطاف و کارایی سیستم قربانی میشه، ولی در عوض، پایداری و امنیت حافظه واقعاً زیاد میشه.

کامپایلرهای باهوش‌تر حتی می‌تونن بفهمن که دو تا تابع خاص هیچ وقت همزمان صدا زده نمیشن؛ برای همین، اجازه میدن حافظهٔ این دو تابع با هم overlap داشته باشه (یعنی یه جا رو مشترک استفاده کنن).
البته این کار یه محدودیت اضافه هم داره: دیگه نمی‌تونیم از function pointerها استفاده کنیم.

اگه می‌خوای واقعاً از حافظهٔ استاتیک لذت ببری، نباید با کارهایی مثل استفادهٔ دوباره از global data برای اهداف مختلف، محیط امن استاتیک رو خراب کنی!

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

مدیریت حافظه با استک!

قدم بعدی تو مدیریت حافظه اینه که به سیستم استک اضافه کنیم. حالا دیگه به جای این‌که فقط یه بلاک حافظه برای هر تابع وجود داشته باشه، برای هر بار فراخوانی تابع باید یه بلاک جدید روی استک ساخته بشه. این بلاک‌ها همون چیزین که بهشون Stack Frame می‌گیم.

استک با اجرای برنامه مدام بزرگ و کوچیک میشه. توی خیلی از برنامه‌ها، پیش‌بینی اینکه “بدترین حالت” مصرف استک چقدره، موقع کامپایل ممکن نیست 🙁
تو سیستم‌های چندوظیفه‌ای (Multitasking)، هر تسک (Task) استک خودش رو داره (و شاید یه استک اضافی هم برای وقفه‌ها).

برنامه‌نویس باید حسابی حواسش جمع باشه که هر استک برای کارهای خودش به اندازه کافی جا داشته باشه.
هیچ چیز بدتر از یه Stack Overflow ناگهانی نیست! مخصوصاً اگه ببینی یه استک دیگه کلی جا خالی داره که اصلاً استفاده نمی‌شه!

متأسفانه اکثر سیستم‌های امبدد هم چیزی به اسم Virtual Memory ندارن که تسک‌ها بتونن در صورت نیاز از حافظهٔ اشتراکی استفاده کنن.
پس طراحی درست اندازهٔ استک‌ها یه مهارت خیلی حیاتی محسوب میشه که حیات و ممات یه پرژه بهش بستگی داره !

مدیریت صحیح حافظه در سیستم های امبدد

یه قاعدهٔ طلایی برای محاسبه سایز استک هست که میگه :

اندازهٔ هر استک رو ۵۰٪ بزرگ‌تر از بیشترین مقداری که موقع تست دیدین، در نظر بگیرین.

اما برای اینکه بتونیم این قانون رو درست اجرا کنیم، اول باید بدونیم استک واقعاً تا کجا رشد کرده بوده. یه روش ساده و خلاقانه برای فهمیدن این موضوع هست بهش میگیم “رنگ کردن” فضای استک.
یعنی قبل از اجرای برنامه، کل فضای استک رو با یه الگوی مشخص (مثلاً یه عدد غیر صفر) پر می‌کنیم. وقتی برنامه اجرا میشه و استک بزرگ و کوچیک میشه، داده‌های واقعی این الگو رو کم‌کم پاک می‌کنن. بعداً با یه حلقه ساده میشه استک رو اسکن کرد و فهمید که رشد استک تا کجا پیش رفته.
(شکل زیر یه تصویر واضح از چرخهٔ عمر یک استک ساده رو نشون میده.)

چرخهٔ عمر یک استک ساده

چرخهٔ عمر یک استک ساده

فقط یادتون باشه « الگویی که برای نوشتن توی استک استفاده می‌کنید نباید صفر باشه » چون خیلی وقتا داده‌هایی که روی استک نوشته میشن مقدار صفر دارن، و اون موقع دیگه نمی‌تونین راحت تشخیص بدین کجا واقعاً استفاده شده و کجا نه.

ردیابی استک آورفلو

خیلی از RTOSها یه ویژگی خوب دارن به اسم stack size tracing. اما اگه سیستم عاملت این قابلیت رو نداره، یا اصلاً از RTOS استفاده نمی‌کنی، خبر خوب اینه که خودت هم می‌تونی یه نسخه ساده‌شو پیاده‌سازی کنی.

این تکنیک هم تو فاز تست به درد می‌خوره (برای بهینه کردن اندازهٔ استک)، هم تو محصول نهایی میشه ازش برای هشدار زودهنگام استفاده کرد. داستان اینطوریه که یه watermark (خط نشون) روی استک تعریف می‌کنی و دوره‌ای چک می‌کنی ببینی الگویی که قبلاً نوشتی، پاک شده یا نه. دیگه نیازی نیست هر بار که روی استک چیزی نوشته میشه، بخوای چک کنی که پر شده یا نه ( این کار خیلی پرهزینه و سنگینه). با یه بررسی زمان‌بندی شده میشه خیلی راحت وضعیت رو کنترل کرد.

البته باید بدونی که این روش ممکنه نتونه سرریزهای خیلی سریع (مثل بی‌نهایت بازگشتی‌ها) رو ردیابی کنه، چون استک خیلی سریع پر میشه. اما برای رشدهای کوچیک و غیرمنتظرهٔ استک، این سیستم هشدار عالی جواب میده!

مدیریت داینامیک حافظه با استفاده از هیپ!

خیلی وقتا تو برنامه‌ها با چیزهایی سروکار داریم (مثل آبجکت‌ها، ساختارها یا بافرها) که طول عمرشون وابسته به اجرای یه تابع خاص نیست.
این موضوع مخصوصاً تو برنامه‌های رویدادمحور (Event-Driven) که تو سیستم‌های امبدد خیلی رایجه، کاملاً به چشم میاد. مثلاً یه رویداد باعث میشه یه آبجکت ساخته بشه و این آبجکت باید بمونه تا یه رویداد دیگه بیاد و حذفش کنه.

توی برنامه‌های C، مدیریت این نوع حافظه رو معمولاً با توابع malloc() و free() انجام میدیم.

  • malloc() اجازه میده یه بلاک حافظه با اندازهٔ دلخواه رزرو کنیم و آدرسش رو بگیریم.
  • free() هم وقتی کارمون با اون حافظه تموم شد، بلاک رو به هیپ برمی‌گردونه تا دوباره قابل‌استفاده بشه.

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

مدیریت استک رو کامپایلر برات انجام میده (تا اینجا خوبه)؛ ولی مدیریت هیپ کاملاً به عهدهٔ برنامه‌نویسه (این بده). و اینجاست که داستان‌های عجیب‌وغریب وارد ماجرا میشن!

فرض کن تو یه جای برنامه نمی‌دونی که آیا یه بلاک حافظه دیگه واقعاً لازمه یا نه. اگه اشتباهی اون بلاک رو free() کنی؛ ولی هنوز یه اشاره‌گر دیگه بهش داشته باشی، برنامه شاید در ظاهر خوب کار کنه.
اما وای به وقتی که همون بلاک حافظه دوباره توسط یه بخش دیگهٔ برنامه اختصاص داده بشه!
اون وقت دو قسمت مختلف برنامه بدون اینکه بدونن، داده‌های همدیگه رو روی‌هم می‌نویسن و اون وقته که درب‌های جهنم باز میشه!

از اون طرف، اگه تصمیم بگیری از ترس این مشکلات کلاً بلاک رو آزاد نکنی، خطر Memory Leak   و پر شدن حافظه تهدیدت می‌کنه.
چون ممکنه در ادامه هیچ راهی برای آزادسازی اون بلاک نداشته باشی (مثلاً همهٔ اشاره‌گرهاش از بین رفته باشن یا جای دیگه‌ای اشاره کنن).

حالا شاید بگی خب، لااقل تو این حالت، منطق برنامه به هم نمی‌ریزه. درسته، ولی اگه اون قسمت از کد که باعث نشت حافظه میشه، مرتب توی اجرا تکرار بشه، مقدار حافظهٔ مصرفی همین‌طور بیشتر و بیشتر میشه… تا جایی که در نهایت، برنامه کند یا حتی کرش کنه.
واقعیت این هست که در نهایت، این مقدار حافظهٔ فیزیکی سیستم شماست که تعیین می‌کنه برنامه چقدر می‌تونه بدون مشکل اجرا بشه.

تو دنیای دسکتاپ، یه نشت حافظهٔ کوچیک معمولاً فاجعه‌آمیز نیست. مثلاً اگه یه کامپایلر برای هر هزار خط کدی که کامپایل می‌کنه فقط ۱۰۰ بایت حافظه نشت بده، باز هم می‌تونه یه فایل ۱۰۰ هزار خطی رو روی یه کامپیوتر امروزی به راحتی کامپایل کنه.
چون در نهایت، وقتی برنامه تموم شد، سیستم‌عامل همهٔ حافظه‌های اختصاص‌داده‌شده رو آزاد می‌کنه.

اما تو سیستم‌های امبدد داستان فرق داره!
اینجا معمولاً برنامه‌ها باید برای مدت‌های طولانی (یا حتی بی‌نهایت) بدون ریست یا خاموش‌شدن اجرا بشن.
پس هیچ نشت حافظه‌ای قابل‌قبول نیست و هر نشتی یه باگ حساب میشه که باید حتماً با اصلاح منطق برنامه برطرف بشه.

یک دردسر دیگه هم داریم بهش میگن تکه‌تکه‌شدن حافظه (Fragmentation)!
علاوه بر نشت، مشکل دیگه‌ای هم وجود داره که این یکی دیگه با منطق برنامه هم قابل‌حل نیست: Fragmentation.
این مشکل ذاتاً در بیشتر پیاده‌سازی‌های malloc() وجود داره. اما چطوری اتفاق می‌افته؟
وقتی برنامه بارهاوبارها حافظه رو malloc() و free() می‌کنه، بلاک‌های حافظه به تکه‌های ریزودرشت تبدیل میشن. در نتیجه، حتی ممکنه کلی فضای خالی داشته باشی، ولی نتونی یه بلاک بزرگ یکپارچه برای درخواست جدیدت پیدا کنی و این یعنی خطای تخصیص حافظه، حتی وقتی حافظه آزاد هست!

با این‌وجود آیا نباید تو امبدد از malloc()  وfree() استفاده کنیم؟ «نه، اصلاً این‌طور نیست!»
اما باید بدونی که محدودیت‌های زیادی وجود داره، طوری که خیلی از برنامه‌نویس‌های امبدد تصمیم می‌گیرن یا کلاً قید استفاده از malloc و free رو بزنن، یا خودشون نسخه‌های خاص و محدودشده‌ای از این توابع رو بازنویسی کنن.

حالا برای اینکه بهتر بفهمیم دقیقاً محدودیت‌ها از کجا ناشی میشن، باید یه نگاهی به نحوهٔ کار  malloc() بندازیم. البته یادت باشه: چیزی که قراره توضیح بدیم، یه پیاده‌سازی رایجه،
ولی استاندارد C اجبار نکرده که malloc() حتماً این‌جوری ساخته بشه. پس پیاده‌سازی‌های مختلفی ممکنه وجود داشته باشه.

هیپ چطور کار می‌کند؟

هیپ در واقع یه بلوک بزرگ از حافظه‌ست که ترکیبیه از بلوک‌های کوچک‌تر: یه سری از این بلوک‌ها به برنامه اختصاص داده شدن (Allocated) و یه سری هم آزاد هستن (Free).

هر بلوک (چه آزاد چه اختصاص‌داده‌شده)، یه هدر (Header) داره که اطلاعات مربوط به خودش رو نگه می‌داره.
(هدر معمولاً شامل اندازهٔ بلوک و وضعیت آزاد یا اشغال بودنشه.)

تو شکل ۳، حالت اولیهٔ هیپ رو می‌بینی و همچنین نتیجهٔ اولین تخصیص ۱۰ بایت حافظه.

مدیریت صحیح حافظه در سیستم های امبدد

یه اشاره‌گر به اسم Free List Pointer همیشه به اولین بلوک آزاد اشاره می‌کنه.
هر بار که برنامه حافظه می‌خواد، این لیست دونه‌دونه چک میشه تا یه بلاک مناسب پیدا بشه.

  • اگه بلاکی با اندازهٔ دقیق موردنیاز موجود بود، همونو تحویل میده.
  • اگه بلاک موجود بزرگ‌تر بود، بلاک به دو قسمت تقسیم میشه:
    یه قسمت دقیقاً هم‌اندازهٔ درخواستی برنامه، و یه قسمت دیگه که تو لیست آزادها می‌مونه.

به‌این‌ترتیب، اون بلوک بزرگ اولیه که کل هیپ بود، کم‌کم تبدیل میشه به یه لیست از بلوک‌های کوچیک و آزاد که بین بلوک‌های تخصیص‌داده‌شده پراکنده شدن و این همون جاییه که کم‌کم مشکل تکه‌تکه‌شدن حافظه یا همون Fragmentation شروع میشه!

مدیریت صحیح حافظه در سیستم های امبدد

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

در سمت چپ تصویر، لیست آزاد (Free List) همچنان تنها شامل یک عنصر است. سپس، یکی از بلوک‌ها آزاد می‌شود و در نتیجه، در سمت راست تصویر، لیست آزاد اکنون دارای یک عنصر دوم نیز هست. اندازهٔ بلوک آزاد موجود ۱۵ بایت است. اگر در این شرایط درخواستی برای تخصیص ۱۰ بایت حافظه انجام شود، این بلوک ۱۵ بایتی ممکن است به دو قسمت تقسیم گردد:

  • یک بلوک ۱۰ بایتی که به برنامه اختصاص داده می‌شود،
  • و یک بلوک باقی‌مانده که شامل فضای آزاد است.

این بلوک باقی‌مانده ممکن است آن‌قدر کوچک باشد که هیچ درخواستی برای حافظه هرگز نتواند آن را به کار گیرد. هرچند ممکن است این بلوک‌های آزاد کوچک بعداً با بلوک‌های آزاد مجاور ادغام شوند، اما همچنان این خطر وجود دارد که برخی از این بلوک‌های کوچک برای همیشه غیرقابل‌استفاده بمانند.

البته نباید فراموش کرد که خطر تکه‌تکه‌شدن حافظه تو بعضی از آزمایش‌های دانشگاهی بیش از حد جدی گرفته شده این آزمایش‌ها معمولاً روی تخصیص‌هایی با اندازه‌های کاملاً تصادفی انجام شدند.
ولی تو دنیای واقعی، داستان یه جور دیگه‌ست!

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

  • ۹۰٪ از تخصیص‌های حافظه فقط تو شش سایز مختلف انجام میشه،
  • و ۹۹.۹٪ از تخصیص‌ها توی ۱۴۱ اندازهٔ متفاوت خلاصه میشه.

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

 

البته توی سیستم‌های امبدد، تنوع اندازهٔ تخصیص‌های حافظه تو یه برنامه معمولاً خیلی کمتر از چیزیه که تو دسکتاپ می‌بینیم. توی برنامه‌های امبدد، کارهایی مثل مدیریت فایل و رشته‌ها (String Handling) خیلی کمتر اتفاق میفته — و این دقیقاً اون حوزه‌هایی هستن که معمولاً اندازهٔ تخصیص حافظه حسابی تغییر می‌کنه.

از طرف دیگه، بیشتر تخصیص‌های حافظه تو امبدد مربوط به استراکچر داده‌ست؛ و خب سایز ساختارهای داده معمولاً تو زمان اجرا تغییر نمی‌کنه این یعنی تخصیص‌های حافظه خیلی منظم‌تر و قابل پیش‌بینی‌ترن. البته، هرچقدر هم الگوی درخواست‌های حافظه مرتب باشه و ریسک Fragmentation کمتر بشه، بازم باید حواسمون باشه که کدهای malloc و free رو طوری بنویسیم که تکه‌تکه‌شدن حافظه رو به حداقل برسونیم.

یکی از راه‌های مهم برای کنترل Fragmentation حافظه، اینه که سیاست درستی برای تخصیص و آزادسازی بلوک‌ها انتخاب کنیم.

🔸 برای تخصیص حافظه، چندتا روش معروف وجود داره:

  • First Fit
    اولین بلوکی که به اندازه کافی بزرگ باشه رو پیدا می‌کنیم (و اگه لازم باشه، اون رو به دو قسمت تقسیم می‌کنیم). این روش ساده و سریع اجرا میشه. ولی خوب ممکنه خیلی بهینه نباشه و بلاک‌های کوچیک ایجاد کنه
  • Best Fit
    یه جستجوی کامل انجام میدیم تا بهترین بلاک ممکن (بلاکی که کمترین فضای اضافی داره) رو پیدا کنیم. این روش می‌تونه فضای کمتری هدر بده، ولی هزینهٔ پردازشی بیشتری داره.

🔸 برای مدیریت لیست بلوک‌های آزاد هم دو سیاست مهم داریم:

  • Address Order
    لیست بلاک‌های آزاد رو بر اساس آدرس مرتب نگه می‌داریم. این کار باعث میشه ادغام بلوک‌های آزاد کنار هم خیلی راحت‌تر انجام بشه.
  • Recently-Used Order
    لیست بلاک‌های آزاد رو بر اساس آخرین بلوک‌هایی که آزادشدن مرتب می‌کنیم.
    این مدل برای مواقعی خوبه که الگوی تخصیص و آزادسازی حافظه به‌صورت پشت‌سرهم و در اندازه‌های مشابه اتفاق میفته (یعنی مثلاً یه سری آبجکت مشابه ساخته و آزاد بشن).

متأسفانه، سیاست‌هایی که باعث کمترین میزان Fragmentation میشن (مثل Best Fit و مرتب‌سازی Free List بر اساس آدرس) بیشترین زمان رو برای تخصیص و آزادسازی حافظه مصرف می‌کنن.
پس انتخاب الگوریتم همیشه یه جور تعادل بین سرعت و بهینگیه.

 

اگه طراحی مکانیزم هیپ با دقت انجام بشه، میشه سیستم‌هایی ساخت که حتی تو برنامه‌های سنگین یونیکس هم فقط حدود ۱٪ از حافظه به خاطر Fragmentation از دست بره!

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

 

پیش‌اختصاص حافظه به‌صورت استاتیک (Static Memory Preallocation)

توی بعضی پروژه‌ها که یا پیچیدگی‌های یه هیپ کامل لازم نیست، یا خطر Fragmentation اصلاً قابل‌قبول نیست، یه تکنیک خیلی خوب وجود داره: «اجازه میدیم حافظه تخصیص پیدا کنه، ولی دیگه آزاد (Free) نشه!»

یعنی چی؟ یعنی بعد از اینکه برنامه بخش راه‌اندازی یا Initialization خودش رو انجام داد، تو حلقهٔ اصلی برنامه (یا حلقهٔ هر تسک)، دیگه هیچ تخصیص حافظهٔ جدیدی انجام نمیشه. هرچی لازم بوده همون اول گرفته شده.

این تکنیک می‌تونه حتی با استفاده از همون malloc() معمولی هم پیاده بشه، ولی من خودم همیشه ترجیح میدم برای این کار یه نسخهٔ سفارشی داشته باشم.

🔸 چرا نسخهٔ اختصاصی بهتره؟

  • سربار (Overhead) مربوط به هدرهای حافظه تو هر بلوک حذف میشه.
  • میشه بعد از پایان Initialization، این تخصیص‌دهنده رو غیرفعال کرد تا دیگه هیچ‌کس نتونه اشتباهی حافظه رزرو کنه.

استفاده از این تکنیک نسبت به تعریف همهٔ حافظه‌ها به‌صورت Global، مزیت‌های دیگه‌ای هم داره:

    • انعطاف در مراحل راه‌اندازی:
      توی راه‌اندازی‌های مختلف (Start-up Sequences)، میشه حافظه رو برای مقاصد متفاوتی رزرو کرد، بدون اینکه لازم باشه برنامه‌نویس صراحتاً نگران این باشه که چه چیزهایی ممکنه هم‌زمان فعال باشن.
    • پاکیزگی در فضای نام (Namespace):
      نیازی نیست برای هر آبجکتی که وجود داره، یه متغیر global یا static تعریف کنیم. فقط یه اشاره‌گر (Pointer) بهش کافیه. این باعث میشه آیتم‌های داخلی برنامه از دسترسی بخش‌های نامناسب کد دور بمونن و ساختار پروژه مرتب‌تر بشه.
    • آمادگی برای آینده:

اگر بعداً تصمیم بگیریم که حافظه‌ها رو آزاد کنیم (free())، اضافه‌کردن این قابلیت به سیستم خیلی راحت‌تر انجام میشه.

 

🔹 Memory Pools؛ مدیریت هوشمندانهٔ حافظه

حالا دوباره می‌رسیم به روش‌هایی که اجازه میدن برنامه بعد از استفاده، حافظه رو آزاد کنه.
یکی از بهترین تکنیک‌ها استفاده از پول‌های حافظه (Memory Pools) یا پارتیشن‌های حافظه است. در این روش، حافظه به بلوک‌های با اندازهٔ ثابت تقسیم میشه.
و این باعث میشه که احتمال تکه‌تکه‌شدن حافظه (Fragmentation) عملاً به صفر برسه. ✨

Memory Pool یه جور تعادل بین تخصیص کاملاً استاتیک و هیپ عمومی ایجاد می‌کنه؛
چون موقع طراحی میشه این پول‌ها رو دقیقاً متناسب با اندازهٔ درخواست‌های موردنیاز برنامه تنظیم کرد.

درحالی‌که malloc() و free() استاندارد باید برای هر جور استفاده‌ای آماده باشن،
توی خیلی از سیستم‌های امبدد، ما فقط یه برنامه ثابت داریم.
پس میشه هیپ یا پول حافظه رو دقیقاً برای نیازهای همون برنامه بهینه کرد — حتی اگه این تنظیمات برای برنامه‌های دیگه اصلاً جواب نده!

هر پول (Pool) یه آرایه از بلوک‌های حافظه داره. بلوک‌های استفاده‌نشده میتونن با هم تو یه لیست به هم وصل بشن، تا مدیریت‌شون راحت‌تر بشه. خود پول‌ها هم به‌صورت آرایه تعریف میشن. با این روش، دیگه

نیازی به اضافه‌کردن هدر به هر بلوک نیست، چون اندازهٔ بلوک تو هر پول ثابت و از قبل مشخصه.

مدیریت صحیح حافظه در سیستم های امبدد

چطور تخصیص انجام میشه؟

شکل ۵ نشون میده که چطور درخواست‌های حافظه مستقیماً به پولی که اندازهٔ بلوکش برابر با اندازهٔ درخواست (یا بزرگ‌تر از اون) هست، هدایت میشن. اگر پولی با اندازهٔ دقیق در دسترس نبود، میریم سراغ اولین پولی که بلوک‌هاش کمی بزرگ‌تر باشن. برای اینکه این سیستم خوب کار کنه، باید موقع طراحی تصمیم بگیریم:

  • چه اندازه بلوک‌هایی داشته باشیم؟
  • و از هر اندازه، چندتا بلوک در پول ذخیره کنیم؟

✅ نکته

اگه هنوز اندازهٔ دقیق درخواست‌های حافظهٔ برنامت رو نمی‌دونی، یه شروع خوب اینه که پول‌ها رو با اندازه‌هایی بر پایهٔ توان‌های عدد ۲ تعریف کنی:
مثلاً ۲، ۴، ۸، ۱۶، ۳۲، ۶۴ بایت و همین‌طور ادامه بده.

یکی از دلایل اصلی اینکه خیلی‌ها تو سیستم‌های امبدد از پول‌ها (Pools) برای ساختن هیپ استفاده می‌کنن اینه که:
با یه پیاده‌سازی دقیق، میشه زمان تخصیص و آزادسازی حافظه رو ثابت نگه داشت.
ولی توی هیپ‌های عمومی، همیشه باید از بین یه لیست متغیر از بلوک‌ها بگردی که زمان اجراش می‌تونه متغیر باشه. بعضی از پیاده‌سازی‌های malloc() باهوش‌تر عمل می‌کنن:

  • برای درخواست‌های کوچیک از Memory Pool استفاده می‌کنن،
  • ولی برای درخواست‌های بزرگ، می‌رن سراغ یه هیپ عمومی.
اطلاعات
275
0
3
لینک و اشتراک
profile

Zeus ‌

متخصص الکترونیک

وبسایت: https://sisoog.com

زئوس هستم ساکن المپ

مقالات بیشتر
slide

پالت | بازار خرید و فروش قطعات الکترونیک

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

آیسی | موتور جستجوی قطعات الکترونیک

سامانه آی سی سیسوگ (Isee) قابلیتی جدید و کاربردی از سیسوگ است. در این سامانه سعی شده است که جستجو، انتخاب و خرید مناسب تر قطعات برای کاربران تسهیل شود. جستجو در آیسی
family

سیسوگ‌شاپ | فروشگاه محصولات Quectel

فروشگاه سیسوگ مجموعه ای متمرکز بر تکنولوژی های مبتنی بر IOT و ماژول های M2M نظیر GSM، GPS، LTE، NB-IOT، WiFi، BT و ... جایی که با تعامل فنی و سازنده، بهترین راهکارها انتخاب می شوند. برو به فروشگاه سیسوگ
family

سیسوگ فروم | محلی برای پاسخ پرسش‌های شما

دغدغه همیشگی فعالان تخصصی هر حوزه وجود بستری برای گفتگو و پرسش و پاسخ است. سیسوگ فروم یک انجمن آنلاین است که بصورت تخصصی امکان بحث، گفتگو و پرسش و پاسخ در حوزه الکترونیک را فراهم می‌کند. پرسش در سیسوگ فرم
family

سیکار | اولین مرجع متن باز ECU در ایران

بررسی و ارائه اطلاعات مربوط به ECU (واحد کنترل الکترونیکی) و نرم‌افزارهای متن باز مرتبط با آن برو به سیکار
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
خانواده سیسوگ
سیسوگ‌شاپ

فروشگاه محصولات Quectel

پالت
سیسوگ فروم

محلی برای پاسخ پرسش‌های شما

سیسوگ جابز
سیسوگ
سیسوگ فروم
سی‌کار

اولین مرجع متن باز ECU در ایران

سیسوگ مگ
آی‌سی

موتور جستجوی قطعات الکترونیکی

سیسوگ آکادمی
پالت

بازار خرید و فروش قطعات الکترونیک

دیدگاه ها

become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله