Min implementering av en ringbuffert i NOR flash

förhistoria

Det finns varuautomater av egen design. Inuti Raspberry Pi och lite ledningar pÄ ett separat kort. En myntmottagare, en sedelmottagare, en bankterminal ansluts... Allt styrs av ett egenskrivet program. Hela arbetshistoriken skrivs till en logg pÄ ett flashminne (MicroSD), som sedan överförs via Internet (med ett USB-modem) till servern, dÀr den lagras i en databas. FörsÀljningsinformation laddas in i 1c, det finns Àven ett enkelt webbgrÀnssnitt för övervakning osv.

Det vill sÀga, tidskriften Àr avgörande - för redovisning (intÀkter, försÀljning, etc.), övervakning (alla typer av misslyckanden och andra force majeure-förhÄllanden); Detta kan man sÀga Àr all information vi har om den hÀr maskinen.

problem

Flash-enheter visar sig vara mycket opÄlitliga enheter. De misslyckas med avundsvÀrd regelbundenhet. Detta leder till bÄde maskinstopp och (om loggen av nÄgon anledning inte kunde överföras online) till dataförlust.

Detta Àr inte den första erfarenheten av att anvÀnda flash-enheter, innan detta fanns ett annat projekt med mer Àn hundra enheter, dÀr tidningen lagrades pÄ USB-minnen, det fanns ocksÄ problem med tillförlitligheten, ibland antalet som misslyckades i en mÄnad var i dussintal. Vi provade olika flashenheter, inklusive mÀrkesvaror med SLC-minne, och vissa modeller Àr mer pÄlitliga Àn andra, men att byta ut flashenheter löste inte problemet radikalt.

Varning! LÄnglÀst! Om du inte Àr intresserad av "varför", utan bara av "hur", kan du gÄ direkt i trÄden artikeln.

beslutet

Det första som kommer att tÀnka pÄ Àr: överge MicroSD, installera till exempel en SSD och starta frÄn den. Teoretiskt möjligt, förmodligen, men relativt dyrt och inte sÄ tillförlitligt (en USB-SATA-adapter lÀggs till; felstatistik för budget-SSD-enheter Àr inte heller uppmuntrande).

USB HDD ser inte heller ut som en sÀrskilt attraktiv lösning.

DÀrför kom vi till det hÀr alternativet: lÀmna uppstarten frÄn MicroSD, men anvÀnd dem i skrivskyddat lÀge, och lagra operationsloggen (och annan information som Àr unik för en viss hÄrdvara - serienummer, sensorkalibreringar, etc.) nÄgon annanstans .

Ämnet med skrivskyddad FS för hallon har redan studerats inifrĂ„n och ut, jag kommer inte att uppehĂ„lla mig vid implementeringsdetaljer i den hĂ€r artikeln (men om det finns intresse kanske jag skriver en miniartikel om detta Ă€mne). Den enda punkt jag skulle vilja notera Ă€r att bĂ„de av personlig erfarenhet och frĂ„n recensioner av de som redan har implementerat det, finns det en vinst i tillförlitlighet. Ja, det Ă€r omöjligt att helt bli av med sammanbrott, men att avsevĂ€rt minska deras frekvens Ă€r fullt möjligt. Och korten hĂ„ller pĂ„ att förenas, vilket gör utbytet mycket lĂ€ttare för servicepersonalen.

HĂ„rdvaran delen

Det rÄdde ingen speciell tvekan om valet av minnestyp - NOR Flash.
Argument:

  • enkel anslutning (oftast SPI-bussen, som du redan har erfarenhet av att anvĂ€nda, sĂ„ inga hĂ„rdvaruproblem förutses);
  • löjligt pris;
  • standardoperativt protokoll (implementeringen finns redan i Linux-kĂ€rnan, om du vill kan du ta en tredje part, som ocksĂ„ finns, eller till och med skriva din egen, lyckligtvis Ă€r allt enkelt);
  • tillförlitlighet och resurs:
    frÄn ett typiskt datablad: data lagras i 20 Är, 100000 XNUMX raderingscykler för varje block;
    frÄn tredje parts kÀllor: extremt lÄg BER, postulerar inget behov av felkorrigeringskoder (vissa verk anser ECC för NOR, men vanligtvis betyder de fortfarande MLC NOR; detta hÀnder ocksÄ).

LÄt oss uppskatta kraven pÄ volym och resurs.

Jag skulle vilja att uppgifterna garanteras sparas i flera dagar. Detta Àr nödvÀndigt sÄ att försÀljningshistoriken inte gÄr förlorad vid eventuella problem med kommunikationen. Vi kommer att fokusera pÄ 5 dagar, under denna period (Àven med hÀnsyn till helger och helgdagar) problemet kan lösas.

Vi samlar för nÀrvarande cirka 100 kb loggar per dag (3-4 tusen poster), men gradvis vÀxer denna siffra - detaljerna ökar, nya hÀndelser lÀggs till. Plus, ibland finns det skurar (vissa sensorer börjar spamma med falska positiva, till exempel). Vi kommer att berÀkna för 10 tusen poster 100 byte vardera - megabyte per dag.

Totalt kommer 5MB ren (vÀl komprimerad) data ut. Mer till dem (grov uppskattning) 1 MB servicedata.

Det vill sÀga, vi behöver ett 8MB-chip om vi inte anvÀnder komprimering, eller 4MB om vi anvÀnder det. Ganska realistiska siffror för denna typ av minne.

NÀr det gÀller resursen: om vi planerar att hela minnet inte ska skrivas om mer Àn en gÄng var 5:e dag, sÄ fÄr vi under 10 Ärs tjÀnst mindre Àn tusen omskrivningscykler.
LÄt mig pÄminna dig om att tillverkaren lovar hundra tusen.

Lite om NOR vs NAND

Idag Àr naturligtvis NAND-minne mycket mer populÀrt, men jag skulle inte anvÀnda det för det hÀr projektet: NAND, till skillnad frÄn NOR, krÀver nödvÀndigtvis anvÀndning av felkorrigeringskoder, en tabell med dÄliga block, etc., och Àven benen pÄ NAND-chips brukar vara mycket mer.

Nackdelarna med NOR inkluderar:

  • liten volym (och följaktligen högt pris per megabyte);
  • lĂ„g kommunikationshastighet (till stor del pĂ„ grund av att ett seriellt grĂ€nssnitt anvĂ€nds, vanligtvis SPI eller I2C);
  • lĂ„ngsam radering (beroende pĂ„ blockstorleken tar det frĂ„n en brĂ„kdel av en sekund till flera sekunder).

Det verkar som att det inte Àr nÄgot kritiskt för oss, sÄ vi fortsÀtter.

Om detaljerna Àr intressanta har mikrokretsen valts at25df321a (Detta Àr dock oviktigt, det finns mÄnga analoger pÄ marknaden som Àr kompatibla i pinout och kommandosystem; Àven om vi vill installera en mikrokrets frÄn en annan tillverkare och/eller en annan storlek, kommer allt att fungera utan att Àndra koda).

Jag anvÀnder drivrutinen inbyggd i Linux-kÀrnan; pÄ Raspberry, tack vare stöd för enhetstrÀdöverlagring, Àr allt vÀldigt enkelt - du mÄste lÀgga den kompilerade överlagringen i /boot/overlays och Àndra /boot/config.txt nÄgot.

