{"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\/ro\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","title":{"rendered":"Implementarea mea a buffer-ului circular \u00een NOR flash","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h1 id=\"predystoriya\">Povestea<\/h1>\n<p><\/p>\n<p>Avem automate de v\u00e2nzare dezvoltate intern. \u00cen interior se afl\u0103 un Raspberry Pi \u0219i pu\u021bin hardware pe o plac\u0103 separat\u0103. Sunt conectate un acceptor de monede, un acceptor de bancnote, un terminal bancar... Totul este gestionat de un program personalizat. \u00centreaga istorie a func\u021bion\u0103rii este scris\u0103 \u00eentr-un jurnal pe un stick USB (MicroSD), care apoi este transmis prin internet (cu ajutorul unui modem USB) pe server, unde este stocat\u0103 \u00eentr-o baz\u0103 de date. Informa\u021biile despre v\u00e2nz\u0103ri sunt \u00eenc\u0103rcate \u00een 1c, exist\u00e2nd de asemenea o interfa\u021b\u0103 web simpl\u0103 pentru monitorizare etc. <\/p>\n<p><\/p>\n<p>A\u0219adar, jurnalul este absolut necesar \u2014 pentru contabilitate (acolo sunt veniturile, v\u00e2nz\u0103rile etc.), monitorizare (diverse erori \u0219i alte for\u021be majore); aceasta este, poate, toat\u0103 informa\u021bia pe care o avem despre acest automat. <\/p>\n<p><\/p>\n<h1 id=\"problema\">Problema<\/h1>\n<p><\/p>\n<p>Sticlele USB se dovedesc a fi dispozitive foarte nesigure. Ele se defecteaz\u0103 cu o regularitate envidiat\u0103. Acest lucru duce at\u00e2t la oprirea automatelor, c\u00e2t \u0219i (dac\u0103 din diverse motive jurnalul nu a putut fi transmis online) la pierderi de date.<\/p>\n<p><\/p>\n<p><em>Aceasta nu este prima experien\u021b\u0103 cu stick-uri USB, \u00eenainte am avut un alt proiect cu mai mult de o sut\u0103 de dispozitive, unde jurnalul era stocat pe stick-uri USB, acolo au fost \u0219i probleme de fiabilitate, uneori num\u0103rul celor defecte pe lun\u0103 se ridica la zeci. Am \u00eencercat diferite stick-uri, inclusiv branduri cu memorie SLC, da, unele modele sunt mai fiabile dec\u00e2t altele, dar \u00eenlocuirea stick-urilor nu a rezolvat problema \u00een mod radical.<\/em><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <\/p>\n<p><strong>Aten\u021bie!<\/strong> Longread! Dac\u0103 nu v\u0103 intereseaz\u0103 \u201ede ce\u201d, ci doar \u201ecum\u201d, pute\u021bi merge direct <noindex><a rel=\"nofollow\" href=\"#format\">la sf\u00e2r\u0219itul<\/a><\/noindex> articolului.<\/p>\n<p><\/p>\n<h1 id=\"reshenie\">Solu\u021bie<\/h1>\n<p><\/p>\n<p>Primul lucru care \u00eemi vine \u00een minte: s\u0103 renun\u021b la MicroSD, s\u0103 pun, de exemplu, un SSD, \u0219i s\u0103 \u00eencarc de pe acesta. Teoretic este posibil, probabil, dar relativ scump \u0219i nu at\u00e2t de fiabil (se adaug\u0103 un adaptor USB-SATA; statisticile de e\u0219ec pentru SSD-urile bugetare nu sunt \u00eembucur\u0103toare).<\/p>\n<p><\/p>\n<p>Un HDD USB de asemenea nu pare o solu\u021bie prea atractiv\u0103.<\/p>\n<p><\/p>\n<p>A\u0219adar, am ajuns la aceast\u0103 variant\u0103: s\u0103 p\u0103str\u0103m \u00eenc\u0103rcarea de pe MicroSD, dar s\u0103 le folosim \u00een modul read-only, iar jurnalul de func\u021bionare (\u0219i alte informa\u021bii unice ale dispozitivului \u2014 num\u0103rul de serie, calibrarea senzorilor etc.) s\u0103 le stoc\u0103m undeva \u00een alt\u0103 parte. <\/p>\n<p><\/p>\n<p>Tema sistemelor de fi\u0219iere read-only pentru Raspberry Pi a fost deja studiat\u0103 pe de-a \u00eentregul, nu voi detalia implementarea \u00een acest articol <em>(dar dac\u0103 va exista interes \u2014 poate voi scrie un articol scurt pe aceast\u0103 tem\u0103)<\/em>. Un singur lucru pe care vreau s\u0103-l men\u021bionez: at\u00e2t din experien\u021ba personal\u0103, c\u00e2t \u0219i din recenziile celor care deja au implementat, exist\u0103 o c\u00e2\u0219tigare \u00een fiabilitate. Da, imposibilitatea de a elimina complet defectele este real\u0103, \u00eens\u0103 reducerea semnificativ\u0103 a frecven\u021bei acestora \u2013 este pe deplin realizabil\u0103. De asemenea, cardurile devin standardizate, ceea ce simplific\u0103 considerabil \u00eenlocuirea pentru personalul de \u00eentre\u021binere.<\/p>\n<p><\/p>\n<h2 id=\"apparatnaya-chast\">Partea hardware<\/h2>\n<p><\/p>\n<p>Nu au fost \u00eendoieli legate de alegerea tipului de memorie \u2013 NOR Flash.<br \/>\nArgumente: <\/p>\n<p><\/p>\n<ul>\n<li>conectare simpl\u0103 (cel mai adesea magistrala SPI, a c\u0103rei utilizare este deja cunoscut\u0103, astfel \u00eenc\u00e2t nu se preconizeaz\u0103 probleme \u201ehardware\u201d);<\/li>\n<li>pre\u021b amuzant;<\/li>\n<li>protocol standard de func\u021bionare (implementarea este deja \u00een kernelul Linux, dac\u0103 se dore\u0219te, se pot lua solu\u021bii externe, care exist\u0103 \u0219i ele, sau chiar se poate scrie una personalizat\u0103, bine\u00een\u021beles c\u0103 este simplu);<\/li>\n<li>fiabilitate \u0219i durabilitate:<br \/>\ndintr-o fi\u0219\u0103 tehnic\u0103 tipic\u0103: datele sunt stocate timp de 20 de ani, 100000 de cicluri de \u0219tergere pentru fiecare bloc;<br \/>\ndin surse externe: BER extrem de sc\u0103zut, se postuleaz\u0103 lipsa necesit\u0103\u021bii codurilor de corec\u021bie a erorilor <em>(\u00een unele lucr\u0103ri se discut\u0103 despre ECC pentru NOR, dar de obicei acolo se refer\u0103 la MLC NOR, exist\u0103 \u0219i a\u0219a ceva)<\/em>.<\/li>\n<\/ul>\n<p><\/p>\n<p>S\u0103 estim\u0103m cerin\u021bele legate de capacitate \u0219i resurs\u0103.<\/p>\n<p><\/p>\n<p>Vreau ca datele s\u0103 fie salvate garantat timp de c\u00e2teva zile. Aceasta este necesar\u0103 pentru ca, \u00een caz de probleme de conectivitate, istoricul v\u00e2nz\u0103rilor s\u0103 nu fie pierdut. Ne vom baza pe 5 zile, \u00een acest interval <em>(chiar \u0219i lu\u00e2nd \u00een considerare weekendurile \u0219i s\u0103rb\u0103torile)<\/em> se poate rezolva problema.<\/p>\n<p><\/p>\n<p>\u00cen prezent, pe parcursul unei zile se acumuleaz\u0103 aproximativ 100 KB de jurnale (3-4 mii de \u00eenregistr\u0103ri), dar treptat acest num\u0103r cre\u0219te \u2013 se detaliaz\u0103, se adaug\u0103 noi evenimente. \u00cen plus, uneori apar v\u00e2rfuri (de exemplu, un senzor \u00eencepe s\u0103 trimit\u0103 alarme false). Vom calcula pe baza a 10 mii de \u00eenregistr\u0103ri de 100 de bi\u021bi \u2013 un megabit pe zi.<\/p>\n<p><\/p>\n<p>Deci avem 5 MB de date pure (care pot fi comprimate bine). La acestea se mai adaug\u0103 <em>(estimare grosier\u0103)<\/em> 1 MB de date de servicii.<\/p>\n<p><\/p>\n<p>Asta \u00eenseamn\u0103 c\u0103 avem nevoie de un circuit integrat de 8 MB dac\u0103 nu utiliz\u0103m compresia, sau 4 MB dac\u0103 o utiliz\u0103m. Cifre perfect realiste pentru acest tip de memorie.<\/p>\n<p><\/p>\n<p>C\u00e2t despre resurse: dac\u0103 planific\u0103m c\u0103 memoria va fi rescris\u0103 complet nu mai des de o dat\u0103 la 5 zile, atunci pe o durat\u0103 de 10 ani de func\u021bionare vom ob\u021bine mai pu\u021bin de o mie de cicluri de rescriere.<br \/>\nReamintesc c\u0103 produc\u0103torul promite o sut\u0103 de mii.<\/p>\n<p>\n<b class=\"spoiler_title\">C\u00e2teva cuvinte despre NOR vs NAND<\/b><\/p>\n<p>Ast\u0103zi, desigur, memoria NAND este mult mai popular\u0103, \u00eens\u0103 pentru acest proiect nu a\u0219 recomanda utilizarea ei: NAND, spre deosebire de NOR, necesit\u0103 \u00een mod obligatoriu utilizarea codurilor de corec\u021bie a erorilor, tabele de blocuri defecte etc., iar pinii microcipurilor NAND sunt de obicei mult mai numero\u0219i.<\/p>\n<p><\/p>\n<p>Printre dezavantajele NOR se pot men\u021biona:<\/p>\n<p><\/p>\n<ul>\n<li>capacitate mic\u0103 (\u0219i, prin urmare, pre\u021b ridicat pe megabyte);<\/li>\n<li>viteza de transfer redus\u0103 (\u00een mare parte din cauza interfe\u021bei secven\u021biale, de obicei SPI sau I2C);<\/li>\n<li>\u0219tergere lent\u0103 (\u00een func\u021bie de dimensiunea blocului, dureaz\u0103 \u00eentre frac\u021biuni de secund\u0103 \u0219i c\u00e2teva secunde).<\/li>\n<\/ul>\n<p><\/p>\n<p>Nu pare nimic critic pentru noi, a\u0219a c\u0103 continu\u0103m.<\/p>\n<p><\/p>\n<p>Dac\u0103 sunte\u021bi interesat de detalii, a fost ales microcipul <noindex><a rel=\"nofollow\" href=\"https:\/\/www.adestotech.com\/wp-content\/uploads\/doc3686.pdf\">at25df321a<\/a><\/noindex> <em>(totu\u0219i, acest aspect nu este crucial, pe pia\u021b\u0103 exist\u0103 o mul\u021bime de analogi, compatibili cu pinout-ul \u0219i sistemul de comenzi; chiar dac\u0103 vom dori s\u0103 instal\u0103m un microcip de la un alt produc\u0103tor \u0219i\/sau cu o alt\u0103 capacitate, totul va func\u021biona f\u0103r\u0103 a modifica codul)<\/em>.<\/p>\n<p><\/p>\n<p>Folosesc driverul \u00eencorporat \u00een nucleul Linux, iar pe Raspberry, datorit\u0103 suportului pentru overlay-uri de arbore de dispozitive, totul este foarte simplu \u2014 trebuie s\u0103 plas\u0103m overlay-ul compilat \u00een \/boot\/overlays \u0219i s\u0103 modific\u0103m pu\u021bin \/boot\/config.txt.<\/p>\n<p>\n<b class=\"spoiler_title\">Exemplu de fi\u0219ier dts<\/b><\/p>\n<p>Sincer s\u0103 fiu, nu sunt sigur c\u0103 este scris f\u0103r\u0103 erori, dar func\u021bioneaz\u0103.<\/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\">\u0218i \u00eenc\u0103 o linie \u00een config.txt<\/b><\/p>\n<pre><code class=\"plaintext\">dtoverlay=at25:spimaxfrequency=50000000<\/code><\/pre>\n<p><\/p>\n<p>Descrierea conexiunii microcipului la Raspberry Pi o voi omite. Pe de o parte, nu sunt expert \u00een electronic\u0103, iar pe de alt\u0103 parte \u2014 totul este banal chiar \u0219i pentru mine: microcipul are doar 8 picioare, dintre care ne trebuie masa, alimentarea, SPI (CS, SI, SO, SCK); nivelurile corespund celor de la Raspberry Pi, nu este necesar\u0103 nicio leg\u0103tur\u0103 suplimentar\u0103 \u2014 este suficient s\u0103 conect\u0103m cele 6 contacte indicate.<\/p>\n<p><\/p>\n<h2 id=\"postanovka-zadachi\">Formularea problemei<\/h2>\n<p><\/p>\n<p>Ca de obicei, formularea problemei trece prin mai multe itera\u021bii, mi se pare c\u0103 a venit timpul pentru \u00eenc\u0103 una. A\u0219a c\u0103 haide\u021bi s\u0103 ne oprim, s\u0103 adun\u0103m tot ce a fost deja scris \u0219i s\u0103 clarific\u0103m detaliile r\u0103mase \u00een umbr\u0103.<\/p>\n<p><\/p>\n<p>A\u0219adar, ne-am decis c\u0103 jurnalul va fi stocat \u00een SPI NOR Flash.<\/p>\n<p>\n<b class=\"spoiler_title\">Ce este NOR Flash pentru cei care nu \u0219tiu<\/b><\/p>\n<p>Este o memorie non-volatil\u0103, cu care se pot efectua trei opera\u021biuni:<\/p>\n<p><\/p>\n<ol>\n<li>Citire:<br \/>\nCea mai obi\u0219nuit\u0103 citire: transmitem adresa \u0219i citim at\u00e2tea bilete c\u00e2te avem nevoie;<\/li>\n<li>Scriere:<br \/>\nScrierea \u00een memorie NOR flash arat\u0103 normal, dar are o caracteristic\u0103: se poate schimba doar 1 \u00een 0, dar nu \u0219i invers. De exemplu, dac\u0103 avem \u00een celula de memorie 0x55, dup\u0103 ce scriem 0x0f, va fi p\u0103strat\u0103 0x05. <em>(vezi tabelul de mai jos)<\/em>;<\/li>\n<li>Stergere:<br \/>\nDesigur, trebuie s\u0103 putem face \u0219i opera\u021bia invers\u0103 - s\u0103 schimb\u0103m 0 \u00een 1, \u0219i anume pentru aceasta exist\u0103 opera\u021bia de \u0219tergere. Spre deosebire de primele dou\u0103, aceasta opereaz\u0103 nu cu octe\u021bi, ci cu blocuri (blocul minim de \u0219tergere \u00een circuitul ales este de 4 KB). \u0218tergerea distruge \u00eentregul bloc \u0219i este singurul mod de a schimba 0 \u00een 1. Astfel, atunci c\u00e2nd lucr\u0103m cu memorie flash, adesea trebuie s\u0103 aliniem structurile de date la limita blocului de \u0219tergere.<br \/>\nScrierea \u00een NOR Flash:<\/li>\n<\/ol>\n<p><\/p>\n<p>Date binare<\/p>\n<p><strong>A fost<\/strong><br \/>\n<code>01010101<\/code><\/p>\n<p><strong>Am scris<\/strong><br \/>\n<code>00001111<\/code><\/p>\n<p><strong>A devenit<\/strong><br \/>\n<code>00000101<\/code><\/p>\n<p><\/p>\n<p>Jurnalul \u00eensu\u0219i reprezint\u0103 o secven\u021b\u0103 de \u00eenregistr\u0103ri de lungime variabil\u0103. Lungimea tipic\u0103 a unei \u00eenregistr\u0103ri este de aproximativ 30 de octe\u021bi (de\u0219i uneori pot ap\u0103rea \u00eenregistr\u0103ri cu lungimi de c\u00e2\u021biva kilobi\u021bi). <em>\u00cen acest caz, lucr\u0103m cu ele pur \u0219i simplu ca cu un set de octe\u021bi, dar, dac\u0103 sunte\u021bi interesat, \u00een interiorul \u00eenregistr\u0103rilor se folose\u0219te CBOR.<\/em><\/p>\n<p><\/p>\n<p>Pe l\u00e2ng\u0103 jurnal, trebuie s\u0103 p\u0103str\u0103m anumite informa\u021bii \u201ede configurare\u201d, at\u00e2t actualizabile, c\u00e2t \u0219i nu: un anumit ID al dispozitivului, calibrarea senzorilor, un semn \u201edispozitivul este temporar oprit\u201d, etc.<br \/>\nAceast\u0103 informa\u021bie const\u0103 \u00eentr-un set de \u00eenregistr\u0103ri cheie-valoare, de asemenea stocat\u0103 \u00een CBOR. Aceast\u0103 informa\u021bie nu este foarte voluminoas\u0103 (maxim c\u00e2\u021biva kilobi\u021bi), fiind actualizat\u0103 rar.<br \/>\n\u00cen continuare, o vom numi context.<\/p>\n<p><\/p>\n<p>Dac\u0103 ne amintim de unde a \u00eenceput acest articol, este foarte important s\u0103 ne asigur\u0103m c\u0103 stocarea datelor este fiabil\u0103 \u0219i, pe c\u00e2t posibil, func\u021bionarea continu\u0103 chiar \u0219i \u00een cazul de defecte hardware\/\u00eent\u00e2rzieri de date.<\/p>\n<p><\/p>\n<p>Ce surse de probleme putem lua \u00een considerare?<\/p>\n<p><\/p>\n<ul>\n<li>Deconectarea sursei de alimentare \u00een timpul opera\u021biunilor write\/erase. Aceasta face parte din \u201enu este nicio sc\u0103pare \u00eempotriva unei for\u021be mai mari\u201d.<br \/>\nInforma\u021bia din <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\">discu\u021bii<\/a><\/noindex> pe stackexchange: c\u00e2nd se deconecteaz\u0103 sursa de alimentare \u00een timpul lucrului cu flash, at\u00e2t erase (setarea la 1), c\u00e2t \u0219i write (setarea la 0) duc la un comportament nedefinit: datele pot fi scrise, scrise par\u021bial (de exemplu, am trimis 10 octe\u021bi\/80 de bi\u021bi, iar am reu\u0219it s\u0103 scriu doar 45 de bi\u021bi), nu se exclude nici faptul c\u0103 unele bi\u021bi pot fi \u00een \u201estare intermediar\u0103\u201d (citirea poate oferi fie 0, fie 1);<\/li>\n<li>Erori ale memoriei flash \u00eens\u0103\u0219i.<br \/>\nBER, de\u0219i foarte sc\u0103zut, nu poate fi egal cu zero;<\/li>\n<li>Erori pe magistral\u0103<br \/>\nDatele transmise prin SPI nu sunt protejate \u00een niciun fel, iar at\u00e2t erorile unice de bit, c\u00e2t \u0219i erorile de sincronizare \u2014 pierderi sau inser\u021bii de bi\u021bi (ceea ce duce la distorsiuni masive ale datelor) se pot \u00eent\u00e2mpla;<\/li>\n<li>Alte erori\/falamente<br \/>\nErorile din cod, \u201eb\u0103taia de joc\u201d Raspberry, interven\u021bia extraterestrilor\u2026<\/li>\n<\/ul>\n<p><\/p>\n<p>Am formulat cerin\u021bele pe care, \u00een opinia mea, este necesar s\u0103 le \u00eendeplinim pentru a asigura fiabilitatea:<\/p>\n<p><\/p>\n<ul>\n<li>\u00eenregistr\u0103rile trebuie s\u0103 ajung\u0103 imediat \u00een memoria flash, nu se ia \u00een considerare scrierea \u00eent\u00e2rziat\u0103; - dac\u0103 apare o eroare, aceasta trebuie detectat\u0103 \u0219i gestionat\u0103 c\u00e2t mai devreme; - sistemul trebuie, dac\u0103 este posibil, s\u0103 \u00ee\u0219i restaureze func\u021bionarea dup\u0103 erori.<br \/>\n<em>(exemplu din via\u021b\u0103 \u201ecum nu ar trebui s\u0103 fie\u201d, cu care, cred, to\u021bi s-au confruntat: dup\u0103 o repornire de urgen\u021b\u0103, sistemul de fi\u0219iere s-a \u201estricat\u201d \u0219i sistemul de operare nu porne\u0219te)<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"idei-podhody-razmyshleniya\">Idei, abord\u0103ri, reflec\u021bii<\/h2>\n<p><\/p>\n<p>C\u00e2nd am \u00eenceput s\u0103 m\u0103 g\u00e2ndesc la aceast\u0103 sarcin\u0103, o mul\u021bime de idei \u00eemi treceau prin minte, de exemplu:<\/p>\n<p><\/p>\n<ul>\n<li>a folosi compresia datelor;<\/li>\n<li>a utiliza structuri de date ingenioase, de exemplu, a stoca titlurile \u00eenregistr\u0103rilor separat de \u00eenregistr\u0103rile \u00een sine, astfel \u00eenc\u00e2t, \u00een cazul unei erori \u00eentr-o \u00eenregistrare, s\u0103 se poat\u0103 citi f\u0103r\u0103 probleme celelalte;<\/li>\n<li>a folosi c\u00e2mpuri de bi\u021bi pentru a controla finalizarea \u00eenregistr\u0103rii \u00een caz de \u00eentrerupere a aliment\u0103rii;<\/li>\n<li>a stoca adrese de control pentru tot \u0219i toate;<\/li>\n<li>a folosi o varietate de codare rezistent\u0103 la interferen\u021be.<\/li>\n<\/ul>\n<p><\/p>\n<p>O parte din aceste idei au fost folosite, iar de parte s-a decis renun\u021barea. S\u0103 le lu\u0103m pe r\u00e2nd.<\/p>\n<p><\/p>\n<h3 id=\"szhatie-dannyh\">Compresia datelor<\/h3>\n<p><\/p>\n<p>Evenimentele \u00een sine, pe care le \u00eenregistr\u0103m \u00een jurnal, sunt destul de uniforme \u0219i repetitive (\u201eam aruncat o moned\u0103 de 5 lei\u201d, \u201eam ap\u0103sat pe butonul de restituire a restului\u201d, \u2026). A\u0219adar, comprimarea ar trebui s\u0103 fie suficient de eficient\u0103.<\/p>\n<p><\/p>\n<p>Costurile de compresie sunt nesemnificative (procesorul nostru este destul de puternic, chiar \u0219i pe primul Pi era un nucleu cu o frecven\u021b\u0103 de 700 MHz, iar pe modelele actuale mai multe nuclee cu frecven\u021be de peste un gigahertz), viteza de schimb cu stocarea este sc\u0103zut\u0103 (c\u00e2teva megabytes pe secund\u0103), dimensiunea \u00eenregistr\u0103rilor nu este mare. \u00cen general, dac\u0103 compresia va avea un impact asupra performan\u021bei, acesta va fi doar pozitiv <em>(absolut necritic, doar constat)<\/em>. Avem un Linux obi\u0219nuit, nu un embedded adev\u0103rat, a\u0219a c\u0103 implementarea nu ar trebui s\u0103 necesite multe eforturi (este suficient s\u0103 leg\u0103m biblioteca \u0219i s\u0103 folosim c\u00e2teva func\u021bii din ea).<\/p>\n<p><\/p>\n<p>A fost preluat un fragment de log de la un dispozitiv func\u021bional (1.7Mb, 70 de mii de \u00eenregistr\u0103ri) \u0219i, pentru \u00eenceput, a fost verificat pentru comprimabilitate folosind gzip, lz4, lzop, bzip2, xz, zstd disponibile pe computer.<\/p>\n<p><\/p>\n<ul>\n<li>gzip, xz, zstd au ar\u0103tat rezultate apropiate (40Kb).<br \/>\nM-a surprins c\u0103 xz, care este popular, a avut performan\u021be la nivelul gzip sau zstd;<\/li>\n<li>lzip cu set\u0103rile implicite a oferit un rezultat pu\u021bin mai slab;<\/li>\n<li>lz4 \u0219i lzop au ar\u0103tat rezultatele nu foarte bune (150Kb);<\/li>\n<li>bzip2 a ar\u0103tat un rezultat surprinz\u0103tor de bun (18Kb).<\/li>\n<\/ul>\n<p><\/p>\n<p>A\u0219adar, datele se comprim\u0103 foarte bine.<br \/>\nDeci, (dac\u0103 nu g\u0103sim defecte fatale) comprimarea este posibil\u0103! Pur \u0219i simplu pentru c\u0103 se vor potrivi mai multe date pe aceea\u0219i unitate flash.<\/p>\n<p><\/p>\n<p>Hai s\u0103 ne g\u00e2ndim la dezavantaje.<\/p>\n<p><\/p>\n<p>Prima problem\u0103: am convenit deja c\u0103 fiecare \u00eenregistrare trebuie s\u0103 ajung\u0103 imediat pe flash. De obicei, arhivatorul colecteaz\u0103 date din fluxul de intrare p\u00e2n\u0103 c\u00e2nd decide c\u0103 este momentul s\u0103 scrie \u00een ie\u0219ire. Noi trebuie s\u0103 ob\u021binem imediat un bloc de date comprimate \u0219i s\u0103-l salv\u0103m \u00een memoria nevolatil\u0103.<\/p>\n<p><\/p>\n<p>V\u0103d trei solu\u021bii:<\/p>\n<p><\/p>\n<ol>\n<li>A comprima fiecare \u00eenregistrare folosind compresia prin dic\u021bionar \u00een loc de algoritmii discutati mai sus.<br \/>\nO variant\u0103 func\u021bional\u0103, dar nu \u00eemi place. Pentru a asigura un nivel decent de comprimare, dictionarul trebuie s\u0103 fie \u201eadaptat\u201d la datele specifice, orice modificare va duce la o c\u0103dere catastrofal\u0103 a nivelului de comprimare. Da, problema este rezolvat\u0103 prin crearea unei noi versiuni a dictionarului, dar aceasta este o durere de cap \u2013 va trebui s\u0103 p\u0103str\u0103m toate versiunile dictionarului; \u00een fiecare \u00eenregistrare va trebui s\u0103 specific\u0103m cu ce versiune a dictionarului a fost comprimat\u0103\u2026<\/li>\n<li>A comprima fiecare \u00eenregistrare cu algoritmi \u201eclasic\u201d, dar independent de alte \u00eenregistr\u0103ri.<br \/>\nAlgoritmii de compresie considera\u021bi nu sunt proiecta\u021bi pentru a lucra cu \u00eenregistr\u0103ri de aceast\u0103 dimensiune (c\u00e2teva zeci de octe\u021bi), coeficientul de comprimare va fi evident sub 1 (adic\u0103 o cre\u0219tere a volumului de date \u00een loc de comprimare);<\/li>\n<li>A efectua un FLUSH dup\u0103 fiecare \u00eenregistrare.<br \/>\n\u00cen multe biblioteci de compresie exist\u0103 suport pentru FLUSH. Aceasta este o comand\u0103 (sau un parametru pentru procedura de compresie) care, odat\u0103 primit\u0103, face ca arhivatorul s\u0103 formeze un flux comprimat astfel \u00eenc\u00e2t s\u0103 se poat\u0103 recupera <strong>tot<\/strong> date necomprimate care au fost deja primite. O analogie <code>sync<\/code> \u00een sistemele de fi\u0219iere sau <code>Configurarea stivei (iStack)<\/code> \u00een sql.<br \/>\nEste important c\u0103 opera\u021biunile ulterioare de comprimare vor putea utiliza dic\u021bionarul acumulat \u0219i gradul de compresie nu va suferi at\u00e2t de mult ca \u00een varianta precedent\u0103.<\/li>\n<\/ol>\n<p><\/p>\n<p>Cred c\u0103 este evident c\u0103 am ales a treia variant\u0103, s\u0103 ne oprim asupra ei \u00een detaliu.<\/p>\n<p><\/p>\n<p>Am g\u0103sit <noindex><a rel=\"nofollow\" href=\"https:\/\/www.bolet.org\/~pornin\/deflate-flush.html\">un articol excelent<\/a><\/noindex> despre FLUSH \u00een zlib.<\/p>\n<p><\/p>\n<p>Am realizat un test, inspirat de articol, am luat 70 de mii de \u00eenregistr\u0103ri din jurnal de pe un dispozitiv real, cu dimensiunea paginii de 60KB <em>(la dimensiunea paginii vom reveni)<\/em> am ob\u021binut:<\/p>\n<p><\/p>\n<p>Datele originale<br \/>\nComprimare gzip -9 (f\u0103r\u0103 FLUSH)<br \/>\nzlib cu Z_PARTIAL_FLUSH<br \/>\nzlib cu Z_SYNC_FLUSH<\/p>\n<p><strong>Volum, KB<\/strong><br \/>\n1692<br \/>\n40<br \/>\n352<br \/>\n604<\/p>\n<p><\/p>\n<p>La prima vedere, costul impus de FLUSH pare excesiv de mare, dar, de fapt, avem o alegere limitat\u0103 \u2014 fie s\u0103 nu comprim\u0103m deloc, fie s\u0103 comprim\u0103m (\u0219i destul de eficient) cu FLUSH. Nu trebuie s\u0103 uit\u0103m c\u0103 avem 70 de mii de \u00eenregistr\u0103ri, iar redundan\u021ba adus\u0103 de Z_PARTIAL_FLUSH este de doar 4-5 octe\u021bi pe \u00eenregistrare. Iar coeficientul de compresie s-a dovedit a fi aproape 5:1, ceea ce reprezint\u0103 un rezultat excelent.<\/p>\n<p>\n<b class=\"spoiler_title\">Poate p\u0103rea surprinz\u0103tor, dar de fapt Z_SYNC_FLUSH - este o metod\u0103 mai eficient\u0103 de a face FLUSH<\/b><\/p>\n<p>\u00cen cazul utiliz\u0103rii Z_SYNC_FLUSH, cei patru octe\u021bi finali ai fiec\u0103rei \u00eenregistr\u0103ri vor fi \u00eentotdeauna 0x00, 0x00, 0xff, 0xff. Iar dac\u0103 \u00eei cunoa\u0219tem \u2014 atunci putem s\u0103 nu-i stoc\u0103m, astfel \u00eenc\u00e2t dimensiunea final\u0103 ajunge la doar 324KB.<\/p>\n<p><\/p>\n<p>\u00cen articolul la care fac referire, exist\u0103 o explica\u021bie:<\/p>\n<p><\/p>\n<blockquote><p>Un nou bloc de tip 0 cu con\u021binuturi goale este ad\u0103ugat.<\/p>\n<p>Un bloc de tip 0 cu con\u021binuturi goale const\u0103 din:<\/p>\n<ul>\n<li>antetul blocului cu trei bi\u021bi;<\/li>\n<li>0 p\u00e2n\u0103 la 7 bi\u021bi egali cu zero, pentru a atinge alinierea pe octe\u021bi;<\/li>\n<li>secven\u021ba de patru octe\u021bi 00 00 FF FF.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Dup\u0103 cum se poate observa, \u00een ultimul bloc \u00eenainte de cei 4 octe\u021bi exist\u0103 \u00eentre 3 \u0219i 10 bi\u021bi nuli. Totu\u0219i, practica a ar\u0103tat c\u0103 bi\u021bii nuli sunt de fapt minimum 10.<\/p>\n<p><\/p>\n<p>Se pare c\u0103 astfel de blocuri de date de dimensiuni reduse sunt \u00een mod obi\u0219nuit (\u00eentotdeauna?) codificate folosind un bloc de tip 1 (bloc fix), care se \u00eencheie \u00eentotdeauna cu 7 bi\u021bi nuli, ob\u021bin\u00e2nd astfel 10-17 bi\u021bi nuli garantat (iar ceilal\u021bi vor fi nuli cu o probabilitate de aproximativ 50%).<\/p>\n<p><\/p>\n<p>A\u0219adar, la datele de test \u00een 100% din cazuri \u00eenainte de 0x00, 0x00, 0xff, 0xff exist\u0103 un octet nul, iar \u00een mai mult de o treime din cazuri \u2014 dou\u0103 octe\u021bi nuli <em>(poate c\u0103 este din cauza c\u0103 folosesc CBOR binar, iar la utilizarea JSON-ului text, blocuri de tip 2 \u2014 bloc dinamic, ar ap\u0103rea mai des, \u0219i astfel ar ap\u0103rea blocuri f\u0103r\u0103 octe\u021bi nuli suplimentari \u00eenainte de 0x00, 0x00, 0xff, 0xff)<\/em>.<\/p>\n<p><\/p>\n<p>\u00cen total, cu datele de test disponibile, se poate realiza o compresie de mai pu\u021bin de 250KB de date comprimate.<\/p>\n<p><\/p>\n<p>Se pot economisi \u0219i ceva mai multe resurse, jongl\u00e2nd cu bi\u021bii: acum ignor\u0103m existen\u021ba mai multor bi\u021bi nul la sf\u00e2r\u0219itul blocului, mai mul\u021bi bi\u021bi la \u00eenceputul blocului nu se schimb\u0103 de asemenea...<br \/>\nDar atunci am luat decizia ferm\u0103 de a m\u0103 opri, altfel, cu acest ritm, a\u0219 putea ajunge s\u0103 dezvolt propriul meu arhivator.<\/p>\n<p><\/p>\n<p>\u00cen total, din datele mele de test am ob\u021binut 3-4 bi\u021bi pe scriere, raportul de compresie a fost mai mare de 6:1. S\u0103 fiu sincer: nu m-am a\u0219teptat la un astfel de rezultat; din punctul meu de vedere, tot ce e mai bun de 2:1 este deja un rezultat care justific\u0103 utilizarea compresiei.<\/p>\n<p><\/p>\n<p>Totul este excelent, dar zlib (deflate) r\u0103m\u00e2ne totu\u0219i un algoritm de compresie arhaic \u0219i pu\u021bin \u00eenvechit. Deja, faptul c\u0103 se folosesc ultimele 32KB din fluxul de date necomprimate ca dic\u021bionar pare ciudat \u00een zilele noastre (adic\u0103, dac\u0103 un bloc de date este foarte similar cu ceea ce a fost \u00een fluxul de intrare acum 40KB, acesta va \u00eencepe s\u0103 fie arhivat din nou, \u0219i nu va face referire la intrarea trecut\u0103). \u00cen arhivatoarele moderne, dimensiunea dic\u021bionarului este adesea m\u0103surat\u0103 \u00een megabytes, nu \u00een kilobytes.<\/p>\n<p><\/p>\n<p>A\u0219a c\u0103 continu\u0103m mini-investigarea noastr\u0103 asupra arhivatorilor.<\/p>\n<p><\/p>\n<p>Urm\u0103torul testat a fost bzip2 (\u00eemi amintesc, f\u0103r\u0103 FLUSH a ar\u0103tat o rat\u0103 de compresie fantastic\u0103, aproape 100:1). Din p\u0103cate, cu FLUSH a avut o performan\u021b\u0103 foarte slab\u0103, dimensiunea datelor comprimate s-a dovedit a fi mai mare dec\u00e2t cea a datelor necomprimate.<\/p>\n<p>\n<b class=\"spoiler_title\">Presupunerile mele cu privire la motivele e\u0219ecului<\/b><\/p>\n<p>Libbz2 ofer\u0103 doar o op\u021biune de flush, care pare s\u0103 cure\u021be dic\u021bionarul (analog cu Z_FULL_FLUSH \u00een zlib), \u0219i nu poate fi vorba despre o compresie eficient\u0103 dup\u0103 asta.<\/p>\n<p><\/p>\n<p>\u0218i \u00een cele din urm\u0103, am testat zstd. \u00cen func\u021bie de parametri, comprim\u0103 fie la nivelul gzip, dar mult mai repede, fie mai bine dec\u00e2t gzip.<\/p>\n<p><\/p>\n<p>Din p\u0103cate, cu FLUSH a ar\u0103tat \u201enu prea bine\u201d: dimensiunea datelor comprimate a fost de aproximativ 700KB.<\/p>\n<p><\/p>\n<p>Eu <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/facebook\/zstd\/issues\/900\">am pus o \u00eentrebare<\/a><\/noindex> pe pagina proiectului pe github, am primit r\u0103spunsul c\u0103 ar trebui s\u0103 ne a\u0219tept\u0103m la p\u00e2n\u0103 la 10 bi\u021bi de date de overhead pentru fiecare bloc de date comprimate, ceea ce este aproape de rezultatele ob\u021binute; deflate nu poate fi dep\u0103\u0219it sub nici o form\u0103.<\/p>\n<p><\/p>\n<p>Aici am decis s\u0103 m\u0103 opresc cu experimentele cu arhivatoarele (\u00eemi amintesc c\u0103 xz, lzip, lzo, lz4 nu s-au dovedit a fi foarte performante nici \u00een stadiul de testare f\u0103r\u0103 FLUSH, iar considerarea unor algoritmi de compresie mai exotici nu a fost o op\u021biune).<\/p>\n<p><\/p>\n<p>Ne \u00eentoarcem la problemele de arhivare.<\/p>\n<p><\/p>\n<p>A doua problem\u0103 (a\u0219a cum se spune, \u00een ordinea numerelor, nu dup\u0103 semnifica\u021bie) este c\u0103 datele comprimate reprezint\u0103 un singur flux, \u00een care se fac trimitere constant\u0103 la segmentele anterioare. Astfel, \u00een cazul deterior\u0103rii unei sec\u021biuni a datelor comprimate, nu doar blocul asociat de date necomprimate va fi pierdut, ci \u0219i toate celelalte ulterioare.<\/p>\n<p><\/p>\n<p>Exist\u0103 abord\u0103ri pentru a rezolva aceast\u0103 problem\u0103:<\/p>\n<p><\/p>\n<ol>\n<li>Avertizarea cu privire la apari\u021bia problemei \u2014 ad\u0103ugarea de redundan\u021b\u0103 \u00een datele comprimate, care va permite detectarea \u0219i corectarea erorilor; despre aceasta vom discuta mai t\u00e2rziu;<\/li>\n<li>Minimizarea consecin\u021belor \u00een cazul apari\u021biei problemei<br \/>\nAm men\u021bionat anterior c\u0103 fiecare bloc de date poate fi comprimat independent, astfel \u00eenc\u00e2t problema s\u0103 dispar\u0103 de la sine (deteriorarea datelor unui bloc va conduce la pierderea doar a datelor acelui bloc). Totu\u0219i, acesta este un caz extrem, \u00een care comprimarea datelor va fi ineficient\u0103. Extremul opus: utilizarea \u00eentregii noastre cip pentru arhivare ca un singur tot, ceea ce ne va oferi o comprimare excelent\u0103, dar consecin\u021be catastrofale \u00een cazul deterior\u0103rii datelor.<br \/>\n<em>Da, este nevoie de un compromis din punct de vedere al fiabilit\u0103\u021bii. Dar trebuie s\u0103 \u021binem cont c\u0103 dezvolt\u0103m un format de stocare a datelor pentru memorie nevolatil\u0103 cu un BER extrem de sc\u0103zut \u0219i o durat\u0103 de via\u021b\u0103 a datelor declarat\u0103 de 20 de ani.<\/em><\/li>\n<\/ol>\n<p><\/p>\n<p>\u00cen timpul experimentelor, am descoperit c\u0103 pierderile notabile la nivelul comprim\u0103rii \u00eencep cu blocuri de date comprimate de dimensiuni mai mici de 10Kb.<br \/>\nA fost men\u021bionat anterior c\u0103 memoria utilizat\u0103 are o organizare pe pagini, nu v\u0103d motive pentru care nu ar trebui s\u0103 folosim coresponden\u021ba \u201eo pagin\u0103 - un bloc de date comprimate\u201d.<\/p>\n<p><\/p>\n<p>Astfel, dimensiunea minim\u0103 rezonabil\u0103 a unei pagini este de 16Kb (cu un buffer pentru informa\u021biile de control). Totu\u0219i, o dimensiune at\u00e2t de mic\u0103 a paginii impune restric\u021bii semnificative asupra dimensiunii maxime a unui \u00eenregistr\u0103ri.<\/p>\n<p><\/p>\n<p>De\u0219i nu prev\u0103d \u00eenregistr\u0103ri mai mari de un kilobyte \u00een form\u0103 comprimat\u0103, am decis s\u0103 folosesc pagini de dimensiune 32Kb (adic\u0103 un total de 128 de pagini pe cip).<\/p>\n<p><\/p>\n<p><strong>Rezumat:<\/strong><\/p>\n<p><\/p>\n<ul>\n<li>Datele sunt stocate comprimate cu zlib (deflate);<\/li>\n<li>Pentru fiecare \u00eenregistrare set\u0103m Z_SYNC_FLUSH;<\/li>\n<li>La fiecare \u00eenregistrare comprimat\u0103, t\u0103iem byte-urile finale <em>(de exemplu, 0x00, 0x00, 0xff, 0xff)<\/em>; \u00een antet indic\u0103m c\u00e2\u021bi byte am t\u0103iat;<\/li>\n<li>Datele sunt stocate \u00een pagini de 32K; \u00een interiorul paginii exist\u0103 un flux continuu de date comprimate; pentru fiecare pagin\u0103, comprimarea \u00eencepe din nou.<\/li>\n<\/ul>\n<p><\/p>\n<p>\u0218i, \u00eenainte de a \u00eencheia procesul de comprimare, a\u0219 dori s\u0103 subliniez c\u0103 datele comprimate rezult\u0103 \u00een doar c\u00e2\u021biva bi\u021bi pe scriere, a\u0219a c\u0103 este extrem de important s\u0103 nu umfl\u0103m informa\u021biile de control, fiecare bit conteaz\u0103.<\/p>\n<p><\/p>\n<h3 id=\"hranenie-zagolovkov-dannyh\">Stocarea header-elor de date<\/h3>\n<p><\/p>\n<p>Deoarece avem \u00eenregistr\u0103ri de lungime variabil\u0103, trebuie s\u0103 g\u0103sim o modalitate de a determina plasarea\/limitele \u00eenregistr\u0103rilor.<\/p>\n<p><\/p>\n<p>\u0218tiu trei abord\u0103ri:<\/p>\n<p><\/p>\n<ol>\n<li>Toate \u00eenregistr\u0103rile sunt stocate \u00eentr-un flux continuu, mai \u00eent\u00e2i se afl\u0103 header-ul \u00eenregistr\u0103rii, care con\u021bine lungimea, urmat de \u00eenregistrare.<br \/>\n\u00cen aceast\u0103 variant\u0103, at\u00e2t header-urile, c\u00e2t \u0219i datele pot avea lungime variabil\u0103.<br \/>\nPractic, ob\u021binem o list\u0103 simplu legat\u0103, utilizat\u0103 frecvent;<\/li>\n<li>Header-urile \u0219i \u00eenregistr\u0103rile \u00een sine sunt stocate \u00een fluxuri separate.<br \/>\nFolosind header-uri de lungime constant\u0103, ne asigur\u0103m c\u0103 deteriorarea unui header nu afecteaz\u0103 celelalte.<br \/>\nAceast\u0103 abordare este utilizat\u0103, de exemplu, \u00een multe sisteme de fi\u0219iere;<\/li>\n<li>\u00cenregistr\u0103rile sunt stocate \u00eentr-un flux continuu, limita \u00eenregistr\u0103rii este determinat\u0103 de un anumit marker (simbol\/serie de simboluri care sunt interzise \u00een interiorul blocurilor de date). Dac\u0103 \u00eent\u00e2lnim un marker \u00een interiorul \u00eenregistr\u0103rii, \u00eel \u00eenlocuim cu o anumit\u0103 secven\u021b\u0103 (\u00eel escap\u0103m).<br \/>\nO astfel de abordare este utilizat\u0103, de exemplu, \u00een protocolul PPP.<\/li>\n<\/ol>\n<p><\/p>\n<p>Voi ilustra.<\/p>\n<p><\/p>\n<p>Variant\u0103 1:<br \/>\n<img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/e5a9676ca21eaca07e64ddf9fcd9f2eb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAici totul este foarte simplu: cunosc\u00e2nd lungimea \u00eenregistr\u0103rii putem calcula adresa urm\u0103torului header. Astfel, ne mi\u0219c\u0103m prin header-uri p\u00e2n\u0103 \u00eent\u00e2lnim o zon\u0103 umplut\u0103 cu 0xff (zona liber\u0103) sau sf\u00e2r\u0219itul paginii.<\/p>\n<p><\/p>\n<p>Variant\u0103 2:<br \/>\n<img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/4fed4860fb659ab6042be9aeeb5c041a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nDin cauza lungimii variabile a \u00eenregistr\u0103rii, nu putem spune din timp c\u00e2t de multe \u00eenregistr\u0103ri (\u0219i, prin urmare, titluri) ne vor fi necesare pe pagin\u0103. Putem distribui titlurile \u0219i datele pe pagini diferite, dar prefer un alt abordare: at\u00e2t titlurile (de dimensiuni constante) c\u00e2t \u0219i datele (de lungime variabil\u0103) sunt plasate pe o singur\u0103 pagin\u0103, \u00eens\u0103 titlurile (de dimensiuni constante) sunt la \u00eenceputul paginii, iar datele (de lungime variabil\u0103) sunt de la sf\u00e2r\u0219it. De \u00eendat\u0103 ce acestea \u201ese \u00eent\u00e2lnesc\u201d (spa\u021biul liber nu este suficient pentru o nou\u0103 \u00eenregistrare) \u2014 consider\u0103m c\u0103 aceast\u0103 pagin\u0103 este complet\u0103.<\/p>\n<p><\/p>\n<p>Variant\u0103 3:<br \/>\n<img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/dfb64009aa7781fcb8a47423ff4160c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNu este nevoie s\u0103 p\u0103str\u0103m \u00een antet lungimea sau alte informa\u021bii despre loca\u021bia datelor, este suficient s\u0103 avem marcatori care s\u0103 indice limitele \u00eenregistr\u0103rilor. Totu\u0219i, datele trebuie prelucrate la scriere\/citire.<br \/>\nCa marcator, a\u0219 folosi 0xff (cu care este umplut\u0103 pagina dup\u0103 \u0219tergere), astfel \u00eenc\u00e2t zona liber\u0103 nu va fi interpretat\u0103 ca date.<\/p>\n<p><\/p>\n<p>Tabel de compara\u021bie:<\/p>\n<p><\/p>\n<p>Op\u021biunea 1<br \/>\nOp\u021biunea 2<br \/>\nOp\u021biunea 3<\/p>\n<p><strong>Rezisten\u021b\u0103 la erori<\/strong><br \/>\n\u2014<br \/>\n+<br \/>\n+<\/p>\n<p><strong>Compactitate<\/strong><br \/>\n+<br \/>\n\u2014<br \/>\n+<\/p>\n<p><strong>Complexitate de implementare<\/strong><br \/>\n*<br \/>\n**<br \/>\n**<\/p>\n<p><\/p>\n<p>Op\u021biunea 1 are un dezavantaj fatal: dac\u0103 se deterioreaz\u0103 unul dintre antete, \u00eentreaga secven\u021b\u0103 ulterioar\u0103 se va distruge. Celelalte op\u021biuni permit recuperarea unei p\u0103r\u021bi din date chiar \u0219i \u00een cazul unor deterior\u0103ri majore.<br \/>\nDar aici este potrivit s\u0103 ne amintim c\u0103 am decis s\u0103 stoc\u0103m datele \u00eentr-o form\u0103 comprimat\u0103, a\u0219a c\u0103, oricum, pierdem toate datele pe pagin\u0103 dup\u0103 o \u201e\u00eenregistrare corupt\u0103\u201d, astfel c\u0103, de\u0219i \u00een tabel este un minus, nu-l lu\u0103m \u00een considerare.<\/p>\n<p><\/p>\n<p>Compactitate:<\/p>\n<p><\/p>\n<ul>\n<li>\u00een prima op\u021biune trebuie s\u0103 p\u0103str\u0103m \u00een antet doar lungimea, iar dac\u0103 folosim variabile de lungime \u00eentreag\u0103, \u00een cele mai multe cazuri putem folosi doar un byte;<\/li>\n<li>\u00een a doua op\u021biune trebuie s\u0103 p\u0103str\u0103m adresa de \u00eenceput \u0219i lungimea; \u00eenregistrarea trebuie s\u0103 fie de dimensiune fix\u0103, eu o estimez la 4 bytes pe \u00eenregistrare (dou\u0103 bytes pentru offset \u0219i dou\u0103 bytes pentru lungime);<\/li>\n<li>\u00een a treia op\u021biune este suficient un singur caracter pentru a indica \u00eenceputul \u00eenregistr\u0103rii, plus c\u0103 \u00eenregistrarea \u00een sine, din cauza escap\u0103rii, va cre\u0219te cu 1-2%. \u00cen general, o paritate estimativ\u0103 cu prima op\u021biune.<\/li>\n<\/ul>\n<p><\/p>\n<p>Ini\u021bial am considerat a doua op\u021biune ca fiind principal\u0103 (\u0219i chiar am scris o implementare). Am renun\u021bat la ea doar atunci c\u00e2nd am decis \u00een mod final s\u0103 folosesc compresia.<\/p>\n<p><\/p>\n<p><em>Poate c\u0103, c\u00e2ndva, voi folosi totu\u0219i o astfel de variant\u0103. De exemplu, dac\u0103 va trebui s\u0103 m\u0103 ocup de stocarea datelor pentru o nav\u0103 care circul\u0103 \u00eentre P\u0103m\u00e2nt \u0219i Marte \u2014 cerin\u021bele de fiabilitate sunt complet diferite, radia\u021bia cosmic\u0103, ...<\/em><\/p>\n<p><\/p>\n<p>C\u00e2t despre a treia op\u021biune: i-am dat dou\u0103 stele pentru complexitatea de implementare pur \u0219i simplu pentru c\u0103 nu-mi place s\u0103 m\u0103 ocup cu escaparea, modificarea lungimii \u00een proces etc. Da, poate c\u0103 este p\u0103rtinitor, dar codul trebuie s\u0103-l scriu eu \u2014 de ce s\u0103 m\u0103 oblig s\u0103 fac ceva ce nu-mi place.<\/p>\n<p><\/p>\n<p><strong>Rezumat:<\/strong> alegem varianta de stocare sub form\u0103 de \u0219iruri \u201etitlu cu lungime \u2014 date de lungime variabil\u0103\u201d din cauza eficien\u021bei \u0219i simplit\u0103\u021bii implement\u0103rii.<\/p>\n<p><\/p>\n<h3 id=\"ispolzovanie-bitovyh-poley-dlya-kontrolya-uspeshnosti-operaciy-zapisi\">Utilizarea c\u00e2mpurilor binare pentru a controla succesul opera\u021biunilor de scriere.<\/h3>\n<p><\/p>\n<p>\u00cenc\u0103 nu-mi amintesc unde am v\u0103zut ideea, dar arat\u0103 cam a\u0219a:<br \/>\nPentru fiecare \u00eenregistrare aloc\u0103m c\u00e2\u021biva bi\u021bi pentru stocarea flagurilor.<br \/>\n<em>Dup\u0103 cum am spus anterior, dup\u0103 erase to\u021bi bi\u021bii sunt umplu\u021bi cu 1, \u0219i putem schimba 1 \u00een 0, dar nu invers.<\/em> A\u0219adar, pentru \u201ef\u0103r\u0103 marcaj\u201d folosim 1, iar pentru \u201emarcaj stabilit\u201d \u2014 0.<\/p>\n<p><\/p>\n<p>Iat\u0103 cum ar putea ar\u0103ta plasarea unei \u00eenregistr\u0103ri de lungime variabil\u0103 \u00een flash:<\/p>\n<p><\/p>\n<ol>\n<li>Set\u0103m flagul \u201e\u00eenceperea scrierii lungimii\u201d;<\/li>\n<li>Scriem lungimea;<\/li>\n<li>Set\u0103m flagul \u201e\u00eenceperea scrierii datelor\u201d;<\/li>\n<li>Scriem datele;<\/li>\n<li>Set\u0103m flagul \u201escrierea s-a terminat\u201d.<\/li>\n<\/ol>\n<p><\/p>\n<p>\u00cen plus, vom avea un flag \u201ea ap\u0103rut o eroare\u201d, deci \u00een total 4 flaguri binare.<\/p>\n<p><\/p>\n<p>\u00cen acest caz, avem dou\u0103 st\u0103ri stabile \u201e1111\u201d - scrierea nu a \u00eenceput \u0219i \u201e1000\u201d - scrierea a avut succes; \u00een cazul unei \u00eentreruperi neprev\u0103zute a procesului de scriere, vom ob\u021bine st\u0103ri intermediare pe care le putem detecta \u0219i gestiona ulterior.<\/p>\n<p><\/p>\n<p>Abordarea este interesant\u0103, dar protejeaz\u0103 doar \u00eempotriva \u00eentreruperilor bruste de alimentare \u0219i a unor astfel de erori, ceea ce este important, dar nu este singura (\u0219i nici m\u0103car principala) cauz\u0103 a posibilelor defec\u021biuni.<\/p>\n<p><\/p>\n<p><strong>Rezumat:<\/strong> S\u0103 continu\u0103m c\u0103ut\u0103rile pentru o solu\u021bie bun\u0103.<\/p>\n<p><\/p>\n<h3 id=\"kontrolnye-summy\">Sumele de control.<\/h3>\n<p><\/p>\n<p>Sumele de control ofer\u0103 \u0219i ele posibilitatea de a ne asigura (cu o probabilitate suficient\u0103) c\u0103 citim exact ceea ce ar fi trebuit s\u0103 fie scris. \u0218i, spre deosebire de c\u00e2mpurile binare discutate mai sus, acestea func\u021bioneaz\u0103 \u00eentotdeauna.<\/p>\n<p><\/p>\n<p>Dac\u0103 analiz\u0103m lista poten\u021bialelor surse de probleme despre care am vorbit mai sus, suma de control poate detecta o eroare indiferent de sursa ei. <em>(cu excep\u021bia, poate, a extratere\u0219trilor r\u0103u inten\u021biona\u021bi - ace\u0219tia ar putea falsifica \u0219i suma de control)<\/em>.<\/p>\n<p><\/p>\n<p>A\u0219adar, dac\u0103 scopul nostru este s\u0103 verific\u0103m c\u0103 datele sunt intacte, sumele de control sunt o idee excelent\u0103.<\/p>\n<p><\/p>\n<p>Alegerea algoritmului de calcul a sumei de control nu a ridicat \u00eentreb\u0103ri - CRC. Pe de o parte, propriet\u0103\u021bile matematice permit capturarea 100% a unor tipuri de erori, pe de alt\u0103 parte - pe datele aleatorii, acest algoritm prezint\u0103 de obicei o probabilitate a coliziunilor nu semnificativ mai mare dec\u00e2t limita teoretic\u0103. <img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/bf9cca3564db7d9d03d3ce49642d3a71.jpg\" style=\"display:block;margin: 0 auto;\" \/>De\u0219i aceasta nu este cea mai rapid\u0103 algoritm\u0103, nici \u00eentotdeauna minim\u0103 \u00een ceea ce prive\u0219te num\u0103rul coliziunilor, are o calitate foarte important\u0103: \u00een testele pe care le-am \u00eent\u00e2lnit, nu am g\u0103sit tipare \u00een care s\u0103 e\u0219ueze vizibil. Stabilitatea este calitatea principal\u0103 \u00een acest caz.<\/p>\n<p><\/p>\n<p>Exemplu de studiu amplu: <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo.html\">partea 1<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo2.html\">partea 2<\/a><\/noindex> <em>(linkuri c\u0103tre narod.ru, \u00eemi pare r\u0103u)<\/em>.<\/p>\n<p><\/p>\n<p>Cu toate acestea, sarcina de a alege o sum\u0103 de control nu este finalizat\u0103, CRC fiind o \u00eentreag\u0103 familie de sume de control. Trebuie s\u0103 ne decidem asupra lungimii \u0219i apoi s\u0103 alegem un polinom.<\/p>\n<p><\/p>\n<p>Alegerea lungimii unei sume de control nu este o \u00eentrebare at\u00e2t de simpl\u0103 pe c\u00e2t pare la prima vedere.<\/p>\n<p><\/p>\n<p>S\u0103 ilustrez:<br \/>\nS\u0103 presupunem c\u0103 avem o probabilitate de eroare \u00een fiecare byte <img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/89a9be2edf2a43cc9115eba37fa02fc8.jpg\" style=\"display:block;margin: 0 auto;\" \/> \u0219i o sum\u0103 de control ideal\u0103, s\u0103 calcul\u0103m num\u0103rul mediu de erori la un milion de \u00eenregistr\u0103ri:<\/p>\n<p><\/p>\n<p>Date, byte<br \/>\nSum\u0103 de control, byte<br \/>\nErori nedetectate<br \/>\nDetect\u0103ri false de erori<br \/>\nTotal erori false<\/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>P\u0103rea simplu \u2014 alege \u00een func\u021bie de lungimea datelor protejate lungimea sumei de control cu un minim de erori false \u2014 \u0219i am terminat.<\/p>\n<p><\/p>\n<p>Cu toate acestea, cu sumele de control scurte apare o problem\u0103: de\u0219i acestea detecteaz\u0103 bine erorile de bit unice, pot cu o mare probabilitate s\u0103 accepte date complet aleatorii ca fiind corecte. Pe Habr a fost deja un articol care descria <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/428746\/\">problema \u00een via\u021ba real\u0103<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Prin urmare, pentru a face coinciden\u021ba aleatoare a sumei de control practic imposibil\u0103, trebuie s\u0103 folosim sume de control de 32 de bi\u021bi sau mai mult. <em>(pentru lungimi de peste 64 de bi\u021bi, se folosesc de obicei func\u021bii de hash criptografice)<\/em>.<\/p>\n<p><\/p>\n<p>De\u0219i am scris anterior c\u0103 trebuie s\u0103 economisim spa\u021biu cu orice pre\u021b, totu\u0219i vom folosi o sum\u0103 de control de 32 de bi\u021bi (16 bi\u021bi sunt pu\u021bini, probabilitatea coliziunii fiind mai mare de 0.01%; iar 24 de bi\u021bi, cum se spune, nu sunt nici aici, nici acolo).<\/p>\n<p><\/p>\n<p>Aici poate ap\u0103rea o obiec\u021bie: am economisit fiecare byte \u00een alegerea compresiei pentru a justifica acum livrarea a 4 bytes imediat? Nu era mai bine s\u0103 nu comprim\u0103m \u0219i s\u0103 nu ad\u0103ug\u0103m suma de control? Desigur c\u0103 nu, absen\u021ba compresiei <em>nu \u00eenseamn\u0103<\/em>, c\u0103 verificarea integrit\u0103\u021bii nu este necesar\u0103.<\/p>\n<p><\/p>\n<p>\u00cen privin\u021ba alegerii polinomului, nu vom reinventa roata, ci vom lua un popular CRC-32C.<br \/>\nAcest cod detecteaz\u0103 6 erori de bi\u021bi \u00een pachete de p\u00e2n\u0103 la 22 de octe\u021bi (probabil cel mai frecvent caz pentru noi), 4 erori de bi\u021bi \u00een pachete de p\u00e2n\u0103 la 655 de octe\u021bi (de asemenea, un caz frecvent pentru noi), 2 sau orice num\u0103r impar de erori de bi\u021bi \u00een pachete de orice lungime rezonabil\u0103.<\/p>\n<p>\n<b class=\"spoiler_title\">Dac\u0103 pe cineva \u00eel intereseaz\u0103 detaliile<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cyclic_redundancy_check\">Articolul Wikipedia<\/a><\/noindex> despre CRC.<\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0x8f6e37a0_len.txt\">Parametrii codului crc-32c<\/a><\/noindex> pe <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/crc\/notes.html\">de pe site-ul lui Kupman<\/a><\/noindex> \u2014 probabil cel mai important specialist pe planet\u0103 \u00een domeniul CRC.<\/p>\n<p><\/p>\n<p>\u00cen <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/networks\/dsn02\/dsn02_koopman.pdf\">articolul s\u0103u<\/a><\/noindex> are <noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0xfa567d89_len.txt\">\u00eenc\u0103 un cod interesant<\/a><\/noindex>, care ofer\u0103 parametrii pu\u021bin mai buni pentru lungimile de pachete relevante pentru noi, dar nu am considerat c\u0103 diferen\u021ba este semnificativ\u0103, \u0219i m\u0103 consider suficient de competent pentru a alege un cod personalizat \u00een locul unuia standard bine cercetat.<\/p>\n<p><\/p>\n<p>De asemenea, av\u00e2nd \u00een vedere c\u0103 datele noastre sunt comprimate, apare \u00eentrebarea: ar trebui s\u0103 calcul\u0103m suma de control pentru date comprimate sau necomprimate?<\/p>\n<p><\/p>\n<p>Argumente \u201epentru\u201d calculul checksum-ului datelor necomprimate:<\/p>\n<p><\/p>\n<ul>\n<li>\u00een cele din urm\u0103 trebuie s\u0103 verific\u0103m integritatea stoc\u0103rii datelor \u2014 iat\u0103 cum o verific\u0103m direct (\u00een acest sens, vor fi verificate \u0219i eventualele erori \u00een implementarea compresiei\/decompresiei, deterior\u0103rile cauzate de memoria defect\u0103 etc.);<\/li>\n<li>algoritmul deflate din zlib are o implementare suficient de matur\u0103 \u0219i <em>nu ar trebui<\/em> se pr\u0103bu\u0219e\u0219te la \u201edate de intrare eronate\u201d, mai mult, adesea poate descoperi singur erorile din fluxul de intrare, reduc\u00e2nd probabilitatea total\u0103 de neidentificare a erorii (am efectuat un test prin inversarea unei singure bi\u021bi \u00eentr-o \u00eenregistrare scurt\u0103, zlib a descoperit eroarea \u00een aproximativ o treime din cazuri).<\/li>\n<\/ul>\n<p><\/p>\n<p>Argumente \u201e\u00eempotriva\u201d calculului checksum-ului datelor necomprimate:<\/p>\n<p><\/p>\n<ul>\n<li>CRC este \u201eascu\u021bit\u201d exact pentru erorile de bi\u021bi pu\u021bine, care sunt caracteristice memoriei flash (o eroare de bi\u021bi \u00een fluxul comprimat poate provoca o modificare masiv\u0103 a fluxului de ie\u0219ire, pe care, teoretic, putem \u201ecaptura\u201d o coliziune);<\/li>\n<li>nu mi se pare pl\u0103cut s\u0103 transmit decompresorului date posibil corupte, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cvedetails.com\/vulnerability-list\/vendor_id-72\/product_id-1820\/GNU-Zlib.html\">cine \u0219tie<\/a><\/noindex>, cum va reac\u021biona.<\/li>\n<\/ul>\n<p><\/p>\n<p>\u00cen acest proiect am decis s\u0103 m\u0103 abat de la practica acceptat\u0103 de a stoca suma de control pentru datele necomprimate.<\/p>\n<p><\/p>\n<p><strong>Rezumat:<\/strong> folosim CRC-32C, suma de control fiind calculat\u0103 pe baza datelor a\u0219a cum sunt ele scrise \u00een flash (dup\u0103 comprimare).<\/p>\n<p><\/p>\n<h3 id=\"izbytochnost\">Redundan\u021b\u0103<\/h3>\n<p><\/p>\n<p>Utilizarea cod\u0103rii redundante nu elimin\u0103, desigur, pierderea de date, totu\u0219i, poate reduce semnificativ (adesea cu multe ordini de m\u0103rime) probabilitatea unei pierderi ireparate de date.<\/p>\n<p><\/p>\n<p>Putem folosi diferite tipuri de redundan\u021b\u0103 pentru a corecta erorile.<br \/>\nCodurile Hamming pot corecta erorile unice de bit, codurile Reed-Solomon sunt simbolice, mai multe copii de date \u00eempreun\u0103 cu sumele de control sau codificarea de tip RAID-6 pot ajuta la recuperarea datelor chiar \u0219i \u00een cazul unor daune extinse.<br \/>\nLa \u00eenceput am fost predispus la utilizarea pe scar\u0103 larg\u0103 a cod\u0103rii rezistente la interferen\u021be, dar apoi am realizat c\u0103 trebuie mai \u00eent\u00e2i s\u0103 avem o idee despre ce tip de erori dorim s\u0103 ne protej\u0103m, \u0219i abia apoi s\u0103 alegem codificarea.<\/p>\n<p><\/p>\n<p>Am men\u021bionat anterior c\u0103 erorile trebuie identificate c\u00e2t mai repede posibil. \u00cen ce momente putem \u00eent\u00e2lni erori?<\/p>\n<p><\/p>\n<ol>\n<li>O \u00eenregistrare neterminat\u0103 (din diverse motive, alimentarea s-a oprit \u00een timpul scrierii, Raspberry s-a blocat, ...)<br \/>\nDin p\u0103cate, \u00een cazul unei astfel de erori, nu r\u0103m\u00e2ne dec\u00e2t s\u0103 ignor\u0103m \u00eenregistr\u0103rile invalide \u0219i s\u0103 consider\u0103m datele pierdute;<\/li>\n<li>Erori de scriere (din diverse motive, a fost scris \u00een memoria flash ceea ce nu a fost de fapt \u00eenregistrat)<br \/>\nAceste erori pot fi detectate imediat dac\u0103, dup\u0103 scriere, facem o citire de control;<\/li>\n<li>Deformarea datelor \u00een memorie \u00een timpul stoc\u0103rii;<\/li>\n<li>Erori de citire<br \/>\nPentru a corecta, este suficient \u00een cazul unei neconcordan\u021be a sumei de control s\u0103 repet\u0103m citirea de c\u00e2teva ori.<\/li>\n<\/ol>\n<p><\/p>\n<p>Adic\u0103, doar erorile de tip trei (degradarea spontan\u0103 a datelor \u00een timpul stoc\u0103rii) nu pot fi corectate f\u0103r\u0103 codificare rezistent\u0103 la interferen\u021be. Se crede c\u0103 astfel de erori sunt foarte pu\u021bin probabile.<\/p>\n<p><\/p>\n<p><strong>Rezumat:<\/strong> s-a decis abandonarea cod\u0103rii redundante, dar, dac\u0103 exploatarea va ar\u0103ta c\u0103 aceast\u0103 decizie este eronat\u0103, se va reveni asupra acestei chestiuni (cu statistica acumulat\u0103 pe defecte, care va permite alegerea celui mai optim tip de codificare).<\/p>\n<p><\/p>\n<h3 id=\"prochee\">Altele<\/h3>\n<p><\/p>\n<p>Desigur, formatul articolului nu permite justificarea fiec\u0103rui bit \u00een format <em>(\u0219i oricum mi-au epuizat for\u021bele)<\/em>, a\u0219a c\u0103 voi trece rapid prin c\u00e2teva aspecte care nu au fost discutate anterior.<\/p>\n<p><\/p>\n<ul>\n<li>S-a decis s\u0103 facem toate paginile \u201eegale\u201d<br \/>\nAsta \u00eenseamn\u0103 c\u0103 nu vor exista pagini speciale cu metadate, fluxuri separate etc., ci un singur flux care rescrie toate paginile pe r\u00e2nd.<br \/>\nAcest lucru asigur\u0103 uzura uniform\u0103 a paginilor, absen\u021ba unui singur punct de defec\u021biune \u0219i pur \u0219i simplu ne place;<\/li>\n<li>Este necesar s\u0103 se prevad\u0103 versiunea formatului.<br \/>\nUn format f\u0103r\u0103 num\u0103rul versiunii \u00een antet este r\u0103u!<br \/>\nEste suficient s\u0103 ad\u0103ug\u0103m \u00een antetul paginii un c\u00e2mp cu un anumit Magic Number (semn\u0103tur\u0103) care va indica versiunea formatului utilizat. <em>(nu cred c\u0103 \u00een practic\u0103 vor fi chiar zece)<\/em>;<\/li>\n<li>Utiliza\u021bi pentru \u00eenregistr\u0103ri (care sunt foarte multe) un antet de lungime variabil\u0103, \u00eencerc\u00e2nd \u00een cele mai multe cazuri s\u0103-l face\u021bi de lungime de 1 byte;<\/li>\n<li>Pentru codificarea lungimii antetului \u0219i a lungimii p\u0103r\u021bii t\u0103iate din \u00eenregistrarea comprimat\u0103, utiliza\u021bi coduri binare de lungime variabil\u0103.<\/li>\n<\/ul>\n<p><\/p>\n<p>M-a ajutat foarte mult <noindex><a rel=\"nofollow\" href=\"https:\/\/planetcalc.com\/2481\/\">generatorul online<\/a><\/noindex> de coduri Huffman. \u00cen doar c\u00e2teva minute am reu\u0219it s\u0103 g\u0103sesc codurile de lungime variabil\u0103 necesare.<\/p>\n<p><\/p>\n<h1 id=\"anchorformatanchoropisanie-formata-hraneniya-dannyh\"><noindex><a rel=\"nofollow\" name=\"format\"><\/a><\/noindex>Descrierea formatului de stocare a datelor<\/h1>\n<p><\/p>\n<h2 id=\"byte-order\">Ordinea octe\u021bilor<\/h2>\n<p><\/p>\n<p>C\u00e2mpurile de dimensiune mai mare de un byte sunt stocate \u00een format big-endian (ordine de byte de re\u021bea), adic\u0103 0x1234 este stocat ca 0x12, 0x34.<\/p>\n<p><\/p>\n<h2 id=\"delenie-na-stranicy\">\u00cemp\u0103r\u021birea \u00een pagini<\/h2>\n<p><\/p>\n<p>\u00centreaga memorie flash este \u00eemp\u0103r\u021bit\u0103 \u00een pagini de dimensiuni egale.<\/p>\n<p><\/p>\n<p>Dimensiunea paginii, \u00een mod implicit, este de 32KB, dar nu mai mult de 1\/4 din dimensiunea total\u0103 a cipului de memorie (pentru un cip de 4MB, se ob\u021bin 128 de pagini).<\/p>\n<p><\/p>\n<p>Fiecare pagin\u0103 stocheaz\u0103 date independent de altele (adic\u0103 datele unei pagini nu se refer\u0103 la datele altei pagini).<\/p>\n<p><\/p>\n<p>Toate paginile sunt numerotate \u00een ordine natural\u0103 (\u00een ordine cresc\u0103toare a adreselor), \u00eencep\u00e2nd cu num\u0103rul 0 (pagina zero \u00eencepe cu adresa 0, prima cu 32KB, a doua cu 64KB etc.)<\/p>\n<p><\/p>\n<p>Circuitul de memorie este folosit ca un buffer circular, adic\u0103 mai \u00eent\u00e2i scrierea se face pe pagina cu num\u0103rul 0, apoi pe pagina cu num\u0103rul 1, ..., c\u00e2nd umplem ultima pagin\u0103, \u00eencepe un nou ciclu \u0219i scrierea continu\u0103 de la pagina zero.<\/p>\n<p><\/p>\n<h2 id=\"vnutri-stranicy\">\u00cen interiorul paginii<\/h2>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/39b45f83dc46bb2fd7081ed2a0b638c9.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nLa \u00eenceputul paginii se afl\u0103 un header de 4 bytes, apoi checksum-ul header-ului (CRC-32C), urmate de \u00eenregistr\u0103ri \u00een formatul \u201eheader, date, checksum\u201d.<\/p>\n<p><\/p>\n<p>Antetul paginii (\u00een diagram\u0103 de culoare verde murdar) const\u0103 din:<\/p>\n<p><\/p>\n<ul>\n<li>un c\u00e2mp de 2 bytes Magic Number (care este \u0219i semnul versiunii formatului)<br \/>\npentru versiunea curent\u0103 a formatului, este considerat ca <code>0xed00 \u2295 num\u0103rul paginii<\/code>;<\/li>\n<li>un contor de dou\u0103 byte \u201eVersiunea paginii\u201d (num\u0103rul ciclului de rescriere a memoriei).<\/li>\n<\/ul>\n<p><\/p>\n<p>\u00cenregistr\u0103rile paginii sunt stocate \u00eentr-un format comprimat (folosind algoritmul deflate). Toate \u00eenregistr\u0103rile de pe o pagin\u0103 sunt comprimate \u00eentr-un singur flux (se folose\u0219te un dic\u021bionar comun), la fiecare pagin\u0103 nou\u0103, comprimarea \u00eencepe de la zero. Asta \u00eenseamn\u0103 c\u0103 pentru decomprimarea oric\u0103rei \u00eenregistr\u0103ri sunt necesare toate \u00eenregistr\u0103rile precedente de pe aceast\u0103 pagin\u0103 (\u0219i doar de pe aceasta).<\/p>\n<p><\/p>\n<p>Fiecare \u00eenregistrare va fi comprimat\u0103 cu flag-ul Z_SYNC_FLUSH, iar la sf\u00e2r\u0219itul fluxului comprimat se vor g\u0103si 4 bytes 0x00, 0x00, 0xff, 0xff, posibil preceda\u021bi de \u00eenc\u0103 unul sau dou\u0103 bytes zero.<br \/>\nAceast\u0103 secven\u021b\u0103 (de lungime 4, 5 sau 6 bytes) este eliminat\u0103 la scrierea \u00een memoria flash.<\/p>\n<p><\/p>\n<p>Antetul \u00eenregistr\u0103rii const\u0103 din 1, 2 sau 3 bytes, care con\u021bin:<\/p>\n<p><\/p>\n<ul>\n<li>un bit (T), care reprezint\u0103 tipul \u00eenregistr\u0103rii: 0 \u2014 context, 1 \u2014 jurnal;<\/li>\n<li>un c\u00e2mp de lungime variabil\u0103 (S) de la 1 la 7 bi\u021bi, care define\u0219te lungimea header-ului \u0219i \u201ecoada\u201d care trebuie ad\u0103ugat\u0103 la \u00eenregistrare pentru decompresie;<\/li>\n<li>lungimea \u00eenregistr\u0103rii (L).<\/li>\n<\/ul>\n<p><\/p>\n<p>Tabelul valorilor S:<\/p>\n<p><\/p>\n<p>S<br \/>\nLungimea antetului, bytes<br \/>\nEliminat la scriere, 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>Am \u00eencercat s\u0103 ilustrez, nu \u0219tiu c\u00e2t de clar a ie\u0219it:<br \/>\n<img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/8c9b739eec5af4429395c32b4ba4044a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nGalbenul reprezint\u0103 c\u00e2mpul T, alb c\u00e2mpul S, verde L (lungimea datelor comprimate \u00een bytes), albastrul \u2014 datele comprimate, ro\u0219u \u2014 bytes finale ale datelor comprimate care nu sunt scrise \u00een memoria flash.<\/p>\n<p><\/p>\n<p>Astfel, anteturile \u00eenregistr\u0103rilor cele mai comune (p\u00e2n\u0103 la 63+5 bytes \u00een form\u0103 comprimat\u0103) le putem scrie \u00eentr-un singur byte.<\/p>\n<p><\/p>\n<p>Dup\u0103 fiecare \u00eenregistrare se afl\u0103 o sum\u0103 de control CRC-32C, care are ca valoare ini\u021bial\u0103 (init) valoarea inversat\u0103 a sumei de control anterioare.<\/p>\n<p><\/p>\n<p><em>CRC are proprietatea de \u201econtinuitate\u201d, func\u021bioneaz\u0103 (plus-minus inversarea bitilor \u00een proces) cu o astfel de formul\u0103: <img decoding=\"async\" alt=\"Implementarea mea a buffer-ului circular \u00een NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/b194d6a3d18ae2f4ae5d4054a93c0ea3.jpg\" style=\"display:block;margin: 0 auto;\" \/>.<br \/>\nAdic\u0103, practic calcul\u0103m CRC pentru to\u021bi bytes anteriori ai antetelor \u0219i datelor de pe aceast\u0103 pagin\u0103.<\/em><\/p>\n<p><\/p>\n<p>Imediat dup\u0103 suma de control se afl\u0103 antetul urm\u0103toarei \u00eenregistr\u0103ri.<\/p>\n<p><\/p>\n<p>Antetul este construit astfel \u00eenc\u00e2t primul s\u0103u byte s\u0103 fie \u00eentotdeauna diferit de 0x00 \u0219i 0xff (dac\u0103 \u00eent\u00e2lnim 0xff \u00een loc de primul byte al antetului, \u00eenseamn\u0103 c\u0103 aceasta este o zon\u0103 neutilizat\u0103; 0x00 semnalizeaz\u0103 o eroare).<\/p>\n<p><\/p>\n<h2 id=\"primernye-algoritmy\">Algoritmi aproximativi<\/h2>\n<p><\/p>\n<h3 id=\"chtenie-iz-flesh-pamyati\">Citire din memoria flash<\/h3>\n<p><\/p>\n<p>Orice citire se face cu verificarea sumei de control.<br \/>\nDac\u0103 suma de control nu corespunde, citirea se repet\u0103 de mai multe ori \u00een speran\u021ba de a citi datele corecte.<\/p>\n<p><\/p>\n<p><em>(aceasta are sens, Linux nu cacheaz\u0103 citirile din NOR Flash, verificat)<\/em><\/p>\n<p><\/p>\n<h3 id=\"zapis-v-flesh-pamyat\">Scriere \u00een memorie flash<\/h3>\n<p><\/p>\n<p>Scriem datele.<br \/>\nLe citim.<\/p>\n<p><\/p>\n<p>Dac\u0103 datele citite nu se potrivesc cu cele scrise, umplem zona cu zerouri \u0219i semnaliz\u0103m o eroare.<\/p>\n<p><\/p>\n<h3 id=\"podgotovka-novoy-mikroshemy-k-rabote\">Preg\u0103tirea noului cip pentru func\u021bionare<\/h3>\n<p><\/p>\n<p>Pentru ini\u021bializare, \u00een prima (mai bine zis, pagina zero) se scrie un header cu versiunea 1.<br \/>\nDup\u0103 aceasta, \u00een aceast\u0103 pagin\u0103 se scrie contextul ini\u021bial (con\u021bine UUID-ul automatului \u0219i set\u0103rile implicite). <\/p>\n<p><\/p>\n<p>Totul, memoria flash este preg\u0103tit\u0103 pentru func\u021bionare.<\/p>\n<p><\/p>\n<h3 id=\"zagruzka-avtomata\">\u00cenc\u0103rcarea automatului<\/h3>\n<p><\/p>\n<p>La \u00eenc\u0103rcare, se citesc primii 8 bi\u021bi din fiecare pagin\u0103 (header + CRC), paginile cu num\u0103r magic necunoscut sau CRC incorect sunt ignorate.<br \/>\nDin paginile \u201ecorecte\u201d sunt selectate paginile cu versiunea maxim\u0103, din acestea se ia pagina cu cel mai mare num\u0103r.<br \/>\nSe cite\u0219te prima \u00eenregistrare, se verific\u0103 corectitudinea CRC-ului, prezen\u021ba steagului \u201econtext\u201d. Dac\u0103 totul este \u00een regul\u0103 \u2014 aceast\u0103 pagin\u0103 este considerat\u0103 curent\u0103. Dac\u0103 nu \u2014 revenim la cea anterioar\u0103, p\u00e2n\u0103 g\u0103sim o pagin\u0103 \u201evie\u201d.<br \/>\npe pagina g\u0103sit\u0103 citim toate \u00eenregistr\u0103rile, cele cu steagul \u201econtext\u201d le aplic\u0103m.<br \/>\nSalv\u0103m dic\u021bionarul zlib (va fi necesar pentru scrierea ulterioar\u0103 pe aceast\u0103 pagin\u0103).<\/p>\n<p><\/p>\n<p>Totul, \u00eenc\u0103rcarea s-a finalizat, contextul a fost restabilit, se poate lucra.<\/p>\n<p><\/p>\n<h3 id=\"dobavlenie-zapisi-v-zhurnal\">Ad\u0103ugarea unei \u00eenregistr\u0103ri \u00een jurnal<\/h3>\n<p><\/p>\n<p>Compres\u0103m \u00eenregistrarea cu dic\u021bionarul corect, indic\u00e2nd Z_SYNC_FLUSH. Verific\u0103m dac\u0103 \u00eenregistrarea comprimat\u0103 \u00eencapaciteaz\u0103 pagina curent\u0103.<br \/>\nDac\u0103 nu \u00eencap (sau pe pagin\u0103 au fost erori CRC) - \u00eencepem o pagin\u0103 nou\u0103 (vezi mai jos).<br \/>\nScriem \u00eenregistrarea \u0219i CRC. Dac\u0103 a ap\u0103rut o eroare - \u00eencepem o pagin\u0103 nou\u0103.<\/p>\n<p><\/p>\n<h3 id=\"novaya-stranica\">Pagin\u0103 nou\u0103<\/h3>\n<p><\/p>\n<p>Alegem o pagin\u0103 liber\u0103 cu cel mai mic num\u0103r (consider\u0103m liber\u0103 o pagin\u0103 cu suma de control gre\u0219it\u0103 \u00een header sau cu versiunea mai mic\u0103 dec\u00e2t cea curent\u0103). Dac\u0103 nu exist\u0103 astfel de pagini, alegem pagina cu cel mai mic num\u0103r dintre cele care au versiunea egal\u0103 cu cea curent\u0103.<br \/>\nFacem pagina aleas\u0103 erase. Compar\u0103m con\u021binutul cu 0xff. Dac\u0103 ceva nu este \u00een regul\u0103, lu\u0103m urmatoarea pagin\u0103 liber\u0103 etc.<br \/>\nPe pagina \u0219tears\u0103 scriem headerul, prima \u00eenregistrare este starea curent\u0103 a contextului, urm\u0103toarea - \u00eenregistrarea jurnalului nescris (dac\u0103 exist\u0103).<\/p>\n<p><\/p>\n<h1 id=\"primenimost-formata\">Aplicabilitatea formatului<\/h1>\n<p><\/p>\n<p>Din punctul meu de vedere, a rezultat un format decent pentru stocarea oric\u0103ror fluxuri de informa\u021bii mai mult sau mai pu\u021bin comprimate (text simplu, JSON, MessagePack, CBOR, poate chiar protobuf) \u00een NOR Flash.<\/p>\n<p><\/p>\n<p>Desigur, formatul este \u201eoptimizat\u201d pentru SLC NOR Flash.<\/p>\n<p><\/p>\n<p>Nu ar trebui folosit cu suporturi cu BER ridicat, cum ar fi NAND sau MLC NOR. <em>(Exist\u0103 vreo astfel de memorie la v\u00e2nzare? Am v\u0103zut doar men\u021biuni \u00een lucr\u0103rile despre coduri de corec\u021bie.)<\/em>.<\/p>\n<p><\/p>\n<p>Cu at\u00e2t mai mult, nu ar trebui folosit cu dispozitive care au propriul FTL: USB flash, SD, MicroSD, etc. <em>(pentru acest tip de memorie am realizat un format cu dimensiunea paginii de 512 byte, semn\u0103tur\u0103 la \u00eenceputul fiec\u0103rei pagini \u0219i numere unice pentru \u00eenregistr\u0103ri \u2014 uneori dintr-o memorie flash \u201ec\u0103zut\u0103\u201d am reu\u0219it s\u0103 recuperez toate datele prin citirea secven\u021bial\u0103 simpl\u0103)<\/em>.<\/p>\n<p><\/p>\n<p>\u00cen func\u021bie de sarcini, formatul poate fi folosit f\u0103r\u0103 modific\u0103ri pe flash-uri de la 128Kbit (16Kb) p\u00e2n\u0103 la 1Gbit (128Mb). Dac\u0103 se dore\u0219te, poate fi folosit \u0219i pe cipuri de o capacitate mai mare, dar, probabil, ar trebui ajustat\u0103 dimensiunea paginii. <em>(Dar aici apare \u00eentrebarea eficien\u021bei economice, pre\u021bul pe NOR Flash de mare capacitate nu este \u00eencurajator.)<\/em>.<\/p>\n<p><\/p>\n<p>Dac\u0103 cineva a g\u0103sit formatul interesant \u0219i vrea s\u0103-l foloseasc\u0103 \u00eentr-un proiect deschis \u2014 scrie\u021bi-mi, voi \u00eencerca s\u0103 g\u0103sesc timp s\u0103 revizuiesc codul \u0219i s\u0103-l public pe github.<\/p>\n<p><\/p>\n<h1 id=\"zaklyuchenie\">Concluzie<\/h1>\n<p><\/p>\n<p>Dup\u0103 cum vedem, \u00een cele din urm\u0103, formatul s-a dovedit a fi simplu. <em>\u0218i chiar plictisitor.<\/em>.<\/p>\n<p><\/p>\n<p>\u00cen articol este greu s\u0103 reflect\u0103m evolu\u021bia punctului meu de vedere, dar crede\u021bi-m\u0103: ini\u021bial mi-am dorit s\u0103 creez ceva sofisticat, indestructibil, capabil s\u0103 supravie\u021buiasc\u0103 chiar \u0219i unei explozii nucleare \u00een imediata apropiere. Cu toate acestea, ra\u021biunea (sper) a \u00eenvins totu\u0219i \u0219i priorit\u0103\u021bile s-au mutat treptat spre simplitate \u0219i compactitate.<\/p>\n<p><\/p>\n<p>Ar putea s\u0103 fie a\u0219a \u00eenc\u00e2t s\u0103 m\u0103 fi \u00een\u0219elat? Da, desigur. Este foarte posibil, de exemplu, s\u0103 fi cump\u0103rat un lot de cipuri de calitate slab\u0103. Sau, din orice alt motiv, echipamentele s\u0103 nu \u00eendeplineasc\u0103 a\u0219tept\u0103rile de fiabilitate.<\/p>\n<p><\/p>\n<p>Am un plan \u00een acest caz? Cred c\u0103, dup\u0103 ce ve\u021bi citi articolul, nu ave\u021bi \u00eendoieli c\u0103 exist\u0103 un plan. \u0218i nu unul singur.<\/p>\n<p><\/p>\n<p>Dac\u0103 s\u0103 fim un pic mai serio\u0219i, formatul a fost dezvoltat simultan at\u00e2t ca variant\u0103 de lucru, c\u00e2t \u0219i ca \u201etest\u201d.<\/p>\n<p><\/p>\n<p>\u00cen prezent, totul func\u021bioneaz\u0103 normal pe birou, \u0219i \u00een c\u00e2teva zile solu\u021bia va fi implementat\u0103. <em>(aproximativ)<\/em> pe o sut\u0103 de dispozitive, vom vedea ce se va \u00eent\u00e2mpla \u00een exploatarea \u201ereal\u0103\u201d (slava Domnului, sper c\u0103 formatul permite detectarea fiabil\u0103 a defectelor; a\u0219a c\u0103 se va putea aduna o statistic\u0103 complet\u0103). \u00cen c\u00e2teva luni, se vor putea face concluzii. <em>(dac\u0103 nu avem noroc \u2014 atunci \u0219i mai devreme)<\/em>.<\/p>\n<p><\/p>\n<p>Dac\u0103, \u00een urma utiliz\u0103rii, vor ap\u0103rea probleme serioase \u0219i vor fi necesare modific\u0103ri, voi scrie cu siguran\u021b\u0103 despre acest lucru.<\/p>\n<p><\/p>\n<h1 id=\"literatura\">Literatur\u0103<\/h1>\n<p><\/p>\n<p>Nu am vrut s\u0103 fac o list\u0103 lung\u0103 \u0219i plictisitoare de lucr\u0103ri utilizate, \u00een fond, Google este la \u00eendem\u00e2n\u0103 pentru toat\u0103 lumea.<\/p>\n<p><\/p>\n<p>Aici am decis s\u0103 p\u0103strez o list\u0103 a descoperirilor care mi s-au p\u0103rut deosebit de interesante, totu\u0219i, treptat acestea au fost integrate direct \u00een textul articolului, iar \u00een list\u0103 a r\u0103mas un singur punct:<\/p>\n<p><\/p>\n<ol>\n<li>Utilitarul <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/madler\/infgen\/\">infgen<\/a><\/noindex> de la autorul zlib. Poate s\u0103 afi\u0219eze, \u00eentr-o form\u0103 clar\u0103, con\u021binutul arhivelor deflate\/zlib\/gzip. Dac\u0103 trebuie s\u0103 te familiarizezi cu structura intern\u0103 a formatului deflate (sau gzip) \u2014 \u00ee\u021bi recomand cu c\u0103ldur\u0103.<\/li>\n<\/ol>\n<p>Sursa: <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.1 - 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\/ro\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"ro_RO\" \/>\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\/ro\/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\udd47Implementarea mea a buffer-ului circular \u00een NOR flash | ProHoster","description":"Context: Exist\u0103 automate de v\u00e2nzare dezvoltate intern. \u00cen interior, exist\u0103 un Raspberry Pi \u0219i pu\u021bin\u0103 echipare pe o plac\u0103 separat\u0103.","canonical_url":"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"ro_RO","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\/ro\/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\/ro\/wp-json\/wp\/v2\/posts\/53751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/comments?post=53751"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts\/53751\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media?parent=53751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/categories?post=53751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/tags?post=53751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}