এম্বেডেড সিস্টেমে মেমরি ম্যানেজমেন্ট এবং Efficient প্রোগ্রামিং টেকনিকস

এই লেখাটি মূলত এমবেডেড সিসটেমে আগ্রহী নতুনদের জন্য লেখা।আমি সল্প কথায় যথাসম্ভব ব্যাখ্যা করার চেষ্টা করেছি যদিও সবটুকু লেখার মাধ্যমে বোঝানো সম্ভব না। বর্তমানে C, C++ ,python , Lua সহ অনেক ল্যাঙ্গুয়েজই এম্বেডেড সিসটেম প্রোগ্রামিং এর জন্য জনপ্রিয়। এ লেখাটি শুধুমাত্র C এবং C++ কে কেন্দ্র করে লেখা। এই লেখা আমার সল্প জ্ঞান এবং সল্প অভিজ্ঞতার উপর ভিত্তি করে লেখা তাই ভুল না থাকাটা অস্বাভাবিক। তাই ভুলগুলো ঠিক করে নেবার সুযোগ দিলে ধন্য হব 🙂

যেকোনো এমবেডেড সিস্টেমের মগজ হলো processor এবং এর সাথে এক্সটার্ণাল অনেক পেরিফেরাল ডিভাইস থাকে। মাইক্রো কন্ট্রোলারে এক বা একাধিক প্রসেসরের সাথে এসবের কিছু পেরিফেরাল ডিভাইস সিংগেল চিপ প্যাকেজে বাজারজাত করা হয়।যেমন  আমাদের সবার মাঝে সুপরিচিত MicroChip ও Atmel এর মাইক্রোকন্ট্রলারগুলোর মাঝে আছে ৮-৩২ বিটের প্রসেসর, পেরিফেরাল ডিভাইস হিসেবে আছে ১৬-৫১২  KB ফ্ল্যাশ এবং 2KB SRAM , 1KB EEPROM , A/D converter ,SPI , UART devices এবং আরো অনেক কিছু। এম্বেডেড সিস্টেমের পেরিফেরাল ডিভাইসগুলার কানেকশন ও কনফিগারেশন সব সময়ই ম্যানুফ্যাকচারার ইন্ডিপেন্ডেন্ট এবং মোটামুটি straight forward. ভাল পারফরমেন্স প্রসেসরের স্পিড এবং ফার্মওয়্যার দুটোর উপরই নির্ভর করে। processor এর স্পিড হালকা overclocking করে বাড়ানো যায় যদিও সেটা রিস্কি এবং টাইম ক্রিটিক্যাল অপারেশনের সময় সমস্যা করতে পারে তাই পারফরম্যান্স এর জন্য ফার্মওয়্যার ডেভেলপমেন্ট এর সময় রিসোর্সগুলার সঠিক ব্যাবহার করা জরুরি এবং বলতে গেলে একমাত্র উপায়। খুব রাফলি লেখা কোড বা মেমরি ইউসেজ কনসিডার না করে লেখা কোড হয়ত লং টার্মে ক্রাশ করতে পারে অথবা প্রসেসরের unnecessary reset এর কারণ হতে পারে।

ব্যাসিকালি একটা সিসটেমে বিভিন্ন ধরনের মেমরি থাকতে পারে। এদের purpose আর  endurance এর মধ্যেও ব্যাপক পার্থক্য আছে। যেমন আমি চাইলেই ৩২ GB পেনড্রাইভ এর মেমরিকে র‌্যামের মত ব্যাবহার করে crisis 3 খেলতে পারব না। (যদিও পেজ মেমরি ব্যাবহার করে খুব হালকা পরিবর্তন আনা যায়)। আবার আমি চাইলেই আমার HD movie গুলা ল্যাপটপের র‌্যাম এ রেখে দিতে পারব না (kernel কখনো এলাও করবে না আর তাছাড়া who is that much stupid??:/)।আমাদের উইন্ডোজ বা লিনাক্স পিসির কারনেল অনেকটাই আমাদের জন্য রিসোরস মেমরি ম্যানেজ করে দেয়(যদিনা কিছু বদ সফটয়ার মেমরি লিক করতে থাকে)। কিন্তু কি হবে যদি আমার কোন অপারেটিং সিস্টেমই না থাকে?? Obviously আমি আমার মাইক্রোকন্ট্রলারের ৫১২ KB ফ্লাশে উইন্ডোজ সেটাপ দিতে পারব না।(উইন্ডোজ কখনো এ উদ্দেশ্যে তৈরি হয় নাই। কিছু সিস্টেমের জন্য আমরা যদিও লাইটওয়েট RTOS ব্যাবহার করি ( যেমন :FreeTos, TI-RTOS, QNX Neutrino) এর সুবিধা অসুবিধা দুইটাই আছে।) আমাদের একমাত্র উপায় থাকে নিজ থেকেই স্পেসিফিক চিপটার জন্য অপারেটিং সিস্টেম (it is a fancy word , for now let’s just use it :p) write করা। তার মানে মেমরি ম্যানেজ করা নিয়ে মাথা চুলকানি শুরু। শুরুতেই দেখা যাক সাধারণত আমাদের লিলিপুট সিসটেমে নরমালি কি ধরণের মেমরি থাকে।