Exempel pÄ dts-fil

För att vara Àrlig Àr jag inte sÀker pÄ att det Àr skrivet utan fel, men det fungerar.

/*
 * Device tree overlay for at25 at spi0.1
 */

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709"; 

    /* disable spi-dev for spi0.1 */
    fragment@0 {
        target = <&spi0>;
        __overlay__ {
            status = "okay";
            spidev@1{
                status = "disabled";
            };
        };
    };

    /* the spi config of the at25 */
    fragment@1 {
        target = <&spi0>;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            flash: m25p80@1 {
                    compatible = "atmel,at25df321a";
                    reg = <1>;
                    spi-max-frequency = <50000000>;

                    /* default to false:
                    m25p,fast-read ;
                    */
            };
        };
    };

    __overrides__ {
        spimaxfrequency = <&flash>,"spi-max-frequency:0";
        fastread = <&flash>,"m25p,fast-read?";
    };
};

Och ytterligare en rad i config.txt

dtoverlay=at25:spimaxfrequency=50000000

Jag kommer att utelĂ€mna beskrivningen av att ansluta chipet till Raspberry Pi. Å ena sidan Ă€r jag ingen expert pĂ„ elektronik, Ă„ andra sidan Ă€r allt hĂ€r banalt Ă€ven för mig: mikrokretsen har bara 8 ben, varav vi behöver jord, kraft, SPI (CS, SI, SO, SCK ); nivĂ„erna Ă€r desamma som för Raspberry Pi, ingen extra ledning krĂ€vs - anslut bara de angivna 6 stiften.

Problem uttalande

Som vanligt gÄr problemformuleringen igenom flera iterationer, och det verkar för mig att det Àr dags för nÀsta. SÄ lÄt oss sluta, sÀtta ihop det som redan har skrivits och förtydliga detaljerna som ligger kvar i skymundan.

SĂ„ vi har beslutat att loggen ska lagras i SPI NOR Flash.

Vad Àr NOR Flash för de som inte vet?

Detta Àr ett icke-flyktigt minne med vilket du kan göra tre operationer:

  1. LĂ€sning:
    Den vanligaste lÀsningen: vi överför adressen och lÀser sÄ mÄnga byte som vi behöver;
  2. rekord:
    Att skriva till NOR flash ser ut som en vanlig, men den har en egenhet: du kan bara Àndra 1 till 0, men inte vice versa. Till exempel, om vi hade 0x55 i en minnescell, efter att ha skrivit 0x0f till den, kommer 0x05 redan att lagras dÀr (se tabellen nedan);
  3. Radera:
    Naturligtvis mÄste vi kunna göra den motsatta operationen - Àndra 0 till 1, det Àr precis vad raderingsoperationen Àr till för. Till skillnad frÄn de tvÄ första fungerar den inte med byte, utan med block (minsta raderingsblock i det valda chipet Àr 4kb). Radera förstör hela blocket och Àr det enda sÀttet att Àndra 0 till 1. NÀr man arbetar med flashminne mÄste man dÀrför ofta anpassa datastrukturer till raderingsblockets grÀns.
    Inspelning i NOR Flash:

BinÀra data

det var
01010101

Spelade in
00001111

Har blivit
00000101

SjÀlva loggen representerar en sekvens av poster med variabel lÀngd. Den typiska lÀngden pÄ en post Àr cirka 30 byte (Àven om poster som Àr flera kilobyte lÄnga ibland förekommer). I det hÀr fallet arbetar vi med dem helt enkelt som en uppsÀttning byte, men om du Àr intresserad anvÀnds CBOR i posterna

Utöver loggen mÄste vi lagra viss "instÀllningsinformation", bÄde uppdaterad och inte: ett visst enhets-ID, sensorkalibreringar, en flagga för "enheten Àr tillfÀlligt inaktiverad", etc.
Den hÀr informationen Àr en uppsÀttning nyckel-vÀrdeposter som ocksÄ lagras i CBOR. Vi har inte mycket av denna information (högst nÄgra fÄ kilobyte), och den uppdateras sÀllan.
I det följande kommer vi att kalla det sammanhang.

Om vi ​​kommer ihĂ„g var denna artikel började Ă€r det mycket viktigt att sĂ€kerstĂ€lla tillförlitlig datalagring och om möjligt kontinuerlig drift Ă€ven vid hĂ„rdvarufel/datakorruption.

Vilka kÀllor till problem kan övervÀgas?

  • StĂ€ng av under skriv/radering. Det hĂ€r Ă€r frĂ„n kategorin "det finns inget trick mot kofot."
    Information frÄn diskussioner pÄ stackexchange: nÀr strömmen Àr avstÀngd nÀr du arbetar med flash leder bÄde radering (stÀllt till 1) och skriv (stÀllt till 0) till odefinierat beteende: data kan skrivas, delvis skrivas (sÀg, vi överförde 10 byte/80 bitar) , men Ànnu inte bara 45 bitar kan skrivas), Àr det ocksÄ möjligt att vissa av bitarna kommer att vara i ett "mellanlÀge" (lÀsning kan ge bÄde 0 och 1);
  • Fel i sjĂ€lva flashminnet.
    BER, Àven om den Àr mycket lÄg, kan inte vara lika med noll;
  • Bussfel
    Data som överförs via SPI Àr inte skyddad pÄ nÄgot sÀtt, enkelbitsfel och synkroniseringsfel kan mycket vÀl uppstÄ - förlust eller infogning av bitar (vilket leder till massiv datakorruption);
  • Andra fel/fel
    Fel i koden, hallonfel, frÀmmande störningar...

Jag har formulerat de krav, vars uppfyllande enligt min mening Àr nödvÀndig för att sÀkerstÀlla tillförlitlighet:

  • poster mĂ„ste lĂ€ggas in i flashminnet omedelbart, fördröjda skrivningar beaktas inte - om ett fel uppstĂ„r mĂ„ste det upptĂ€ckas och bearbetas sĂ„ tidigt som möjligt - systemet ska om möjligt Ă„terstĂ€lla sig frĂ„n fel.
    (ett exempel frÄn livet "hur det inte borde vara", som jag tror att alla har stött pÄ: efter en nödstart Àr filsystemet "trasigt" och operativsystemet startar inte)

Idéer, förhÄllningssÀtt, reflektioner

NÀr jag började tÀnka pÄ det hÀr problemet, flöt mÄnga idéer genom mitt huvud, till exempel:

  • anvĂ€nd datakomprimering;
  • anvĂ€nd smarta datastrukturer, till exempel lagra postrubriker separat frĂ„n sjĂ€lva posterna, sĂ„ att om det finns ett fel i nĂ„gon post kan du lĂ€sa resten utan problem;
  • anvĂ€nd bitfĂ€lt för att kontrollera slutförandet av inspelningen nĂ€r strömmen stĂ€ngs av;
  • lagra kontrollsummor för allt;
  • anvĂ€nd nĂ„gon typ av brusbestĂ€ndig kodning.

NÄgra av dessa idéer anvÀndes, vissa beslutades att överges. LÄt oss gÄ i ordning.

Datakomprimering

SjÀlva hÀndelserna som vi registrerar i journalen Àr ganska lika och repeterbara ("kastade ett 5 rubelmynt", "tryckte pÄ knappen för att ge vÀxel", ...). DÀrför bör kompression vara ganska effektiv.

Kompressionskostnaden Àr obetydlig (vÄr processor Àr ganska kraftfull, Àven den första Pi hade en kÀrna med en frekvens pÄ 700 MHz, nuvarande modeller har flera kÀrnor med en frekvens pÄ över en gigahertz), vÀxelkursen med lagringen Àr lÄg (flera megabyte per sekund) Àr storleken pÄ posterna liten. I allmÀnhet, om komprimering har en inverkan pÄ prestanda, kommer det bara att vara positivt. (absolut okritisk, bara konstaterar). Dessutom har vi inte riktigt inbÀddad, men vanlig Linux - sÄ implementeringen borde inte krÀva mycket anstrÀngning (det rÀcker att bara lÀnka biblioteket och anvÀnda flera funktioner frÄn det).

En bit av loggen togs frÄn en fungerande enhet (1.7 MB, 70 tusen poster) och kontrollerades först för kompressibilitet med hjÀlp av gzip, lz4, lzop, bzip2, xz, zstd tillgÀngliga pÄ datorn.

  • gzip, xz, zstd visade liknande resultat (40Kb).
    Jag blev förvÄnad över att den fashionabla xz visade sig hÀr pÄ nivÄn gzip eller zstd;
  • lzip med standardinstĂ€llningar gav nĂ„got sĂ€mre resultat;
  • lz4 och lzop visade inte sĂ€rskilt bra resultat (150Kb);
  • bzip2 visade ett överraskande bra resultat (18Kb).

SÄ data komprimeras vÀldigt bra.
SÄ (om vi inte hittar fatala brister) kommer det att bli kompression! Helt enkelt för att mer data fÄr plats pÄ samma flashenhet.

LÄt oss tÀnka pÄ nackdelarna.

Första problemet: vi har redan kommit överens om att varje skiva omedelbart mÄste gÄ att blinka. Vanligtvis samlar arkiveraren in data frÄn inmatningsströmmen tills den bestÀmmer sig för att det Àr dags att skriva pÄ helgen. Vi mÄste omedelbart ta emot ett komprimerat datablock och lagra det i ett icke-flyktigt minne.

Jag ser tre sÀtt:

  1. Komprimera varje post med hjÀlp av ordbokskomprimering istÀllet för algoritmerna som diskuterats ovan.
    Det Àr ett helt fungerande alternativ, men jag gillar det inte. För att sÀkerstÀlla en mer eller mindre anstÀndig nivÄ av komprimering mÄste ordboken "skrÀddarsys" till specifika data; varje förÀndring kommer att leda till att komprimeringsnivÄn faller katastrofalt. Ja, problemet kan lösas genom att skapa en ny version av ordboken, men detta Àr en huvudvÀrk - vi kommer att behöva lagra alla versioner av ordboken; i varje post mÄste vi ange med vilken version av ordboken den komprimerades...
  2. Komprimera varje post med "klassiska" algoritmer, men oberoende av de andra.
    Komprimeringsalgoritmerna som övervÀgs Àr inte utformade för att fungera med poster av denna storlek (tiotals byte), komprimeringsförhÄllandet kommer helt klart att vara mindre Àn 1 (det vill sÀga att öka datavolymen istÀllet för att komprimera);
  3. Gör FLUSH efter varje inspelning.
    MÄnga komprimeringsbibliotek har stöd för FLUSH. Detta Àr ett kommando (eller en parameter till komprimeringsproceduren), vid mottagandet bildar arkiveraren en komprimerad ström sÄ att den kan anvÀndas för att ÄterstÀlla alla okomprimerad data som redan har tagits emot. En sÄdan analog sync i filsystem eller commit i sql.
    Vad som Àr viktigt Àr att efterföljande komprimeringsoperationer kommer att kunna anvÀnda den ackumulerade ordboken och komprimeringsförhÄllandet kommer inte att drabbas lika mycket som i den tidigare versionen.

Jag tycker att det Àr uppenbart att jag valde det tredje alternativet, lÄt oss titta pÄ det mer i detalj.

Hittades bra artikel om FLUSH i zlib.

Jag gjorde ett knÀtest baserat pÄ artikeln, tog 70 tusen loggposter frÄn en riktig enhet, med en sidstorlek pÄ 60Kb (vi Äterkommer till sidstorlek senare) mottagen:

RĂ„ data
Komprimering gzip -9 (ingen FLUSH)
zlib med Z_PARTIAL_FLUSH
zlib med Z_SYNC_FLUSH

Volym, KB
1692
40
352
604

Vid första anblicken Àr priset som FLUSH bidrar med överdrivet högt, men i verkligheten har vi lite val - antingen att inte komprimera alls, eller att komprimera (och mycket effektivt) med FLUSH. Vi fÄr inte glömma att vi har 70 tusen poster, redundansen som introduceras av Z_PARTIAL_FLUSH Àr bara 4-5 byte per post. Och kompressionsförhÄllandet visade sig vara nÀstan 5:1, vilket Àr mer Àn ett utmÀrkt resultat.

Det kan komma som en överraskning, men Z_SYNC_FLUSH Àr faktiskt ett mer effektivt sÀtt att göra FLUSH

NÀr du anvÀnder Z_SYNC_FLUSH kommer de sista 4 byten av varje post alltid att vara 0x00, 0x00, 0xff, 0xff. Och om vi kÀnner till dem behöver vi inte lagra dem, sÄ den slutliga storleken Àr bara 324Kb.

Artikeln jag lÀnkade till har en förklaring:

Ett nytt typ 0-block med tomt innehÄll lÀggs till.

Ett typ 0-block med tomt innehÄll bestÄr av:

  • trebitars blockhuvudet;
  • 0 till 7 bitar lika med noll, för att uppnĂ„ byte-inriktning;
  • fyrabytesekvensen 00 00 FF FF.

Som du lÀtt kan se finns det i det sista blocket före dessa 4 byte frÄn 3 till 10 nollbitar. Men praxis har visat att det faktiskt finns minst 10 nollbitar.

Det visar sig att sÄdana korta datablock vanligtvis (alltid?) kodas med ett block av typ 1 (fast block), som nödvÀndigtvis slutar med 7 nollbitar, vilket ger totalt 10-17 garanterade nollbitar (och resten kommer att vara noll med en sannolikhet pÄ cirka 50 %).

SÄ pÄ testdata finns det i 100 % av fallen en nollbyte före 0x00, 0x00, 0xff, 0xff, och i mer Àn en tredjedel av fallen finns det tvÄ nollbyte (kanske faktum Àr att jag anvÀnder binÀr CBOR, och nÀr jag anvÀnder text JSON skulle block av typ 2 - dynamiskt block vara vanligare, respektive block utan ytterligare noll byte före 0x00, 0x00, 0xff, 0xff skulle pÄtrÀffas).

Totalt, med hjÀlp av tillgÀngliga testdata, Àr det möjligt att passa in i mindre Àn 250Kb komprimerad data.

