{"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\/de\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","title":{"rendered":"Meine Implementierung eines Ringpuffers in NOR-Flash","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h1 id=\"predystoriya\">Vorgeschichte<\/h1>\n<p><\/p>\n<p>Es gibt eigene Verkaufsautomaten. Im Inneren befindet sich ein Raspberry Pi und etwas Verdrahtung auf einer separaten Platine. Angeschlossen sind ein M\u00fcnz- und ein Geldscheineinzahlungsger\u00e4t sowie ein Bankterminal\u2026 Alles wird von einem selbstgeschriebenen Programm gesteuert. Die gesamte Historie der Betriebsabl\u00e4ufe wird auf einem Flash-Laufwerk (MicroSD) in ein Protokoll geschrieben, das dann \u00fcber das Internet (mithilfe eines USB-Modems) auf einen Server \u00fcbermittelt wird, wo es in einer Datenbank gespeichert wird. Informationen \u00fcber Verk\u00e4ufe werden in 1C hochgeladen; es gibt auch eine einfache Web-Oberfl\u00e4che zur \u00dcberwachung usw. <\/p>\n<p><\/p>\n<p>Das bedeutet, dass das Protokoll lebensnotwendig ist \u2013 f\u00fcr die Buchhaltung (Umsatz, Verk\u00e4ufe usw.), die \u00dcberwachung (s\u00e4mtliche Ausf\u00e4lle und andere au\u00dfergew\u00f6hnliche Umst\u00e4nde); sozusagen sind das alle Informationen, die wir \u00fcber diesen Automaten haben. <\/p>\n<p><\/p>\n<h1 id=\"problema\">Problem<\/h1>\n<p><\/p>\n<p>Die Flash-Laufwerke erweisen sich als sehr ung zuverl\u00e4ssige Ger\u00e4te. Sie gehen mit beachtlicher Regelm\u00e4\u00dfigkeit kaputt. Dies f\u00fchrt sowohl zu Ausfallzeiten der Automaten als auch (wenn das Protokoll aus irgendwelchen Gr\u00fcnden nicht online \u00fcbermittelt werden konnte) zu Datenverlusten.<\/p>\n<p><\/p>\n<p><em>Das ist bereits nicht die erste Erfahrung mit Flash-Laufwerken; zuvor gab es ein anderes Projekt mit mehr als hundert Ger\u00e4ten, bei dem das Protokoll auf USB-Flash-Laufwerken gespeichert wurde. Auch dort gab es Probleme mit der Zuverl\u00e4ssigkeit, manchmal war die Anzahl der Ausf\u00e4lle pro Monat in den Dutzenden. Wir haben verschiedene Flash-Laufwerke getestet, darunter auch Markenprodukte mit SLC-Speicher; ja, einige Modelle sind zuverl\u00e4ssiger als andere, aber der Austausch der Flash-Laufwerke hat das Problem nicht grundlegend gel\u00f6st.<\/em><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <\/p>\n<p><strong>Achtung!<\/strong> Longread! Wenn Sie sich nicht f\u00fcr das \u201ewarum\u201c interessieren, sondern nur f\u00fcr das \u201ewie\u201c, k\u00f6nnen Sie direkt gehen. <noindex><a rel=\"nofollow\" href=\"#format\">Ende<\/a><\/noindex> Artikel.<\/p>\n<p><\/p>\n<h1 id=\"reshenie\">L\u00f6sung<\/h1>\n<p><\/p>\n<p>Das Erste, was mir in den Sinn kommt: auf MicroSD verzichten, beispielsweise ein SSD einsetzen und von dort booten. Theoretisch ist das vielleicht m\u00f6glich, aber relativ teuer und nicht so zuverl\u00e4ssig (es muss ein USB-SATA-Adapter hinzugef\u00fcgt werden; die Statistiken \u00fcber Ausf\u00e4lle bei budgetfreundlichen SSDs sind ebenfalls nicht erfreulich).<\/p>\n<p><\/p>\n<p>USB HDD sieht auch nicht besonders attraktiv aus.<\/p>\n<p><\/p>\n<p>Deshalb sind wir zu folgender L\u00f6sung gekommen: Den Bootvorgang von MicroSD beizubehalten, sie aber im Nur-Lese-Modus zu verwenden und das Betriebsprotokoll (und andere spezifische Informationen f\u00fcr das jeweilige Ger\u00e4t \u2013 Seriennummer, Kalibrierung der Sensoren usw.) woanders zu speichern. <\/p>\n<p><\/p>\n<p>Das Thema read-only FS f\u00fcr den Raspberry Pi wurde bereits umfassend untersucht, ich werde in diesem Artikel nicht auf die Einzelheiten der Umsetzung eingehen. <em>(aber wenn Interesse besteht \u2013 vielleicht schreibe ich einen Mini-Artikel zu diesem Thema)<\/em>. Der einzige Punkt, den ich anmerken m\u00f6chte: sowohl aus pers\u00f6nlicher Erfahrung als auch aus den R\u00fcckmeldungen der bereits implementierenden Nutzer ist ein Gewinn an Zuverl\u00e4ssigkeit vorhanden. Ja, es ist unm\u00f6glich, vollst\u00e4ndig von Ausf\u00e4llen abzusehen, aber die H\u00e4ufigkeit erheblich zu reduzieren \u2014 das ist durchaus realistisch. Zudem werden die Karten vereinheitlicht, was den Austausch f\u00fcr das Servicepersonal erheblich vereinfacht.<\/p>\n<p><\/p>\n<h2 id=\"apparatnaya-chast\">Hardware<\/h2>\n<p><\/p>\n<p>Bei der Wahl des Speichertyps gab es keine besonderen Zweifel \u2014 NOR Flash.<br \/>\nArgumente: <\/p>\n<p><\/p>\n<ul>\n<li>einfache Verbindung (meistens SPI-Bus, von dem bereits Erfahrung besteht, sodass keine \u201eHardware\u201c-Probleme zu erwarten sind);<\/li>\n<li>l\u00e4cherlicher Preis;<\/li>\n<li>Standardarbeitsprotokoll (eine Implementierung ist bereits im Linux-Kernel vorhanden, bei Bedarf kann man auch Drittanbieterl\u00f6sungen nutzen, die ebenfalls vorhanden sind, oder sogar eine eigene schreiben, da alles recht einfach ist);<\/li>\n<li>Zuverl\u00e4ssigkeit und Lebensdauer:<br \/>\naus einem typischen Datenblatt: die Daten werden 20 Jahre lang aufbewahrt, 100000 L\u00f6schzyklen f\u00fcr jeden Block;<br \/>\naus externen Quellen: extrem niedriger BER, es wird postuliert, dass keine Fehlerkorrekturcodes erforderlich sind. <em>(in einigen Arbeiten wird ECC f\u00fcr NOR betrachtet, aber normalerweise ist hier von MLC NOR die Rede, was ebenfalls vorkommen kann)<\/em>.<\/li>\n<\/ul>\n<p><\/p>\n<p>Lassen Sie uns die Anforderungen an Volumen und Lebensdauer absch\u00e4tzen.<\/p>\n<p><\/p>\n<p>Wir w\u00fcnschen uns, dass die Daten f\u00fcr mehrere Tage sicher gespeichert werden. Dies ist wichtig, damit im Falle von Kommunikationsproblemen die Verkaufsstatistik nicht verloren geht. Wir orientieren uns an 5 Tagen, in diesem Zeitraum <em>(sogar unter Ber\u00fccksichtigung von Wochenenden und Feiertagen)<\/em> kann das Problem gel\u00f6st werden.<\/p>\n<p><\/p>\n<p>Aktuell sammeln wir t\u00e4glich etwa 100KB Protokolle (3-4 Tausend Eintr\u00e4ge), aber diese Zahl w\u00e4chst allm\u00e4hlich \u2014 die Detaillierung nimmt zu, neue Ereignisse werden hinzugef\u00fcgt. Au\u00dferdem gibt es manchmal Spitzen (ein gewisser Sensor f\u00e4ngt an, mit falschen Ausl\u00f6sungen zu spammen, zum Beispiel). Wir rechnen mit 10 Tausend Eintr\u00e4gen zu je 100 Byte \u2014 ein Megabyte pro Tag.<\/p>\n<p><\/p>\n<p>Insgesamt ergibt das 5MB an reinen (gut komprimierbaren) Daten. Dazu kommen noch <em>(grobe Sch\u00e4tzung)<\/em> 1MB an Steuerdaten.<\/p>\n<p><\/p>\n<p>Das hei\u00dft, wir ben\u00f6tigen einen Chip mit 8MB, wenn wir keine Komprimierung verwenden, oder 4MB, wenn wir es tun. V\u00f6llig realistische Zahlen f\u00fcr diesen Speichertyp.<\/p>\n<p><\/p>\n<p>Was die Lebensdauer betrifft: Wenn wir planen, dass der Speicher insgesamt nicht h\u00e4ufiger als alle 5 Tage \u00fcberschrieben wird, erhalten wir bei 10 Jahren Betriebsdauer weniger als tausend Wiederbeschreibungszyklen.<br \/>\nIch erinnere daran, dass der Hersteller zehntausend verspricht.<\/p>\n<p>\n<b class=\"spoiler_title\">Ein wenig zu NOR vs NAND<\/b><\/p>\n<p>Heute ist nat\u00fcrlich NAND-Speicher viel beliebter, aber f\u00fcr dieses Projekt w\u00fcrde ich ihn nicht verwenden: NAND erfordert im Gegensatz zu NOR unbedingt die Verwendung von Fehlerkorrekturcodes, fehlerhaften Blocktabellen usw. Au\u00dferdem haben NAND-Chips normalerweise viel mehr Pins.<\/p>\n<p><\/p>\n<p>Als Nachteile von NOR kann man angeben:<\/p>\n<p><\/p>\n<ul>\n<li>geringe Kapazit\u00e4t (und entsprechend hoher Preis pro Megabyte);<\/li>\n<li>nicht hohe \u00dcbertragungsgeschwindigkeit (haupts\u00e4chlich aufgrund des verwendeten seriellen Interfaces, \u00fcblicherweise SPI oder I2C);<\/li>\n<li>langsame L\u00f6schvorg\u00e4nge (je nach Blockgr\u00f6\u00dfe dauert es von Bruchteilen einer Sekunde bis zu mehreren Sekunden).<\/li>\n<\/ul>\n<p><\/p>\n<p>Nichts Kritisches scheint es f\u00fcr uns zu sein, also machen wir weiter.<\/p>\n<p><\/p>\n<p>Wenn Sie an Einzelheiten interessiert sind, wurde der Chip ausgew\u00e4hlt <noindex><a rel=\"nofollow\" href=\"https:\/\/www.adestotech.com\/wp-content\/uploads\/doc3686.pdf\">at25df321a<\/a><\/noindex> <em>(das ist jedoch nicht entscheidend, auf dem Markt gibt es viele Analoga, die hinsichtlich Pinbelegung und Befehlssatz kompatibel sind; selbst wenn wir einen Chip eines anderen Herstellers und\/oder mit anderer Kapazit\u00e4t verwenden m\u00f6chten, wird alles ohne \u00c4nderung des Codes funktionieren)<\/em>.<\/p>\n<p><\/p>\n<p>Ich verwende den im Linux-Kernel eingebauten Treiber, auf Raspberry ist es dank der Unterst\u00fctzung des Device Tree Overlays ganz einfach \u2013 man muss einfach den kompilierten Overlay in \/boot\/overlays legen und \/boot\/config.txt ein wenig modifizieren.<\/p>\n<p>\n<b class=\"spoiler_title\">Beispiel einer dts-Datei<\/b><\/p>\n<p>Ehrlich gesagt bin ich mir nicht sicher, ob es fehlerfrei geschrieben ist, aber es funktioniert.<\/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\">Und noch eine Zeile in config.txt<\/b><\/p>\n<pre><code class=\"plaintext\">dtoverlay=at25:spimaxfrequency=50000000<\/code><\/pre>\n<p><\/p>\n<p>Die Beschreibung der Anschlussmethode des Chips an den Raspberry Pi lasse ich weg. Einerseits bin ich kein Elektronikspezialist, andererseits ist es f\u00fcr mich auch banal: Der Chip hat nur 8 Pins, von denen wir Masse, Stromversorgung, SPI (CS, SI, SO, SCK) ben\u00f6tigen; die Pegel stimmen mit den entsprechenden beim Raspberry Pi \u00fcberein, es sind keine zus\u00e4tzlichen Verbindungen erforderlich \u2013 man verbindet einfach die angegebenen 6 Kontakte.<\/p>\n<p><\/p>\n<h2 id=\"postanovka-zadachi\">Aufgabenstellung<\/h2>\n<p><\/p>\n<p>Wie gewohnt gibt es mehrere Iterationen bei der Aufgabenstellung, ich denke, es ist an der Zeit f\u00fcr eine weitere. Lassen Sie uns also innehalten, das, was bereits geschrieben wurde, zusammenfassen und die noch im Schatten verbliebenen Details kl\u00e4ren.<\/p>\n<p><\/p>\n<p>Also haben wir uns darauf geeinigt, dass das Journal im SPI NOR Flash gespeichert wird.<\/p>\n<p>\n<b class=\"spoiler_title\">Was ist NOR Flash f\u00fcr diejenigen, die es nicht wissen<\/b><\/p>\n<p>Es ist nichtfl\u00fcchtiger Speicher, mit dem man drei Operationen durchf\u00fchren kann:<\/p>\n<p><\/p>\n<ol>\n<li>Lesen:<br \/>\nDas ganz gew\u00f6hnliche Lesen: wir \u00fcbergeben die Adresse und lesen so viele Bytes, wie wir ben\u00f6tigen;<\/li>\n<li>Aufzeichnung:<br \/>\nDer Schreibvorgang in NOR-Flash sieht gew\u00f6hnlich aus, hat jedoch eine Besonderheit: Man kann nur 1 in 0 umwandeln, nicht umgekehrt. Wenn zum Beispiel in einer Speichereinheit 0x55 gespeichert war, w\u00fcrde nach dem Schreiben von 0x0f dort 0x05 gespeichert sein. <em>(siehe Tabelle etwas weiter unten)<\/em>;<\/li>\n<li>L\u00f6schen:<br \/>\nNat\u00fcrlich m\u00fcssen wir auch die umgekehrte Operation beherrschen \u2013 0 in 1 umzuwandeln, genau daf\u00fcr gibt es die L\u00f6schoperation. Im Gegensatz zu den ersten beiden operiert sie nicht mit Bytes, sondern mit Bl\u00f6cken (der minimale L\u00f6schblock im ausgew\u00e4hlten Chip betr\u00e4gt 4 KB). L\u00f6schen zerst\u00f6rt den gesamten Block und dies ist die einzige M\u00f6glichkeit, 0 in 1 umzuwandeln. Daher muss man beim Arbeiten mit Flash-Speicher oft Datenstrukturen an die Grenzen des L\u00f6schblocks anpassen.<br \/>\nSchreiben in NOR-Flash:<\/li>\n<\/ol>\n<p><\/p>\n<p>Bin\u00e4rdaten<\/p>\n<p><strong>War<\/strong><br \/>\n<code>01010101<\/code><\/p>\n<p><strong>Gespeichert<\/strong><br \/>\n<code>00001111<\/code><\/p>\n<p><strong>Wurde<\/strong><br \/>\n<code>00000101<\/code><\/p>\n<p><\/p>\n<p>Das Journal selbst besteht aus einer Sequenz von Eintr\u00e4gen variabler L\u00e4nge. Eine typische Eintragsl\u00e4nge betr\u00e4gt etwa 30 Bytes (obwohl manchmal auch Eintr\u00e4ge mit mehreren Kilobyte vorkommen). <em>In diesem Fall behandeln wir sie einfach als eine Menge von Bytes, aber wenn es interessiert, werden innerhalb der Eintr\u00e4ge CBOR verwendet.<\/em><\/p>\n<p><\/p>\n<p>Neben dem Protokoll m\u00fcssen wir einige \u201eKonfigurations\u201c-Informationen speichern, sowohl solche, die aktualisiert werden, als auch solche, die es nicht werden: eine Art Ger\u00e4te-ID, Kalibrierungen der Sensoren, ein Flag \u201eGer\u00e4t vor\u00fcbergehend abgeschaltet\u201c, usw.<br \/>\nDiese Informationen bestehen aus einer Reihe von Schl\u00fcssel-Wert-Paaren und werden ebenfalls in CBOR gespeichert. Wir haben nicht viele dieser Informationen (h\u00f6chstens einige Kilobyte) und sie werden nicht h\u00e4ufig aktualisiert.<br \/>\nIm weiteren Verlauf werden wir sie Kontext nennen.<\/p>\n<p><\/p>\n<p>Wenn wir uns erinnern, womit dieser Artikel begann, ist es sehr wichtig, die Zuverl\u00e4ssigkeit der Datenspeicherung zu gew\u00e4hrleisten und, wenn m\u00f6glich, einen ununterbrochenen Betrieb selbst bei Hardwarefehlern oder Datenbesch\u00e4digungen sicherzustellen.<\/p>\n<p><\/p>\n<p>Welche Problemquellen k\u00f6nnen wir betrachten?<\/p>\n<p><\/p>\n<ul>\n<li>Stromausfall w\u00e4hrend der Schreib-\/L\u00f6schvorg\u00e4nge. Das geh\u00f6rt zur Kategorie \u201eGegen einen Vorschlag gibt es kein Rezept\u201c.<br \/>\nInformation aus <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\">Diskussion<\/a><\/noindex> auf stackexchange: Bei Stromausfall w\u00e4hrend der Arbeit mit Flash f\u00fchren sowohl erase (auf 1 setzen) als auch write (auf 0 setzen) zu undefiniertem Verhalten: Daten k\u00f6nnen geschrieben werden, teilweise geschrieben werden (sagen wir, wir haben 10 Bytes\/80 Bit \u00fcbermittelt, aber es wurden nur 45 Bit erfolgreich geschrieben), es ist auch m\u00f6glich, dass einige Bits in einem \u201eZwischen\u201c-Zustand bleiben (das Lesen kann sowohl 0 als auch 1 ergeben);<\/li>\n<li>Fehler im Flash-Speicher selbst.<br \/>\nObwohl die BER sehr niedrig ist, kann sie nicht null sein;<\/li>\n<li>Fehler \u00fcber den Bus<br \/>\nDie \u00fcber SPI \u00fcbertragenen Daten sind nicht gesch\u00fctzt und k\u00f6nnen sowohl einfache Bitfehler als auch Synchronisationsfehler \u2013 den Verlust oder das Einf\u00fcgen von Bits (was zu massiven Datenverzerrungen f\u00fchrt) \u2013 aufweisen;<\/li>\n<li>Weitere Fehler\/Fehlfunktionen<br \/>\nFehler im Code, \u201eGlitches\u201c bei Raspberry, Eingreifen von Au\u00dferirdischen\u2026<\/li>\n<\/ul>\n<p><\/p>\n<p>Ich habe die Anforderungen formuliert, deren Erf\u00fcllung meiner Meinung nach notwendig ist, um die Zuverl\u00e4ssigkeit zu gew\u00e4hrleisten:<\/p>\n<p><\/p>\n<ul>\n<li>Aufzeichnungen m\u00fcssen sofort in den Flash-Speicher geschrieben werden, verz\u00f6gerte Aufzeichnungen werden nicht in Betracht gezogen; - wenn ein Fehler auftritt, muss dieser so fr\u00fch wie m\u00f6glich erkannt und verarbeitet werden; - das System sollte nach M\u00f6glichkeit nach Fehlern wieder funktionsf\u00e4hig gemacht werden.<br \/>\n<em>(Beispiel aus dem Leben \u201ewie es nicht sein sollte\u201c, dem ich denke, dass jeder schon begegnet ist: Nach einem Notneustart war das Dateisystem \u201ebesch\u00e4digt\u201c und das Betriebssystem l\u00e4sst sich nicht booten)<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"idei-podhody-razmyshleniya\">Ideen, Ans\u00e4tze, \u00dcberlegungen<\/h2>\n<p><\/p>\n<p>Als ich anfing, \u00fcber diese Aufgabe nachzudenken, schoss mir eine Menge Ideen durch den Kopf, zum Beispiel:<\/p>\n<p><\/p>\n<ul>\n<li>Datenkompression verwenden;<\/li>\n<li>schlaue Datenstrukturen verwenden, zum Beispiel die Kopfzeilen von Aufzeichnungen getrennt von den eigentlichen Aufzeichnungen speichern, damit bei einem Fehler in einer Aufzeichnung die anderen problemlos gelesen werden k\u00f6nnen;<\/li>\n<li>Bitfelder zur Kontrolle der Vollst\u00e4ndigkeit der Aufzeichnung bei Stromausfall verwenden;<\/li>\n<li>Pr\u00fcfziffern f\u00fcr alles und jedes speichern;<\/li>\n<li>eine Art von fehlerresistenter Codierung verwenden.<\/li>\n<\/ul>\n<p><\/p>\n<p>Ein Teil dieser Ideen wurde umgesetzt, teilweise wurde entschieden, darauf zu verzichten. Lassen Sie uns der Reihe nach vorgehen.<\/p>\n<p><\/p>\n<h3 id=\"szhatie-dannyh\">Datenkompression<\/h3>\n<p><\/p>\n<p>Die Ereignisse, die wir im Protokoll festhalten, sind genug einheitlich und wiederholbar (\u201ewir haben eine 5-Rubel-M\u00fcnze geworfen\u201c, \u201ewir haben auf die Taste f\u00fcr das Wechselgeld gedr\u00fcckt\u201c, \u2026). Daher sollte die Komprimierung ziemlich effektiv sein.<\/p>\n<p><\/p>\n<p>Die Kosten f\u00fcr die Kompression sind gering (wir haben einen ziemlich leistungsstarken Prozessor, selbst das erste Pi hatte einen Kern mit 700 MHz, bei den aktuellen Modellen mehrere Kerne mit \u00fcber einem Gigahertz), die Geschwindigkeit des Austauschs mit dem Speicher ist niedrig (einige Megabyte pro Sekunde), die Gr\u00f6\u00dfe der Aufzeichnungen ist klein. Insgesamt, wenn die Kompression einen Einfluss auf die Leistung hat, dann nur positiv. <em>(absolut nicht kritisch, ich stelle es einfach fest)<\/em>. Wir haben ja kein echtes Embedded, sondern ein gew\u00f6hnliches Linux \u2013 die Implementierung sollte also nicht viele Anstrengungen erfordern (es reicht aus, die Bibliothek einfach zu verlinken und einige Funktionen daraus zu verwenden).<\/p>\n<p><\/p>\n<p>Es wurde ein Ausschnitt aus dem Protokoll eines funktionierenden Ger\u00e4ts (1,7 MB, 70.000 Eintr\u00e4ge) entnommen und zun\u00e4chst auf Komprimierbarkeit mit den auf dem Computer verf\u00fcgbaren gzip, lz4, lzop, bzip2, xz, zstd \u00fcberpr\u00fcft.<\/p>\n<p><\/p>\n<ul>\n<li>Gzip, xz und zstd zeigten \u00e4hnliche Ergebnisse (40 KB).<br \/>\nEs erstaunte, dass das angesagte xz hier auf dem Niveau von gzip oder zstd abschneidet;<\/li>\n<li>Lzip mit den Standardeinstellungen lieferte ein etwas schlechteres Ergebnis;<\/li>\n<li>Lz4 und lzop zeigten kein besonders gutes Ergebnis (150 KB);<\/li>\n<li>Bzip2 zeigte ein \u00fcberraschend gutes Ergebnis (18 KB).<\/li>\n<\/ul>\n<p><\/p>\n<p>Die Daten komprimieren sich also sehr gut.<br \/>\nWenn wir also (wenn wir keine fatalen M\u00e4ngel finden) das Komprimieren umsetzen! Einfach, weil mehr Daten auf dasselbe Flash-Laufwerk passen.<\/p>\n<p><\/p>\n<p>Lassen Sie uns \u00fcber die Nachteile nachdenken.<\/p>\n<p><\/p>\n<p>Das erste Problem: Wir haben bereits vereinbart, dass jeder Eintrag sofort auf das Flash-Laufwerk geschrieben werden muss. Normalerweise sammelt ein Archivierungsprogramm die Daten aus dem Eingabestrom, bis es entscheidet, dass es Zeit ist, sie in den Ausgang zu schreiben. Wir hingegen m\u00fcssen sofort einen komprimierten Datenblock erhalten und ihn im nichtfl\u00fcchtigen Speicher speichern.<\/p>\n<p><\/p>\n<p>Ich sehe drei Wege:<\/p>\n<p><\/p>\n<ol>\n<li>Jeden Eintrag mit Hilfe von Dictionary-Kompression statt der oben genannten Algorithmen zu komprimieren.<br \/>\nEine durchaus funktionierende Variante, aber ich mag sie nicht. Um ein mehr oder weniger akzeptables Ma\u00df an Kompression zu gew\u00e4hrleisten, muss das W\u00f6rterbuch auf bestimmte Daten \u201eabgestimmt\u201c sein; jede \u00c4nderung f\u00fchrt dazu, dass der Kompressionsgrad katastrophal sinkt. Ja, das Problem l\u00e4sst sich durch die Erstellung einer neuen Version des W\u00f6rterbuchs l\u00f6sen, aber das ist ein Kopfzerbrechen \u2013 wir m\u00fcssen alle W\u00f6rterbuchversionen speichern; in jedem Eintrag m\u00fcssen wir angeben, mit welcher Version des W\u00f6rterbuchs er komprimiert wurde\u2026<\/li>\n<li>Jeden Eintrag mit \u201eklassischen\u201c Algorithmen komprimieren, aber unabh\u00e4ngig von anderen.<br \/>\nDie betrachteten Kompressionsalgorithmen sind nicht f\u00fcr die Verarbeitung von Eintr\u00e4gen dieser Gr\u00f6\u00dfe (Dutzende von Bytes) konzipiert; der Kompressionsfaktor wird eindeutig unter 1 liegen (das hei\u00dft, die Datenmenge wird statt komprimiert vergr\u00f6\u00dfert);<\/li>\n<li>Nach jedem Eintrag FLUSH zu machen.<br \/>\nIn vielen Kompressionsbibliotheken gibt es Unterst\u00fctzung f\u00fcr FLUSH. Dies ist ein Befehl (oder ein Parameter des Komprimierungsprozesses), der, wenn er empfangen wird, sicherstellt, dass der Archivator den komprimierten Stream so strukturiert, dass die bereits erhaltenen <strong>Ressourcen) abgeschlossen ist, l\u00f6sen wir<\/strong> nicht komprimierten Daten wiederhergestellt werden k\u00f6nnen. Eine solche Analogie <code>sync<\/code> in Dateisystemen oder <code>commit<\/code> in SQL.<br \/>\nEs ist wichtig, dass die folgenden Komprimierungsoperationen das angesammelte W\u00f6rterbuch verwenden k\u00f6nnen und der Grad der Kompression nicht so stark leidet wie im vorherigen Fall.<\/li>\n<\/ol>\n<p><\/p>\n<p>Ich denke, es ist offensichtlich, dass ich die dritte Option gew\u00e4hlt habe; lassen Sie uns genauer darauf eingehen.<\/p>\n<p><\/p>\n<p>Gefunden <noindex><a rel=\"nofollow\" href=\"https:\/\/www.bolet.org\/~pornin\/deflate-flush.html\">ein ausgezeichneter Artikel<\/a><\/noindex> \u00fcber FLUSH in zlib.<\/p>\n<p><\/p>\n<p>Ich habe einen Test basierend auf einem Artikel gemacht, ich nahm 70.000 Protokolleintr\u00e4ge von einem echten Ger\u00e4t, bei einer Seitengr\u00f6\u00dfe von 60KB <em>(auf die Seitengr\u00f6\u00dfe kommen wir noch zur\u00fcck)<\/em> erhalten:<\/p>\n<p><\/p>\n<p>Stammdaten<br \/>\nGzip-Kompression -9 (ohne FLUSH)<br \/>\nzlib mit Z_PARTIAL_FLUSH<br \/>\nzlib mit Z_SYNC_FLUSH<\/p>\n<p><strong>Volumen, KB<\/strong><br \/>\n1692<br \/>\n40<br \/>\n352<br \/>\n604<\/p>\n<p><\/p>\n<p>Auf den ersten Blick scheint der Preis, den FLUSH verursacht, \u00fcberm\u00e4\u00dfig hoch zu sein; jedoch haben wir tats\u00e4chlich nicht viele Optionen - entweder gar nicht komprimieren oder (sehr effektiv) mit FLUSH komprimieren. Man sollte nicht vergessen, dass wir 70.000 Eintr\u00e4ge haben, und die \u00dcberfl\u00fcssigkeit, die durch Z_PARTIAL_FLUSH eingef\u00fchrt wird, betr\u00e4gt nur 4-5 Bytes pro Eintrag. Der Kompressionsfaktor stellte sich als fast 5:1 heraus, was mehr als ein hervorragendes Ergebnis ist.<\/p>\n<p>\n<b class=\"spoiler_title\">Es mag unerwartet erscheinen, aber tats\u00e4chlich ist Z_SYNC_FLUSH eine effektivere Methode, um FLUSH durchzuf\u00fchren.<\/b><\/p>\n<p>Wenn Z_SYNC_FLUSH verwendet wird, werden die letzten 4 Bytes jedes Eintrags immer 0x00, 0x00, 0xff, 0xff sein. Und wenn wir sie kennen, dann k\u00f6nnen wir sie nicht speichern, sodass die endg\u00fcltige Gr\u00f6\u00dfe nur 324KB betr\u00e4gt.<\/p>\n<p><\/p>\n<p>In dem Artikel, auf den ich mich beziehe, gibt es eine Erkl\u00e4rung:<\/p>\n<p><\/p>\n<blockquote><p>Ein neuer Typ-0-Block mit leeren Inhalten wird angeh\u00e4ngt.<\/p>\n<p>Ein Typ-0-Block mit leeren Inhalten besteht aus:<\/p>\n<ul>\n<li>dem dreibitigen Blockkopf;<\/li>\n<li>0 bis 7 Bits, die gleich Null sind, um die Byte-Ausrichtung zu erreichen;<\/li>\n<li>der vier-Byte-Sequenz 00 00 FF FF.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Wie man leicht erkennen kann, gehen vor diesen 4 Bytes im letzten Block 3 bis 10 Nullbits. Die Praxis hat jedoch gezeigt, dass es tats\u00e4chlich mindestens 10 Nullbits gibt.<\/p>\n<p><\/p>\n<p>Es stellt sich heraus, dass so kurze Datenbl\u00f6cke normalerweise (immer?) mit einem Block vom Typ 1 (fester Block) codiert werden, der unbedingt mit 7 Nullbits endet, sodass wir insgesamt 10-17 garantiert null Bits erhalten (und die anderen werden mit einer Wahrscheinlichkeit von etwa 50 % null sein).<\/p>\n<p><\/p>\n<p>Daher zeigt sich in den Testdaten, dass in 100 % der F\u00e4lle vor 0x00, 0x00, 0xff, 0xff ein Nullbyte kommt, und in mehr als einem Drittel der F\u00e4lle - zwei Nullbytes <em>(m\u00f6glicherweise liegt es daran, dass ich bin\u00e4res CBOR verwende, und bei der Verwendung von textlichem JSON w\u00fcrden wir wahrscheinlich \u00f6fter auf Bl\u00f6cke vom Typ 2 - dynamischer Block - sto\u00dfen, weshalb wir auch auf Bl\u00f6cke ohne zus\u00e4tzliche Nullbytes vor 0x00, 0x00, 0xff, 0xff sto\u00dfen w\u00fcrden)<\/em>.<\/p>\n<p><\/p>\n<p>Insgesamt kann man mit den vorhandenen Testdaten in weniger als 250KB komprimierten Daten unterkommen.<\/p>\n<p><\/p>\n<p>Man kann noch etwas sparen, indem man mit den Bits jongliert: Wir ignorieren derzeit das Vorhandensein mehrerer Null-Bits am Ende des Blocks, einige Bits am Anfang des Blocks bleiben ebenfalls unver\u00e4ndert ...<br \/>\nAber dann traf ich die Entscheidung, aufzuh\u00f6ren, sonst k\u00f6nnte ich in der Geschwindigkeit zur Entwicklung meines eigenen Archivierers gelangen.<\/p>\n<p><\/p>\n<p>Insgesamt habe ich aus meinen Testdaten 3-4 Bytes pro Schreibvorgang erhalten, das Komprimierungsverh\u00e4ltnis liegt bei \u00fcber 6:1. Ehrlich gesagt: Auf ein solches Ergebnis hatte ich nicht gehofft, meiner Meinung nach ist alles, was besser als 2:1 ist, bereits ein Ergebnis, das die Verwendung von Kompression rechtfertigt.<\/p>\n<p><\/p>\n<p>Alles gut, aber zlib (deflate) ist dennoch ein ehrw\u00fcrdiger und etwas altmodischer Komprimierungsalgorithmus. Schon alleine, dass als W\u00f6rterbuch die letzten 32 Kb des unkomprimierten Datenstroms verwendet werden, sieht heute seltsam aus (das hei\u00dft, wenn ein bestimmter Datenblock dem \u00e4hnelt, was vor 40 Kb im Eingabestrom war, wird er neu archiviert und verweist nicht auf den vorherigen Vorkommen). In modernen und angesagten Archivierern wird die Dictionary-Gr\u00f6\u00dfe h\u00e4ufig in Megabyte und nicht in Kilobyte gemessen.<\/p>\n<p><\/p>\n<p>Also machen wir unsere Mini-Studie zu Archivierern weiter.<\/p>\n<p><\/p>\n<p>Als N\u00e4chstes wurde bzip2 ausprobiert (ich erinnere daran, dass es ohne FLUSH einen fantastischen Komprimierungsgrad von fast 100:1 zeigte). Leider hat es mit FLUSH sehr schlecht abgeschnitten, die Gr\u00f6\u00dfe der komprimierten Daten war gr\u00f6\u00dfer als die der unkomprimierten.<\/p>\n<p>\n<b class=\"spoiler_title\">Meine Vermutungen \u00fcber die Gr\u00fcnde f\u00fcr das Scheitern<\/b><\/p>\n<p>Libbz2 bietet nur eine Flush-Option an, die anscheinend das W\u00f6rterbuch l\u00f6scht (analog zu Z_FULL_FLUSH in zlib), sodass man nicht von einer effektiven Kompression danach sprechen kann.<\/p>\n<p><\/p>\n<p>Und zuletzt wurde zstd ausprobiert. Je nach Parametern komprimiert es entweder auf Gzip-Niveau, jedoch viel schneller, oder besser als Gzip.<\/p>\n<p><\/p>\n<p>Leider hat sich Z_SYNC_FLUSH auch nicht als \u201esehr gut\u201c erwiesen: Die Gr\u00f6\u00dfe der komprimierten Daten betrug etwa 700 KB.<\/p>\n<p><\/p>\n<p>Ich <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/facebook\/zstd\/issues\/900\">Ich habe eine Frage gestellt<\/a><\/noindex> auf der Projektseite in GitHub, und erhielt die Antwort, dass man mit bis zu 10 Bytes an Verwaltungsdaten pro Block der komprimierten Daten rechnen sollte, was den erhaltenen Ergebnissen nahekommt, den deflate wird man nicht einholen k\u00f6nnen.<\/p>\n<p><\/p>\n<p>Damit habe ich beschlossen, meine Experimente mit Archivierern zu beenden (ich erinnere daran, dass xz, lzip, lzo, lz4 sich bei den Tests ohne FLUSH nicht bew\u00e4hrt haben, und ich habe es nicht f\u00fcr n\u00f6tig gehalten, exotischere Komprimierungsalgorithmen zu betrachten).<\/p>\n<p><\/p>\n<p>Kehren wir zu den Problemen der Archivierung zur\u00fcck.<\/p>\n<p><\/p>\n<p>Das zweite (wie es sagt, nach Reihenfolge, nicht nach Bedeutung) Problem ist, dass komprimierte Daten einen einheitlichen Strom darstellen, in dem st\u00e4ndig Verweise auf vorherige Abschnitte gemacht werden. Dadurch verlieren wir bei Besch\u00e4digung eines Abschnitts der komprimierten Daten nicht nur den damit verbundenen Block unkomprimierter Daten, sondern auch alle nachfolgenden.<\/p>\n<p><\/p>\n<p>Es gibt Ans\u00e4tze zur L\u00f6sung dieses Problems:<\/p>\n<p><\/p>\n<ol>\n<li>Das Auftreten des Problems verhindern \u2013 redundante Informationen zu den komprimierten Daten hinzuf\u00fcgen, die es erm\u00f6glichen, Fehler zu erkennen und zu korrigieren; dar\u00fcber werden wir sp\u00e4ter sprechen;<\/li>\n<li>Die Folgen im Fall eines Problems minimieren.<br \/>\nWir haben bereits fr\u00fcher gesagt, dass wir jeden Datenblock unabh\u00e4ngig komprimieren k\u00f6nnen, wodurch das Problem von selbst verschwinden w\u00fcrde (eine Besch\u00e4digung der Daten eines Blocks w\u00fcrde nur zum Verlust der Daten dieses Blocks f\u00fchren). Dies ist jedoch der Extremfall, in dem die Datenkompression ineffizient wird. Der entgegengesetzte Extremfall: alle 4Mb unseres Mikrochips als ein einziges Archiv zu verwenden, was uns eine hervorragende Komprimierung bieten w\u00fcrde, aber katastrophale Folgen im Fall von Datenbesch\u00e4digung h\u00e4tte.<br \/>\n<em>Ja, ein Kompromiss ist aus Sicht der Zuverl\u00e4ssigkeit erforderlich. Aber wir m\u00fcssen uns daran erinnern, dass wir ein Datenformat f\u00fcr nichtfl\u00fcchtigen Speicher mit extrem niedrigem BER und einer angegebenen Datenhaltbarkeit von 20 Jahren entwickeln.<\/em><\/li>\n<\/ol>\n<p><\/p>\n<p>Im Verlauf der Experimente habe ich festgestellt, dass sp\u00fcrbare Verluste in der Kompressionsrate bei komprimierten Datenbl\u00f6cken von weniger als 10Kb beginnen.<br \/>\nEs wurde bereits erw\u00e4hnt, dass der verwendete Speicher eine Seitenorganisation hat; ich sehe keinen Grund, warum man nicht das Verh\u00e4ltnis \u201eeine Seite \u2013 ein Block komprimierter Daten\u201c verwenden sollte.<\/p>\n<p><\/p>\n<p>Das hei\u00dft, die minimal vern\u00fcnftige Seitengr\u00f6\u00dfe betr\u00e4gt 16Kb (mit einem Puffer f\u00fcr Verwaltungsinformationen). Allerdings bringt eine so kleine Seitengr\u00f6\u00dfe erhebliche Einschr\u00e4nkungen f\u00fcr die maximale Aufzeichnungsgr\u00f6\u00dfe mit sich.<\/p>\n<p><\/p>\n<p>Obwohl ich bisher keine Aufzeichnungen gr\u00f6\u00dfer als ein Kilobyte in komprimierter Form plane, habe ich mich entschieden, Seiten mit einer Gr\u00f6\u00dfe von 32Kb zu verwenden (insgesamt 128 Seiten pro Chip).<\/p>\n<p><\/p>\n<p><strong>Zusammenfassung:<\/strong><\/p>\n<p><\/p>\n<ul>\n<li>Die Daten werden komprimiert mit zlib (deflate) gespeichert;<\/li>\n<li>F\u00fcr jede Aufzeichnung setzen wir Z_SYNC_FLUSH;<\/li>\n<li>Jede komprimierte Aufzeichnung wird an den Endbytes gek\u00fcrzt. <em>(zum Beispiel 0x00, 0x00, 0xff, 0xff)<\/em>; im Header geben wir an, wie viele Bytes wir gek\u00fcrzt haben;<\/li>\n<li>Die Daten speichern wir seitenweise mit 32 KB; innerhalb der Seite l\u00e4uft ein einheitlicher Strom komprimierter Daten; bei jeder Seite beginnen wir die Kompression von neuem.<\/li>\n<\/ul>\n<p><\/p>\n<p>Und bevor wir mit der Kompression fertig sind, m\u00f6chte ich darauf hinweisen, dass wir nur einige Bytes an komprimierten Daten pro Aufzeichnung erhalten, daher ist es \u00e4u\u00dferst wichtig, die Verwaltungsinformationen nicht aufzubl\u00e4hen, jedes Byte z\u00e4hlt hier.<\/p>\n<p><\/p>\n<h3 id=\"hranenie-zagolovkov-dannyh\">Speicherung von Daten\u00fcberschriften<\/h3>\n<p><\/p>\n<p>Da wir Aufzeichnungen variabler L\u00e4nge haben, m\u00fcssen wir eine M\u00f6glichkeit finden, die Platzierung\/Grenzen der Aufzeichnungen zu bestimmen.<\/p>\n<p><\/p>\n<p>Ich kenne drei Ans\u00e4tze:<\/p>\n<p><\/p>\n<ol>\n<li>Alle Aufzeichnungen werden in einem kontinuierlichen Strom gespeichert, zuerst kommt die \u00dcberschrift der Aufzeichnung, die die L\u00e4nge enth\u00e4lt, und danach die eigentliche Aufzeichnung.<br \/>\nIn dieser Variante k\u00f6nnen sowohl die \u00dcberschriften als auch die Daten eine variable L\u00e4nge haben.<br \/>\nIm Grunde erhalten wir eine einfach verkettete Liste, die \u00fcberall verwendet wird;<\/li>\n<li>\u00dcberschriften und die eigentlichen Aufzeichnungen werden in separaten Str\u00f6men gespeichert.<br \/>\nDurch die Verwendung von \u00dcberschriften fester L\u00e4nge sorgen wir daf\u00fcr, dass die Besch\u00e4digung einer \u00dcberschrift keine Auswirkungen auf die anderen hat.<br \/>\nEin \u00e4hnlicher Ansatz wird zum Beispiel in vielen Dateisystemen verwendet;<\/li>\n<li>Aufzeichnungen werden in einem kontinuierlichen Strom gespeichert, die Grenze der Aufzeichnung wird durch einen bestimmten Marker (Symbol\/Zeichenfolge, die innerhalb der Datenbl\u00f6cke verboten ist) bestimmt. Wenn innerhalb einer Aufzeichnung ein Marker gefunden wird, ersetzen wir ihn durch eine bestimmte Folge (wir maskieren ihn).<br \/>\nEin \u00e4hnlicher Ansatz wird beispielsweise im PPP-Protokoll verwendet.<\/li>\n<\/ol>\n<p><\/p>\n<p>Ich werde es veranschaulichen.<\/p>\n<p><\/p>\n<p>Option 1:<br \/>\n<img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/e5a9676ca21eaca07e64ddf9fcd9f2eb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEs ist ganz einfach: Wenn wir die L\u00e4nge der Aufzeichnung kennen, k\u00f6nnen wir die Adresse der n\u00e4chsten \u00dcberschrift berechnen. So bewegen wir uns durch die \u00dcberschriften, bis wir auf einen Bereich treffen, der mit 0xff gef\u00fcllt ist (freier Bereich) oder das Seitenende erreicht ist.<\/p>\n<p><\/p>\n<p>Option 2:<br \/>\n<img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/4fed4860fb659ab6042be9aeeb5c041a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAufgrund der variablen L\u00e4nge der Aufzeichnung k\u00f6nnen wir nicht im Voraus sagen, wie viele Datens\u00e4tze (und somit \u00dcberschriften) wir auf die Seite ben\u00f6tigen. Wir k\u00f6nnen die \u00dcberschriften und die eigentlichen Daten auf verschiedene Seiten aufteilen, aber mir gef\u00e4llt ein anderer Ansatz besser: Sowohl die \u00dcberschriften (in fester Gr\u00f6\u00dfe) als auch die Daten (variabler L\u00e4nge) befinden sich auf derselben Seite, wobei die \u00dcberschriften am Anfang der Seite und die Daten am Ende stehen. Sobald sie sich \"treffen\" (der Platz reicht nicht mehr f\u00fcr einen neuen Datensatz) \u2013 betrachten wir diese Seite als voll.<\/p>\n<p><\/p>\n<p>Variante 3:<br \/>\n<img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/dfb64009aa7781fcb8a47423ff4160c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEs ist nicht notwendig, die L\u00e4nge oder andere Informationen \u00fcber die Datenanordnung im Header zu speichern; Marker, die die Grenzen der Datens\u00e4tze anzeigen, sind ausreichend. Allerdings m\u00fcssen die Daten beim Schreiben\/Lesen verarbeitet werden.<br \/>\nAls Marker w\u00fcrde ich 0xff verwenden (mit dem die Seite nach dem L\u00f6schen gef\u00fcllt ist), so dass der freie Bereich sicherlich nicht als Daten interpretiert wird.<\/p>\n<p><\/p>\n<p>Vergleichstabelle:<\/p>\n<p><\/p>\n<p>Option 1<br \/>\nOption 2<br \/>\nOption 3<\/p>\n<p><strong>Fehlerresistenz<\/strong><br \/>\n\u2014<br \/>\n+<br \/>\n+<\/p>\n<p><strong>Kompaktheit<\/strong><br \/>\n+<br \/>\n\u2014<br \/>\n+<\/p>\n<p><strong>Implementierungskomplexit\u00e4t<\/strong><br \/>\n*<br \/>\n**<br \/>\n**<\/p>\n<p><\/p>\n<p>Variante 1 hat einen fatalen Nachteil: Wenn einer der Header besch\u00e4digt wird, wird die gesamte nachfolgende Kette zerst\u00f6rt. Die anderen Varianten erm\u00f6glichen es, Teile der Daten selbst bei massiven Besch\u00e4digungen wiederherzustellen.<br \/>\nHier ist es angebracht zu erw\u00e4hnen, dass wir beschlossen haben, die Daten in komprimierter Form zu speichern. So verlieren wir ohnehin alle Daten auf der Seite nach dem \"besch\u00e4digten\" Datensatz, sodass wir den Minuswert in der Tabelle nicht ber\u00fccksichtigen.<\/p>\n<p><\/p>\n<p>Kompaktheit:<\/p>\n<p><\/p>\n<ul>\n<li>Bei der ersten Variante m\u00fcssen wir im Header nur die L\u00e4nge speichern; bei der Verwendung variabler L\u00e4ngen Ganzzahlen k\u00f6nnen wir in den meisten F\u00e4llen mit einem Byte auskommen;<\/li>\n<li>In der zweiten Variante m\u00fcssen wir die Startadresse und die L\u00e4nge speichern; der Datensatz sollte eine konstante Gr\u00f6\u00dfe haben, ich sch\u00e4tze 4 Byte pro Datensatz (zwei Bytes f\u00fcr die Verschiebung und zwei Bytes f\u00fcr die L\u00e4nge);<\/li>\n<li>Die dritte Variante ben\u00f6tigt nur ein Zeichen, um den Anfang des Datensatzes zu kennzeichnen; aufgrund der Maskierung wird der Datensatz um 1-2% gr\u00f6\u00dfer. Insgesamt gibt es eine ungef\u00e4hre Parit\u00e4t zur ersten Variante.<\/li>\n<\/ul>\n<p><\/p>\n<p>Urspr\u00fcnglich betrachtete ich die zweite Variante als die Hauptvariante (und habe sogar eine Implementierung geschrieben). Ich habe sie nur verworfen, als ich endg\u00fcltig beschloss, Kompression zu verwenden.<\/p>\n<p><\/p>\n<p><em>M\u00f6glicherweise werde ich irgendwann tats\u00e4chlich eine solche Variante verwenden. Zum Beispiel, wenn ich mit der Speicherung von Daten f\u00fcr ein Raumschiff zu tun habe, das zwischen der Erde und dem Mars pendelt \u2013 ganz andere Anforderungen an die Zuverl\u00e4ssigkeit, kosmische Strahlung, \u2026<\/em><\/p>\n<p><\/p>\n<p>Was die dritte Variante angeht: Ich habe ihr wegen der Komplexit\u00e4t der Implementierung zwei Sterne gegeben, einfach weil ich nicht gerne mit Maskierung, L\u00e4ngen\u00e4nderung w\u00e4hrend des Prozesses usw. umgehe. Ja, m\u00f6glicherweise voreingenommen, aber ich muss den Code schreiben \u2013 warum sollte ich mich zwingen, etwas zu tun, das mir nicht gef\u00e4llt.<\/p>\n<p><\/p>\n<p><strong>Zusammenfassung:<\/strong> Wir w\u00e4hlen die Speicheroption in Form von Ketten \"\u00dcberschrift mit L\u00e4nge \u2013 Daten variabler L\u00e4nge\" aufgrund der Effizienz und Einfachheit der Implementierung.<\/p>\n<p><\/p>\n<h3 id=\"ispolzovanie-bitovyh-poley-dlya-kontrolya-uspeshnosti-operaciy-zapisi\">Die Verwendung von Bitfeldern zur Kontrolle des Erfolgs von Schreibvorg\u00e4ngen<\/h3>\n<p><\/p>\n<p>Ich erinnere mich nicht mehr, wo ich die Idee gesehen habe, aber es sieht ungef\u00e4hr so aus:<br \/>\nF\u00fcr jeden Eintrag reservieren wir mehrere Bits zur Speicherung von Flags.<br \/>\n<em>Wie bereits erw\u00e4hnt, sind nach dem L\u00f6schen alle Bits auf 1 gesetzt, und wir k\u00f6nnen 1 auf 0 \u00e4ndern, aber nicht umgekehrt.<\/em> F\u00fcr \"Flag nicht gesetzt\" verwenden wir 1, f\u00fcr \"Flag gesetzt\" \u2013 0.<\/p>\n<p><\/p>\n<p>So k\u00f6nnte die Unterbringung eines variablen L\u00e4ngeneintrags im Flash aussehen:<\/p>\n<p><\/p>\n<ol>\n<li>Setzen des Flags \u201eL\u00e4nge des Schreibens begonnen\u201c;<\/li>\n<li>L\u00e4nge aufzeichnen;<\/li>\n<li>Setzen des Flags \u201eDatenaufzeichnung begonnen\u201c;<\/li>\n<li>Daten aufzeichnen;<\/li>\n<li>Setzen des Flags \u201eAufzeichnung abgeschlossen\u201c.<\/li>\n<\/ol>\n<p><\/p>\n<p>Dar\u00fcber hinaus haben wir ein Flag \u201eFehler aufgetreten\u201c, insgesamt also 4 Bit-Flags.<\/p>\n<p><\/p>\n<p>In diesem Fall haben wir zwei stabile Zust\u00e4nde \u201e1111\u201c \u2014 Aufzeichnung nicht begonnen und \u201e1000\u201c \u2014 Aufzeichnung war erfolgreich; bei unvorhergesehenen Unterbrechungen des Schreibvorgangs erhalten wir Zwischenzust\u00e4nde, die wir sp\u00e4ter erkennen und verarbeiten k\u00f6nnen.<\/p>\n<p><\/p>\n<p>Der Ansatz ist interessant, sch\u00fctzt aber nur vor pl\u00f6tzlichem Stromausfall und \u00e4hnlichen St\u00f6rungen, was nat\u00fcrlich wichtig ist, jedoch bei weitem nicht die einzige (und selbst nicht die Haupt-) Ursache f\u00fcr m\u00f6gliche St\u00f6rungen darstellt.<\/p>\n<p><\/p>\n<p><strong>Zusammenfassung:<\/strong> Lass uns weiter auf der Suche nach einer guten L\u00f6sung.<\/p>\n<p><\/p>\n<h3 id=\"kontrolnye-summy\">Pr\u00fcfziffern<\/h3>\n<p><\/p>\n<p>Pr\u00fcfziffern erm\u00f6glichen ebenfalls zu verifizieren (mit ausreichender Wahrscheinlichkeit), dass wir genau das lesen, was h\u00e4tte aufgezeichnet werden sollen. Und im Gegensatz zu den oben betrachteten Bitfeldern funktionieren sie immer.<\/p>\n<p><\/p>\n<p>Wenn wir die Liste potenzieller Problemerkenner betrachten, die wir zuvor besprochen haben, kann die Pr\u00fcfziffer einen Fehler unabh\u00e4ngig von seiner Herkunft erkennen <em>(au\u00dfer nat\u00fcrlich bei b\u00f6swilligen Au\u00dferirdischen \u2014 die k\u00f6nnen auch die Pr\u00fcfziffer f\u00e4lschen)<\/em>.<\/p>\n<p><\/p>\n<p>Wenn unser Ziel also darin besteht, zu \u00fcberpr\u00fcfen, ob die Daten intakt sind, sind Pr\u00fcfziffern eine hervorragende Idee.<\/p>\n<p><\/p>\n<p>Die Wahl des Algorithmus zur Berechnung der Pr\u00fcfziffer war unproblematisch \u2014 CRC. Einerseits erm\u00f6glichen mathematische Eigenschaften, dass einige Typen von Fehlern zu 100 % erkannt werden, andererseits zeigt dieser Algorithmus bei zuf\u00e4lligen Daten normalerweise eine Kollisionswahrscheinlichkeit, die nicht wesentlich \u00fcber dem theoretischen Limit liegt. <img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/bf9cca3564db7d9d03d3ce49642d3a71.jpg\" style=\"display:block;margin: 0 auto;\" \/>M\u00f6ge es nicht der schnellste Algorithmus sein und nicht immer die geringste Anzahl von Kollisionen aufweisen, aber er hat eine sehr wichtige Eigenschaft: In den mir begegneten Tests gab es keine Muster, bei denen er klar versagte. Stabilit\u00e4t ist in diesem Fall die wichtigste Eigenschaft.<\/p>\n<p><\/p>\n<p>Beispiel f\u00fcr eine umfassende Untersuchung: <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo.html\">Teil 1<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo2.html\">Teil 2<\/a><\/noindex> <em>(Links zu narod.ru, entschuldigung)<\/em>.<\/p>\n<p><\/p>\n<p>Die Aufgabe der Auswahl einer Pr\u00fcfziffer ist jedoch noch nicht abgeschlossen, CRC ist eine ganze Familie von Pr\u00fcfziffern. Man muss sich mit der L\u00e4nge entscheiden und dann ein Polynom ausw\u00e4hlen.<\/p>\n<p><\/p>\n<p>Die Auswahl der L\u00e4nge der Pr\u00fcfziffer ist nicht so einfache Frage, wie sie auf den ersten Blick erscheint.<\/p>\n<p><\/p>\n<p>Ich werde es veranschaulichen:<br \/>\nAngenommen, wir haben eine Fehlerwahrscheinlichkeit in jedem Byte <img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/89a9be2edf2a43cc9115eba37fa02fc8.jpg\" style=\"display:block;margin: 0 auto;\" \/> und eine ideale Pr\u00fcfziffer, berechnen wir die durchschnittliche Anzahl von Fehlern auf eine Million Eintr\u00e4ge:<\/p>\n<p><\/p>\n<p>Daten, Byte<br \/>\nPr\u00fcfziffer, Byte<br \/>\nNicht entdeckte Fehler<br \/>\nFalschpositive Fehlererkennungen<br \/>\nInsgesamt falsche Ausl\u00f6sungen<\/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>Es scheint einfach zu sein \u2013 w\u00e4hle je nach L\u00e4nge der gesch\u00fctzten Daten die L\u00e4nge der Pr\u00fcfziffer mit minimalen falschen Ausl\u00f6sungen \u2013 und alles ist erledigt.<\/p>\n<p><\/p>\n<p>Das Problem bei kurzen Pr\u00fcfziffern ist jedoch, dass sie, obwohl sie einzelne Bitfehler gut erkennen, mit einer erheblichen Wahrscheinlichkeit v\u00f6llig zuf\u00e4llige Daten f\u00fcr korrekt halten k\u00f6nnen. Auf habra wurde bereits ein Artikel beschrieben, der <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/428746\/\">das Problem im wirklichen Leben behandelt<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Um zuf\u00e4llige \u00dcbereinstimmungen der Pr\u00fcfziffer praktisch unm\u00f6glich zu machen, m\u00fcssen Pr\u00fcfziffern mit einer L\u00e4nge von 32 Bit oder mehr verwendet werden <em>(F\u00fcr L\u00e4ngen \u00fcber 64 Bit verwendet man normalerweise kryptografische Hash-Funktionen)<\/em>.<\/p>\n<p><\/p>\n<p>Obwohl ich zuvor geschrieben habe, dass wir jeden Byte Platz sparen sollten, werden wir dennoch eine 32-Bit-Pr\u00fcfziffer verwenden (16 Bit sind zu wenig, die Kollisionswahrscheinlichkeit liegt \u00fcber 0,01 %; und 24 Bit sind sozusagen weder hier noch dort).<\/p>\n<p><\/p>\n<p>Hier k\u00f6nnte der Einwand aufkommen: Haben wir jeden Byte beim Komprimieren gespart, nur um jetzt sofort 4 Byte abzugeben? W\u00e4re es nicht besser gewesen, nicht zu komprimieren und keine Pr\u00fcfziffer hinzuzuf\u00fcgen? Nat\u00fcrlich nicht, das Fehlen von Komprimierung <em>bedeutet<\/em>, dass die Integrit\u00e4tspr\u00fcfung f\u00fcr uns nicht notwendig ist.<\/p>\n<p><\/p>\n<p>Bei der Auswahl des Polynoms werden wir das Rad nicht neu erfinden und das derzeit beliebte CRC-32C verwenden.<br \/>\nDieser Code erkennt 6 Bitfehler in Paketen bis zu 22 Bytes (wahrscheinlich der h\u00e4ufigste Fall f\u00fcr uns), 4 Bitfehler in Paketen bis zu 655 Bytes (auch ein h\u00e4ufiger Fall f\u00fcr uns), 2 oder eine beliebige ungerade Anzahl von Bitfehlern in Paketen beliebiger sinnvoller L\u00e4nge.<\/p>\n<p>\n<b class=\"spoiler_title\">Falls jemand an den Details interessiert ist<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cyclic_redundancy_check\">Wikipedia-Artikel<\/a><\/noindex> \u00fcber CRC.<\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0x8f6e37a0_len.txt\">Parameter des Codes crc-32c<\/a><\/noindex> auf <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/crc\/notes.html\">der Seite von Kupman<\/a><\/noindex> \u2013 wahrscheinlich der f\u00fchrende Experte f\u00fcr CRC auf dem Planeten.<\/p>\n<p><\/p>\n<p>Im <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/networks\/dsn02\/dsn02_koopman.pdf\">seinem Artikel<\/a><\/noindex> einen <noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0xfa567d89_len.txt\">eine weitere interessante Codierung<\/a><\/noindex>, die etwas bessere Parameter f\u00fcr die f\u00fcr uns relevanten Paketl\u00e4ngen bietet, aber ich habe den Unterschied als nicht signifikant angesehen und f\u00fchle mich kompetent genug, um einen benutzerdefinierten Code anstelle eines standardisierten und gut erforschten Codes zu w\u00e4hlen.<\/p>\n<p><\/p>\n<p>Au\u00dferdem stellt sich, da unsere Daten komprimiert sind, die Frage: Soll die Pr\u00fcfziffer f\u00fcr komprimierte oder unkomprimierte Daten berechnet werden?<\/p>\n<p><\/p>\n<p>Argumente \"f\u00fcr\" die Berechnung der Pr\u00fcfziffer unkomprimierter Daten:<\/p>\n<p><\/p>\n<ul>\n<li>Wir m\u00fcssen letztendlich die Integrit\u00e4t der Datenspeicherung \u00fcberpr\u00fcfen \u2013 das pr\u00fcfen wir direkt (und dabei werden auch m\u00f6gliche Fehler bei der Implementierung von Kompression\/Dekompression sowie Besch\u00e4digungen, die durch defekten Speicher verursacht werden, \u00fcberpr\u00fcft);<\/li>\n<li>der deflate-Algorithmus in zlib hat eine ausreichend ausgereifte Implementierung und <em>sollte<\/em> Scheitern bei \"unausgereiften\" Eingangsdaten, zudem kann er oft selbst Fehler im Eingangsfluss erkennen und so die Wahrscheinlichkeit von unentdeckten Fehlern verringern (ich habe einen Test mit der Invertierung eines einzelnen Bits in einem kurzen Datensatz durchgef\u00fchrt, zlib hat den Fehler in etwa einem Drittel der F\u00e4lle erkannt).<\/li>\n<\/ul>\n<p><\/p>\n<p>Argumente \"gegen\" die Berechnung der Pr\u00fcfziffer unkomprimierter Daten:<\/p>\n<p><\/p>\n<ul>\n<li>CRC ist speziell f\u00fcr wenige Bitfehler ausgelegt, die typisch f\u00fcr Flash-Speicher sind (ein Bitfehler im komprimierten Fluss kann massive \u00c4nderungen im Ausgangsfluss verursachen, bei denen wir theoretisch eine Kollision \"erfassen\" k\u00f6nnten);<\/li>\n<li>ich bin nicht so begeistert von der Idee, potenziell fehlerhafte Daten an den Dekompressor zu \u00fcbergeben, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cvedetails.com\/vulnerability-list\/vendor_id-72\/product_id-1820\/GNU-Zlib.html\">wer kann schon wissen<\/a><\/noindex>, wie er reagieren wird.<\/li>\n<\/ul>\n<p><\/p>\n<p>In diesem Projekt habe ich beschlossen, von der \u00fcblichen Praxis abzuweichen, die Pr\u00fcfziffer f\u00fcr unkomprimierte Daten zu speichern.<\/p>\n<p><\/p>\n<p><strong>Zusammenfassung:<\/strong> Wir verwenden CRC-32C, die Pr\u00fcfziffer wird von den Daten in der Form berechnet, in der sie in den Flash geschrieben werden (nach der Kompression).<\/p>\n<p><\/p>\n<h3 id=\"izbytochnost\">Redundanz<\/h3>\n<p><\/p>\n<p>Der Einsatz von Redundanzkodierung kann zwar nicht verhindern, dass Daten verloren gehen, aber er kann die Wahrscheinlichkeit eines irreversiblen Datenverlusts erheblich (oft um viele Gr\u00f6\u00dfenordnungen) verringern.<\/p>\n<p><\/p>\n<p>Wir k\u00f6nnen verschiedene Arten von Redundanz verwenden, um Fehler zu korrigieren.<br \/>\nHamming-Codes k\u00f6nnen einzelne Bitfehler korrigieren, Reed-Solomon-Codes sind symbolbasierend, mehrere Kopien von Daten zusammen mit Pr\u00fcfziffern oder Kodierungen wie RAID-6 k\u00f6nnen helfen, Daten selbst im Falle massiver Sch\u00e4den wiederherzustellen.<br \/>\nZun\u00e4chst war ich f\u00fcr den breiten Einsatz von fehlerresistenten Kodierungen eingestellt, verstand dann aber, dass man zun\u00e4chst eine Vorstellung davon haben muss, vor welchen Fehlern man sich sch\u00fctzen m\u00f6chte, bevor man die Kodierung w\u00e4hlt.<\/p>\n<p><\/p>\n<p>Wir haben zuvor gesagt, dass Fehler so schnell wie m\u00f6glich erkannt werden m\u00fcssen. Zu welchen Zeitpunkten k\u00f6nnen wir mit Fehlern konfrontiert werden?<\/p>\n<p><\/p>\n<ol>\n<li>Unvollst\u00e4ndige Aufzeichnung (aus irgendwelchen Gr\u00fcnden wurde zum Zeitpunkt der Aufnahme die Stromversorgung unterbrochen, Raspberry hing, \u2026)<br \/>\nLeider bleibt im Falle eines solchen Fehlers nur, ung\u00fcltige Aufzeichnungen zu ignorieren und die Daten als verloren zu betrachten;<\/li>\n<li>Schreibfehler (aus irgendeinem Grund wurde in den Flash-Speicher etwas anderes geschrieben als beabsichtigt)<br \/>\nSolche Fehler k\u00f6nnen wir sofort erkennen, wenn wir unmittelbar nach dem Schreiben eine Pr\u00fcflesung durchf\u00fchren;<\/li>\n<li>Datenverzerrung im Speicher w\u00e4hrend der Speicherung;<\/li>\n<li>Lese-Fehler<br \/>\nZur Behebung gen\u00fcgt es, im Falle eines Abgleichs der Pr\u00fcfziffer das Lesen mehrere Male zu wiederholen.<\/li>\n<\/ol>\n<p><\/p>\n<p>Das hei\u00dft, nur Fehler des dritten Typs (spontane Datenkorruption bei der Speicherung) k\u00f6nnen ohne fehlerresistente Kodierung nicht behoben werden. Man k\u00f6nnte denken, dass solche Fehler dennoch \u00e4u\u00dferst unwahrscheinlich sind.<\/p>\n<p><\/p>\n<p><strong>Zusammenfassung:<\/strong> Es wurde beschlossen, auf Redundanzkodierung zu verzichten, aber wenn der Betrieb zeigt, dass diese Entscheidung falsch war, wird man die Frage erneut pr\u00fcfen (mit bereits gesammelter Statistik \u00fcber Ausf\u00e4lle, die es erm\u00f6glichen wird, die optimale Art der Kodierung auszuw\u00e4hlen).<\/p>\n<p><\/p>\n<h3 id=\"prochee\">Sonstiges<\/h3>\n<p><\/p>\n<p>Nat\u00fcrlich erlaubt das Artikelformat nicht, jedes Bit im Format zu begr\u00fcnden <em>(und mir sind auch die Kr\u00e4fte ausgegangen)<\/em>, deshalb werde ich einige Punkte kurz anrei\u00dfen, die zuvor nicht angesprochen wurden.<\/p>\n<p><\/p>\n<ul>\n<li>Es wurde beschlossen, alle Seiten \"gleichberechtigt\" zu gestalten.<br \/>\nDas hei\u00dft, es wird keine speziellen Seiten mit Metadaten oder separaten Streams usw. geben, stattdessen einen einzigen Stream, der alle Seiten nacheinander \u00fcberschreibt.<br \/>\nDas sorgt f\u00fcr einen gleichm\u00e4\u00dfigen Verschlei\u00df der Seiten, vermeidet einen einzelnen Fehlerpunkt und gef\u00e4llt einfach.<\/li>\n<li>Die Versionierung des Formats sollte unbedingt ber\u00fccksichtigt werden.<br \/>\nEin Format ohne Versionsnummer im Header ist eine Katastrophe!<br \/>\nEs reicht aus, im Header der Seite ein Feld mit einer Art Magic Number (Signatur) hinzuzuf\u00fcgen, die auf die verwendete Versionsnummer des Formats hinweist. <em>(Ich denke nicht, dass es in der Praxis mehr als ein Dutzend davon geben wird.)<\/em>;<\/li>\n<li>Verwenden Sie f\u00fcr die Eintr\u00e4ge (von denen es sehr viele gibt) einen Header variabler L\u00e4nge und versuchen Sie, ihn in den meisten F\u00e4llen auf 1 Byte zu beschr\u00e4nken.<\/li>\n<li>Zur Kodierung der L\u00e4nge des Headers und der L\u00e4nge des abgeschnittenen Teils des komprimierten Eintrags sollten bin\u00e4re Codes variabler L\u00e4nge verwendet werden.<\/li>\n<\/ul>\n<p><\/p>\n<p>Online-Generator <noindex><a rel=\"nofollow\" href=\"https:\/\/planetcalc.com\/2481\/\">von Huffman-Codes hat sehr geholfen. Innerhalb von Minuten wurden die ben\u00f6tigten variablen L\u00e4ngen-Codes gefunden.<\/a><\/noindex> Beschreibung des Datenspeicherformats<\/p>\n<p><\/p>\n<h1 id=\"anchorformatanchoropisanie-formata-hraneniya-dannyh\"><noindex><a rel=\"nofollow\" name=\"format\"><\/a><\/noindex>Byte-Reihenfolge<\/h1>\n<p><\/p>\n<h2 id=\"byte-order\">Felder, die gr\u00f6\u00dfer als ein Byte sind, werden im Big-Endian-Format (Netzwerk-Byte-Reihenfolge) gespeichert, das hei\u00dft, 0x1234 wird als 0x12, 0x34 gespeichert.<\/h2>\n<p><\/p>\n<p>Seitenaufteilung<\/p>\n<p><\/p>\n<h2 id=\"delenie-na-stranicy\">Der gesamte Flash-Speicher ist in gleich gro\u00dfe Seiten unterteilt.<\/h2>\n<p><\/p>\n<p>Die standardm\u00e4\u00dfige Seitengr\u00f6\u00dfe betr\u00e4gt 32KB, jedoch nicht mehr als 1\/4 der gesamten Gr\u00f6\u00dfe des Speicherchips (f\u00fcr einen 4MB-Chip ergibt das 128 Seiten).<\/p>\n<p><\/p>\n<p>Jede Seite speichert Daten unabh\u00e4ngig von anderen (d. h. die Daten einer Seite verweisen nicht auf die Daten einer anderen Seite).<\/p>\n<p><\/p>\n<p>Alle Seiten sind in nat\u00fcrlicher Reihenfolge (in aufsteigender Adressfolge) nummeriert, beginnend mit der Nummer 0 (die Nullseite beginnt mit Adresse 0, die erste mit 32KB, die zweite mit 64KB usw.).<\/p>\n<p><\/p>\n<p>Der Speicherchip wird als zirkul\u00e4rer Puffer (Ringpuffer) verwendet, d. h. zuerst wird in die Seite mit der Nummer 0 geschrieben, dann in die Seite mit der Nummer 1, \u2026 wenn wir die letzte Seite f\u00fcllen, beginnt ein neuer Zyklus und das Schreiben geht mit der Nullseite weiter.<\/p>\n<p><\/p>\n<p>Der Speicherchip wird als Ringpuffer verwendet, das hei\u00dft, zun\u00e4chst wird in die Seite mit der Nummer 0 geschrieben, dann in die mit der Nummer 1, \u2026 wenn wir die letzte Seite gef\u00fcllt haben, beginnt der neue Zyklus, und das Schreiben geht weiter mit der Nullseite.<\/p>\n<p><\/p>\n<h2 id=\"vnutri-stranicy\">Am Anfang der Seite wird ein 4-Byte-Header gespeichert, gefolgt von einer Pr\u00fcfziffer des Headers (CRC-32C) und dann werden die Eintr\u00e4ge im Format \u201eHeader, Daten, Pr\u00fcfziffer\u201c gespeichert.<\/h2>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/39b45f83dc46bb2fd7081ed2a0b638c9.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAm Anfang der Seite befindet sich eine 4-Byte-Seiten\u00fcberschrift, dann die Pr\u00fcfziffer der \u00dcberschrift (CRC-32C), danach werden die Eintr\u00e4ge im Format \u201e\u00dcberschrift, Daten, Pr\u00fcfziffer\u201c gespeichert.<\/p>\n<p><\/p>\n<p>einem zweibyteigen Feld Magic Number (auch als Versionsbezeichnung des Formats bekannt)<\/p>\n<p><\/p>\n<ul>\n<li>f\u00fcr die aktuelle Version des Formats wird er als<br \/>\nF\u00fcr die aktuelle Version des Formats wird er als <code>0xed00 \u2295 Seitenzahl<\/code>;<\/li>\n<li>ein zweibyte Z\u00e4hler \u201eSeitenversion\u201c (Nummer des Zyklus der Speicher\u00fcberschreibung).<\/li>\n<\/ul>\n<p><\/p>\n<p>Die Aufzeichnungen auf der Seite werden in komprimierter Form gespeichert (es wird der Algorithmus deflate verwendet). Alle Aufzeichnungen auf einer Seite werden in einem Stream komprimiert (das wird ein gemeinsames W\u00f6rterbuch verwendet), auf jeder neuen Seite beginnt die Kompression erneut. Das hei\u00dft, f\u00fcr die Dekomprimierung jeder Aufzeichnung sind alle vorhergehenden Aufzeichnungen von dieser Seite notwendig (und nur von dieser).<\/p>\n<p><\/p>\n<p>Jede Aufzeichnung wird mit dem Flag Z_SYNC_FLUSH komprimiert, dabei befinden sich am Ende des komprimierten Streams 4 Bytes 0x00, 0x00, 0xff, 0xff, m\u00f6glicherweise vorangestellt durch ein oder zwei Nullbytes.<br \/>\nDiese Sequenz (von 4, 5 oder 6 Bytes) wird bei der Aufzeichnung in den Flash-Speicher verworfen.<\/p>\n<p><\/p>\n<p>Der Header der Aufzeichnung besteht aus 1, 2 oder 3 Bytes, die folgendes speichern:<\/p>\n<p><\/p>\n<ul>\n<li>ein Bit (T), das den Typ der Aufzeichnung anzeigt: 0 \u2014 Kontext, 1 \u2014 Protokoll;<\/li>\n<li>ein variabel langes Feld (S) von 1 bis 7 Bit, das die L\u00e4nge der \u00dcberschrift und den \u201eSchwanz\u201c bestimmt, der zur Aufzeichnung f\u00fcr die Dekomprimierung hinzugef\u00fcgt werden muss;<\/li>\n<li>die L\u00e4nge der Aufzeichnung (L).<\/li>\n<\/ul>\n<p><\/p>\n<p>Tabelle der Werte S:<\/p>\n<p><\/p>\n<p>S<br \/>\nL\u00e4nge des Headers, Bytes<br \/>\nWird bei der Aufzeichnung verworfen, 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>Ich habe versucht, es zu veranschaulichen, ich wei\u00df nicht, wie anschaulich es geworden ist:<br \/>\n<img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/8c9b739eec5af4429395c32b4ba4044a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHier ist das Feld T gelb gekennzeichnet, das Feld S wei\u00df, gr\u00fcn ist L (die L\u00e4nge der komprimierten Daten in Bytes), blau sind die komprimierten Daten, rot sind die Endbytes der komprimierten Daten, die nicht im Flash-Speicher geschrieben werden.<\/p>\n<p><\/p>\n<p>Somit k\u00f6nnen wir die Header der am h\u00e4ufigsten vorkommenden L\u00e4nge (bis zu 63+5 Bytes in komprimierter Form) mit einem Byte aufzeichnen.<\/p>\n<p><\/p>\n<p>Nach jeder Aufzeichnung wird eine CRC-32C-Pr\u00fcfziffer gespeichert, bei der als Initialwert (init) der invertierte Wert der vorherigen Pr\u00fcfziffer verwendet wird.<\/p>\n<p><\/p>\n<p><em>CRC hat die Eigenschaft der \u201eFortdauer\u201c, die folgende Formel wirkt (plus\/minus Umkehrung der Bits im Prozess): <img decoding=\"async\" alt=\"Meine Implementierung eines Ringpuffers in NOR-Flash\" src=\"\/wp-content\/uploads\/2019\/12\/b194d6a3d18ae2f4ae5d4054a93c0ea3.jpg\" style=\"display:block;margin: 0 auto;\" \/>.<br \/>\nDas hei\u00dft, wir berechnen tats\u00e4chlich die CRC aller vorhergehenden Bytes der Header und Daten auf dieser Seite.<\/em><\/p>\n<p><\/p>\n<p>Direkt nach der Pr\u00fcfziffer befindet sich der Header der n\u00e4chsten Aufzeichnung.<\/p>\n<p><\/p>\n<p>Der Header ist so konstruiert, dass das erste Byte immer unterschiedlich von 0x00 und 0xff ist (wenn wir anstelle des ersten Bytes des Headers 0xff antreffen, bedeutet das, dass es sich um einen nicht verwendeten Bereich handelt; 0x00 signalisiert einen Fehler).<\/p>\n<p><\/p>\n<h2 id=\"primernye-algoritmy\">Ungef\u00e4hre Algorithmen<\/h2>\n<p><\/p>\n<h3 id=\"chtenie-iz-flesh-pamyati\">Lesen aus dem Flash-Speicher<\/h3>\n<p><\/p>\n<p>Jedes Lesen erfolgt mit einer \u00dcberpr\u00fcfung der Pr\u00fcfziffer.<br \/>\nWenn die Pr\u00fcfziffer nicht \u00fcbereinstimmt, wird das Lesen mehrmals wiederholt, in der Hoffnung, die tats\u00e4chlichen Daten korrekt zu lesen.<\/p>\n<p><\/p>\n<p><em>(das macht Sinn, Linux cached das Lesen aus NOR Flash nicht, getestet)<\/em><\/p>\n<p><\/p>\n<h3 id=\"zapis-v-flesh-pamyat\">Schreiben in den Flash-Speicher<\/h3>\n<p><\/p>\n<p>Wir schreiben Daten.<br \/>\nWir lesen sie.<\/p>\n<p><\/p>\n<p>Wenn die gelesenen Daten nicht mit den geschriebenen \u00fcbereinstimmen, f\u00fcllen wir den Bereich mit Nullen und signalisieren einen Fehler.<\/p>\n<p><\/p>\n<h3 id=\"podgotovka-novoy-mikroshemy-k-rabote\">Vorbereitung des neuen Chips zur Verwendung<\/h3>\n<p><\/p>\n<p>Zur Initialisierung wird im ersten (genauer gesagt, nullten) Seitenbereich ein Header mit der Version 1 geschrieben.<br \/>\nDanach wird in diesen Seitenbereich der Anfangskontext (enth\u00e4lt die UUID der Maschine und die Standardkonfigurationen) geschrieben. <\/p>\n<p><\/p>\n<p>Alles, der Flash-Speicher ist bereit zur Verwendung.<\/p>\n<p><\/p>\n<h3 id=\"zagruzka-avtomata\">Laden der Maschine<\/h3>\n<p><\/p>\n<p>Beim Laden werden die ersten 8 Byte jeder Seite (Header + CRC) gelesen. Seiten mit unbekannter Magic Number oder ung\u00fcltigem CRC werden ignoriert.<br \/>\nAus den \u201eg\u00fcltigen\u201c Seiten werden die Seiten mit der maximalen Version ausgew\u00e4hlt, von diesen wird die Seite mit der h\u00f6chsten Nummer genommen.<br \/>\nDer erste Eintrag wird gelesen, die CRC-Integrit\u00e4t und das Vorhandensein des \u201eKontext\u201c-Flags werden \u00fcberpr\u00fcft. Wenn alles in Ordnung ist, gilt diese Seite als aktuell. Wenn nicht, gehen wir zur vorherigen zur\u00fcck, bis wir eine \u201elebendige\u201c Seite finden.<br \/>\nAuf der gefundenen Seite lesen wir alle Eintr\u00e4ge, die mit dem \u201eKontext\u201c-Flag versehen sind, anwenden.<br \/>\nWir speichern das zlib-W\u00f6rterbuch (wird f\u00fcr das Nachschreiben in diese Seite ben\u00f6tigt).<\/p>\n<p><\/p>\n<p>Alles, der Ladevorgang ist abgeschlossen, der Kontext wurde wiederhergestellt, man kann arbeiten.<\/p>\n<p><\/p>\n<h3 id=\"dobavlenie-zapisi-v-zhurnal\">Hinzuf\u00fcgen eines Eintrags zum Protokoll<\/h3>\n<p><\/p>\n<p>Wir komprimieren den Eintrag mit dem richtigen W\u00f6rterbuch und geben Z_SYNC_FLUSH an. Wir pr\u00fcfen, ob der komprimierte Eintrag auf die aktuelle Seite passt.<br \/>\nWenn er nicht passt (oder die Seite CRC-Fehler aufwies) \u2013 beginnen wir eine neue Seite (siehe unten).<br \/>\nWir schreiben den Eintrag und den CRC. Tritt ein Fehler auf, beginnen wir eine neue Seite.<\/p>\n<p><\/p>\n<h3 id=\"novaya-stranica\">Neue Seite<\/h3>\n<p><\/p>\n<p>W\u00e4hlen Sie die freie Seite mit der minimalen Nummer aus (eine Seite gilt als frei, wenn sie eine falsche Pr\u00fcfziffer im Header oder eine Version hat, die kleiner ist als die aktuelle). Wenn es solche Seiten nicht gibt, w\u00e4hlen wir die Seite mit der minimalen Nummer aus denjenigen aus, die die gleiche Version wie die aktuelle haben.<br \/>\nWir f\u00fchren ein Erase f\u00fcr die gew\u00e4hlte Seite durch. Wir vergleichen den Inhalt mit 0xff. Wenn etwas nicht stimmt, nehmen wir die n\u00e4chste freie Seite usw.<br \/>\nAuf die gel\u00f6schte Seite schreiben wir den Header, als ersten Eintrag den aktuellen Zustand des Kontextes, als n\u00e4chsten den nicht geschriebenen Eintrag aus dem Protokoll (wenn vorhanden).<\/p>\n<p><\/p>\n<h1 id=\"primenimost-formata\">Anwendbarkeit des Formats<\/h1>\n<p><\/p>\n<p>Meiner Meinung nach ist das ein ganz passables Format zur Speicherung beliebiger mehr oder weniger komprimierbarer Informationsstr\u00f6me (einfacher Text, JSON, MessagePack, CBOR, m\u00f6glicherweise Protobuf) im NOR Flash.<\/p>\n<p><\/p>\n<p>Nat\u00fcrlich ist das Format \u201eauf SLC NOR Flash abgestimmt\u201c.<\/p>\n<p><\/p>\n<p>Es sollte nicht mit Tr\u00e4gern mit hohem BER verwendet werden, wie NAND oder MLC NOR <em>(gibt es solche Speicher \u00fcberhaupt im Handel? Ich habe nur Erw\u00e4hnungen in Arbeiten \u00fcber Fehlerkorrektur gefunden)<\/em>.<\/p>\n<p><\/p>\n<p>Umso mehr sollte es nicht mit Ger\u00e4ten verwendet werden, die \u00fcber ein eigenes FTL verf\u00fcgen: USB-Flash, SD, MicroSD usw. <em>(F\u00fcr diesen Speicher habe ich ein Format mit einer Seitengr\u00f6\u00dfe von 512 Byte, einer Signatur am Anfang jeder Seite und einzigartigen Eintragsnummern erstellt \u2013 manchmal konnte ich aus einer \u201egest\u00f6rten\u201c Flash-Memory durch einfaches sequenzielles Lesen alle Daten wiederherstellen)<\/em>.<\/p>\n<p><\/p>\n<p>Je nach Aufgabe kann das Format unver\u00e4ndert auf Flash-Laufwerken von 128 Kbit (16 KB) bis 1 Gbit (128 MB) verwendet werden. Bei Bedarf kann es auch auf Chips mit gr\u00f6\u00dferem Volumen verwendet werden, aber wahrscheinlich muss die Seitengr\u00f6\u00dfe angepasst werden. <em>(Aber hier stellt sich bereits die Frage der Wirtschaftlichkeit, der Preis f\u00fcr gro\u00dfen NOR Flash ist nicht erfreulich)<\/em>.<\/p>\n<p><\/p>\n<p>Wenn jemand das Format interessant findet und es in einem Open-Source-Projekt verwenden m\u00f6chte \u2014 schreibt mir, ich werde versuchen, Zeit zu finden, um den Code zu \u00fcberarbeiten und ihn auf GitHub hochzuladen.<\/p>\n<p><\/p>\n<h1 id=\"zaklyuchenie\">Fazit<\/h1>\n<p><\/p>\n<p>Wie man sieht, ist das Format am Ende einfach <em>und sogar langweilig<\/em>.<\/p>\n<p><\/p>\n<p>In dem Artikel ist es schwierig, die Evolution meiner Sichtweise widerzuspiegeln, aber glauben Sie mir: Urspr\u00fcnglich wollte ich etwas Komplexes, Unzerst\u00f6rbares schaffen, das selbst nach einer nuklearen Explosion in unmittelbarer N\u00e4he \u00fcberleben kann. Doch die Vernunft (hoffentlich) hat letztendlich gesiegt und die Priorit\u00e4ten verschoben sich allm\u00e4hlich in Richtung Einfachheit und Kompaktheit.<\/p>\n<p><\/p>\n<p>Kann es sein, dass ich mich geirrt habe? Ja, nat\u00fcrlich. Es k\u00f6nnte durchaus sein, dass wir zum Beispiel eine Partie minderwertiger Chips gekauft haben. Oder aus einem anderen Grund erf\u00fcllt die Hardware nicht die Erwartungen an die Zuverl\u00e4ssigkeit.<\/p>\n<p><\/p>\n<p>Habe ich einen Plan f\u00fcr diesen Fall? Ich denke, dass Sie beim Lesen des Artikels keinen Zweifel haben, dass ich einen Plan habe. Und sogar mehrere.<\/p>\n<p><\/p>\n<p>Wenn wir es etwas ernsthafter betrachten, wurde das Format sowohl als Arbeitsvariante als auch als \u201eVersuch\u201c entwickelt.<\/p>\n<p><\/p>\n<p>Im Moment funktioniert alles auf meinem Tisch normal, in den n\u00e4chsten Tagen wird die L\u00f6sung bereitgestellt <em>(ungef\u00e4hr)<\/em> Auf hundert Ger\u00e4ten, mal sehen, wie sich das im \u201eechten\u201c Einsatz bew\u00e4hrt (zum Gl\u00fcck, hoffe ich, dass das Format zuverl\u00e4ssig Ausf\u00e4lle erkennen kann; so dass wir umfassende Statistiken sammeln k\u00f6nnen). Nach einigen Monaten k\u00f6nnen R\u00fcckschl\u00fcsse gezogen werden. <em>(und wenn wir Pech haben \u2013 sogar fr\u00fcher)<\/em>.<\/p>\n<p><\/p>\n<p>Falls bei der Nutzung schwerwiegende Probleme festgestellt werden und Anpassungen erforderlich sind, werde ich unbedingt dar\u00fcber berichten.<\/p>\n<p><\/p>\n<h1 id=\"literatura\">Literatur<\/h1>\n<p><\/p>\n<p>Ich wollte keine lange, langweilige Liste der verwendeten Arbeiten erstellen; schlie\u00dflich hat jeder Google.<\/p>\n<p><\/p>\n<p>Hier habe ich beschlossen, eine Liste von Entdeckungen zu f\u00fchren, die mir besonders interessant erschienen. Diese sind jedoch allm\u00e4hlich in den Artikeltext gewandert, und in der Liste blieb nur ein Punkt \u00fcbrig:<\/p>\n<p><\/p>\n<ol>\n<li>Das Tool <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/madler\/infgen\/\">infgen<\/a><\/noindex> von Autor zlib. Kann den Inhalt von deflate\/zlib\/gzip-Archiven in verst\u00e4ndlicher Form anzeigen. Wenn Sie sich mit dem inneren Aufbau des deflate-Formats (oder gzip) auseinandersetzen m\u00fcssen, kann ich es dringend empfehlen.<\/li>\n<\/ol>\n<p>Quelle: <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.2 - 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\/de\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\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\/de\/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\udd47Meine Implementierung eines Ringpuffers in NOR-Flash | ProHoster","description":"Hintergrund Es gibt Verkaufsautomaten, die selbst entwickelt wurden. Im Inneren befindet sich ein Raspberry Pi und etwas Zubeh\u00f6r auf einer separaten Platine.","canonical_url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","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\/de\/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\/de\/wp-json\/wp\/v2\/posts\/53751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=53751"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/53751\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=53751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=53751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=53751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}