১। ফ্ল্যাশ মেমরিঃ এখানে ইউজারের লেখা কোডটির ইনস্ট্রাকশন সিকুয়েন্স থাকে। অর্থাৎ বুটলোডার এবং আমাদের লেখা ফার্মওয়্যার এখানে স্টোর করা থাকবে। এই মেমরি থেকেই প্রসেসর একটা একটা করে ইন্সট্রাকশন পড়বে এবং execute করবে। এটা non-volatile মেমরি। অর্থাৎ একবার লিখে দিলে erase না করা পর্যন্ত data stored থাকবে।( হ্যা ঠিক পেনড্রাইভের মেমরির মতই.. harddisks are a little different …they are SSDs.. google for it :D)। এর read/write speed তুলনামূলক কম। এর আরেকটি সমস্যা হল endurance…only 10,000 erase/write cycles. মানে মাত্র ১০০০০ বার write করা যাবে। (তাই এমন কোন কোড যেটা ফ্ল্যাশ মেমরিতে frequently write করে সেটা ব্যাবহার করার আগে ১০ বার বিকল্প চিন্তা করা উচিত)

২।SRAM: Static RAM ( আরেকটা আছে DRAM, যেটা SRAM এর মত ব্যাবহার করার জন্য এক্সট্রা  circuit    লাগে।) কে আমি শুধু RAM হিসেবেই উল্লেখ করব। এটা volatile memory .Power off করলে এর data মুছে যায়। এটা ফ্ল্যাশ এর তুলনায় অনেক faster  (cache memory is even faster than RAM so memory size is very small). কিন্তু সমস্যা হল এইটার খরচ বেশি পড়ে বলে যে কোন সিসটেমে এর সাইজ ফ্ল্যাশ থেকে অনেক কম।যেমন Atmega328 এ আছে ২KB র‌্যাম্‌ (ফ্ল্যাশ ৫১২ KB) আবার esp-12 এ আছে প্রায় ৯২KB RAM(ফ্ল্যাশ 4MB).

৩। EEPROM:  non-volatile memory এবং এন্ডুরেন্স প্রায় 100,000 cycle erase/write। এর erase/write cycle ফ্ল্যাশ থেকে বেশি তাই ডাটা এখানে স্টোর করা যায়(যদিও আনলিমিটেড বার না)।

ফ্লাশ ,EEPROM এর ম্যানেজমেন্ট ইউজারের উপর ডিপেন্ডেন্ট, সে যখন ইচ্ছা read বা write করবে। তাই মূলত RAM এর ম্যানেজমেন্টএ একটু ফোকাস করতে হয়। RAM কে কয়টা সেগমেন্টে ভাগ করা হবে, এর কোথায় কি কি রাখবে সেটা কম্পাইলার  ঠিক করে দেয়, তাই প্রোগ্রামটা লোয়ার লেভেলে কি করছে তা বুঝা দরকার।

RAM কে কল্পনা করা যায় অনেকটা সেফ ডিপোসিট বক্স (নিচে) এর সারির মত

box

প্রতিটা বক্সের এক একটা আলাদা এড্রেস আছে। নির্দিষ্ট এড্রেসের বক্সে থাকতে পারে কোন টেম্পোরারি variable, কোন variable এর এড্রেস বা পয়েন্টার, কোন ফাংশনের রিটার্ণ এড্রেস অথবা কোন ইন্সট্রাকশন।ত

এখন দেখা যাক  C তে মেমরি মেনেজমেন্ট কিভাবে করা হয়।  C কম্পাইলার RAM এর টোটাল স্পেসকে  কয়েকটা ভাগে ভাগ করে

1.Text segment

