Table of Contents
एम्बेडेड सिस्टम को अक्सर पूर्ण डेटाबेस सर्वर के ओवरहेड के बिना स्थानीय डेटा प्रबंधन की आवश्यकता होती है। C में एक हल्के डेटाबेस इंजन को लागू करने से डेवलपर्स को स्मृति, प्रदर्शन और भंडारण पर सीधे नियंत्रण मिलता है। यह लेख एक सरल एम्बेडेड डेटाबेस इंजन के डिजाइन और कार्यान्वयन के माध्यम से चलता है, जिसमें डेटा संरचनाओं, CRUD संचालन, अनुक्रमण और संसाधन-वित्तीय वातावरण के लिए दृढ़ता रणनीति शामिल है।
एक एम्बेडेड डेटाबेस इंजन के लिए कोर आवश्यकताएं
एक एम्बेडेड डेटाबेस इंजन को रैम, फ्लैश और प्रोसेसिंग स्पीड पर तंग सीमाओं के भीतर काम करना चाहिए। विशिष्ट आवश्यकताओं में अवधी व्यवहार, न्यूनतम कोड पदचिह्न और कोई बाहरी निर्भरता शामिल नहीं है। इंजन को बुनियादी कार्यों का समर्थन करना चाहिए: सम्मिलित, पुनर्प्राप्ति, अद्यतन, हटाएं और खोज। कई एम्बेडेड डेटाबेस को गैर-वोलाटाइल मेमोरी जैसे EEPROM, SPI फ्लैश, या SD कार्ड पर बिजली हानि और स्टोर डेटा को जीवित रखने की भी आवश्यकता है।
सही डेटा संरचना का चयन करना पहला डिजाइन निर्णय है। ऐरे स्थिर आकारों से सरल लेकिन सीमित हैं। लिंक्ड लिस्ट गतिशील विकास की अनुमति देती हैं लेकिन पॉइंटर ओवरहेड जोड़ती हैं। संतुलित प्रदर्शन के लिए, एक हाइब्रिड दृष्टिकोण जो एक मुफ्त सूची के साथ फिक्स्ड-आकार के रिकॉर्ड पूल का उपयोग कर अच्छी तरह से काम कर सकता है। SQLite के डिजाइन सिद्धांतों बहुत सरल इंजनों के लिए भी उपयोगी अंतर्दृष्टि प्रदान करते हैं।
रिकॉर्ड संग्रहण परत को डिजाइन करना
भंडारण परत प्रबंधन करता है कि कैसे रिकॉर्ड स्मृति में या डिस्क पर रखा जाता है। एक सामान्य पैटर्न प्रत्येक रिकॉर्ड को एक निश्चित लंबाई वाली संरचना के रूप में पॉइंटर अंकगणित को सरल बनाने और प्रत्यक्ष अनुक्रमण की अनुमति देने के लिए इलाज करना है। चर लंबाई रिकॉर्ड जटिल विखंडन और एक स्मृति प्रबंधक की आवश्यकता होती है।
एक रिकॉर्ड पूल के साथ फिक्स्ड-लेंथ रिकॉर्ड
रिकॉर्ड की अधिकतम संख्या (जैसे, ) को परिभाषित करें और एक स्थिर सरणी आवंटित करें। एक बिटमैप या फ्री-लिस्ट ट्रैक जो स्लॉट का उपयोग किया जाता है। जब एक रिकॉर्ड हटा दिया जाता है, तो इसका स्लॉट पूल में वापस आता है। यह दृष्टिकोण गतिशील आवंटन से बच जाता है और ओ (1) आवंटन समय की गारंटी देता है।
#define MAX_RECORDS 256
typedef struct {
int id;
char name[32];
float value;
int active; // 1 if slot in use
} Record;
Record pool[MAX_RECORDS];
लगातार भंडारण के लिए, पूल को एक फ़ाइल या फ्लैश के क्षेत्र द्वारा समर्थित किया जा सकता है। स्टार्ट-अप पर, इंजन ने गैर-वोलाटाइल मेमोरी से रैम में पूल को पढ़ाया, और शटडाउन (या समय-समय पर) पर यह इसे वापस लिखता है।
डेटा अखंडता जांच
भ्रष्टाचार का पता लगाने के लिए प्रत्येक रिकॉर्ड में एक सरल चेकसम क्षेत्र जोड़ें। CRC-32 एम्बेडेड सिस्टम के लिए एक अच्छा विकल्प है, त्रुटि पहचान ताकत के साथ संतुलन जटिलता।
मूल CRUD संचालन को कार्यान्वित करना
रिकॉर्ड पूल परिभाषित के साथ, रिकॉर्ड डालने, खोजने, अद्यतन करने और हटाने के लिए कार्यों को लागू करें। खोज संचालन अक्सर प्रदर्शन की बोतलबंदी होती है, इसलिए केवल छोटे डेटाबेस (कुछ सौ रिकॉर्ड) के लिए एक नौसैनिक खोज स्वीकार्य है।
फ्रीलिस्ट प्रबंधन के साथ सम्मिलित करें
सूचकांकों की एक स्वतंत्र सूची बनाए रखें। सम्मिलित होने पर, एक इंडेक्स को मुफ्त-लिस्ट से पॉप करें, रिकॉर्ड को भरें और इसे सक्रिय रूप से चिह्नित करें। स्वतंत्र सूची स्वयं पूर्णांकों की एक सरणी का उपयोग करके एक सरल स्टैक हो सकती है।
int free_list[MAX_RECORDS];
int free_count = MAX_RECORDS;
for (int i = 0; i < MAX_RECORDS; i++) free_list[i] = i;
int db_insert(int id, const char* name, float value) {
if (free_count == 0) return -1; // no space
int idx = free_list[--free_count];
pool[idx].id = id;
strncpy(pool[idx].name, name, sizeof(pool[idx].name)-1);
pool[idx].value = value;
pool[idx].active = 1;
return idx;
}
खोज और अद्यतन
एक साधारण खोज यह है कि पूल पर केवल सक्रिय रिकॉर्ड की जाँच की जाती है। अद्यतन के लिए, रिकॉर्ड का पता लगाएं, फ़ील्ड को संशोधित करें और वैकल्पिक रूप से मुक्त सूची की जाँच करें यदि रिकॉर्ड हटा दिया गया है।
int db_find_by_id(int id) {
for (int i = 0; i < MAX_RECORDS; i++) {
if (pool[i].active && pool[i].id == id) return i;
}
return -1;
}
void db_update(int idx, float new_value) {
if (idx >= 0 && idx < MAX_RECORDS && pool[idx].active)
pool[idx].value = new_value;
}
उन्नत अवधारणाएं: इंडेक्सिंग और दृढ़ता
चूंकि रिकॉर्ड की संख्या बढ़ती है, रैखिक खोज महंगी हो जाती है। एक साधारण सूचकांक जोड़ना - कुंजी सूचकों या द्विआधारी खोज पेड़ की एक छंटनी वाली सरणी - पुनर्प्राप्ति समय में सुधार करता है। एम्बेडेड सिस्टम के लिए, द्विआधारी खोज के साथ कुंजी पर छंटनी की एक स्थिर सारणी अक्सर पर्याप्त होती है यदि आवेषण अक्सर अपर्याप्त होते हैं।
बाइनरी खोज के साथ क्रमबद्ध सूचकांक
खोज कुंजी (जैसे, ID) द्वारा क्रमबद्ध रिकॉर्ड सूचकांकों की एक समानांतर सरणी बनाए रखें। जब एक नया रिकॉर्ड डालने के बाद, अपने इंडेक्स को द्विआधारी खोज के माध्यम से क्रमबद्ध सरणी में डालें। फिर खोज द्विआधारी खोज के माध्यम से ओ (लॉग एन) बन जाती है। हटावों को इंडेक्स सरणी को स्थानांतरित करने की आवश्यकता होती है, लेकिन छोटे डेटाबेस के लिए यह स्वीकार्य है।
लगातार भंडारण फ़ाइल I/O का उपयोग करना
बिना किसी फ़ाइल सिस्टम के माइक्रोकंट्रोलर पर, कच्चे फ्लैश मेमोरी लिखते हैं आम हैं लिनक्स आधारित एम्बेडेड सिस्टम पर, मानक POSIX /]]/] अच्छी तरह से काम करते हैं। एक सरल फ़ाइल प्रारूप का उपयोग करें: एक हेडर (मैजिक नंबर, संस्करण, रिकॉर्ड गिनती) लिखने के बाद कच्चे पूल सरणी। दुर्घटना लचीलापन के लिए, एक लिखने वाला लॉग (WAL) मदद कर सकता है, लेकिन बुनियादी इंजनों के लिए एक एकल परमाणु लिखने (जो एक फ्लैश पेज में फिट बैठता है) पर्याप्त है। FreeRTOS + FAT]
void db_save(const char* filename) {
FILE* fp = fopen(filename, "wb");
if (!fp) return;
fwrite(pool, sizeof(pool), 1, fp);
fclose(fp);
}
void db_load(const char* filename) {
FILE* fp = fopen(filename, "rb");
if (!fp) return;
fread(pool, sizeof(pool), 1, fp);
fclose(fp);
// Rebuild free-list from pool
free_count = 0;
for (int i = 0; i < MAX_RECORDS; i++) {
if (!pool[i].active) free_list[free_count++] = i;
}
}
हैंडलिंग कंस्ट्रक्शन और ट्रेड-ऑफ
एम्बेडेड डेटाबेस इंजन सुविधाओं और संसाधन उपयोग के बीच एक स्थिर व्यापार बंद का सामना करते हैं। किस सुविधाओं को शामिल करने के लिए चुनना आवेदन पर निर्भर करता है:
- ACID अनुपालन - आमतौर पर आवश्यक नहीं है। सरल परमाणु अधिकांश सेंसर डेटा लॉगिंग के लिए पर्याप्त लिखते हैं।
- ]Indexing – सम्मिलित लागत जोड़ता है लेकिन पढ़ने की गति को बढ़ाता है। लिखने के लिए भारी वर्कलोड, सूचकांक छोड़ें।
- Concurrency – अधिकांश एम्बेडेड सिस्टम एक धागा चलाते हैं। यदि एक RTOS का उपयोग कर उत्तोलन mutexes।
- ]Memory footprint - स्थैतिक आवंटन गतिशील ] से सुरक्षित है। ] का प्रयोग करें बफर आकार के लिए स्थिरांक।
- ]Power loss – फ्लैश स्टोरेज के लिए, लगातार छोटे लिखने से बचें। बैच अद्यतन और एक डबल बफर योजना का उपयोग करें।
प्रैक्टिकल उदाहरण: एक तापमान लकड़हारा डाटाबेस
एक IoT तापमान सेंसर पर विचार करें जो हर मिनट रीडिंग रिकॉर्ड करता है और उन्हें स्थानीय रूप से 24 घंटे तक स्टोर करता है। डेटाबेस इंजन को 1440 रिकॉर्ड (एक मिनट) को संभालना चाहिए। प्रत्येक रिकॉर्ड में टाइमस्टैम्प (यूनिक्स एपोच), एक तापमान (फ्लोट), और एक सेंसर आईडी हो सकता है। 256 स्लॉट के साथ निश्चित रिकॉर्ड पूल का उपयोग बहुत छोटा है; यहां हमें आवश्यकता है । 28 बाइट प्रति रिकॉर्ड (4+4+4+4+4+4 के साथ) के साथ, पूल लगभग 40 KB का उपयोग करता है, जो 128 KB रैम वाले कई माइक्रोकंट्रोलरों पर संभव है।
इंजन एक रिंग-बफर फैशन में डेटा स्टोर कर सकता है: जब पूल पूरा होता है, तो सबसे पुराना रिकॉर्ड ओवरराइट किया जाता है। अगले लेखन स्लॉट के लिए "हेड" पॉइंटर को लागू करें और सबसे पुराने सक्रिय रिकॉर्ड के लिए "टेल" लागू करें। यह मुफ्त सूची तर्क से बचा जाता है और O (1) सम्मिलित करता है। खोज को टाइमस्टैम्प पर द्विआधारी खोज के साथ अनुकूलित किया जा सकता है यदि रिकॉर्ड क्रोनोलॉजिकल क्रम में संग्रहीत किए जाते हैं।
typedef struct {
uint32_t timestamp;
float temp_c;
uint8_t sensor_id;
uint8_t active; // not needed if using ring buffer
} TemperatureRecord;
#define MAX_LOGS 1440
TemperatureRecord logs[MAX_LOGS];
uint16_t head = 0; // next write position
uint16_t count = 0; // number of valid records
एक रीडिंग डालने: , वृद्धि modulo ], वृद्धि (]]]]]] पर कब्जा कर लिया)। एक विशिष्ट समय के लिए खोज: यदि गिनती == मैक्स LOGS, तो लॉग सिर से सिर तक एक विषम अनुक्रम है-1 (रैप)। आभासी प्रारंभ की गणना के बाद द्विआधारी खोज का उपयोग करें। यह पैटर्न अत्यंत हल्का और व्यापक रूप से टेलीमेट्री सिस्टम में उपयोग किया जाता है। Ring बफर मूल अतिरिक्त कार्यान्वयन विकल्प प्रदान करते हैं।
लक्ष्य हार्डवेयर पर परीक्षण और अनुकूलन
हमेशा वास्तविक एम्बेडेड हार्डवेयर पर डेटाबेस इंजन का परीक्षण करें। एमुलेटर्स टाइमिंग बाधाओं को याद करते हैं, विशेष रूप से फ्लैश लिखने के चक्र और बिजली हानि परिदृश्यों के लिए। एक प्रोफाइलिंग टूल के साथ रैम उपयोग की निगरानी करें और किनारे के मामलों को सत्यापित करें: पूर्ण भंडारण, भ्रष्ट डेटा, मध्य-लेखन रीसेट करें। एक सरल परीक्षण दोहन एक सुनहरा मॉडल की तुलना करते समय हजारों यादृच्छिक आवेषण, खोज और हटाता है।
- ]फ्लैश वियर लेवलिंग - यदि EEPROM या NOR फ्लैश को लिखते हैं, तो कुल मिलाकर कुछ सौ हजार तक लिखते हैं। एक पहनने-स्तर परत के साथ एक परिपत्र बफर जीवन काल को बढ़ाता है।
- ]]Power-fail safe – एक प्रतिबद्ध चिह्न का प्रयोग करें: रिकॉर्ड के एक पूर्ण बैच के बाद एक ध्वज बाइट लिखें। पुनरारंभ पर, ध्वज की जांच करें; यदि लापता हो तो अंतिम बैच को त्याग दें और पिछले राज्य में वापस लौटें।
- Memory pooling – गहरी पुनरावृत्ति से बचें। कार्य कॉल स्टैक उथले रखें। फ़ाइल I/O के लिए स्थैतिक बफर का प्रयोग करें।
निष्कर्ष
एम्बेडेड सिस्टम के लिए C में एक बुनियादी डेटाबेस इंजन का निर्माण संसाधन-सीमित उपकरणों में डेटा के प्रबंधन के लिए एक व्यावहारिक दृष्टिकोण है। फिक्स्ड-रिकॉर्ड पूल और रिंग बफर जैसे सरल डेटा संरचनाओं पर ध्यान केंद्रित करके, डेवलपर्स को न्यूनतम ओवरहेड के साथ कुशल CRUD संचालन प्राप्त होता है। वैकल्पिक अनुक्रमण और बुनियादी दृढ़ता जोड़ना एक विश्वसनीय स्थानीय डेटास्टोर में एक सरल सरणी बदल जाता है। तकनीकों ने यहां छोटे सेंसर लॉगर से अधिक परिष्कृत प्रणालियों तक स्केल किया है, और वे यह समझने के लिए एक नींव प्रदान करते हैं कि SQLite या Berkeley DB जैसे बड़े एम्बेडेड डेटाबेस हुड के तहत काम करते हैं।