Du kan spara lite mer genom att jonglera lite: för nÀrvarande ignorerar vi nÀrvaron av nÄgra nollbitar i slutet av blocket, nÄgra bitar i början av blocket förÀndras inte heller...
Men sedan tog jag ett viljestarkt beslut att sluta, annars kunde jag i den hÀr takten sluta med att utveckla mitt eget arkiv.

Totalt, frÄn mina testdata, fick jag 3-4 byte per skrivning, kompressionsförhÄllandet visade sig vara mer Àn 6:1. Jag ska vara Àrlig: jag förvÀntade mig inte ett sÄdant resultat; enligt min mening Àr allt bÀttre Àn 2:1 redan ett resultat som motiverar anvÀndningen av komprimering.

Allt Àr bra, men zlib (deflate) Àr fortfarande en arkaisk, vÀlförtjÀnt och lite gammaldags komprimeringsalgoritm. Bara det faktum att de sista 32Kb av den okomprimerade dataströmmen anvÀnds som en ordbok ser konstigt ut idag (det vill sÀga om nÄgot datablock Àr vÀldigt likt det som fanns i inmatningsströmmen för 40Kb sedan, dÄ kommer det att börja arkiveras igen, och kommer inte att hÀnvisa till en tidigare hÀndelse). I fashionabla moderna arkiverare mÀts ordboksstorleken ofta i megabyte snarare Àn kilobyte.

SÄ vi fortsÀtter vÄr ministudie av arkivarier.

DÀrefter testade vi bzip2 (kom ihÄg, utan FLUSH visade den ett fantastiskt kompressionsförhÄllande pÄ nÀstan 100:1). TyvÀrr fungerade det mycket dÄligt med FLUSH, storleken pÄ den komprimerade datan visade sig vara större Àn den okomprimerade datan.

Mina antaganden om orsakerna till misslyckandet

Libbz2 erbjuder bara ett spolningsalternativ, vilket verkar rensa ordboken (analogt med Z_FULL_FLUSH i zlib), det Àr inte tal om nÄgon effektiv komprimering efter detta.

Och den sista som testades var zstd. Beroende pÄ parametrarna komprimeras den antingen pÄ nivÄn för gzip, men mycket snabbare eller bÀttre Àn gzip.

TyvÀrr, med FLUSH fungerade det inte sÀrskilt bra: storleken pÄ den komprimerade datan var cirka 700Kb.

Я stÀllde en frÄga pÄ projektets github-sida fick jag ett svar att du borde rÀkna med upp till 10 byte servicedata för varje block med komprimerad data, vilket Àr nÀra de erhÄllna resultaten; det finns inget sÀtt att komma ikapp med deflate.

Jag bestÀmde mig för att sluta vid denna tidpunkt i mina experiment med arkiverare (lÄt mig pÄminna dig om att xz, lzip, lzo, lz4 inte visade sig ens pÄ teststadiet utan FLUSH, och jag övervÀgde inte mer exotiska komprimeringsalgoritmer).

LÄt oss ÄtergÄ till arkiveringsproblem.

Det andra (som de sÀger i ordning, inte i vÀrde) problemet Àr att den komprimerade datan Àr en enda ström, dÀr det stÀndigt finns referenser till tidigare avsnitt. Om en del av komprimerad data skadas förlorar vi alltsÄ inte bara det associerade blocket med okomprimerad data, utan Àven alla efterföljande.

Det finns ett sÀtt att lösa detta problem:

  1. Förhindra att problemet uppstÄr - lÀgg till redundans till de komprimerade data, vilket gör att du kan identifiera och korrigera fel; vi ska prata om detta senare;
  2. Minimera konsekvenserna om ett problem uppstÄr
    Vi har redan sagt tidigare att du kan komprimera varje datablock sjÀlvstÀndigt, och problemet kommer att försvinna av sig sjÀlvt (skada pÄ data frÄn ett block kommer att leda till förlust av data endast för detta block). Detta Àr dock ett extremfall dÀr datakomprimering kommer att vara ineffektiv. Den motsatta ytterligheten: anvÀnd alla 4 MB av vÄrt chip som ett enda arkiv, vilket kommer att ge oss utmÀrkt komprimering, men katastrofala konsekvenser i hÀndelse av datakorruption.
    Ja, det krÀvs en kompromiss nÀr det gÀller tillförlitlighet. Men vi mÄste komma ihÄg att vi utvecklar ett datalagringsformat för icke-flyktigt minne med extremt lÄg BER och en deklarerad datalagringstid pÄ 20 Är.

Under experimenten upptÀckte jag att mer eller mindre mÀrkbara förluster i komprimeringsnivÄn börjar pÄ block med komprimerad data mindre Àn 10 KB i storlek.
Det nÀmndes tidigare att minnet som anvÀnds Àr sökt, jag ser ingen anledning till att korrespondensen "en sida - ett block med komprimerad data" inte skulle anvÀndas.

Det vill sÀga den minsta rimliga sidstorleken Àr 16Kb (med en reserv för serviceinformation). En sÄ liten sidstorlek medför dock betydande begrÀnsningar för den maximala poststorleken.

Även om jag Ă€nnu inte förvĂ€ntar mig poster större Ă€n nĂ„gra kilobyte i komprimerad form, bestĂ€mde jag mig för att anvĂ€nda 32Kb-sidor (för totalt 128 sidor per chip).

Sammanfattning:

  • Vi lagrar data komprimerad med zlib (deflate);
  • För varje post stĂ€ller vi in ​​Z_SYNC_FLUSH;
  • För varje komprimerad post trimmar vi de efterföljande byten (t.ex. 0x00, 0x00, 0xff, 0xff); i rubriken anger vi hur mĂ„nga byte vi skĂ€r av;
  • Vi lagrar data pĂ„ 32Kb-sidor; det finns en enda ström av komprimerad data inuti sidan; PĂ„ varje sida börjar vi komprimera igen.

Och innan jag avslutar med komprimering vill jag uppmÀrksamma er pÄ att vi bara har nÄgra byte komprimerad data per post, sÄ det Àr extremt viktigt att inte blÄsa upp serviceinformationen, varje byte rÀknas hÀr.

Lagra datahuvuden

Eftersom vi har poster med variabel lÀngd mÄste vi pÄ nÄgot sÀtt bestÀmma posternas placering/grÀnser.

Jag kÀnner till tre tillvÀgagÄngssÀtt:

  1. Alla poster lagras i en kontinuerlig ström, först finns det en posthuvud som innehÄller lÀngden, och sedan sjÀlva posten.
    I denna utföringsform kan bÄde rubriker och data vara av variabel lÀngd.
    I huvudsak fÄr vi en enkellÀnkad lista som anvÀnds hela tiden;
  2. Rubriker och sjÀlva posterna lagras i separata strömmar.
    Genom att anvÀnda rubriker med konstant lÀngd sÀkerstÀller vi att skador pÄ en rubrik inte pÄverkar de andra.
    Ett liknande tillvÀgagÄngssÀtt anvÀnds till exempel i mÄnga filsystem;
  3. Poster lagras i en kontinuerlig ström, postgrÀnsen bestÀms av en viss markör (ett tecken/sekvens av tecken som Àr förbjudet inom datablock). Om det finns en markör inuti posten, sÄ ersÀtter vi den med nÄgon sekvens (escape den).
    Ett liknande tillvÀgagÄngssÀtt anvÀnds till exempel i PPP-protokollet.

Jag ska illustrera.

Alternativ 1:
Min implementering av en ringbuffert i NOR flash
Allt Àr vÀldigt enkelt hÀr: genom att veta lÀngden pÄ posten kan vi berÀkna adressen till nÀsta rubrik. SÄ vi gÄr igenom rubrikerna tills vi möter ett omrÄde fyllt med 0xff (fritt omrÄde) eller slutet av sidan.

Alternativ 2:
Min implementering av en ringbuffert i NOR flash
PÄ grund av den varierande postlÀngden kan vi inte i förvÀg sÀga hur mÄnga poster (och dÀrmed rubriker) vi kommer att behöva per sida. Du kan sprida rubrikerna och sjÀlva data över olika sidor, men jag föredrar ett annat tillvÀgagÄngssÀtt: vi placerar bÄde rubrikerna och data pÄ en sida, men rubrikerna (med konstant storlek) kommer frÄn början av sidan, och data (av variabel lÀngd) kommer frÄn slutet. SÄ fort de "trÀffas" (det finns inte tillrÀckligt med ledigt utrymme för en ny post) anser vi denna sida vara komplett.

Alternativ 3:
Min implementering av en ringbuffert i NOR flash
Det finns inget behov av att lagra lÀngden eller annan information om platsen för data i rubriken, det rÀcker med markörer som anger grÀnserna för posterna. Uppgifterna mÄste dock bearbetas vid skrivning/lÀsning.
Jag skulle anvÀnda 0xff som en markör (som fyller sidan efter radering), sÄ det fria omrÄdet kommer definitivt inte att behandlas som data.

JÀmförelsetabell:

Alternativ 1
Alternativ 2
Alternativ 3

Feltolerans
-
+
+

densitet
+
-
+

Implementeringskomplexitet
*
**
**

Alternativ 1 har ett ödesdigert fel: om nÄgon av huvuden Àr skadad, förstörs hela efterföljande kedja. De ÄterstÄende alternativen lÄter dig ÄterstÀlla vissa data Àven i hÀndelse av massiv skada.
Men hÀr Àr det lÀmpligt att komma ihÄg att vi bestÀmde oss för att lagra data i en komprimerad form, och sÄ förlorar vi all data pÄ sidan efter en "bruten" post, sÄ Àven om det finns ett minus i tabellen, gör vi det inte ta hÀnsyn till det.

Kompakthet:

  • i det första alternativet behöver vi bara lagra lĂ€ngden i rubriken, om vi anvĂ€nder heltal med variabel lĂ€ngd kan vi i de flesta fall klara oss med en byte;
  • i det andra alternativet mĂ„ste vi lagra startadressen och lĂ€ngden; posten mĂ„ste ha en konstant storlek, jag uppskattar 4 byte per post (tvĂ„ byte för offset och tvĂ„ byte för lĂ€ngden);
  • det tredje alternativet behöver bara ett tecken för att indikera starten av inspelningen, plus att sjĂ€lva inspelningen ökar med 1-2 % pĂ„ grund av avskĂ€rmning. I allmĂ€nhet ungefĂ€r paritet med det första alternativet.

Till en början ansÄg jag det andra alternativet som det viktigaste (och skrev till och med implementeringen). Jag övergav det först nÀr jag Àntligen bestÀmde mig för att anvÀnda komprimering.

En dag kanske jag fortfarande kommer att anvÀnda ett liknande alternativ. Om jag till exempel ska ta itu med datalagring för ett fartyg som fÀrdas mellan jorden och Mars kommer det att finnas helt andra krav pÄ tillförlitlighet, kosmisk strÄlning, ...

NÀr det gÀller det tredje alternativet: jag gav det tvÄ stjÀrnor för svÄrigheten att implementera helt enkelt för att jag inte gillar att brÄka med skÀrmning, Àndra lÀngden i processen, etc. Ja, jag kanske Àr partisk, men jag mÄste skriva koden - varför tvinga dig sjÀlv att göra nÄgot du inte gillar.

Sammanfattning: Vi vÀljer lagringsalternativet i form av kedjor "header med lÀngd - data av variabel lÀngd" pÄ grund av effektivitet och enkel implementering.

AnvÀnda bitfÀlt för att övervaka framgÄngen för skrivoperationer

Jag minns inte var jag fick idén nu, men det ser ut ungefÀr sÄ hÀr:
För varje post tilldelar vi flera bitar för att lagra flaggor.
Som vi sa tidigare, efter radering Àr alla bitar fyllda med 1:or, och vi kan Àndra 1 till 0, men inte vice versa. SÄ för "flaggan Àr inte satt" anvÀnder vi 1, för "flaggan Àr instÀlld" anvÀnder vi 0.

SÄ hÀr kan det se ut att lÀgga en skiva med variabel lÀngd i flash:

  1. StÀll in flaggan "lÀngdinspelning har startat";
  2. Anteckna lÀngden;
  3. StÀll in flaggan "datainspelning har startat";
  4. Vi registrerar data;
  5. StÀll in flaggan "inspelning avslutad".

Dessutom kommer vi att ha en "fel intrÀffade" flagga, för totalt 4 bitars flaggor.

I det hÀr fallet har vi tvÄ stabila tillstÄnd "1111" - inspelningen har inte startat och "1000" - inspelningen lyckades; vid ett ovÀntat avbrott i inspelningsprocessen kommer vi att fÄ mellanliggande tillstÄnd som vi sedan kan upptÀcka och bearbeta.

TillvÀgagÄngssÀttet Àr intressant, men det skyddar bara mot plötsliga strömavbrott och liknande fel, vilket naturligtvis Àr viktigt, men detta Àr lÄngt ifrÄn den enda (eller ens huvud) orsaken till eventuella fel.

Sammanfattning: LÄt oss gÄ vidare och leta efter en bra lösning.

Kontrollsummor

Kontrollsummor gör det ocksÄ möjligt att försÀkra sig (med rimlig sannolikhet) att vi lÀser exakt vad som borde ha skrivits. Och till skillnad frÄn bitfÀlten som diskuterats ovan fungerar de alltid.

Om vi ​​övervĂ€ger listan över potentiella problemkĂ€llor som vi diskuterade ovan, kan kontrollsumman kĂ€nna igen ett fel oavsett dess ursprung (förutom kanske illvilliga utomjordingar - de kan ocksĂ„ förfalska kontrollsumman).

SÄ om vÄrt mÄl Àr att verifiera att data Àr intakt Àr kontrollsummor en utmÀrkt idé.

Valet av algoritm för att berĂ€kna kontrollsumman vĂ€ckte inga frĂ„gor - CRC. Å ena sidan gör matematiska egenskaper det möjligt att fĂ„nga vissa typer av fel till 100 %, Ă„ andra sidan visar denna algoritm pĂ„ slumpmĂ€ssiga data att sannolikheten för kollisioner inte Ă€r mycket större Ă€n den teoretiska grĂ€nsen Min implementering av en ringbuffert i NOR flash. Det Ă€r kanske inte den snabbaste algoritmen, och det Ă€r inte heller alltid det minsta nĂ€r det gĂ€ller antalet kollisioner, men den har en mycket viktig egenskap: i de tester jag stötte pĂ„ fanns det inga mönster dĂ€r den tydligt misslyckades. Stabilitet Ă€r huvudkvaliteten i detta fall.

Exempel pÄ en volymetrisk studie: Del 1, Del 2 (lÀnkar till narod.ru, förlÄt).

Uppgiften att vÀlja en kontrollsumma Àr dock inte komplett, CRC Àr en hel familj av kontrollsummor. Du mÄste bestÀmma lÀngden och sedan vÀlja ett polynom.

Att vÀlja kontrollsummans lÀngd Àr inte en sÄ enkel frÄga som det verkar vid första anblicken.

LĂ„t mig illustrera:
LÄt oss ha sannolikheten för ett fel i varje byte Min implementering av en ringbuffert i NOR flash och en idealisk kontrollsumma, lÄt oss berÀkna det genomsnittliga antalet fel per miljon poster:

Data, byte
Kontrollsumma, byte
OupptÀckta fel
Falska feldetekteringar
Totalt falska positiva

1
0
1000
0
1000

1
1
4
999
1003

1
2
≈0
1997
1997

1
4
≈0
3990
3990

10
0
9955
0
9955

10
1
39
990
1029

10
2
≈0
1979
1979

10
4
≈0
3954
3954

1000
0
632305
0
632305

1000
1
2470
368
2838

1000
2
10
735
745

1000
4
≈0
1469
1469

Det verkar som att allt Àr enkelt - beroende pÄ lÀngden pÄ data som skyddas, vÀlj lÀngden pÄ kontrollsumman med ett minimum av felaktiga positiva - och tricket Àr i bagaget.

Det uppstÄr dock ett problem med korta kontrollsummor: Àven om de Àr bra pÄ att upptÀcka enstaka bitfel kan de med ganska stor sannolikhet acceptera helt slumpmÀssiga data som korrekta. Det fanns redan en artikel om Habré som beskrev problem i verkliga livet.

DÀrför, för att göra en slumpmÀssig kontrollsumma matchning nÀstan omöjlig, mÄste du anvÀnda kontrollsummor som Àr 32 bitar eller lÀngre. (för lÀngder större Àn 64 bitar anvÀnds vanligtvis kryptografiska hashfunktioner).

Trots det faktum att jag skrev tidigare att vi mÄste spara utrymme med alla medel, kommer vi fortfarande att anvÀnda en 32-bitars kontrollsumma (16 bitar Àr inte tillrÀckligt, sannolikheten för en kollision Àr mer Àn 0.01%; och 24 bitar, eftersom de sÀg, Àr varken hÀr eller dÀr).

En invÀndning kan uppstÄ hÀr: sparade vi varje byte nÀr vi valde komprimering för att nu ge 4 byte pÄ en gÄng? Skulle det inte vara bÀttre att inte komprimera eller lÀgga till en kontrollsumma? Naturligtvis inte, ingen kompression betyder inte, att vi inte behöver integritetskontroll.

NÀr vi vÀljer ett polynom kommer vi inte att uppfinna hjulet pÄ nytt, utan ta den nu populÀra CRC-32C.
Denna kod upptÀcker 6 bitars fel pÄ paket upp till 22 byte (kanske det vanligaste fallet för oss), 4 bitars fel pÄ paket upp till 655 byte (ocksÄ ett vanligt fall för oss), 2 eller vilket udda antal bitfel som helst pÄ paket av nÄgon rimlig lÀngd.

Om nÄgon Àr intresserad av detaljerna

Wikipedia-artikel om CRC.

Kodparametrar crc-32c pĂ„ Koopmans hemsida — kanske den ledande CRC-specialisten pĂ„ planeten.

В hans artikel finns en annan intressant kod, vilket ger nĂ„got bĂ€ttre parametrar för de paketlĂ€ngder som Ă€r relevanta för oss, men jag ansĂ„g inte skillnaden signifikant, och jag var kompetent nog att vĂ€lja anpassad kod istĂ€llet för den vanliga och vĂ€l undersökta.

Eftersom vÄr data Àr komprimerad uppstÄr ocksÄ frÄgan: ska vi berÀkna kontrollsumman för komprimerad eller okomprimerad data?

Argument för att berÀkna kontrollsumman för okomprimerade data:

  • Vi mĂ„ste i slutĂ€ndan kontrollera sĂ€kerheten för datalagring - sĂ„ vi kontrollerar den direkt (samtidigt kommer eventuella fel i implementeringen av komprimering/dekompression, skador orsakade av trasigt minne, etc. att kontrolleras);
  • Deflate-algoritmen i zlib har en ganska mogen implementering och bör inte falla med "krokiga" indata; dessutom kan den ofta sjĂ€lvstĂ€ndigt upptĂ€cka fel i ingĂ„ngsströmmen, vilket minskar den totala sannolikheten för att oupptĂ€cka ett fel (genomförde ett test med invertering av en enda bit i en kort post, zlib upptĂ€ckte ett fel i ungefĂ€r en tredjedel av fallen).

Argument mot att berÀkna kontrollsumman för okomprimerade data:

  • CRC Ă€r "skrĂ€ddarsydd" specifikt för de fĂ„ bitfel som Ă€r karakteristiska för flashminne (ett bitfel i en komprimerad ström kan orsaka en massiv förĂ€ndring i utströmmen, dĂ€r vi rent teoretiskt kan "fĂ„nga" en kollision);
  • Jag gillar inte riktigt tanken pĂ„ att skicka potentiellt trasiga data till dekomprimeraren, Vem vethur han kommer att reagera.

I detta projekt bestÀmde jag mig för att avvika frÄn den allmÀnt accepterade praxis att lagra en kontrollsumma av okomprimerad data.

Sammanfattning: Vi anvÀnder CRC-32C, vi berÀknar kontrollsumman frÄn data i den form som de skrivs för att blinka (efter komprimering).

Redundans

AnvÀndningen av redundant kodning eliminerar naturligtvis inte dataförlust, men det kan avsevÀrt (ofta i mÄnga storleksordningar) minska sannolikheten för oÄterkallelig dataförlust.

Vi kan anvÀnda olika typer av redundans för att rÀtta till fel.
Hamming-koder kan korrigera enbitsfel, Reed-Solomon-teckenkoder, flera kopior av data i kombination med kontrollsummor eller kodningar som RAID-6 kan hjÀlpa till att ÄterstÀlla data Àven i hÀndelse av massiv korruption.
Till en början var jag engagerad i den utbredda anvÀndningen av felbestÀndig kodning, men sedan insÄg jag att vi först mÄste ha en uppfattning om vilka fel vi vill skydda oss frÄn, och sedan vÀlja kodning.

Vi sa tidigare att fel mÄste fÄngas upp sÄ snabbt som möjligt. Vid vilka punkter kan vi stöta pÄ fel?

  1. Oavslutad inspelning (av nÄgon anledning vid tidpunkten för inspelningen stÀngdes strömmen av, hallonet frös, ...)
    TyvÀrr, i hÀndelse av ett sÄdant fel ÄterstÄr bara att ignorera ogiltiga poster och betrakta data som förlorade;
  2. Skrivfel (av nÄgon anledning var det som skrevs till flashminnet inte det som skrevs)
    Vi kan omedelbart upptÀcka sÄdana fel om vi gör en testavlÀsning direkt efter inspelningen;
  3. FörvrÀngning av data i minnet under lagring;
  4. LĂ€sfel
    För att korrigera det, om kontrollsumman inte stÀmmer överens, rÀcker det att upprepa avlÀsningen flera gÄnger.

Det vill sÀga, endast fel av den tredje typen (spontan korruption av data under lagring) kan inte korrigeras utan felbestÀndig kodning. Det verkar som att sÄdana fel fortfarande Àr extremt osannolika.

Sammanfattning: det beslutades att överge redundant kodning, men om operationen visar felet i detta beslut, ÄtergÄ till övervÀgande av problemet (med redan ackumulerad statistik om fel, vilket gör det möjligt att vÀlja den optimala typen av kodning).

Andra

Naturligtvis tillÄter inte formatet pÄ artikeln oss att motivera varje bit i formatet (och min kraft har redan tagit slut), sÄ jag ska kort gÄ igenom nÄgra punkter som inte berörts tidigare.

  • Det beslutades att göra alla sidor "lika"
    Det vill sÀga att det inte blir nÄgra speciella sidor med metadata, separata trÄdar etc, utan istÀllet en enda trÄd som skriver om alla sidor i tur och ordning.
    Detta sÀkerstÀller jÀmnt slitage pÄ sidorna, ingen enskild punkt av misslyckande, och jag bara gillar det;
  • Det Ă€r absolut nödvĂ€ndigt att tillhandahĂ„lla versionshantering av formatet.
    Ett format utan ett versionsnummer i rubriken Àr dÄligt!
    Det rÀcker att lÀgga till ett fÀlt med ett visst magiskt nummer (signatur) till sidhuvudet, vilket kommer att indikera versionen av formatet som anvÀnds (Jag tror inte att det i praktiken kommer att finnas ens ett dussin av dem);
  • AnvĂ€nd en rubrik med variabel lĂ€ngd för poster (som det finns mĂ„nga av), och försök göra den 1 byte lĂ„ng i de flesta fall;
  • För att koda lĂ€ngden pĂ„ rubriken och lĂ€ngden pĂ„ den trimmade delen av den komprimerade posten, anvĂ€nd binĂ€ra koder med variabel lĂ€ngd.

HjÀlpte mycket online generator Huffman koder. PÄ bara nÄgra minuter kunde vi vÀlja önskade koder med variabel lÀngd.

Beskrivning av datalagringsformat

Byte-ordning

FÀlt som Àr större Àn en byte lagras i big-endian-format (nÀtverksbyteordning), det vill sÀga 0x1234 skrivs som 0x12, 0x34.

Paginering

Allt flashminne Àr uppdelat i lika stora sidor.

Standardsidstorleken Àr 32Kb, men inte mer Àn 1/4 av minneskretsens totala storlek (för ett 4MB-chip erhÄlls 128 sidor).

Varje sida lagrar data oberoende av de andra (det vill sÀga data pÄ en sida refererar inte till data pÄ en annan sida).

Alla sidor Àr numrerade i naturlig ordning (i stigande adressordning), börjar med nummer 0 (sida noll börjar pÄ adress 0, första sidan börjar pÄ 32Kb, andra sidan börjar pÄ 64Kb, etc.)

Minneschippet anvÀnds som en cyklisk buffert (ringbuffert), det vill sÀga att först skrivning gÄr till sidnummer 0, sedan nummer 1, ..., nÀr vi fyller sista sidan börjar en ny cykel och inspelningen fortsÀtter frÄn sida noll .

Inne pÄ sidan

Min implementering av en ringbuffert i NOR flash
I början av sidan lagras en sidhuvud pÄ 4 byte, sedan en kontrollsumma för rubriken (CRC-32C), sedan lagras poster i formatet "huvud, data, kontrollsumma".

Sidtiteln (smutsig grön i diagrammet) bestÄr av:

  • tvĂ„-byte Magic Number-fĂ€lt (ocksĂ„ ett tecken pĂ„ formatversionen)
    för den aktuella versionen av formatet berĂ€knas det som 0xed00 ⊕ ĐœĐŸĐŒĐ”Ń€ ŃŃ‚Ń€Đ°ĐœĐžŃ†Ń‹;
  • tvĂ„-byte rĂ€knare "Sidversion" (minnes omskrivningscykelnummer).

Poster pÄ sidan lagras i komprimerad form (deflate-algoritmen anvÀnds). Alla poster pÄ en sida komprimeras i en trÄd (en gemensam ordbok anvÀnds), och pÄ varje ny sida börjar komprimeringen pÄ nytt. Det vill sÀga, för att dekomprimera en post krÀvs alla tidigare poster frÄn denna sida (och endast denna).

Varje post kommer att komprimeras med Z_SYNC_FLUSH-flaggan, och i slutet av den komprimerade strömmen kommer det att finnas 4 byte 0x00, 0x00, 0xff, 0xff, eventuellt föregÄs av ytterligare en eller tvÄ nollbyte.
Vi förkastar denna sekvens (4, 5 eller 6 byte lÄng) nÀr vi skriver till flashminnet.

Posthuvudet Àr 1, 2 eller 3 byte som lagrar:

  • en bit (T) som indikerar typen av post: 0 - kontext, 1 - log;
  • ett fĂ€lt med variabel lĂ€ngd (S) frĂ„n 1 till 7 bitar, som definierar lĂ€ngden pĂ„ rubriken och "svansen" som mĂ„ste lĂ€ggas till posten för dekomprimering;
  • rekordlĂ€ngd (L).

S-vÀrdestabell:

S
Rubrikens lÀngd, byte
Kasserade vid skrivning, byte

0
1
5 (00 00 00 ff ff)

10
1
6 (00 00 00 00 ff ff)

110
2
4 (00 00 ff ff)

1110
2
5 (00 00 00 ff ff)

11110
2
6 (00 00 00 00 ff ff)

1111100
3
4 (00 00 ff ff)

1111101
3
5 (00 00 00 ff ff)

1111110
3
6 (00 00 00 00 ff ff)

Jag försökte illustrera, jag vet inte hur tydligt det blev:
Min implementering av en ringbuffert i NOR flash
Gult hÀr indikerar T-fÀltet, vitt S-fÀltet, grönt L (lÀngden pÄ den komprimerade datan i byte), blÄ den komprimerade datan, röd den slutliga byten av den komprimerade datan som inte skrivs till flashminnet.

SÄledes kan vi skriva postrubriker av den vanligaste lÀngden (upp till 63+5 byte i komprimerad form) i en byte.

Efter varje post lagras en CRC-32C-kontrollsumma, i vilken det inverterade vÀrdet av den föregÄende kontrollsumman anvÀnds som initialvÀrde (init).

CRC har egenskapen "varaktighet", följande formel fungerar (plus eller minus bitinversion i processen): Min implementering av en ringbuffert i NOR flash.
Det vill sÀga, i sjÀlva verket berÀknar vi CRC för alla tidigare bytes med rubriker och data pÄ den hÀr sidan.

Direkt efter kontrollsumman Àr rubriken för nÀsta post.