2.Initialized data segment
3. Uninitialized data segment or BSS segment

4.Stack

5. Heap

 

মেমরি সেগমেন্ট এর সবচেয়ে ইম্পরটেন্ট ২ টা সেগমেন্ট হল stack এবং heap. তাই এগুলোর দিকেই বেশি ফোকাস করব।নিচে একটা মেমরির টিপিকাল লে-আউট দেয়া হল।

 

memseg

ধরা যাক একটা কোড লিখা হল অনেকটা এমন,

#include<library.h>               // এর যে কোডগুলা আমরা ব্যাবহার করব শুধু সেগুলাই ফ্ল্যাশ

এ যায়গা নিবে

#define    pi   3.1416                          //  এই ম্যক্রো টা আলাদাভাবে মেমরি ফ্ল্যাশ allocate করবে না, শুধু কোডের                                                                            যেসব যায়গায়  pi ডিক্লেয়ার করা সেসব যায়গায় 3.1416 রিপ্লেস করবে

int variable1= 0x22;               // initialized data segment এ স্টোর হবে

char p;                                     // BSS বা uninitialized data segment  এ স্টোর হবে

void main(){                           // main() function এর পয়েন্টার স্টোর হবে text segment এ

int a=0;                                   // Stack এ স্টোর হবে এবং ফাংশনের কাজ শেষে হারিয়ে যাবে

int b=98;                                 // Stack এ স্টোর হবে এবং ফাংশনের কাজ শেষে হারিয়ে যাবে

add(a,b);                                 // Stack এ add() এর এড্রেস, রিটার্ণ এড্রেস স্টোর হবে এবং ফাংশনের রিটার্ণ করলে                                                                                                                              হারিয়ে যাবে।

}

 

 

int add(int a,int b){                // add() function এর পয়েন্টার স্টোর হবে text segment এ

int sum = a+b;                        // Stack এ স্টোর হবে এবং ফাংশনের কাজ শেষে হারিয়ে যাবে

return sum;

}

 

 

Stack এ থাকে local variable গুলা আর, ফাংশনের return address. এইটা Last in First out (LIFO) data structure অনুযায়ী কাজ করে। উপরের example code টার ক্ষেত্রে যেমন…

প্রোগ্রাম শুরু হবে main() এ তাই main এর address Stack memory তে পুশ করবে। মেইন এক্সিকিউট করার সময় int a, int b variable গুলার জন্য মেমরি এলোকেট হবে Stack এ। এরপর যখন add() ফাংশন কল করা হচ্ছে তখন add ফাংশনটির শুরু যেই ফ্ল্যাশের যে এড্রেসে সেই এড্রেস Stack e pushed হবে। খেয়াল করা যাক যে main() এবং add() ফাংশন দুটির মধ্যে লাস্ট এ পুশ করা হয়েছিল add() কে । তাই তার এক্সিকিউশন প্রথমে শেষ হবে এবং ফার্স্ট এ Stack থেকে আউট হবে। এখন, কি হবে যদি আমি  add() ফাংশনটির ভেতরে আরো একটা ফাংশন কল করি? হ্যা, আগে সেই নতুন ফাংশনের কাজ শেষ হবে , তারপর add() এর কাজ শেষ হয়ে main() এ রিটার্ণ করবে এবং ওই নতুন ফাংশনটি এবং add() এর জন্য allocated memory release করে দিবে। কি হবে যদি recursive function ব্যাবহার করি যার কাজ শেষ করতে অনেকগুলা recursion এর দরকার হবে? Stack এর সাইজ বাড়তে বাড়তে এক সময় heap  এর যায়গায় চলে যাবে (আগের ছবি দেখুন) বা র‌্যাম এর শেষ ব্লকও allocate করে ফেলবে এবং এর পর stack overflow ঘটবে।

Heap তাহলে কি? Heap হল dynamically allocated memory. C তে যেটা করা হয় malloc(), realloc() ফাংশন দিয়ে( C++ এ new keyword দিয়ে)। আমরা জানি র‌্যাম অনেক ফাস্ট read write করতে পারে। যদি এমন সাইজের ডাটা আমাকে র‌্যাম এ স্টোর করতে হয় যেটার জন্য প্রয়োজনীয় জায়গার পরিমাণ আমার জানা নেই  (হতে পারে অনেক মানুষের পাসওয়ার্ড ,যেটা র‌্যাম এ রাখতে হবে ইনপুট দেয়া পাসওয়ার্ডের সাথে কুইক ম্যাচিং এর জন্য) তখন dynamically memory allocate করে নিতে হতে পারে। হিপ এ এলোকেটেড মেমরি স্ট্যাকের মত অটোমেটিক রিলিজ হয় না। একে সোর্স কোডেই রিলিজ করে নিতে হয় free() ফাংশন ব্যাবহার করে। যদি না করা হয় তাহলে প্রোগ্রাম প্রয়োজনীয় মেমরির চেয়ে বেশি মেমরি ইউজ করবে এবং স্ট্যাকের জন্য এভেইলেবল যায়গা কমে যাবে। একে বলা হয় মেমরি লিক।

