Table of Contents
Innebygde systemer krever ofte lokal datahåndtering uten overhead av fulle databaseservere. Implementering av en lett databasemotor i C gir utviklere direkte kontroll over minne, ytelse og lagring. Denne artikkelen går gjennom design og implementering av en enkel innebygd databasemotor, dekker datastrukturer, CRUD operasjoner, indeksering og utholdenhetsstrategier for ressursbegrensede miljøer.
Kjernekrav til en innebygd databasemotor
En innebygd databasemotor må fungere innenfor trange grenser for RAM, blits og prosesshastighet. Typiske krav inkluderer deterministisk oppførsel, minimal kodeavtrykk og ingen eksterne avhengigheter. Motoren bør støtte grunnleggende operasjoner: innlegg, hente, oppdatere, slette og søk. Mange innebygde databaser må også overleve strømtap og lagre data om ikke-flyktig minne som EEPROM, SPI-flash eller SD-kort.
Valg av riktig datastruktur er den første designbeslutningen. Arrays er enkle, men begrensede av statiske størrelser. Linked lister tillater dynamisk vekst, men legger til peker overhead. For balansert ytelse, kan en hybrid tilnærming ved hjelp av fast størrelse platebassenger med en gratisliste fungere godt. SQLites designprinsipp tilbyr nyttig innsikt selv for mye enklere motorer.
Designe opptakslagringslaget
Lagringslaget administrerer hvordan poster er lagt i minne eller på disk. Et vanlig mønster er å behandle hver post som en fast lengde struktur for å forenkle pekeraritikk og tillate direkte indeksering. Variabel-lengde poster komplisere fragmentering og krever en minne manager.
Faste length-plater med et rekordbasseng
Definer et maksimalt antall poster (f.eks. ) og tildel en statisk rekkefølge. En punktgrafikk eller frilistespor som spilleautomater brukes. Når en post slettes, returnerer sporet til bassenget. Denne tilnærmingen unngår dynamisk tildeling og garanterer O(1) tildelingstid.
#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];
For vedvarende lagring kan bassenget sikkerhetskopieres av en fil eller et område av blits. Ved oppstart leser motoren bassenget fra ikke-flyktig minne til RAM, og ved nedstenging (eller periodisk) skriver den det tilbake.
Dataintegritetskontroll
Legg til et enkelt kontrollsumfelt i hver rekord for å oppdage korrupsjon. CRC-32 er et godt valg for innebygde systemer, balansere kompleksiteten med feildetekteringsstyrke.
Basis CRUD-operasjoner
Med det platebasseng definerte, implementere funksjoner for å sette inn, finne, oppdatere og slette poster. Søkeoperasjoner er ofte ytelsesflaskerhals, så et naivt lineært søk er akseptabelt bare for små databaser (et par hundre poster).
Sett inn med gratislistestyring
Behold en gratis liste over indekser. På sett inn, pop en indeks fra gratislisten, fyll rekorden og marker den aktiv. Frilisten selv kan være en enkel stabel ved hjelp av en rekke heltall.
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;
}
Søk og oppdater
En enkel søk gjentar over bassenget, sjekker bare aktive poster. For oppdateringer, lokalisere posten, endre feltene og eventuelt sjekke gratislisten hvis posten slettes.
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;
}
Avanserte konsept: Indexing og varighet
Etter hvert som antall poster vokser, blir lineær søk dyrt. Legger til en enkel indeks - som en sortert rekke nøkkelpekere eller et binær søketre -improves retrieval tid. For innebygde systemer, er en statisk tabell sortert på nøkkelen med binær søk ofte tilstrekkelig hvis innsatser er sjelden.
Sortert indeks med binær søk
Behold en parallell rekke registerindekser sortert etter søkenøkkelen (f.eks. ID). Når du setter inn en ny post, sett inn indeksen i den sorterte tabellen ved hjelp av en binær innsetting. Deretter blir søk O( log n) via binær søk. Deleksjoner krever å flytte indeksarrangøren, men for små databaser er dette akseptabelt.
Vedvarende lagring ved hjelp av fil I/O
På mikrokontrollere uten et filsystem er råflash-minneskrivere vanlige. På Linux-baserte innebygde systemer fungerer standard POSIX // godt. Bruk et enkelt filformat: skrive et header (magisk nummer, versjon, rekordtall) etterfulgt av råbassengarrangementet. For krasjmotstand kan en skrive-ahead-logg (WAL) hjelpe til, men for grunnmotorer er det nok å skrive en enkelt atom (som passer på én flash-side). FreertOS+FAT et lett systemalternativ for dypt innebygde systemer.
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;
}
}
Håndtering av rester og avdrag
Innbyggede databasemotorer står overfor en konstant avlevering mellom funksjoner og ressursbruk. Velger hvilke funksjoner som skal inkluderes avhenger av programmet:
- ACID compliance ⁇ Vanligvis ikke nødvendig. Enkelt atom skriver tilstrekkelig for de fleste sensordatalogging.
- Indexing ⁇ Legger til innleggskostnad, men hastigheter opp leser. For skrive-tung arbeidsbelastning, hoppe over indekser.
- Konvalusjon ⁇ De fleste innebygde systemene kjører en enkelt tråd. Levering dempes hvis du bruker en RTOS.
- ] ⁇ Statisk tildeling er tryggere enn dynamisk . Bruk konstanter for bufferstørrelser.
- Power tap ⁇ For flashlagring, unngå hyppige små skriver. Batch oppdateringer og bruk en dobbel-buffer-skjema.
Praktisk eksempel: En temperaturloggerdatabase
Tenk på en IoT-temperatursensor som registrerer lesing hvert minutt og lagrer dem lokalt i 24 timer. Databasemotoren må håndtere 1440 poster (ett per minutt). Hver rekord kan inneholde en tidsstempel (unix epoke), en temperatur (flytende) og en sensor-ID. Ved å bruke fast-registrert bassenget med 256 spor er for liten; her trenger vi . Med 28 bytes per rekord (4+4+4+4 for overhead) bruker bassenget ca. 40 KB, mulig på mange mikrokontrollere med 128 KB RAM.
Motoren kan lagre data på en ring-buffer mote: når bassenget er full, den eldste rekorden er overskrivet. Implementer en -head - peker for neste skriveautomat og a - detaljer - for den eldste aktive rekorden. Dette unngår friliste logikk og gir O(1) innsats. Søk kan optimaliseres med et binær søk på tidsstempler hvis poster lagres i kronologisk rekkefølge.
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
Innsetter en lesing: skrive til , trinn modulo ], trinn ] (kapslet på ]). Søker etter en bestemt tidsstempel: hvis count == MAX LOGS, loggen er en sammenhengende sekvens fra hodet til hodet-1 (wrap). Bruk binær søk etter å ha datorisert den virtuelle start. Dette mønsteret er ekstremt lett og mye brukt i telemetrisystemer. ]Ring buffer grunnleggende gir ekstra implementeringsalternativer.
Testing og optimalisering på målhardware
Test alltid databasemotoren på den faktiske innebygde maskinvaren. Emulatorer mangler tidsbegrensninger, spesielt for flash-skrivesykluser og power tap scenarier. Overvåk RAM-bruk med et profileringsverktøy og verifiser kant tilfeller: full lagring, ødelagte data, tilbakestille midtskrivelse. En enkel test sele kjører tusenvis av tilfeldige innsatser, søk og sletter mens du sammenligner med en gylden modell.
- Flash slitasje utjevning ⁇ Hvis du skriver til EEPROM eller NOR-flash, begrenser du totale skrivinger til noen hundre tusen. En sirkulær buffer med slitasjeutjevning gir levetid.
- Power-fail trygt[] ⁇ Bruk et pliktmerke: skriv et flaggbyte etter en komplett sats med poster. På omstart, sjekk flagget; Hvis det mangler, kast det siste partiet og gå tilbake til forrige tilstand.
- Minne basseng ⁇ Unngå dyp recitering. Hold funksjonen anropshauger grunn. Bruk statiske buffere for fil I/O.
Konklusjon
Bygge en grunnleggende databasemotor i C for innebygde systemer er en praktisk tilnærming til å administrere data i ressursbegrensede enheter. Ved å fokusere på enkle datastrukturer som faste opptaksbassenger og ringbuffere, oppnår utviklerne effektive CRUD-operasjoner med minimal overhead. Legge til valgfri indeksering og grunnleggende utholdenhet gjør en enkel rekke til en pålitelig lokal datalagring. Teknikkene som er beskrevet her skala fra små sensorloggere til mer sofistikerte systemer, og de gir et grunnlag for å forstå hvordan større innebygde databaser som SQLite eller Berkeley DB opererer under hetten.