Headern Àr utformad pÄ ett sÄdant sÀtt att dess första byte alltid skiljer sig frÄn 0x00 och 0xff (om vi istÀllet för den första byten i headern stöter pÄ 0xff, betyder det att detta Àr ett oanvÀnt omrÄde; 0x00 signalerar ett fel).

Exempel pÄ algoritmer

LÀser frÄn flashminnet

Alla avlÀsningar kommer med en kontrollsumma.
Om kontrollsumman inte stÀmmer överens upprepas avlÀsningen flera gÄnger i hopp om att lÀsa rÀtt data.

(det hÀr Àr vettigt, Linux cachelagrar inte lÀsningar frÄn NOR Flash, testat)

Skriv till flashminnet

Vi registrerar uppgifterna.
LÄt oss lÀsa dem.

Om den lÀsta datan inte stÀmmer överens med den skrivna datan fyller vi omrÄdet med nollor och signalerar ett fel.

Förbereder en ny mikrokrets för drift

För initiering skrivs en rubrik med version 1 till den första (eller snarare noll) sidan.
DÀrefter skrivs det initiala sammanhanget till den hÀr sidan (innehÄller maskinens UUID och standardinstÀllningar).

Det Àr allt, flashminnet Àr klart att anvÀndas.

Laddar maskinen

Vid laddning lÀses de första 8 byten av varje sida (huvud + CRC), sidor med ett okÀnt magiskt nummer eller en felaktig CRC ignoreras.
FrÄn de "rÀtta" sidorna vÀljs sidor med den maximala versionen, och sidan med det högsta antalet tas frÄn dem.
Den första posten lÀses, korrektheten av CRC och nÀrvaron av "kontext"-flaggan kontrolleras. Om allt Àr bra anses denna sida vara aktuell. Om inte, rullar vi tillbaka till den föregÄende tills vi hittar en "live"-sida.
och pÄ den hittade sidan lÀser vi alla poster, de som vi anvÀnder med "kontext"-flaggan.
Spara zlib-ordboken (den kommer att behövas för att lÀgga till den hÀr sidan).

Det Àr allt, nedladdningen Àr klar, sammanhanget Àr ÄterstÀllt, du kan arbeta.

LĂ€gga till en journalanteckning

Vi komprimerar posten med rÀtt ordbok, specificerar Z_SYNC_FLUSH. Vi ser om den komprimerade posten passar pÄ den aktuella sidan.
Om det inte passar (eller om det fanns CRC-fel pÄ sidan), starta en ny sida (se nedan).
Vi skriver ner posten och CRC. Om ett fel uppstÄr, starta en ny sida.

Ny sida

Vi vÀljer en gratissida med minsta antalet (vi anser att en gratissida Àr en sida med en felaktig kontrollsumma i rubriken eller med en mindre version Àn den nuvarande). Om det inte finns nÄgra sÄdana sidor, vÀlj sidan med minsta antal bland de som har en version som Àr lika med den nuvarande.
Vi raderar den valda sidan. Vi kontrollerar innehÄllet med 0xff. Om nÄgot Àr fel, ta nÀsta gratissida osv.
Vi skriver en rubrik pÄ den raderade sidan, den första posten Àr det aktuella tillstÄndet för sammanhanget, nÀsta Àr den oskrivna loggposten (om det finns en).

FormattillÀmplighet

Enligt min mening visade det sig vara ett bra format för att lagra eventuella mer eller mindre komprimerbara informationsströmmar (vanlig text, JSON, MessagePack, CBOR, eventuellt protobuf) i NOR Flash.

Naturligtvis Àr formatet "skrÀddarsytt" för SLC NOR Flash.

Den ska inte anvÀndas med media med hög BER som NAND eller MLC NOR (finns ett sÄdant minne ens till salu? Jag har bara sett det nÀmnt i arbeten om korrigeringskoder).

Dessutom bör den inte anvÀndas med enheter som har sin egen FTL: USB-flash, SD, MicroSD, etc (för sÄdant minne skapade jag ett format med en sidstorlek pÄ 512 byte, en signatur i början av varje sida och unika postnummer - ibland var det möjligt att ÄterstÀlla all data frÄn en "felaktig" flashenhet genom enkel sekventiell lÀsning).

Beroende pÄ uppgifterna kan formatet anvÀndas utan Àndringar pÄ flashenheter frÄn 128Kbit (16Kb) till 1Gbit (128MB). Om sÄ önskas kan du anvÀnda den pÄ större marker, men du behöver förmodligen justera sidstorleken (Men hÀr uppstÄr redan frÄgan om ekonomisk genomförbarhet; priset för stora volymer NOR Flash Àr inte uppmuntrande).

Om nÄgon tycker att formatet Àr intressant och vill anvÀnda det i ett öppet projekt, skriv, jag ska försöka hitta tiden, polera koden och lÀgga upp den pÄ github.

Slutsats

Som du kan se visade sig formatet till slut vara enkelt och till och med trÄkigt.

Det Àr svÄrt att spegla utvecklingen av min synvinkel i en artikel, men tro mig: frÄn början ville jag skapa nÄgot sofistikerat, oförstörbart, som kan överleva till och med en kÀrnvapenexplosion i nÀrheten. Men förnuftet (hoppas jag) vann ÀndÄ och gradvis skiftade prioriteringarna mot enkelhet och kompakthet.

Kan det vara sÄ att jag hade fel? Ja visst. Det kan mycket vÀl visa sig till exempel att vi köpt ett parti av lÄgkvalitativa mikrokretsar. Eller av nÄgon annan anledning kommer utrustningen inte att uppfylla tillförlitlighetsförvÀntningarna.

Har jag en plan för detta? Jag tror att du efter att ha lÀst artikeln inte tvivlar pÄ att det finns en plan. Och inte ens ensam.

PÄ en lite mer seriös notering utvecklades formatet bÄde som ett fungerande alternativ och som en "provballong".

För tillfÀllet fungerar allt pÄ bordet bra, bokstavligen hÀromdagen kommer lösningen att distribueras (ungefÀr) pÄ hundratals enheter, lÄt oss se vad som hÀnder i "strid" operation (lyckligtvis hoppas jag att formatet gör att du kan upptÀcka fel pÄ ett tillförlitligt sÀtt; sÄ att du kan samla in fullstÀndig statistik). Om nÄgra mÄnader kommer det att vara möjligt att dra slutsatser (och om du har otur, Ànnu tidigare).

Om, baserat pÄ resultatet av anvÀndningen, allvarliga problem upptÀcks och förbÀttringar krÀvs, kommer jag definitivt att skriva om det.

Litteratur

Jag ville inte göra en lÄng trÄkig lista över begagnade verk; trots allt har alla Google.

HÀr bestÀmde jag mig för att lÀmna en lista över fynd som verkade sÀrskilt intressanta för mig, men gradvis migrerade de direkt in i artikelns text, och ett objekt fanns kvar pÄ listan:

  1. Verktyg infgen frÄn författaren zlib. Kan tydligt visa innehÄllet i deflate/zlib/gzip-arkiv. Om du har att göra med den interna strukturen för deflate (eller gzip)-formatet rekommenderar jag det starkt.

KĂ€lla: will.com

LĂ€gg en kommentar