হিপ এলোকেট , ডিলোকেট করায় সাবধান না হলে আরেকটা সমস্যা দেখা দেয় যাকে memory fragmentation বলে। হ্যা, এটা আমাদের হার্ডডিস্ক ফ্রাগমেন্টেড হওয়ার মতই, শুধু একে রান টাইমে defragment করা যায় না। এই প্রবলেমটা আরো সিরিয়াস কারণ ফ্রি র‌্যাম চেক করলে হয়ত দেখা যাবে অনেকটাই খালি আছে তবুও প্রোগ্রাম ক্রাশ করছে বা প্রসেসর রিসেট নিচ্ছে।নিচে ফ্রাগমেন্টেশন প্রবলেমটা ইলাস্ট্রেট করা হয়েছে।

 

kabom

তাহলে ইফিসিয়েন্ট প্রোগ্রাম লেখার জন্য কি কি করা দরকার?

১। ডাটা টাইপের সাইজ চেক করা এবং proper টাইপ ব্যাবহার করা। ধরা যাক, আমার একটা Boolean flag দরকার যার মান 1 অথবা 0 .  এর জন্য আমি যদি  int data type ( size 2/4 bytes) ইউজ করি তাহলে আমার ৩ বাইট যায়গাই অযথা নষ্ট হবে। আমি যদি boolean data type (size 1 byte) ব্যাবহার করি তাহলে শুধু ১ বাইট বা ৮ বিট মেমরি ব্যাবহার হবে যদিও আমার দরকার ১ টা বিট। এক্ষেত্রে structure ব্যাবহার করে আরো অপটিমাইজেশন আনা যায়। ( গুগলান :p )

২। Storage Class Specifiers ( static , volatile, extern ,register ) ব্যাবহার করা।

৩। যতটা সম্ভব Global variable avoid করা এবং কম ফাংশন ব্যাবহার করা( যদিও ফাংশন ব্যাবহার কোডকে হাজারগুন সহজ করে)। গ্লোবাল ভ্যারিএবল এবং ফাংশনের আগে  static  ডিক্লেয়ার করা যতটা সম্ভব। এবং প্রয়োজন না হলে ফাংশনের লোকাল ভ্যারিএবল্গুলা static না করা।

৪। variable signed নাকি unsigned সে অনুযায়ী Data type Declare করা।

৫। const ব্যাবহার করে কোন ভ্যালুকে Read Only portion এ রাখা যাতে RAM স্পেস ব্যাবহার না হয়।

৬। যেসব ভ্যারিয়েবল হার্ডওয়ারের চেঞ্জ এর উপর ডিপেন্ড করে অর্থাৎ প্রোগ্রাম ফ্লো এর বাইরে পরিবর্তন হতে পারে( যেমন বিভিন্ন ফ্ল্যাগ রেজিস্টার) তাদেরকে volatile declare করা।

৭। String class avoid করে C string ব্যাবহার করা। কারণ String dynamically memory allocate  করে নেয় যেটা ফ্রাগমেনটেশনের কারণ হতে পারে।

৮। সল্প মেমরির সিসটেমে dynamically memory allocate না করে সম্ভব হলে EEPROM ব্যাবহার করা।

৯। পর পর dynamically allocated মেমরি ফ্রি করে ফ্রাগমেন্টেশন কমানো।

১০। খুব কম access করা লাগে এমন ডাটাকে ফ্ল্যাশ বা EEPROM এ রাখা।

 

রিসোর্স লিঙ্কঃ

http://gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html

https://commons.wikimedia.org/wiki/File:School_lockers,_National_University_of_Singapore.jpg

http://www.geeksforgeeks.org/memory-layout-of-c-program/

http://www.atmel.com/products/microcontrollers/avr/

https://learn.adafruit.com/memories-of-an-arduino/optimizing-sram

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *