{"id":53751,"date":"2019-12-09T00:00:00","date_gmt":"2019-12-08T21:00:00","guid":{"rendered":"https:\/\/prohoster.info\/blog\/blog_prohoster\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash"},"modified":"2020-02-18T14:01:41","modified_gmt":"2020-02-18T11:01:41","slug":"moya-realizatsiya-koltsevogo-bufera-v-nor-flash","status":"publish","type":"post","link":"https:\/\/prohoster.info\/nl\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","title":{"rendered":"Mijn implementatie van een ringbuffer in NOR-flash","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h1 id=\"predystoriya\">Achtergrond<\/h1>\n<p><\/p>\n<p>Er zijn handelsautomaten die we zelf hebben ontwikkeld. Binnenin zit een Raspberry Pi en een beetje extra hardware op een aparte printplaat. Aangesloten zijn een muntinvoerapparaat, bankbiljetinvoerapparaat, en een bankterminal... Alles wordt bestuurd door een zelfgeschreven programma. Alle werkverrichtingen worden in een logboek op een flashkaart (MicroSD) opgeslagen, dat vervolgens via internet (met behulp van een USB-modem) naar een server wordt verzonden, waar het in een database wordt opgeslagen. Informatie over verkopen wordt in 1C geladen; er is ook een eenvoudige webinterface voor monitoring, enzovoorts. <\/p>\n<p><\/p>\n<p>Dat betekent dat het logboek van vitaal belang is \u2014 voor administratie (opbrengst, verkopen, enzovoorts), monitoring (diverse storingen en andere overmachtsituaties); dit is, kan je zeggen, alle informatie die we over deze automaat hebben. <\/p>\n<p><\/p>\n<h1 id=\"problema\">Probleem<\/h1>\n<p><\/p>\n<p>Flashkaarten blijken zeer onbetrouwbare apparaten te zijn. Ze kunnen met vervelende regelmaat uitvallen. Dit leidt zowel tot uitval van de automaten als (als om de een of andere reden het logboek niet online kan worden verzonden) tot verlies van gegevens.<\/p>\n<p><\/p>\n<p><em>Dit is niet de eerste ervaring met flashkaarten. Voorheen was er een ander project met meer dan honderd apparaten waarin het logboek op USB-flashkaarten werd opgeslagen. Ook daar waren er problemen met de betrouwbaarheid, soms werden tientallen apparaten per maand onbruikbaar. We hebben verschillende flashkaarten geprobeerd, waaronder merkproducten met SLC-geheugen. Sommige modellen zijn betrouwbaarder dan andere, maar het vervangen van flashkaarten heeft het probleem niet echt opgelost.<\/em><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <\/p>\n<p><strong>Let op!<\/strong> Longread! Als je alleen ge\u00efnteresseerd bent in het 'hoe', en niet in het 'waarom', kun je meteen verder. <noindex><a rel=\"nofollow\" href=\"#format\">einde<\/a><\/noindex> van het artikel gaan.<\/p>\n<p><\/p>\n<h1 id=\"reshenie\">Oplossing<\/h1>\n<p><\/p>\n<p>Het eerste wat in me opkomt: stop met MicroSD, gebruik bijvoorbeeld een SSD en boot daarvan. Theoretisch is het misschien mogelijk, maar relatief duur, en niet zo betrouwbaar (er komt een USB-SATA-adapter bij; de foutstatistieken voor goedkope SSD's zijn ook niet bemoedigend).<\/p>\n<p><\/p>\n<p>USB HDD's lijken ook niet echt een aantrekkelijk alternatief te zijn.<\/p>\n<p><\/p>\n<p>Daarom zijn we tot de volgende oplossing gekomen: blijf booten vanaf MicroSD, maar gebruik ze in read-only modus, en sla het logboek van de werking (en andere unieke informatie voor dit specifieke apparaat \u2014 serienummer, kalibraties van sensoren, enzovoorts) ergens anders op. <\/p>\n<p><\/p>\n<p>Het onderwerp van een read-only bestandssysteem voor de Raspberry Pi is al uitvoerig bestudeerd, ik zal in dit artikel niet op de details van de implementatie ingaan. <em>(maar als er interesse is, schrijf ik misschien een mini-artikel over dit onderwerp)<\/em>. Een enkele opmerking die ik wil maken: zowel op basis van persoonlijke ervaring als op basis van getuigenissen van degenen die al hebben ge\u00efmplementeerd, is de winst in betrouwbaarheid er. Het is waar dat het onmogelijk is om volledig afscheid te nemen van storingen, maar het is heel goed mogelijk om hun frequentie aanzienlijk te verlagen. Bovendien worden de kaarten gestandaardiseerd, wat de vervanging voor het onderhoudspersoneel aanzienlijk vereenvoudigt.<\/p>\n<p><\/p>\n<h2 id=\"apparatnaya-chast\">Hardwaregedeelte<\/h2>\n<p><\/p>\n<p>Met de keuze van het type geheugen waren er geen bijzondere twijfels - NOR Flash.<br \/>\nArgumenten: <\/p>\n<p><\/p>\n<ul>\n<li>Eenvoudige aansluiting (meestal een SPI-bus, waarvan de ervaring al aanwezig is, dus 'hardware'-problemen zijn niet te verwachten);<\/li>\n<li>lacherige prijs;<\/li>\n<li>standaardwerkprotocol (de implementatie is al in de kernel van Linux aanwezig, desgewenst kunnen externe versies worden genomen die ook beschikbaar zijn, of zelfs een eigen versie worden geschreven, wat gelukkig eenvoudig is);<\/li>\n<li>betrouwbaarheid en levensduur:<br \/>\nuit een typische datasheet: gegevens worden 20 jaar bewaard, 100.000 erasecycli voor elk blok;<br \/>\nuit externe bronnen: extreem lage BER, wordt gesteld dat er geen foutcorrectiecodes nodig zijn. <em>(in sommige studies wordt ECC voor NOR overwogen, maar meestal bedoelen ze toch MLC NOR, dat komt ook voor)<\/em>.<\/li>\n<\/ul>\n<p><\/p>\n<p>Laten we de vereisten voor volume en levensduur inschatten.<\/p>\n<p><\/p>\n<p>Het is wenselijk dat gegevens gegarandeerd enkele dagen bewaard blijven. Dit is nodig zodat in geval van problemen met de communicatie de verkoopgeschiedenis niet verloren gaat. We zullen ons richten op 5 dagen, in deze periode <em>(zelfs rekening houdend met weekenden en feestdagen)<\/em> kan het probleem worden opgelost.<\/p>\n<p><\/p>\n<p>We verzamelen momenteel ongeveer 100 kB aan logboeken per dag (3-4 duizend records), maar geleidelijk groeit dit cijfer - er komt meer detail en er worden nieuwe gebeurtenissen toegevoegd. Daarnaast zijn er soms pieken (bijvoorbeeld wanneer een bepaalde sensor vals alarm slaat). We zullen rekenen op 10.000 records van 100 byte - een megabyte per dag.<\/p>\n<p><\/p>\n<p>Totaal komt dit neer op 5 MB aan 'schone' (goed te comprimeren) gegevens. Hierbij komen nog <em>(ruwe schatting)<\/em> 1 MB aan overheadgegevens.<\/p>\n<p><\/p>\n<p>Dat betekent dat we een chip van 8 MB nodig hebben als we compressie niet gebruiken, of 4 MB als we het wel doen. Dit zijn heel haalbare cijfers voor dit type geheugen.<\/p>\n<p><\/p>\n<p>Wat betreft de levensduur: als we ervan uitgaan dat het geheugen in zijn geheel niet vaker dan eens in de 5 dagen wordt overschreven, dan krijgen we over 10 jaar dienst minder dan duizend herschrijfcycli.<br \/>\nIk herinner me dat de fabrikant honderdduizend belooft.<\/p>\n<p>\n<b class=\"spoiler_title\">Een beetje over NOR vs NAND<\/b><\/p>\n<p>Vandaag is NAND-geheugen natuurlijk veel populairder, maar voor dit project zou ik het niet gebruiken: NAND vereist, in tegenstelling tot NOR, absoluut het gebruik van foutcorrectiecodes, foutbloktabellen, enzovoort, en de pinnen van NAND-chips zijn meestal ook veel meer.<\/p>\n<p><\/p>\n<p>Als nadelen van NOR kunnen worden genoemd:<\/p>\n<p><\/p>\n<ul>\n<li>kleine capaciteit (en daardoor een hoge prijs per megabyte);<\/li>\n<li>lage uitwisselsnelheid (grotendeels omdat er een seri\u00eble interface wordt gebruikt, meestal SPI of I2C);<\/li>\n<li>langzame wissen (afhankelijk van de blokgrootte, duurt het van een fractie van een seconde tot meerdere seconden).<\/li>\n<\/ul>\n<p><\/p>\n<p>Lijkt verder niets kritiek te zijn voor ons, dus we gaan door.<\/p>\n<p><\/p>\n<p>Als je ge\u00efnteresseerd bent in de details, is de chip gekozen: <noindex><a rel=\"nofollow\" href=\"https:\/\/www.adestotech.com\/wp-content\/uploads\/doc3686.pdf\">at25df321a<\/a><\/noindex> <em>(hoewel dat niet relevant is, er zijn veel analogen op de markt die compatibel zijn qua pinout en instructiesysteem; zelfs als we willen overstappen naar een chip van een andere fabrikant en\/of andere capaciteit, werkt alles gewoon zonder codewijzigingen.)<\/em>.<\/p>\n<p><\/p>\n<p>Ik gebruik de ingebouwde driver in de Linux-kernel; op de Raspberry is het dankzij de ondersteuning van device tree overlay heel eenvoudig \u2014 je moet de gecompileerde overlay in \/boot\/overlays plaatsen en een beetje de \/boot\/config.txt aanpassen.<\/p>\n<p>\n<b class=\"spoiler_title\">Voorbeeld van een dts-bestand<\/b><\/p>\n<p>Eerlijk gezegd ben ik niet zeker of het zonder fouten is geschreven, maar het werkt.<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">\/*\n * Device tree overlay for at25 at spi0.1\n *\/\n\n\/dts-v1\/;\n\/plugin\/;\n\n\/ {\n    compatible = \"brcm,bcm2835\", \"brcm,bcm2836\", \"brcm,bcm2708\", \"brcm,bcm2709\"; \n\n    \/* disable spi-dev for spi0.1 *\/\n    fragment@0 {\n        target = &lt;&amp;spi0&gt;;\n        __overlay__ {\n            status = \"okay\";\n            spidev@1{\n                status = \"disabled\";\n            };\n        };\n    };\n\n    \/* the spi config of the at25 *\/\n    fragment@1 {\n        target = &lt;&amp;spi0&gt;;\n        __overlay__ {\n            #address-cells = &lt;1&gt;;\n            #size-cells = &lt;0&gt;;\n            flash: m25p80@1 {\n                    compatible = \"atmel,at25df321a\";\n                    reg = &lt;1&gt;;\n                    spi-max-frequency = &lt;50000000&gt;;\n\n                    \/* default to false:\n                    m25p,fast-read ;\n                    *\/\n            };\n        };\n    };\n\n    __overrides__ {\n        spimaxfrequency = &lt;&amp;flash&gt;,\"spi-max-frequency:0\";\n        fastread = &lt;&amp;flash&gt;,\"m25p,fast-read?\";\n    };\n};<\/code><\/pre>\n<p>\n<b class=\"spoiler_title\">En nog een regel in config.txt<\/b><\/p>\n<pre><code class=\"plaintext\">dtoverlay=at25:spimaxfrequency=50000000<\/code><\/pre>\n<p><\/p>\n<p>Ik laat de beschrijving van de aansluiting van de chip op de Raspberry Pi achterwege. Aan de ene kant ben ik geen specialist in elektronica, aan de andere kant \u2014 het is zelfs voor mij vrij eenvoudig: de chip heeft slechts 8 pinnen, waarvan we aarde, voeding, SPI (CS, SI, SO, SCK) nodig hebben; de spanningsniveaus komen overeen met die van de Raspberry Pi, er is geen extra bedrading nodig \u2014 gewoon de genoemde 6 verbindingen aansluiten.<\/p>\n<p><\/p>\n<h2 id=\"postanovka-zadachi\">Taakstelling<\/h2>\n<p><\/p>\n<p>Zoals gebruikelijk, gaat de probleemstelling door verschillende iteraties, en ik denk dat het tijd is voor een volgende. Dus laten we stoppen, verzamelen wat al is geschreven, en de resterende onduidelijkheden ophelderen.<\/p>\n<p><\/p>\n<p>Dus hebben we besloten dat het logboek wordt opgeslagen in SPI NOR Flash.<\/p>\n<p>\n<b class=\"spoiler_title\">Wat is NOR Flash voor degenen die het niet weten?<\/b><\/p>\n<p>Dit is niet-vluchtig geheugen waarop drie operaties kunnen worden uitgevoerd:<\/p>\n<p><\/p>\n<ol>\n<li>Lezen:<br \/>\nHet meest gewone lezen: we geven een adres door en lezen zoveel bytes als we nodig hebben;<\/li>\n<li>Schrijven:<br \/>\nSchrijven in NOR-flash lijkt normaal, maar heeft \u00e9\u00e9n bijzonder kenmerk: je kunt alleen 1 naar 0 veranderen, maar niet omgekeerd. Bijvoorbeeld, als er in ons geheugencel 0x55 stond, dan zal na het schrijven van 0x0f er al 0x05 zijn opgeslagen. <em>(zie tabel iets lager)<\/em>;<\/li>\n<li>Wissen:<br \/>\nNatuurlijk moeten we ook de omgekeerde operatie kunnen uitvoeren - een 0 naar een 1 veranderen; daarvoor bestaat de wisoperatie. In tegenstelling tot de eerste twee, werkt deze niet met bytes, maar met blokken (het minimale erase-blok in de gekozen chip is 4kb). Wissen vernietigt het gehele blok en is de enige manier om een 0 naar een 1 te veranderen. Daarom moet je bij het werken met flash-geheugen vaak de datastucturen uitlijnen op de grens van het erase-blok.<br \/>\nSchrijven in NOR Flash:<\/li>\n<\/ol>\n<p><\/p>\n<p>Binaire gegevens<\/p>\n<p><strong>Was<\/strong><br \/>\n<code>01010101<\/code><\/p>\n<p><strong>Geschreven<\/strong><br \/>\n<code>00001111<\/code><\/p>\n<p><strong>Is<\/strong><br \/>\n<code>00000101<\/code><\/p>\n<p><\/p>\n<p>Het logboek zelf is een reeks records van variabele lengte. De typische lengte van een record is ongeveer 30 bytes (hoewel er soms ook records van enkele kilobytes voorkomen). <em>In dit geval behandelen we ze gewoon als een set bytes, maar als je ge\u00efnteresseerd bent, wordt binnen de records CBOR gebruikt.<\/em><\/p>\n<p><\/p>\n<p>Naast het logboek moeten we enige 'configuratie'-informatie opslaan, zowel verupdate als niet: een soort apparaat-ID, sensor kalibraties, een vlag 'apparaat tijdelijk uitgeschakeld', enz.<br \/>\nDeze informatie bestaat uit een set key-value records, die ook in CBOR worden opgeslagen. We hebben niet veel van deze informatie (maximaal enkele kilobytes), en deze wordt niet vaak bijgewerkt.<br \/>\nHierna zullen we het de context noemen.<\/p>\n<p><\/p>\n<p>Als we terugdenken aan het begin van dit artikel, is het zeer belangrijk om de betrouwbaarheid van gegevensopslag te waarborgen en, indien mogelijk, ononderbroken werking te garanderen, zelfs in het geval van hardwarestoringen\/datacorruptie.<\/p>\n<p><\/p>\n<p>Welke probleembronnen kunnen we overwegen?<\/p>\n<p><\/p>\n<ul>\n<li>Uitschakelen van de voeding tijdens write\/erase operaties. Dit valt onder het 'tegen een dwaasheid zijn er geen middelen'.<br \/>\nInformatie uit <noindex><a rel=\"nofollow\" href=\"https:\/\/electronics.stackexchange.com\/questions\/225956\/what-would-happen-in-case-of-power-outage-during-nor-flash-erase-or-programming\">discussie<\/a><\/noindex> op stackexchange: als de voeding wordt uitgeschakeld tijdens het werken met flash, leidt zowel erase (instelling op 1) als write (instelling op 0) tot ongedefinieerd gedrag: gegevens kunnen volledig worden opgeslagen, gedeeltelijk (bijvoorbeeld, we stuurden 10 bytes\/80 bits, maar konden slechts 45 bits opslaan), het is niet uitgesloten dat enkele bits in een 'tussen'-staat belanden (lezen kan zowel 0 als 1 opleveren);<\/li>\n<li>Fouten in het flash-geheugen zelf.<br \/>\nHoewel de BER zeer laag is, kan deze niet gelijk aan nul zijn;<\/li>\n<li>Fouten op de bus<br \/>\nDe gegevens die via SPI worden verzonden zijn op geen enkele manier beveiligd, en er kunnen zowel enkele bitfouten als synchronisatiefouten optreden - verlies of invoeging van bits (wat leidt tot zware vervormingen van gegevens);<\/li>\n<li>Andere fouten\/storingen<br \/>\nFouten in de code, 'glitches' van de Raspberry, bemoeienis van aliens...<\/li>\n<\/ul>\n<p><\/p>\n<p>Ik heb de vereisten geformuleerd waarvan ik denk dat ze noodzakelijk zijn voor de betrouwbaarheid:<\/p>\n<p><\/p>\n<ul>\n<li>opnames moeten onmiddellijk naar flashgeheugen worden geschreven; uitgestelde opname wordt niet overwogen; - als er een fout optreedt, moet deze zo vroeg mogelijk worden gedetecteerd en verwerkt; - het systeem moet in staat zijn om na fouten weer te herstellen waar mogelijk.<br \/>\n<em>(een voorbeeld uit de praktijk van 'hoe het niet moet zijn', waarmee ik denk dat iedereen wel eens te maken heeft gehad: na een noodherstart is het bestandssysteem 'beschadigd' en laadt het besturingssysteem niet)<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"idei-podhody-razmyshleniya\">Idee\u00ebn, benaderingen, overpeinzing<\/h2>\n<p><\/p>\n<p>Toen ik begon na te denken over deze taak, schoten er allerlei idee\u00ebn door mijn hoofd, bijvoorbeeld:<\/p>\n<p><\/p>\n<ul>\n<li>gegevenscompressie gebruiken;<\/li>\n<li>slimme datastructuren gebruiken, bijvoorbeeld de headers van de opnames apart opslaan van de opnames zelf, zodat bij een fout in een opname de anderen zonder problemen kunnen worden gelezen;<\/li>\n<li>bitvelden gebruiken om de voltooiing van opnames te controleren bij een stroomonderbreking;<\/li>\n<li>controlegetallen opslaan voor alles en nog wat;<\/li>\n<li>een of andere vorm van foutccorrectie codering gebruiken.<\/li>\n<\/ul>\n<p><\/p>\n<p>Deel van deze idee\u00ebn is gebruikt, een deel is weggelaten. Laten we dit stap voor stap doornemen.<\/p>\n<p><\/p>\n<h3 id=\"szhatie-dannyh\">Gegevenscompressie<\/h3>\n<p><\/p>\n<p>De gebeurtenissen die we in het logboek vastleggen zijn vrij eentonig en herhalend ('werd een munt van 5 roebel gegooid', 'drukte op de knop voor wisselgeld', ...). Daarom zou compressie behoorlijk effectief moeten zijn.<\/p>\n<p><\/p>\n<p>De overhead van compressie is marginaal (de processor is krachtig genoeg, zelfs op de eerste Pi had je \u00e9\u00e9n kern met een frequentie van 700MHz, op de actuele modellen zijn er meerdere kernen met een frequentie van meer dan een gigahertz), de snelheid van de opslag is niet hoog (enkele megabytes per seconde), de grootte van de opnames is beperkt. Kortom, als compressie invloed heeft op de prestatie, zal dit alleen positief zijn. <em>(absoluut niet kritiek, ik constateer gewoon)<\/em>. Plus, we don't actually have a genuine embedded system, but rather a regular Linux, so the implementation shouldn't require much effort (just linking the library and using a few of its functions).<\/p>\n<p><\/p>\n<p>A segment of the log from the operational device (1.7MB, 70,000 entries) was taken and initially checked for compressibility using the available tools on the computer: gzip, lz4, lzop, bzip2, xz, zstd.<\/p>\n<p><\/p>\n<ul>\n<li>gzip, xz, and zstd showed similar results (40KB).<br \/>\nIt was surprising that the trendy xz performed at the same level as gzip or zstd;<\/li>\n<li>lzip with default settings yielded slightly worse results;<\/li>\n<li>lz4 and lzop showed not very good results (150KB);<\/li>\n<li>bzip2 surprisingly provided a good result (18KB).<\/li>\n<\/ul>\n<p><\/p>\n<p>So, the data compresses very well.<br \/>\nTherefore, (if we do not find any fatal flaws) compression is a go! Simply because more data can fit on the same flash drive.<\/p>\n<p><\/p>\n<p>Let's think about the drawbacks.<\/p>\n<p><\/p>\n<p>The first problem: we already agreed that each entry must be immediately written to the flash. Typically, a compressor accumulates data from the input stream until it decides it's time to write to the output. We, however, need to immediately obtain a compressed block of data and store it in non-volatile memory.<\/p>\n<p><\/p>\n<p>I see three ways:<\/p>\n<p><\/p>\n<ol>\n<li>Compress each entry using dictionary compression instead of the algorithms discussed above.<br \/>\nEen werkbare optie, maar ik vind het niet leuk. Om een redelijke mate van compressie te waarborgen, moet de woordenlijst 'aangepast' zijn aan specifieke gegevens, elke wijziging zal leiden tot een catastrofale daling van het compressieniveau. Ja, het probleem kan worden opgelost door een nieuwe versie van de woordenlijst te cre\u00ebren, maar dat is een hoofdpijn - we moeten alle versies van de woordenlijst opslaan; in elke vermelding moeten we aangeven met welke versie van de woordenlijst deze werd gecomprimeerd...<\/li>\n<li>Elke vermelding 'klassiek' compressie-algoritmen laten toepassen, maar onafhankelijk van anderen.<br \/>\nThe discussed compression algorithms are not designed to work with entries of this size (tens of bytes), the compression ratio will clearly be less than 1 (that is, an increase in data volume instead of compression);<\/li>\n<li>Perform a FLUSH after each entry.<br \/>\nMany compression libraries support FLUSH. This is a command (or parameter to the compression procedure) that, when received, instructs the compressor to generate a compressed stream so that it can be restored based on it. <strong>all<\/strong> ongecomprimeerde gegevens die al zijn verkregen. Een dergelijke analogie <code>sync<\/code> in bestandssystemen of <code>commit<\/code> in sql.<br \/>\nWat belangrijk is, is dat latere compressieoperaties de opgebouwde woordenlijst kunnen gebruiken en de compressiegraad niet zo veel zal lijden als in de vorige variant.<\/li>\n<\/ol>\n<p><\/p>\n<p>Ik denk dat het duidelijk is dat ik voor de derde optie heb gekozen, laten we daar dieper op ingaan.<\/p>\n<p><\/p>\n<p>Vond <noindex><a rel=\"nofollow\" href=\"https:\/\/www.bolet.org\/~pornin\/deflate-flush.html\">geweldig artikel<\/a><\/noindex> over FLUSH in zlib.<\/p>\n<p><\/p>\n<p>Ik heb een op de artikel gebaseerde test gedaan, ik nam 70.000 logrecords van een echt apparaat, met een paginagrootte van 60KB <em>(over de paginagrootte komen we nog terug)<\/em> kreeg:<\/p>\n<p><\/p>\n<p>Brongegevens<br \/>\ngzip compressie -9 (zonder FLUSH)<br \/>\nzlib met Z_PARTIAL_FLUSH<br \/>\nzlib met Z_SYNC_FLUSH<\/p>\n<p><strong>Grootte, KB<\/strong><br \/>\n1692<br \/>\n40<br \/>\n352<br \/>\n604<\/p>\n<p><\/p>\n<p>Op het eerste gezicht lijkt de prijs die FLUSH met zich meebrengt overdreven hoog, maar eigenlijk hebben we niet veel keus \u2014 of helemaal niet comprimeren, of comprimeren (en zeer effectief) met FLUSH. Vergeet niet dat we 70.000 records hebben, de overbodigheid die Z_PARTIAL_FLUSH toevoegt bedraagt slechts 4-5 bytes per record. En de compressieverhouding bleek bijna 5:1 te zijn, wat een uitstekende uitkomst is.<\/p>\n<p>\n<b class=\"spoiler_title\">Het lijkt misschien onverwacht, maar in werkelijkheid is Z_SYNC_FLUSH een effectievere manier om FLUSH uit te voeren.<\/b><\/p>\n<p>Bij gebruik van Z_SYNC_FLUSH zijn de laatste 4 bytes van elke record altijd 0x00, 0x00, 0xff, 0xff. En als we die kunnen kennen \u2014 dan hoeven we ze niet op te slaan, waardoor de uiteindelijke grootte slechts 324KB bedraagt.<\/p>\n<p><\/p>\n<p>In het artikel waar ik naar verwijs, is er een uitleg:<\/p>\n<p><\/p>\n<blockquote><p>Een nieuw type 0-blok met lege inhoud wordt toegevoegd.<\/p>\n<p>Een type 0-blok met lege inhoud bestaat uit:<\/p>\n<ul>\n<li>de drie-bits blokheader;<\/li>\n<li>0 tot 7 bits gelijk aan nul, om byte-alignment te bereiken;<\/li>\n<li>de vier-byte reeks 00 00 FF FF.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Zoals gemakkelijk te zien is, gaat er in het laatste blok voor deze 4 bytes tussen de 3 en 10 nullen. Echter, de praktijk heeft aangetoond dat er daadwerkelijk minimaal 10 nullen zijn.<\/p>\n<p><\/p>\n<p>Het blijkt dat zulke korte datablocks meestal (altijd?) gecodeerd worden met behulp van een blok type 1 (fixed block), dat altijd eindigt met 7 nullen, wat in totaal 10-17 gegarandeerde nullen oplevert (en de overige zullen met ongeveer 50% waarschijnlijkheid nul zijn).<\/p>\n<p><\/p>\n<p>Dus, op de testgegevens gaat bij 100% van de gevallen voor 0x00, 0x00, 0xff, 0xff een nullenbyte vooraf, en in meer dan een derde geval - twee nullenbytes <em>(misschien komt het omdat ik binair CBOR gebruik, en bij het gebruik van tekst-JSON zouden blokken van type 2 \u2014 dynamisch blok waarschijnlijk vaker voorkomen, en zouden er blokken zonder extra nullen voor 0x00, 0x00, 0xff, 0xff zijn)<\/em>.<\/p>\n<p><\/p>\n<p>In totaal kunnen we met de beschikbare testgegevens minder dan 250 KB aan gecomprimeerde gegevens halen.<\/p>\n<p><\/p>\n<p>We kunnen nog wat besparen door met bits te jongleren: momenteel negeren we de aanwezigheid van meerdere nulbits aan het einde van een blok; ook veranderen een paar bits aan het begin van een blok niet...<br \/>\nMaar hier nam ik de beslissing om te stoppen, anders zouden we met deze snelheid zelf een archiveringsprogramma gaan ontwikkelen.<\/p>\n<p><\/p>\n<p>Al met al heb ik uit mijn testgegevens 3-4 bytes per schrijven gehaald, met een compressieverhouding van meer dan 6:1. Eerlijk gezegd verwachtte ik niet zo'n resultaat; naar mijn mening is alles beter dan 2:1 al een resultaat dat het gebruik van compressie rechtvaardigt.<\/p>\n<p><\/p>\n<p>Alles is geweldig, maar zlib (deflate) is toch een verouderd en enigszins ouderwets compressie-algoritme. Het feit dat de laatste 32 KB van de ongecomprimeerde gegevensstroom als woordenboek worden gebruikt, voelt tegenwoordig vreemd aan (dit betekent dat als een datablock erg lijkt op dat wat 40 KB geleden in de invoerstroom was, het opnieuw wordt gecomprimeerd in plaats van te verwijzen naar de vorige inzet). In moderne archiveringsprogramma's wordt de grootte van het woordenboek vaak in megabytes gemeten in plaats van kilobytes.<\/p>\n<p><\/p>\n<p>Dus we gaan door met ons mini-onderzoek naar archiveringsprogramma's.<\/p>\n<p><\/p>\n<p>De volgende die we testten was bzip2 (ter herinnering, zonder FLUSH toonde het een fantastische compressieverhouding van bijna 100:1). Helaas, met FLUSH presteerde het heel slecht, de grootte van de gecomprimeerde gegevens was groter dan die van de ongecomprimeerde.<\/p>\n<p>\n<b class=\"spoiler_title\">Mijn vermoedens over de redenen voor het falen<\/b><\/p>\n<p>Libbz2 biedt slechts \u00e9\u00e9n optie voor flush, die schijnbaar het woordenboek leegt (vergelijkbaar met Z_FULL_FLUSH in zlib), waardoor effici\u00ebnt comprimeren na dit punt niet mogelijk is.<\/p>\n<p><\/p>\n<p>En als laatste werd zstd getest. Afhankelijk van de parameters compresseert het of op het niveau van gzip, maar veel sneller, of beter dan gzip.<\/p>\n<p><\/p>\n<p>Helaas presteerde het met FLUSH ook 'niet zo goed': de grootte van de gecomprimeerde gegevens was ongeveer 700Kb.<\/p>\n<p><\/p>\n<p>Ik <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/facebook\/zstd\/issues\/900\">ik stelde een vraag<\/a><\/noindex> op de projectpagina op github, kreeg het antwoord dat je rekening moet houden met tot 10 bytes aan overheadgegevens per blok van gecomprimeerde gegevens, wat dicht bij de verkregen resultaten ligt; deflate zal je niet kunnen inhalen.<\/p>\n<p><\/p>\n<p>Hierop besloot ik mijn experimenten met archiveringsprogramma's te be\u00ebindigen (ter herinnering, xz, lzip, lzo, lz4 hebben zich nog niet bewezen in de testfase zonder FLUSH, en ik wilde niet meer exotische compressie-algoritmen overwegen).<\/p>\n<p><\/p>\n<p>Laten we terugkeren naar de problemen van archivering.<\/p>\n<p><\/p>\n<p>Het tweede probleem (zoals we het in volgorde zeggen, en niet op betekenis) is dat de gecomprimeerde gegevens een enkele stroom vormen, waarin voortdurend verwijzingen naar eerdere delen voorkomen. Bij beschadiging van een deel van de gecomprimeerde gegevens verliezen we niet alleen het gerelateerde onbewerkte gegevensblok, maar ook alle daaropvolgende.<\/p>\n<p><\/p>\n<p>Er zijn verschillende benaderingen om dit probleem op te lossen:<\/p>\n<p><\/p>\n<ol>\n<li>Voorkom het ontstaan van het probleem - voeg redundantie toe aan de gecomprimeerde gegevens, zodat fouten kunnen worden gedetecteerd en gecorrigeerd; hierover zullen we later spreken;<\/li>\n<li>Minimaliseer de gevolgen in geval van een probleem.<br \/>\nWe hebben eerder besproken dat we elk gegevensblok onafhankelijk kunnen comprimeren, wat het probleem van zichzelf zou wegnemen (de schade aan de gegevens van \u00e9\u00e9n blok leidt alleen tot verlies van die gegevens). Dit is echter een uiterste oplossing, waarbij compressie van gegevens niet effectief zal zijn. De tegenovergestelde extreem: gebruik de hele 4 MB van onze chip als \u00e9\u00e9n enkel archief, wat ons geweldige compressie zal geven, maar catastrofale gevolgen zal hebben in geval van gegevensbeschadiging.<br \/>\n<em>Ja, er moet een compromis zijn vanuit het oogpunt van betrouwbaarheid. Maar we moeten ons ervan bewust zijn dat we een gegevensopslagformaat ontwikkelen voor niet-vluchtig geheugen met een extreem lage BER en een geclaimde levensduur van gegevens van 20 jaar.<\/em><\/li>\n<\/ol>\n<p><\/p>\n<p>Tijdens experimenten merkte ik dat merkbare verliezen in compressieniveau beginnen bij gecomprimeerde gegevensblokken van minder dan 10 KB.<br \/>\nEerder werd vermeld dat het gebruikte geheugen een paginanaire organisatie heeft, ik zie geen reden om de overeenkomst '\u00e9\u00e9n pagina - \u00e9\u00e9n blok gecomprimeerde gegevens' niet te gebruiken.<\/p>\n<p><\/p>\n<p>Dus de minimale redelijke paginagrootte is 16 KB (met ruimte voor beheergegevens). Een dergelijke kleine paginagrootte legt echter aanzienlijke beperkingen op aan de maximale grootte van een record.<\/p>\n<p><\/p>\n<p>Hoewel ik momenteel geen records van meer dan enkele kilobytes in gecomprimeerde vorm verwacht, heb ik besloten om pagina's van 32 KB te gebruiken (in totaal 128 pagina's op de chip).<\/p>\n<p><\/p>\n<p><strong>Samenvatting:<\/strong><\/p>\n<p><\/p>\n<ul>\n<li>We slaan de gegevens gecomprimeerd op met zlib (deflate);<\/li>\n<li>Voor elk record stellen we Z_SYNC_FLUSH in;<\/li>\n<li>We snijden de eindbytes van elke gecomprimeerde opname af, <em>(bijvoorbeeld, 0x00, 0x00, 0xff, 0xff)<\/em>; in de header geven we aan hoeveel bytes we hebben afgesneden;<\/li>\n<li>We store data in pages of 32KB; inside each page, there is a continuous stream of compressed data; we start compression anew on each page.<\/li>\n<\/ul>\n<p><\/p>\n<p>And before finishing with the compression, I would like to point out that we only get a few bytes of compressed data per record, so it is crucial not to inflate the overhead information; every byte counts here.<\/p>\n<p><\/p>\n<h3 id=\"hranenie-zagolovkov-dannyh\">Storage of data headers<\/h3>\n<p><\/p>\n<p>Since we have variable-length records, we need a way to determine the placement\/boundaries of the records.<\/p>\n<p><\/p>\n<p>I know three approaches:<\/p>\n<p><\/p>\n<ol>\n<li>All records are stored in a continuous stream, starting with a record header that contains the length, followed by the record itself.<br \/>\nIn this variant, both the headers and the data can have variable length.<br \/>\nEssentially, we end up with a singly linked list, which is used quite often;<\/li>\n<li>Headers and records themselves are stored in separate streams.<br \/>\nBy using fixed-length headers, we ensure that the corruption of one header does not affect the others.<br \/>\nThis approach is used, for example, in many file systems;<\/li>\n<li>Records are stored in a continuous stream, with the boundary of the record defined by some marker (symbol\/sequence of symbols that are prohibited within data blocks). If a marker is found inside a record, we replace it with a certain sequence (escape it).<br \/>\nThis approach is used, for example, in the PPP protocol.<\/li>\n<\/ol>\n<p><\/p>\n<p>Let me illustrate.<\/p>\n<p><\/p>\n<p>Option 1:<br \/>\n<img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/e5a9676ca21eaca07e64ddf9fcd9f2eb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHere everything is very simple: knowing the length of the record, we can calculate the address of the next header. This way, we move through the headers until we encounter an area filled with 0xff (free space) or the end of the page.<\/p>\n<p><\/p>\n<p>Option 2:<br \/>\n<img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/4fed4860fb659ab6042be9aeeb5c041a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nVanwege de variabele lengte van de record kunnen we niet van tevoren zeggen hoeveel records (en dus ook koppen) we op de pagina nodig hebben. We kunnen de koppen en de gegevens over verschillende pagina's verspreiden, maar ik geef de voorkeur aan een andere benadering: we plaatsen zowel de koppen (van constante grootte) als de gegevens (van variabele lengte) op dezelfde pagina, waarbij de koppen aan het begin van de pagina staan en de gegevens aan het einde. Zodra ze elkaar \u2018ontmoeten\u2019 (er is niet genoeg vrije ruimte voor een nieuw record) beschouwen we deze pagina als vol.<\/p>\n<p><\/p>\n<p>Option 3:<br \/>\n<img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/dfb64009aa7781fcb8a47423ff4160c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHet is niet nodig om de lengte of andere informatie over de locatie van gegevens in de header op te slaan; markers die de grenzen van records aangeven zijn voldoende. Echter, de gegevens moeten worden verwerkt bij het schrijven\/lezen.<br \/>\nAls marker zou ik 0xff gebruiken (waarmee de pagina na het wissen is gevuld), zodat het vrije gebied zeker niet als gegevens wordt ge\u00efnterpreteerd.<\/p>\n<p><\/p>\n<p>Vergelijkingstabel:<\/p>\n<p><\/p>\n<p>Optie 1<br \/>\nOptie 2<br \/>\nOptie 3<\/p>\n<p><strong>Fouttolerantie<\/strong><br \/>\n\u2014<br \/>\n+<br \/>\n+<\/p>\n<p><strong>Compactheid<\/strong><br \/>\n+<br \/>\n\u2014<br \/>\n+<\/p>\n<p><strong>Complexiteit van implementatie<\/strong><br \/>\n*<br \/>\n**<br \/>\n**<\/p>\n<p><\/p>\n<p>Optie 1 heeft een fatale tekortkoming: als een van de headers beschadigd is, vervalt de gehele daaropvolgende keten. De andere opties stellen ons in staat om een deel van de gegevens te herstellen, zelfs bij massale beschadigingen.<br \/>\nMaar het is op dit punt relevant om te herinneren dat we besloten hebben de gegevens in gecomprimeerde vorm op te slaan, en zo verliezen we alle gegevens op de pagina na het \u2018beschadigde\u2019 record, dus hoewel er in de tabel een min staat, negeren we deze.<\/p>\n<p><\/p>\n<p>Compactheid:<\/p>\n<p><\/p>\n<ul>\n<li>in de eerste optie hoeven we alleen de lengte in de header op te slaan; als we hele variabelen van variabele lengte gebruiken, kunnen we in de meeste gevallen volstaan met \u00e9\u00e9n byte;<\/li>\n<li>in de tweede optie moeten we het startadres en de lengte opslaan; de opname moet een constante grootte hebben, ik schat 4 bytes per opname (twee bytes voor offset en twee bytes voor lengte);<\/li>\n<li>de derde optie heeft slechts \u00e9\u00e9n symbool nodig om het begin van de opname aan te duiden, plus de opname zelf zal door escaping met 1-2% toenemen. Over het geheel genomen is er een vergelijkbare verhouding met de eerste optie.<\/li>\n<\/ul>\n<p><\/p>\n<p>In eerste instantie beschouwde ik de tweede optie als de primaire (en ik had zelfs een implementatie geschreven). Ik heb deze pas verworpen toen ik definitief besloot om compressie te gebruiken.<\/p>\n<p><\/p>\n<p><em>Misschien zal ik ooit zo'n optie gebruiken. Bijvoorbeeld als ik verantwoordelijk ben voor het opslaan van gegevens voor een ruimtevaartuig dat tussen de Aarde en Mars reist - totaal andere eisen aan betrouwbaarheid, kosmische straling, ...<\/em><\/p>\n<p><\/p>\n<p>Wat de derde optie betreft: ik heb deze twee sterren gegeven voor de complexiteit van implementatie, gewoon omdat ik niet hou van het rommelen met escaping, het wijzigen van de lengte in het proces, enzovoort. Ja, misschien ben ik bevooroordeeld, maar ik moet de code schrijven - waarom zou ik mezelf dwingen om te doen wat ik niet leuk vind.<\/p>\n<p><\/p>\n<p><strong>Samenvatting:<\/strong> we kiezen voor de opslaan in de vorm van ketens \u2018kop met lengte - gegevens van variabele lengte\u2019 vanwege de effici\u00ebntie en eenvoud van implementatie.<\/p>\n<p><\/p>\n<h3 id=\"ispolzovanie-bitovyh-poley-dlya-kontrolya-uspeshnosti-operaciy-zapisi\">Het gebruik van bitvelden voor het controleren van het succes van schrijfoperaties.<\/h3>\n<p><\/p>\n<p>Ik herinner me nu al niet meer waar ik het idee heb opgepikt, maar het ziet er ongeveer zo uit:<br \/>\nVoor elke invoer toewijzen we een paar bits om vlaggen op te slaan.<br \/>\n<em>Zoals eerder vermeld, zijn na het wissen alle bits gevuld met 1, en we kunnen 1 naar 0 veranderen, maar niet andersom.<\/em> Dus voor \u2018vlag niet ingesteld\u2019 gebruiken we 1, voor \u2018vlag ingesteld\u2019 gebruiken we 0.<\/p>\n<p><\/p>\n<p>Zo kan de invoer van variabele lengte in flash eruitzien:<\/p>\n<p><\/p>\n<ol>\n<li>We stellen de vlag \"lengte schrijfproces begonnen\" in;<\/li>\n<li>We schrijven de lengte;<\/li>\n<li>We stellen de vlag \"data schrijfproces begonnen\" in;<\/li>\n<li>We schrijven de data;<\/li>\n<li>We stellen de vlag \"schrijven is voltooid\" in.<\/li>\n<\/ol>\n<p><\/p>\n<p>Daarnaast hebben we de vlag \"er is een fout opgetreden\", in totaal 4 bit-vlaggen.<\/p>\n<p><\/p>\n<p>In dit geval hebben we twee stabiele toestanden \"1111\" - schrijven is niet begonnen en \"1000\" - schrijven is succesvol verlopen; in het geval van een onverwachte onderbreking van het schrijfproces krijgen we tussenliggende toestanden, die we dan kunnen detecteren en verwerken.<\/p>\n<p><\/p>\n<p>Een interessante benadering, maar het beschermt alleen tegen onverwachte uitschakelingen van de voeding en soortgelijke storingen, wat zeker belangrijk is, maar het is verre van de enige (en zelfs niet de belangrijkste) reden voor mogelijke fouten.<\/p>\n<p><\/p>\n<p><strong>Samenvatting:<\/strong> Laten we verder gaan in de zoektocht naar een goede oplossing.<\/p>\n<p><\/p>\n<h3 id=\"kontrolnye-summy\">Controlegetallen.<\/h3>\n<p><\/p>\n<p>Controlegetallen bieden ook de mogelijkheid om te verifi\u00ebren (met een hoge waarschijnlijkheid) dat we precies lezen wat er zou moeten zijn geschreven. En in tegenstelling tot de eerder besproken bitvelden werken ze altijd.<\/p>\n<p><\/p>\n<p>Als we de lijst van potenti\u00eble bronnen van problemen die we eerder hebben besproken bekijken, kan een controlegetal een fout herkennen, ongeacht de oorsprong ervan. <em>(behalve, misschien, kwaadaardige buitenaardse wezens - die kunnen ook een controlegetal vervalsen)<\/em>.<\/p>\n<p><\/p>\n<p>Dus als ons doel is om te controleren of de gegevens intact zijn, zijn controlegetallen een geweldig idee.<\/p>\n<p><\/p>\n<p>De keuze van het algoritme voor het berekenen van het controlegetal was geen vraag - CRC. Enerzijds maken de wiskundige eigenschappen het mogelijk om 100% van bepaalde types fouten te detecteren, anderzijds toont dit algoritme bij willekeurige gegevens meestal een kans op botsingen die niet veel hoger is dan de theoretische limiet. <img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/bf9cca3564db7d9d03d3ce49642d3a71.jpg\" style=\"display:block;margin: 0 auto;\" \/>Ook al is dit misschien niet het snelste algoritme of het minimals qua aantal botsingen, het heeft een zeer belangrijke eigenschap: in de tests die ik heb uitgevoerd, kwam ik geen patronen tegen waar het duidelijk falen vertoonde. Stabiliteit is in dit geval de belangrijkste kwaliteit.<\/p>\n<p><\/p>\n<p>Voorbeeld van een omvangrijk onderzoek: <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo.html\">deel 1<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo2.html\">deel 2<\/a><\/noindex> <em>(links naar narod.ru, sorry)<\/em>.<\/p>\n<p><\/p>\n<p>Echter, de taak van het kiezen van een checksum is nog niet voltooid; CRC is een hele familie van checksums. We moeten beslissen over de lengte, en daarna een polynoom kiezen.<\/p>\n<p><\/p>\n<p>De keuze van de lengte van de checksum is niet zo'n eenvoudige kwestie als het op het eerste gezicht lijkt.<\/p>\n<p><\/p>\n<p>Ik zal het illustreren:<br \/>\nLaten we zeggen dat de kans op een fout in elke byte <img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/89a9be2edf2a43cc9115eba37fa02fc8.jpg\" style=\"display:block;margin: 0 auto;\" \/> en de ideale checksum is, laten we het gemiddelde aantal fouten per miljoen records berekenen:<\/p>\n<p><\/p>\n<p>Gegevens, byte<br \/>\nChecksum, byte<br \/>\nNiet-ontdekte fouten<br \/>\nValse positieven voor fouten<br \/>\nTotaal aantal onjuiste detecties<\/p>\n<p>1<br \/>\n0<br \/>\n1000<br \/>\n0<br \/>\n1000<\/p>\n<p>1<br \/>\n1<br \/>\n4<br \/>\n999<br \/>\n1003<\/p>\n<p>1<br \/>\n2<br \/>\n\u22480<br \/>\n1997<br \/>\n1997<\/p>\n<p>1<br \/>\n4<br \/>\n\u22480<br \/>\n3990<br \/>\n3990<\/p>\n<p>10<br \/>\n0<br \/>\n9955<br \/>\n0<br \/>\n9955<\/p>\n<p>10<br \/>\n1<br \/>\n39<br \/>\n990<br \/>\n1029<\/p>\n<p>10<br \/>\n2<br \/>\n\u22480<br \/>\n1979<br \/>\n1979<\/p>\n<p>10<br \/>\n4<br \/>\n\u22480<br \/>\n3954<br \/>\n3954<\/p>\n<p>1000<br \/>\n0<br \/>\n632305<br \/>\n0<br \/>\n632305<\/p>\n<p>1000<br \/>\n1<br \/>\n2470<br \/>\n368<br \/>\n2838<\/p>\n<p>1000<br \/>\n2<br \/>\n10<br \/>\n735<br \/>\n745<\/p>\n<p>1000<br \/>\n4<br \/>\n\u22480<br \/>\n1469<br \/>\n1469<\/p>\n<p><\/p>\n<p>Het lijkt eenvoudig \u2014 kies de lengte van de checksum afhankelijk van de lengte van de beschermde gegevens met een minimum aan onjuiste detecties \u2014 en het is opgelost.<\/p>\n<p><\/p>\n<p>Echter, bij korte checksums ontstaat er een probleem: hoewel ze goed zijn in het detecteren van enkele bitfouten, kunnen ze met een behoorlijke kans volledig willekeurige gegevens als geldig beschouwen. Er was al een artikel op Habr dat het beschreef. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/428746\/\">de probleem in het echte leven<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Dus, om een willekeurige checksum-overeenstemming praktisch onmogelijk te maken, moet je checksums gebruiken van 32 bits of meer. <em>(voor lengtes van meer dan 64 bits worden meestal cryptografische hashfuncties gebruikt)<\/em>.<\/p>\n<p><\/p>\n<p>Hoewel ik eerder schreef dat we zoveel mogelijk ruimte moeten besparen, zullen we toch een 32-bits checksum gebruiken (16 bits is te weinig, de kans op een botsing is meer dan 0,01%; en 24 bits, zoals ze zeggen, is een tussenoplossing).<\/p>\n<p><\/p>\n<p>Hier kan een bezwaar ontstaan: was dat wel de reden waarom we elke byte bij het kiezen van compressie hebben bespaard, om nu meteen 4 bytes te geven? Was het niet beter om niet te comprimeren en geen checksum toe te voegen? Natuurlijk niet, het ontbreken van compressie <em>betekent niet<\/em>, dat we geen integriteitscontrole nodig hebben.<\/p>\n<p><\/p>\n<p>Wat betreft de keuze van de polynoom zullen we niet de fiets opnieuw uitvinden, maar zullen we de momenteel populaire CRC-32C gebruiken.<br \/>\nDeze code detecteert 6 bitfouten in pakketten tot 22 byte (misschien de meest voorkomende situatie voor ons), 4 bitfouten in pakketten tot 655 byte (ook een vaak voorkomende situatie voor ons), 2 of een oneven aantal bitfouten in pakketten van elke redelijke lengte.<\/p>\n<p>\n<b class=\"spoiler_title\">Als iemand ge\u00efnteresseerd is in de details<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cyclic_redundancy_check\">Wikipedia-artikel<\/a><\/noindex> over CRC.<\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0x8f6e37a0_len.txt\">Parameters van de crc-32c code<\/a><\/noindex> en een werkende opdracht krijgen. <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/crc\/notes.html\">op de website van Kupman<\/a><\/noindex> \u2014 waarschijnlijk de grootste specialist in CRC op de planeet.<\/p>\n<p><\/p>\n<p>In <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/networks\/dsn02\/dsn02_koopman.pdf\">in zijn artikel<\/a><\/noindex> bestaande <noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0xfa567d89_len.txt\">een andere interessante code<\/a><\/noindex>, die iets betere parameters biedt voor de voor ons relevante pakketlengtes, maar ik vond het verschil niet significant genoeg en mezelf bekwaam genoeg om een aangepaste code boven de standaard en goed onderzochte te kiezen.<\/p>\n<p><\/p>\n<p>Bovendien, aangezien onze gegevens gecomprimeerd zijn, rijst de vraag: moeten we de checksum berekenen over gecomprimeerde of ongecomprimeerde gegevens?<\/p>\n<p><\/p>\n<p>Argumenten \u2018voor\u2019 het berekenen van de controlegetal van ongecomprimeerde gegevens:<\/p>\n<p><\/p>\n<ul>\n<li>we moeten uiteindelijk de integriteit van de gegevensopslag controleren \u2014 dat is wat we hier rechtstreeks doen (bij deze controle worden ook mogelijke fouten in de compressie-\/decompressie-implementatie en beschadigingen door defecte geheugen gecontroleerd);<\/li>\n<li>het deflate-algoritme in zlib heeft een voldoende volwassen implementatie en <em>mag niet<\/em> het faalt bij \u2018ondeugdelijke\u2019 invoergegevens, bovendien is het vaak in staat om zelf fouten in de invoerstroom te detecteren, wat het algehele risico op ongedetecteerde fouten vermindert (ik heb een test uitgevoerd met het inverteren van een enkele bit in een korte record, zlib detecteerde de fout ongeveer \u00e9\u00e9n derde van de keren).<\/li>\n<\/ul>\n<p><\/p>\n<p>Argumenten \u2018tegen\u2019 het berekenen van de controlegetal van ongecomprimeerde gegevens:<\/p>\n<p><\/p>\n<ul>\n<li>CRC is precies \u2018afgestemd\u2019 op de zeldzame bitfouten die karakteristiek zijn voor flash-geheugen (een bitfout in een gecomprimeerde stroom kan leiden tot een massale wijziging van de uitvoerstroom, waarbij we, puur theoretisch, een \u2018botsing\u2019 kunnen \u2018vangen\u2019);<\/li>\n<li>ik ben niet echt blij met het idee om potentieel defecte gegevens naar de decompressiemodule te sturen, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cvedetails.com\/vulnerability-list\/vendor_id-72\/product_id-1820\/GNU-Zlib.html\">wie weet<\/a><\/noindex>, hoe deze zal reageren.<\/li>\n<\/ul>\n<p><\/p>\n<p>In dit project heb ik besloten om af te wijken van de gangbare praktijk van het opslaan van de checksum van ongecomprimeerde gegevens.<\/p>\n<p><\/p>\n<p><strong>Samenvatting:<\/strong> we gebruiken CRC-32C, de checksum berekenen we over de gegevens in de vorm waarin ze in de flash worden opgeslagen (na compressie).<\/p>\n<p><\/p>\n<h3 id=\"izbytochnost\">Redundantie<\/h3>\n<p><\/p>\n<p>Het gebruik van redundant coderen elimineert natuurlijk niet het verlies van gegevens, maar kan de kans op onherstelbaar gegevensverlies aanzienlijk (vaak met vele ordes) verlagen.<\/p>\n<p><\/p>\n<p>We kunnen verschillende soorten redundanties gebruiken om fouten te corrigeren.<br \/>\nHamming-codes kunnen enkele bitfouten corrigeren, Reed-Solomon-codes zijn symbolisch, verschillende kopie\u00ebn van gegevens samen met checksums of codering zoals RAID-6 kunnen helpen gegevens te herstellen, zelfs in geval van massale beschadigingen.<br \/>\nOorspronkelijk was ik voorstander van breed gebruik van foutcorrigerende codering, maar later besefte ik dat we eerst moeten begrijpen tegen welke fouten we ons willen beschermen, en vervolgens de codering moeten kiezen.<\/p>\n<p><\/p>\n<p>We hebben eerder besproken dat fouten zo snel mogelijk moeten worden gedetecteerd. Wanneer kunnen we met fouten geconfronteerd worden?<\/p>\n<p><\/p>\n<ol>\n<li>Een niet-afgeronde record (om welke reden dan ook, de voeding werd tijdens de opname uitgeschakeld, de Raspberry bevroor, ...)<br \/>\nHelaas, in het geval van een dergelijke fout blijven we alleen over om ongeldige vermeldingen te negeren en de gegevens als verloren te beschouwen;<\/li>\n<li>Schrijffouten (om welke reden dan ook is niet wat geschreven zou worden in flash-geheugen geschreven)<br \/>\nDergelijke fouten kunnen we onmiddellijk detecteren als we direct na opname een controlelezen uitvoeren;<\/li>\n<li>Vervorming van gegevens in het geheugen tijdens opslag;<\/li>\n<li>Leesfouten<br \/>\nVoor correctie is het voldoende om bij een mismatch van de checksum een aantal keren te herlezen.<\/li>\n<\/ol>\n<p><\/p>\n<p>Dat wil zeggen, alleen fouten van het derde type (spontane gegevenscorruptie bij opslag) kunnen niet worden gecorrigeerd zonder foutcorrigerende codering. Dergelijke fouten lijken me uiterst onwaarschijnlijk.<\/p>\n<p><\/p>\n<p><strong>Samenvatting:<\/strong> Het werd besloten af te zien van redundante codering, maar als de praktijk aantoont dat deze beslissing fout is, dan zal de kwestie opnieuw worden bekeken (met reeds verzamelde statistieken over storingen, die ons in staat zal stellen de optimale codering te kiezen).<\/p>\n<p><\/p>\n<h3 id=\"prochee\">Overige<\/h3>\n<p><\/p>\n<p>Het format van het artikel laat natuurlijk niet toe om elk bit in het formaat te rechtvaardigen <em>(en ik ben inmiddels ook uitgeput)<\/em>, daarom zal ik kort langs enkele punten gaan die eerder niet zijn aangestipt.<\/p>\n<p><\/p>\n<ul>\n<li>Besloten is om alle pagina's \u2018gelijkwaardig\u2019 te maken<br \/>\nEr zijn geen speciale pagina's met metadata, afzonderlijke stromen, enzovoorts; in plaats daarvan is er \u00e9\u00e9n stroom die alle pagina's achtereenvolgens herschrijft.<br \/>\nDit zorgt voor gelijkmatige slijtage van pagina's, het ontbreken van een enkel stevigere punt en gewoon omdat het leuk is.<\/li>\n<li>Versiebeheer van het formaat moet absoluut worden voorzien.<br \/>\nEen formaat zonder versienummer in de header is slecht!<br \/>\nHet is genoeg om een veld met een bepaalde Magic Number (handtekening) toe te voegen aan de header van de pagina, dat de gebruikte versie van het formaat aangeeft. <em>(ik denk niet dat er in de praktijk zelfs een tiental zullen zijn)<\/em>;<\/li>\n<li>Gebruik voor records (waarvan er heel veel zijn) een header van variabele lengte, en probeer voor de meeste gevallen de lengte op 1 byte te houden.<\/li>\n<li>Gebruik binaire codes van variabele lengte om de lengte van de header en de lengte van het afgesneden deel van de gecomprimeerde opname te coderen.<\/li>\n<\/ul>\n<p><\/p>\n<p>Het hielp enorm <noindex><a rel=\"nofollow\" href=\"https:\/\/planetcalc.com\/2481\/\">de online generator<\/a><\/noindex> van Huffman-codes. In slechts enkele minuten lukte het om de juiste variabele lengtecodes te vinden.<\/p>\n<p><\/p>\n<h1 id=\"anchorformatanchoropisanie-formata-hraneniya-dannyh\"><noindex><a rel=\"nofollow\" name=\"format\"><\/a><\/noindex>Beschrijving van het gegevensopslagformaat<\/h1>\n<p><\/p>\n<h2 id=\"byte-order\">Byte volgorde<\/h2>\n<p><\/p>\n<p>Velden groter dan \u00e9\u00e9n byte worden opgeslagen in big-endian formaat (netwerk byte volgorde), dat wil zeggen dat 0x1234 wordt opgeslagen als 0x12, 0x34.<\/p>\n<p><\/p>\n<h2 id=\"delenie-na-stranicy\">Verdeling in pagina's<\/h2>\n<p><\/p>\n<p>Het hele flashgeheugen is verdeeld in pagina's van gelijke grootte.<\/p>\n<p><\/p>\n<p>De standaard pagina-grootte is 32 KB, maar niet meer dan 1\/4 van de totale grootte van de geheugenchip (voor een chip van 4 MB resulteren 128 pagina's).<\/p>\n<p><\/p>\n<p>Elke pagina slaat gegevens onafhankelijk van de andere op (dat wil zeggen, de gegevens van de ene pagina verwijzen niet naar de gegevens van een andere pagina).<\/p>\n<p><\/p>\n<p>Alle pagina's zijn genummerd in natuurlijke volgorde (op oplopende adressen), beginnend met nummer 0 (de nulpagina begint bij adres 0, de eerste bij 32 KB, de tweede bij 64 KB, enzovoorts).<\/p>\n<p><\/p>\n<p>Het geheugenchip wordt gebruikt als een ringbuffer, wat betekent dat we eerst naar pagina 0 schrijven, dan naar pagina 1, ..., en wanneer we de laatste pagina vullen, start een nieuwe cyclus en gaat het schrijven verder vanaf pagina nul.<\/p>\n<p><\/p>\n<h2 id=\"vnutri-stranicy\">Binnen de pagina<\/h2>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/39b45f83dc46bb2fd7081ed2a0b638c9.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAan het begin van de pagina staat een 4-byte header van de pagina, gevolgd door een checksum van de header (CRC-32C), en daarna worden de records opgeslagen in het formaat 'header, data, checksum'.<\/p>\n<p><\/p>\n<p>De header van de pagina (in de schets vuilgroen) bestaat uit:<\/p>\n<p><\/p>\n<ul>\n<li>een twee-byte veld van de Magic Number (de indicator van de versie van het formaat)<br \/>\nvoor de huidige versie van het formaat wordt het beschouwd als <code>0xed00 \u2295 paginanummer<\/code>;<\/li>\n<li>een twee-byte tellertje 'Versie van de pagina' (het nummer van de geheugen herschrijfcyclus).<\/li>\n<\/ul>\n<p><\/p>\n<p>Op de pagina worden de records gecomprimeerd opgeslagen (de compressie-algoritme deflate wordt gebruikt). Alle records op \u00e9\u00e9n pagina worden in \u00e9\u00e9n stroom gecomprimeerd (met behulp van een gemeenschappelijk woordenboek), bij elke nieuwe pagina begint de compressie opnieuw. Dit betekent dat voor de decompressie van elk record alle voorgaande records van deze pagina nodig zijn (en alleen van deze).<\/p>\n<p><\/p>\n<p>Elk record wordt gecomprimeerd met de vlag Z_SYNC_FLUSH, waarbij aan het einde van de gecomprimeerde stroom 4 bytes 0x00, 0x00, 0xff, 0xff komen, mogelijk voorafgegaan door nog \u00e9\u00e9n of twee nullen.<br \/>\nDeze reeks (van 4, 5 of 6 bytes) wordt weggelaten bij het opschrijven naar flash-geheugen.<\/p>\n<p><\/p>\n<p>De header van het record bestaat uit 1, 2 of 3 bytes, die het volgende bevatten:<\/p>\n<p><\/p>\n<ul>\n<li>een bit (T), dat het type record aangeeft: 0 \u2014 context, 1 \u2014 log;<\/li>\n<li>een veld van variabele lengte (S) van 1 tot 7 bits, dat de lengte van de header en 'staart' definieert die aan de opname moet worden toegevoegd voor decompressie;<\/li>\n<li>de lengte van het record (L).<\/li>\n<\/ul>\n<p><\/p>\n<p>Waarden tabel S:<\/p>\n<p><\/p>\n<p>S<br \/>\nLengte van de header, bytes<br \/>\nWordt weggelaten bij het opschrijven, bytes<\/p>\n<p><code>0<\/code><br \/>\n1<br \/>\n5 (<code>00 00 00 ff ff<\/code>)<\/p>\n<p><code>10<\/code><br \/>\n1<br \/>\n6 (<code>00 00 00 00 ff ff<\/code>)<\/p>\n<p><code>110<\/code><br \/>\n2<br \/>\n4 (<code>00 00 ff ff<\/code>)<\/p>\n<p><code>1110<\/code><br \/>\n2<br \/>\n5 (<code>00 00 00 ff ff<\/code>)<\/p>\n<p><code>11110<\/code><br \/>\n2<br \/>\n6 (<code>00 00 00 00 ff ff<\/code>)<\/p>\n<p><code>1111100<\/code><br \/>\n3<br \/>\n4 (<code>00 00 ff ff<\/code>)<\/p>\n<p><code>1111101<\/code><br \/>\n3<br \/>\n5 (<code>00 00 00 ff ff<\/code>)<\/p>\n<p><code>1111110<\/code><br \/>\n3<br \/>\n6 (<code>00 00 00 00 ff ff<\/code>)<\/p>\n<p><\/p>\n<p>Ik heb geprobeerd het te illustreren, ik weet niet hoe duidelijk het is geworden:<br \/>\n<img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/8c9b739eec5af4429395c32b4ba4044a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nGeel hier geeft veld T aan, wit - veld S, groen - L (lengte van gecomprimeerde gegevens in bytes), lichtblauw - gecomprimeerde gegevens, rood - eindbytes van gecomprimeerde gegevens die niet in flash-geheugen worden geschreven.<\/p>\n<p><\/p>\n<p>Op deze manier kunnen we headers van de meest voorkomende lengte (tot 63+5 bytes in gecomprimeerde vorm) met \u00e9\u00e9n byte opschrijven.<\/p>\n<p><\/p>\n<p>Na elk record staat een controlesom CRC-32C, waarbij het initi\u00eble waarde (init) de omgekeerde waarde van de vorige controlesom is.<\/p>\n<p><\/p>\n<p><em>CRC heeft de eigenschap van 'continu\u00efteit', het werkt (plus of min de inversie van de bits in het proces) volgens deze formule: <img decoding=\"async\" alt=\"Mijn implementatie van een ringbuffer in NOR-flash\" src=\"\/wp-content\/uploads\/2019\/12\/b194d6a3d18ae2f4ae5d4054a93c0ea3.jpg\" style=\"display:block;margin: 0 auto;\" \/>.<br \/>\nDat wil zeggen, we berekenen feitelijk de CRC van alle voorgaande bytes van headers en gegevens op deze pagina.<\/em><\/p>\n<p><\/p>\n<p>Direct achter de controlesom ligt de header van het volgende record.<\/p>\n<p><\/p>\n<p>De header is op zo'n manier geconstrueerd dat de eerste byte altijd verschillend is van 0x00 en 0xff (als we in plaats van de eerste byte van de header 0xff tegenkomen, betekent dit dat dit een onbeheerd gebied is; 0x00 geeft een fout aan).<\/p>\n<p><\/p>\n<h2 id=\"primernye-algoritmy\">Voorlopige algoritmen<\/h2>\n<p><\/p>\n<h3 id=\"chtenie-iz-flesh-pamyati\">Lezen uit flash-geheugen<\/h3>\n<p><\/p>\n<p>Elke lezing gaat gepaard met een controlesom verificatie.<br \/>\nAls de checksum niet overeenkomt, wordt het lezen meerdere keren herhaald in de hoop de juiste gegevens te lezen.<\/p>\n<p><\/p>\n<p><em>(dit is logisch, Linux cache leest niet uit NOR Flash, dit is bevestigd)<\/em><\/p>\n<p><\/p>\n<h3 id=\"zapis-v-flesh-pamyat\">Schrijven naar flash-geheugen<\/h3>\n<p><\/p>\n<p>We schrijven gegevens.<br \/>\nWe lezen ze.<\/p>\n<p><\/p>\n<p>Als de gelezen gegevens niet overeenkomen met de geschreven gegevens, vullen we het gebied met nullen en signaleren we een fout.<\/p>\n<p><\/p>\n<h3 id=\"podgotovka-novoy-mikroshemy-k-rabote\">De nieuwe chip voorbereiden voor gebruik<\/h3>\n<p><\/p>\n<p>Voor initialisatie wordt in de eerste (eigenlijk nulde) pagina een header met versie 1 geschreven.<br \/>\nDaarna wordt de begincontext in deze pagina geschreven (bevat UUID van de machine en standaardinstellingen). <\/p>\n<p><\/p>\n<p>Klaar, het flash-geheugen is gereed voor gebruik.<\/p>\n<p><\/p>\n<h3 id=\"zagruzka-avtomata\">Laden van de machine<\/h3>\n<p><\/p>\n<p>Bij het laden worden de eerste 8 bytes van elke pagina (header + CRC) gelezen; pagina's met een onbekend magic number of onjuiste CRC worden genegeerd.<br \/>\nVan de 'juiste' pagina's worden de pagina's met de hoogste versie geselecteerd, en daarvan wordt de pagina met het hoogste nummer genomen.<br \/>\nDe eerste opname wordt gelezen, de correctheid van de CRC wordt gecontroleerd, evenals de aanwezigheid van de 'context' vlag. Als alles in orde is, wordt deze pagina als de huidige beschouwd. Zo niet, dan keren we terug naar de vorige pagina totdat we een 'levende' pagina vinden.<br \/>\nOp de gevonden pagina lezen we alle records, de records met de 'context' vlag passen we toe.<br \/>\nWe slaan de zlib dictionary op (dit zal nodig zijn voor het opnieuw schrijven naar deze pagina).<\/p>\n<p><\/p>\n<p>Klaar, de laadsessie is voltooid, de context is hersteld, en we kunnen werken.<\/p>\n<p><\/p>\n<h3 id=\"dobavlenie-zapisi-v-zhurnal\">Een record aan het logboek toevoegen<\/h3>\n<p><\/p>\n<p>We comprimeren het record met de juiste dictionary, waarbij we Z_SYNC_FLUSH opgeven. We kijken of het gecomprimeerde record op de huidige pagina past.<br \/>\nAls het niet past (of er waren CRC-fouten op de pagina) - beginnen we een nieuwe pagina (zie hieronder).<br \/>\nWe schrijven het record en de CRC. Als er een fout optreedt - beginnen we een nieuwe pagina.<\/p>\n<p><\/p>\n<h3 id=\"novaya-stranica\">Nieuwe pagina<\/h3>\n<p><\/p>\n<p>We kiezen een vrije pagina met het laagste nummer (we beschouwen een pagina als vrij als deze een onjuiste checksum in de header heeft of een versie die lager is dan de huidige). Als er geen dergelijke pagina's zijn - kiezen we de pagina met het laagste nummer met een versie die gelijk is aan de huidige.<br \/>\nWe maken de gekozen pagina leeg. We vergelijken de inhoud met 0xff. Als er iets niet klopt - nemen we de volgende vrije pagina, enzovoort.<br \/>\nOp de geleegde pagina schrijven we de header, de eerste record is de huidige staat van de context, de volgende is het ongeschreven record van het logboek (als die er is).<\/p>\n<p><\/p>\n<h1 id=\"primenimost-formata\">Toepasselijkheid van het formaat<\/h1>\n<p><\/p>\n<p>Naar mijn mening is dit een behoorlijk goede indeling voor het opslaan van elke soort samendrukbare informatie (gewone tekst, JSON, MessagePack, CBOR, mogelijk protobuf) in NOR Flash.<\/p>\n<p><\/p>\n<p>Natuurlijk is het formaat 'geoptimaliseerd' voor SLC NOR Flash.<\/p>\n<p><\/p>\n<p>Het moet niet gebruikt worden met media met een hoge BER, bijvoorbeeld NAND of MLC NOR. <em>(Bestaat die soort geheugen \u00fcberhaupt te koop? Ik heb alleen vermeldingen gezien in werken over foutencorrectie).<\/em>.<\/p>\n<p><\/p>\n<p>Bovendien moet het niet gebruikt worden met apparaten die hun eigen FTL hebben: USB flash, SD, MicroSD, etc. <em>(voor deze geheugens heb ik een formaat gemaakt met een paginagrootte van 512 bytes, een handtekening aan het begin van elke pagina en unieke recordnummers - soms kon ik eenvoudig door sequentieel te lezen alle gegevens herstellen uit een 'vastgelopen' flash.<\/em>.<\/p>\n<p><\/p>\n<p>Afhankelijk van de taken kan het formaat zonder wijzigingen gebruikt worden op flash stations van 128Kbit (16Kb) tot 1Gbit (128Mb). Als je wilt, kan het ook op chips van grotere capaciteit gebruikt worden, maar dan moet de paginagrootte waarschijnlijk aangepast worden. <em>(Maar hier rijst de vraag naar economische haalbaarheid, de prijs voor grote NOR Flash is niet bemoedigend).<\/em>.<\/p>\n<p><\/p>\n<p>Als iemand het formaat interessant vindt en het wil gebruiken in een open project \u2014 laat het me weten, ik zal proberen tijd te vinden om de code op te schonen en op GitHub te publiceren.<\/p>\n<p><\/p>\n<h1 id=\"zaklyuchenie\">Conclusie<\/h1>\n<p><\/p>\n<p>Zoals we zien, blijkt het formaat uiteindelijk eenvoudig te zijn. <em>en zelfs saai.<\/em>.<\/p>\n<p><\/p>\n<p>In het artikel is het moeilijk om de evolutie van mijn mening weer te geven, maar geloof me: aanvankelijk wilde ik iets geavanceerds, onoverwinnelijks cre\u00ebren dat zou kunnen overleven, zelfs na een nucleaire explosie in de buurt. Echter, de rede (hoop ik) heeft uiteindelijk triomfantelijk gewonnen en geleidelijk zijn de prioriteiten verschoven naar eenvoud en compactheid.<\/p>\n<p><\/p>\n<p>Kan het zo zijn dat ik me vergis? Ja, natuurlijk. Het is heel goed mogelijk dat we bijvoorbeeld een partij niet-kwaliteit chips hebben ingekocht. Of om een andere reden voldoet de apparatuur niet aan de verwachtingen qua betrouwbaarheid.<\/p>\n<p><\/p>\n<p>Heb ik een plan voor dit geval? Ik denk dat je na het lezen van het artikel geen twijfels hebt dat er een plan is. En zelfs meerdere.<\/p>\n<p><\/p>\n<p>Als we het iets serieuzer bekijken, is het formaat tegelijkertijd ontwikkeld als een werkende variant en als een 'proefballon'.<\/p>\n<p><\/p>\n<p>Op dit moment werkt alles goed op tafel, letterlijk binnen een paar dagen zal de oplossing uitgerold worden. <em>(ongeveer)<\/em> op een honderdtal apparaten, laten we zien wat er zal gebeuren in 'echte' exploitatie (gelukkig hoop ik dat het formaat het mogelijk maakt om storingen betrouwbaar te detecteren; zodat we volledige statistieken kunnen verzamelen). Over een paar maanden zullen we conclusies kunnen trekken. <em>(en als we pech hebben - dan eerder)<\/em>.<\/p>\n<p><\/p>\n<p>Als er na gebruik ernstige problemen worden ontdekt en er aanpassingen nodig zijn, zal ik daar zeker over schrijven.<\/p>\n<p><\/p>\n<h1 id=\"literatura\">Literatuur<\/h1>\n<p><\/p>\n<p>Ik wilde geen lange saaie lijst van gebruikte bronnen opstellen, tenslotte heeft iedereen Google.<\/p>\n<p><\/p>\n<p>Hier besloot ik een lijst van ontdekkingen achter te laten die mij bijzonder interessant leken, maar geleidelijk zijn ze in de tekst van het artikel terechtgekomen, en is er nog maar \u00e9\u00e9n punt in de lijst overgebleven:<\/p>\n<p><\/p>\n<ol>\n<li>Hulpprogramma <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/madler\/infgen\/\">infgen<\/a><\/noindex> van de auteur zlib. Kan de inhoud van deflate\/zlib\/gzip archieven op een begrijpelijke manier weergeven. Als je moet snappen hoe het deflate (of gzip) formaat intern werkt, raad ik het ten zeerste aan.<\/li>\n<\/ol>\n<p>Bron: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/479044\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f \u0415\u0441\u0442\u044c \u0442\u043e\u0440\u0433\u043e\u0432\u044b\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u044b \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0412\u043d\u0443\u0442\u0440\u0438 Raspberry Pi \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0432\u044f\u0437\u043a\u0438 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0435. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u043c\u043e\u043d\u0435\u0442\u043e\u043f\u0440\u0438\u0451\u043c\u043d\u0438\u043a, \u043a\u0443\u043f\u044e\u0440\u043e\u043f\u0440\u0438\u0451\u043c\u043d\u0438\u043a, \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0439 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u2026 \u0423\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u0441\u0435\u043c \u0441\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u0430\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430. \u0412\u0441\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0432 \u0436\u0443\u0440\u043d\u0430\u043b \u043d\u0430 \u0444\u043b\u0435\u0448\u043a\u0435 (MicroSD), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0442\u043e\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 (\u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e USB-\u043c\u043e\u0434\u0435\u043c\u0430) \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440, \u0442\u0430\u043c \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0411\u0414. \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u0440\u043e\u0434\u0430\u0436\u0430\u0445 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0432 1\u0441, \u0442\u0430\u043a\u0436\u0435 \u0435\u0441\u0442\u044c [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-53751","post","type-post","status-publish","format-standard","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.3 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f \u0415\u0441\u0442\u044c \u0442\u043e\u0440\u0433\u043e\u0432\u044b\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u044b \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0412\u043d\u0443\u0442\u0440\u0438 Raspberry Pi \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0432\u044f\u0437\u043a\u0438 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0435.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/nl\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"nl_NL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041c\u043e\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043a\u043e\u043b\u044c\u0446\u0435\u0432\u043e\u0433\u043e \u0431\u0443\u0444\u0435\u0440\u0430 \u0432 NOR flash | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f \u0415\u0441\u0442\u044c \u0442\u043e\u0440\u0433\u043e\u0432\u044b\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u044b \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0412\u043d\u0443\u0442\u0440\u0438 Raspberry Pi \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0432\u044f\u0437\u043a\u0438 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0435.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/nl\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-12-08T21:00:00+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-02-18T11:01:41+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Mijn implementatie van een ringbuffer in NOR flash | ProHoster","description":"Achtergrond Er zijn zelf ontwikkelde automaten. Binnenin een Raspberry Pi en een beetje bedrading op een aparte printplaat.","canonical_url":"https:\/\/prohoster.info\/nl\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"nl_NL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041c\u043e\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043a\u043e\u043b\u044c\u0446\u0435\u0432\u043e\u0433\u043e \u0431\u0443\u0444\u0435\u0440\u0430 \u0432 NOR flash | ProHoster","og:description":"\u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f \u0415\u0441\u0442\u044c \u0442\u043e\u0440\u0433\u043e\u0432\u044b\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u044b \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0412\u043d\u0443\u0442\u0440\u0438 Raspberry Pi \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0432\u044f\u0437\u043a\u0438 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0435.","og:url":"https:\/\/prohoster.info\/nl\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-12-08T21:00:00+00:00","article:modified_time":"2020-02-18T11:01:41+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"53751","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-24 08:36:23","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 20:18:30","updated":"2026-01-24 08:36:23","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts\/53751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/comments?post=53751"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts\/53751\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media?parent=53751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/categories?post=53751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/tags?post=53751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}