{"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\/it\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","title":{"rendered":"La mia implementazione di un buffer circolare nella memoria NOR flash","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h1 id=\"predystoriya\">Antefatti<\/h1>\n<p><\/p>\n<p>Ci sono distributori automatici di nostra progettazione. All'interno Raspberry Pi e un po' di circuiti su una scheda separata. Sono collegati un accettatore di monete, un accettatore di banconote, un terminale bancario\u2026 Tutto \u00e8 gestito da un programma scritto da noi. Tutta la cronologia delle operazioni viene registrata in un diario su una chiavetta USB (MicroSD), che poi viene trasferita tramite internet (con un modem USB) su un server, dove viene archiviata in un database. Le informazioni sulle vendite vengono caricate in 1C, c'\u00e8 anche una semplice interfaccia web per il monitoraggio e simili. <\/p>\n<p><\/p>\n<p>Cio\u00e8, il diario \u00e8 vitale \u2014 per la contabilizzazione (ci sono le entrate, le vendite, ecc.), il monitoraggio (tutti i tipi di malfunzionamenti e altre circostanze straordinarie); questa \u00e8, si pu\u00f2 dire, tutta l'informazione che abbiamo su questo distributore. <\/p>\n<p><\/p>\n<h1 id=\"problema\">Problema<\/h1>\n<p><\/p>\n<p>Le chiavette USB si dimostrano dispositivi molto poco affidabili. Si guastano con una regolarit\u00e0 sorprendente. Questo porta sia a fermi dei distributori, sia (se per qualche motivo il diario non pu\u00f2 essere trasferito online) alla perdita di dati.<\/p>\n<p><\/p>\n<p><em>Questa non \u00e8 la prima esperienza con le chiavette USB; in precedenza c'era un altro progetto con pi\u00f9 di un centinaio di dispositivi, dove il diario era memorizzato su chiavette USB, anche l\u00ec ci sono stati problemi di affidabilit\u00e0, a volte il numero di guasti al mese superava le decine. Abbiamo provato diverse chiavette, incluse quelle di marca con memoria SLC, s\u00ec, alcuni modelli sono pi\u00f9 affidabili di altri, ma la sostituzione delle chiavette non ha risolto il problema in modo decisivo.<\/em><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <\/p>\n<p><strong>Attenzione!<\/strong> Long read! Se se non ti interessa il 'perch\u00e9' ma solo il 'come', puoi andare direttamente <noindex><a rel=\"nofollow\" href=\"#format\">alla fine<\/a><\/noindex> articoli.<\/p>\n<p><\/p>\n<h1 id=\"reshenie\">Soluzione<\/h1>\n<p><\/p>\n<p>La prima cosa che viene in mente: rinunciare al MicroSD, installare, ad esempio, un SSD, e avviarsi da esso. Teoricamente \u00e8 possibile, penso, ma relativamente costoso, e non \u00e8 cos\u00ec affidabile (si aggiunge un adattatore USB-SATA; anche per gli SSD economici le statistiche sui guasti non sono incoraggianti).<\/p>\n<p><\/p>\n<p>I dischi rigidi USB non sembrano una soluzione particolarmente allettante.<\/p>\n<p><\/p>\n<p>Pertanto, siamo giunti a questa soluzione: mantenere l'avvio dal MicroSD, ma utilizzarli in modalit\u00e0 di sola lettura, e conservare il diario delle operazioni (e altre informazioni uniche per ciascun dispositivo, come il numero di serie, le calibrazioni dei sensori, ecc.) da qualche altra parte. <\/p>\n<p><\/p>\n<p>Il tema dei filesystem di sola lettura per Raspberry Pi \u00e8 gi\u00e0 stato studiato a fondo, non mi soffermer\u00f2 sui dettagli dell'implementazione in questo articolo. <em>(ma se ci sar\u00e0 interesse \u2014 forse scriver\u00f2 un mini-articolo su questo tema)<\/em>. L'unico punto che desidero sottolineare: sia per esperienza personale che per le recensioni di chi ha gi\u00e0 implementato, ci sono migliorie in termini di affidabilit\u00e0. S\u00ec, \u00e8 impossibile eliminare completamente i guasti, ma ridurne significativamente la frequenza \u00e8 assolutamente fattibile. Inoltre, le schede diventano uniformi, il che semplifica notevolmente la sostituzione per il personale di supporto.<\/p>\n<p><\/p>\n<h2 id=\"apparatnaya-chast\">Parte hardware<\/h2>\n<p><\/p>\n<p>Non ci sono stati dubbi sulla scelta del tipo di memoria: NOR Flash.<br \/>\nArgomenti: <\/p>\n<p><\/p>\n<ul>\n<li>una semplice connessione (per lo pi\u00f9 un bus SPI, di cui abbiamo gi\u00e0 esperienza, quindi non ci sono previsti problemi 'hardware');<\/li>\n<li>prezzo ridicolo;<\/li>\n<li>protocollo di lavoro standard (la sua implementazione \u00e8 gi\u00e0 nel kernel di Linux, su richiesta si possono utilizzare soluzioni di terze parti, che sono anche disponibili, o addirittura scrivere una propria, dato che \u00e8 piuttosto semplice);<\/li>\n<li>affidabilit\u00e0 e durata:<br \/>\nda un datasheet tipico: i dati vengono conservati per 20 anni, 100000 cicli di cancellazione per ogni blocco;<br \/>\nda fonti esterne: BER estremamente basso, si postula l'assenza della necessit\u00e0 di codici di correzione degli errori <em>(in alcuni lavori si considera l'ECC per NOR, ma di solito si intende MLC NOR, il che pu\u00f2 anche succedere)<\/em>.<\/li>\n<\/ul>\n<p><\/p>\n<p>Calcoliamo i requisiti di volume e durata.<\/p>\n<p><\/p>\n<p>Vogliamo garantire che i dati siano conservati per diversi giorni. Questo \u00e8 necessario affinch\u00e9, in caso di problemi di connessione, la storia delle vendite non venga persa. Ci orienteremo su 5 giorni, durante i quali <em>(anche considerando i fine settimana e le festivit\u00e0)<\/em> si pu\u00f2 risolvere il problema.<\/p>\n<p><\/p>\n<p>Attualmente accumuliamo circa 100kb di log al giorno (3-4 mila registrazioni), ma questa cifra sta crescendo progressivamente: aumenta la granularit\u00e0, si aggiungono nuovi eventi. Inoltre, ci sono a volte picchi (un certo sensore inizia a inviare falsi allarmi, per esempio). Calcoliamo su 10mila registrazioni da 100 byte \u2014 un megabyte al giorno.<\/p>\n<p><\/p>\n<p>In totale si traducono in 5Mb di dati 'puliti' (ben compressibili). A questi si aggiungono <em>(una stima grossolana)<\/em> 1Mb di dati di servizio.<\/p>\n<p><\/p>\n<p>Quindi abbiamo bisogno di un chip da 8Mb se non utilizziamo la compressione, o 4Mb se la utilizziamo. Cifre assolutamente realistico per questo tipo di memoria.<\/p>\n<p><\/p>\n<p>Per quanto riguarda la durata: se prevediamo che la memoria venga sovrascritta interamente non pi\u00f9 spesso di una volta ogni 5 giorni, nel corso di 10 anni di utilizzo otteniamo meno di mille cicli di riscrittura.<br \/>\nRicordo che il produttore promette centomila.<\/p>\n<p>\n<b class=\"spoiler_title\">Un po' di NOR vs NAND<\/b><\/p>\n<p>Oggi, ovviamente, la memoria NAND \u00e8 molto pi\u00f9 popolare, tuttavia per questo progetto non la utilizzerei: a differenza della NOR, la NAND richiede necessariamente l'uso di codici di correzione degli errori, tabelle dei blocchi difettosi, ecc., e le piedinature dei chip NAND sono di solito molto pi\u00f9 numerose.<\/p>\n<p><\/p>\n<p>Tra i difetti della NOR si possono indicare:<\/p>\n<p><\/p>\n<ul>\n<li>basso volume (e, di conseguenza, alto costo per megabyte);<\/li>\n<li>bassa velocit\u00e0 di trasferimento (in gran parte a causa dell'uso dell'interfaccia seriale, di solito SPI o I2C);<\/li>\n<li>cancellazione lenta (a seconda della dimensione del blocco, pu\u00f2 richiedere da frazioni di secondo a diversi secondi).<\/li>\n<\/ul>\n<p><\/p>\n<p>Non sembra ci sia nulla di critico per noi, quindi continuiamo.<\/p>\n<p><\/p>\n<p>Se sei interessato ai dettagli, \u00e8 stato scelto il chip <noindex><a rel=\"nofollow\" href=\"https:\/\/www.adestotech.com\/wp-content\/uploads\/doc3686.pdf\">at25df321a<\/a><\/noindex> <em>(tuttavia, ci\u00f2 \u00e8 irrilevante, ci sono una miriade di analoghi sul mercato, compatibili con il pinout e il sistema di comandi; anche se decidessimo di installare un chip di un altro produttore e\/o di un'altra capacit\u00e0, tutto funzioner\u00e0 senza modifiche al codice)<\/em>.<\/p>\n<p><\/p>\n<p>Utilizzo il driver integrato nel kernel Linux, su Raspberry grazie al supporto del device tree overlay \u00e8 molto semplice: basta posizionare l'overlay compilato in \/boot\/overlays e modificare leggermente \/boot\/config.txt.<\/p>\n<p>\n<b class=\"spoiler_title\">Esempio di file dts<\/b><\/p>\n<p>Onestamente, non sono sicuro che sia scritto senza errori, ma funziona.<\/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\">E un'altra riga in config.txt<\/b><\/p>\n<pre><code class=\"plaintext\">dtoverlay=at25:spimaxfrequency=50000000<\/code><\/pre>\n<p><\/p>\n<p>Tralascio la descrizione del collegamento del chip al Raspberry Pi. Da un lato, non sono un esperto di elettronica, dall'altro \u2014 qui \u00e8 tutto banale anche per me: il chip ha solo 8 pin, di cui abbiamo bisogno di terra, alimentazione, SPI (CS, SI, SO, SCK); i livelli corrispondono a quelli del Raspberry Pi, non \u00e8 necessaria alcuna ulteriore interconnessione: basta collegare i 6 pin indicati.<\/p>\n<p><\/p>\n<h2 id=\"postanovka-zadachi\">Definizione del compito<\/h2>\n<p><\/p>\n<p>Come al solito, la definizione del compito passa attraverso diverse iterazioni, mi sembra che sia giunto il momento per un'altra. Quindi fermiamoci, raggruppiamo ci\u00f2 che \u00e8 gi\u00e0 stato scritto e facciamo chiarezza sui dettagli rimasti in ombra.<\/p>\n<p><\/p>\n<p>Quindi abbiamo deciso che il registro sar\u00e0 memorizzato nella SPI NOR Flash.<\/p>\n<p>\n<b class=\"spoiler_title\">Cos'\u00e8 la NOR Flash per chi non lo sa<\/b><\/p>\n<p>\u00c8 una memoria non volatile, con cui \u00e8 possibile effettuare tre operazioni:<\/p>\n<p><\/p>\n<ol>\n<li>Lettura:<br \/>\nLa lettura pi\u00f9 semplice: inviamo l'indirizzo e leggiamo quanti byte ci servono;<\/li>\n<li>Registrazione:<br \/>\nLa scrittura nella NOR flash appare normale, ma ha una particolarit\u00e0: \u00e8 possibile modificare solo 1 in 0, ma non viceversa. Ad esempio, se nella cella di memoria c'era 0x55, dopo aver scritto 0x0f al suo interno si trover\u00e0 0x05. <em>(vedi tabella qui sotto)<\/em>;<\/li>\n<li>Cancellazione:<br \/>\nNaturalmente, \u00e8 necessario saper effettuare anche l'operazione inversa: cambiare 0 in 1, ed \u00e8 proprio per questo che esiste l'operazione di cancellazione. A differenza delle prime due, essa opera non su byte, ma su blocchi (il blocco minimo di cancellazione nel chip scelto \u00e8 di 4kB). La cancellazione distrugge l'intero blocco e questo \u00e8 l'unico modo per cambiare 0 in 1. Pertanto, quando si lavora con la memoria flash spesso \u00e8 necessario allineare le strutture dati al confine del blocco di cancellazione.<br \/>\nScrittura nella NOR Flash:<\/li>\n<\/ol>\n<p><\/p>\n<p>Dati binari<\/p>\n<p><strong>Era<\/strong><br \/>\n<code>01010101<\/code><\/p>\n<p><strong>Registrato<\/strong><br \/>\n<code>00001111<\/code><\/p>\n<p><strong>Diventato<\/strong><br \/>\n<code>00000101<\/code><\/p>\n<p><\/p>\n<p>Il registro stesso rappresenta una sequenza di registrazioni di lunghezza variabile. La lunghezza tipica di una registrazione \u00e8 di circa 30 byte (anche se a volte si verificano registrazioni lunghe diversi kilobyte). <em>In questo caso, lavoriamo con loro semplicemente come un insieme di byte, ma, se interessa, all'interno delle registrazioni viene utilizzato CBOR.<\/em><\/p>\n<p><\/p>\n<p>Oltre al registro, abbiamo bisogno di memorizzare alcune informazioni di 'configurazione', sia aggiornabili che statiche: un certo ID dell'apparecchio, le calibrazioni dei sensori, un flag 'apparecchio temporaneamente disattivato', ecc.<br \/>\nQueste informazioni costituiscono un insieme di registrazioni chiave-valore, anch'esse memorizzate in CBOR. Non abbiamo molte di queste informazioni (massimo qualche kilobyte), e non vengono aggiornate spesso.<br \/>\nIn seguito ci riferiremo ad esse come contesto.<\/p>\n<p><\/p>\n<p>Se ricordiamo da dove \u00e8 iniziato questo articolo, \u00e8 molto importante garantire l'affidabilit\u00e0 dell'archiviazione dei dati e, se possibile, un funzionamento continuo anche in caso di guasti hardware\/danneggiamento dei dati.<\/p>\n<p><\/p>\n<p>Quali fonti di problemi possiamo considerare?<\/p>\n<p><\/p>\n<ul>\n<li>Spegnere l'alimentazione durante le operazioni di scrittura\/cancellazione. Questo \u00e8 qualcosa del tipo 'non c'\u00e8 rimedio contro il forte'.<br \/>\nInformazioni da <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\">una discussione<\/a><\/noindex> su stackexchange: durante lo spegnimento dell'alimentazione mentre si lavora con la flash, sia la cancellazione (impostazione a 1) che la scrittura (impostazione a 0) portano a un comportamento indefinito: i dati potrebbero essere scritti, scritti parzialmente (diciamo, abbiamo inviato 10 byte\/80 bit, ma siamo riusciti a scrivere solo 45 bit), non \u00e8 escluso che alcuni bit possano trovarsi in uno stato 'intermedio' (la lettura pu\u00f2 restituire sia 0 che 1);<\/li>\n<li>Errori nella stessa memoria flash.<br \/>\nIl BER, sebbene molto basso, non pu\u00f2 essere nullo;<\/li>\n<li>Errori sulla linea<br \/>\nI dati trasmessi tramite SPI non sono affatto protetti e potrebbero verificarsi sia errori singoli di bit che errori di sincronizzazione, come la perdita o l'inserimento di bit (che porta a distorsioni massicce dei dati);<\/li>\n<li>Altri errori\/fallimenti<br \/>\nErrori nel codice, 'bug' Raspberry, intervento di alieni\u2026<\/li>\n<\/ul>\n<p><\/p>\n<p>Ho formulato i requisiti la cui attuazione ritengo necessaria per garantire l'affidabilit\u00e0:<\/p>\n<p><\/p>\n<ul>\n<li>le registrazioni devono essere scritte nella memoria flash immediatamente, la scrittura ritardata non \u00e8 presa in considerazione; - se si verifica un errore, deve essere rilevato e gestito il prima possibile; - il sistema deve ripristinare il funzionamento dopo gli errori, se possibile.<br \/>\n<em>(esempio dalla vita 'di come non dovrebbe essere', con cui, credo, tutti si sono imbattuti: dopo un riavvio di emergenza, il file system si \u00e8 'rovinato' e il sistema operativo non si avvia)<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"idei-podhody-razmyshleniya\">Idee, approcci, riflessioni<\/h2>\n<p><\/p>\n<p>Quando ho iniziato a pensare a quest'attivit\u00e0, mi sono passate per la testa un sacco di idee, ad esempio:<\/p>\n<p><\/p>\n<ul>\n<li>utilizzare la compressione dei dati;<\/li>\n<li>utilizzare strutture dati intelligenti, ad esempio memorizzare le intestazioni delle registrazioni separatamente dai dati stessi, in modo che in caso di errore in una registrazione si possano leggere senza problemi le altre;<\/li>\n<li>utilizzare campi di bit per controllare il completamento della registrazione in caso di interruzione dell'alimentazione;<\/li>\n<li>memorizzare le somme di controllo per tutto e per tutti;<\/li>\n<li>utilizzare qualche tipo di codifica resistente ai disturbi.<\/li>\n<\/ul>\n<p><\/p>\n<p>Parte di queste idee \u00e8 stata utilizzata, mentre altre sono state scartate. Procediamo con ordine.<\/p>\n<p><\/p>\n<h3 id=\"szhatie-dannyh\">Compressione dei dati<\/h3>\n<p><\/p>\n<p>Gli eventi che registriamo nel registro sono abbastanza omogenei e ripetitivi ('abbiamo lanciato una moneta da 5 rubli', 'abbiamo premuto il pulsante per la restituzione del resto', \u2026). Pertanto, la compressione dovrebbe risultare abbastanza efficace.<\/p>\n<p><\/p>\n<p>Le spese generali per la compressione sono trascurabili (il nostro processore \u00e8 abbastanza potente; anche il primo Pi aveva un nucleo con una frequenza di 700 MHz, mentre i modelli attuali hanno pi\u00f9 nuclei con frequenze superiori a un gigahertz), la velocit\u00e0 di scambio con il deposito non \u00e8 alta (alcuni megabyte al secondo) e le dimensioni delle registrazioni sono ridotte. In generale, se la compressione influisce sulle prestazioni, sar\u00e0 solo in modo positivo. <em>(assolutamente non critico, lo constato semplicemente)<\/em>. Inoltre, non abbiamo un embedded vero e proprio, ma un normale Linux, quindi l'implementazione non dovrebbe richiedere molto sforzo (basta semplicemente collegare la libreria e usare alcune funzioni in essa).<\/p>\n<p><\/p>\n<p>\u00c8 stato prelevato un campione di log da un dispositivo in funzione (1,7 MB, 70 mila registrazioni) e inizialmente verificato per la compressibilit\u00e0 utilizzando gzip, lz4, lzop, bzip2, xz, zstd.<\/p>\n<p><\/p>\n<ul>\n<li>gzip, xz, zstd hanno mostrato risultati simili (40 KB).<br \/>\n\u00c8 stato sorprendente che il trendy xz si sia comportato a livello di gzip o zstd;<\/li>\n<li>lzip con le impostazioni predefinite ha dato un risultato leggermente peggiore;<\/li>\n<li>lz4 e lzop hanno mostrato un risultato non molto buono (150 KB);<\/li>\n<li>bzip2 ha mostrato risultati sorprendentemente buoni (18 KB).<\/li>\n<\/ul>\n<p><\/p>\n<p>Quindi, i dati si comprimono molto bene.<br \/>\nQuindi (se non troviamo difetti fatali) la compressione ci sar\u00e0! Solo perch\u00e9 ci sar\u00e0 pi\u00f9 spazio per i dati sulla stessa chiavetta.<\/p>\n<p><\/p>\n<p>Pensiamo ai difetti.<\/p>\n<p><\/p>\n<p>Primo problema: abbiamo gi\u00e0 concordato che ogni registrazione deve essere subito trasferita sulla chiavetta. Di solito, il compressore raccoglie dati dal flusso di input fino a quando non decide che \u00e8 il momento di scrivere in uscita. A noi serve invece ricevere immediatamente un blocco di dati compressi e salvarlo nella memoria non volatile.<\/p>\n<p><\/p>\n<p>Vedo tre soluzioni:<\/p>\n<p><\/p>\n<ol>\n<li>Comprimere ogni registrazione utilizzando la compressione basata su dizionario invece degli algoritmi considerati sopra.<br \/>\nUna soluzione praticabile, ma non mi piace. Per garantire un livello di compressione perlomeno decente, il dizionario deve essere 'ottimizzato' per i dati specifici, qualsiasi modifica comporter\u00e0 un drammatico calo del livello di compressione. S\u00ec, il problema si risolve creando una nuova versione del dizionario, ma \u00e8 una vera seccatura - dobbiamo memorizzare tutte le versioni del dizionario; in ogni record dobbiamo indicare con quale versione del dizionario \u00e8 stata compressa\u2026<\/li>\n<li>Comprimere ogni record con algoritmi 'classici', ma indipendentemente dagli altri.<br \/>\nGli algoritmi di compressione considerati non sono progettati per lavorare con registrazioni di questa dimensione (decine di byte), il rapporto di compressione sar\u00e0 chiaramente inferiore a 1 (cio\u00e8 un aumento del volume dei dati invece di una compressione);<\/li>\n<li>Eseguire un FLUSH dopo ogni registrazione.<br \/>\nMolte librerie di compressione supportano il FLUSH. \u00c8 un comando (o un parametro della procedura di compressione) che, una volta ricevuto, permette al compressor di generare un flusso compresso in modo tale da poter ripristinare <strong>tutti<\/strong> i dati non compressi che sono gi\u00e0 stati ricevuti. Un'analogia del genere <code>sync<\/code> nei sistemi di file o <code>commit<\/code> in sql.<br \/>\n\u00c8 importante notare che le operazioni di compressione successive possono utilizzare il dizionario accumulato e il tasso di compressione non sar\u00e0 cos\u00ec compromesso come nel caso precedente.<\/li>\n<\/ol>\n<p><\/p>\n<p>Penso sia ovvio che ho scelto la terza opzione, approfondiamo questo argomento.<\/p>\n<p><\/p>\n<p>Trovata <noindex><a rel=\"nofollow\" href=\"https:\/\/www.bolet.org\/~pornin\/deflate-flush.html\">ottimo articolo<\/a><\/noindex> su FLUSH in zlib.<\/p>\n<p><\/p>\n<p>Ho effettuato un test ispirato a un articolo, ho preso 70.000 registrazioni di log da un dispositivo reale, con dimensioni di pagina di 60 KB <em>(torneremo sulle dimensioni della pagina)<\/em> ho ottenuto:<\/p>\n<p><\/p>\n<p>Dati di origine<br \/>\nCompressione gzip -9 (senza FLUSH)<br \/>\nzlib con Z_PARTIAL_FLUSH<br \/>\nzlib con Z_SYNC_FLUSH<\/p>\n<p><strong>Dimensione, KB<\/strong><br \/>\n1692<br \/>\n40<br \/>\n352<br \/>\n604<\/p>\n<p><\/p>\n<p>A prima vista, il costo introdotto da FLUSH appare eccessivo, ma in realt\u00e0 abbiamo una scelta non molto ricca: o non comprimere affatto, o comprimere (e in modo molto efficace) con FLUSH. Non dobbiamo dimenticare che abbiamo 70.000 registrazioni, e l'esubero introdotto da Z_PARTIAL_FLUSH \u00e8 solo di 4-5 byte per registrazione. E il tasso di compressione si \u00e8 rivelato quasi 5:1, il che \u00e8 sicuramente un ottimo risultato.<\/p>\n<p>\n<b class=\"spoiler_title\">Potrebbe sembrare inaspettato, ma in realt\u00e0 Z_SYNC_FLUSH \u00e8 un modo pi\u00f9 efficiente per fare FLUSH<\/b><\/p>\n<p>Nel caso dell'uso di Z_SYNC_FLUSH, gli ultimi 4 byte di ogni registrazione saranno sempre 0x00, 0x00, 0xff, 0xff. E se li conosciamo, possiamo evitarne la memorizzazione, di conseguenza la dimensione finale risulta di solo 324 KB.<\/p>\n<p><\/p>\n<p>Nell'articolo che cito, c'\u00e8 una spiegazione:<\/p>\n<p><\/p>\n<blockquote><p>Un nuovo blocco di tipo 0 con contenuti vuoti viene aggiunto.<\/p>\n<p>Un blocco di tipo 0 con contenuti vuoti \u00e8 composto da:<\/p>\n<ul>\n<li>l'intestazione del blocco di tre bit;<\/li>\n<li>da 0 a 7 bit pari a zero, per ottenere l'allineamento dei byte;<\/li>\n<li>la sequenza di quattro byte 00 00 FF FF.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Come \u00e8 facile notare, nell'ultimo blocco prima di questi 4 byte ci sono da 3 a 10 bit zero. Tuttavia, la pratica ha dimostrato che i bit zero sono in realt\u00e0 almeno 10.<\/p>\n<p><\/p>\n<p>Si scopre che blocchi di dati cos\u00ec brevi sono solitamente (sempre?) codificati usando un blocco di tipo 1 (blocco fisso), che deve necessariamente terminare con 7 bit zero, ottenendo cos\u00ec 10-17 bit zero garantiti (e gli altri saranno zero con una probabilit\u00e0 di circa il 50%).<\/p>\n<p><\/p>\n<p>Pertanto, nei dati di test, nel 100% dei casi prima di 0x00, 0x00, 0xff, 0xff c'\u00e8 un byte zero, e in pi\u00f9 di un terzo dei casi - due byte zero <em>(forse \u00e8 dovuto al fatto che sto usando CBOR binario, mentre usando JSON testuale si sarebbero incontrati pi\u00f9 spesso blocchi di tipo 2 - blocco dinamico, quindi ci sarebbero stati blocchi senza byte zero aggiuntivi prima di 0x00, 0x00, 0xff, 0xff)<\/em>.<\/p>\n<p><\/p>\n<p>In totale, con i dati di test disponibili, si pu\u00f2 ottenere meno di 250 KB di dati compressi.<\/p>\n<p><\/p>\n<p>\u00c8 possibile risparmiare ancora un po', praticando il giocoliere con i bit: al momento ignoriamo la presenza di diversi bit zero alla fine del blocco, alcuni bit all'inizio del blocco rimangono invariati\u2026<br \/>\nMa qui ho preso una decisione ferma di fermarmi, altrimenti con questo ritmo potrei arrivare a sviluppare il mio compressore.<\/p>\n<p><\/p>\n<p>In totale, dai miei dati di test ho ottenuto 3-4 byte per la scrittura, il fattore di compressione \u00e8 risultato superiore a 6:1. A dire il vero: non mi aspettavo un risultato del genere, per me tutto ci\u00f2 che \u00e8 migliore di 2:1 \u00e8 gi\u00e0 un risultato che giustifica l'uso della compressione.<\/p>\n<p><\/p>\n<p>Va tutto bene, ma zlib (deflate) \u00e8 comunque un algoritmo di compressione piuttosto arcaico, meritato e un po' antiquato. Solo il fatto che come dizionario vengano utilizzati gli ultimi 32KB del flusso di dati non compressi appare strano oggi (cio\u00e8 se qualche blocco di dati \u00e8 molto simile a quello che era nel flusso di input 40KB fa, inizier\u00e0 a essere compresso da capo, senza fare riferimento alla precedente occorrenza). Negli archivi moderni alla moda, la dimensione del dizionario viene spesso misurata in megabyte, e non in kilobyte.<\/p>\n<p><\/p>\n<p>Cos\u00ec continuiamo la nostra mini-ricerca sugli archivi.<\/p>\n<p><\/p>\n<p>Il successivo test \u00e8 stato bzip2 (ricordo che, senza FLUSH, ha mostrato un fantastico grado di compressione, quasi 100:1). Purtroppo, con FLUSH ha mostrato scarsi risultati; la dimensione dei dati compressi \u00e8 risultata maggiore rispetto a quella non compressa.<\/p>\n<p>\n<b class=\"spoiler_title\">Le mie ipotesi sulle ragioni del fallimento<\/b><\/p>\n<p>Libbz2 offre solo un'opzione di flush, che sembra pulire il dizionario (analogo a Z_FULL_FLUSH in zlib), non si pu\u00f2 parlare di qualche compressione efficace dopo questo.<\/p>\n<p><\/p>\n<p>E infine, ho testato zstd. A seconda dei parametri, comprime al livello di gzip, ma molto pi\u00f9 velocemente, o meglio di gzip.<\/p>\n<p><\/p>\n<p>Sfortunatamente, con FLUSH si \u00e8 mostrato 'non molto' efficace: la dimensione dei dati compressi \u00e8 risultata circa 700Kb.<\/p>\n<p><\/p>\n<p>Io <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/facebook\/zstd\/issues\/900\">ho posto una domanda<\/a><\/noindex> sulla pagina del progetto su github, ho ricevuto risposta che \u00e8 ragionevole aspettarsi fino a 10 byte di dati di servizio per ogni blocco di dati compressi, che \u00e8 vicino ai risultati ottenuti; non riuscir\u00f2 mai a raggiungere deflate.<\/p>\n<p><\/p>\n<p>A questo punto ho deciso di fermare gli esperimenti con i compressori (ricordo che xz, lzip, lzo, lz4 non si sono dimostrati ancora nella fase di testing senza FLUSH, e non ho considerato algoritmi di compressione pi\u00f9 esotici).<\/p>\n<p><\/p>\n<p>Torniamo ai problemi di archiviazione.<\/p>\n<p><\/p>\n<p>Il secondo (come si dice in ordine, non per importanza) problema \u00e8 che i dati compressi rappresentano un flusso unico, in cui ci sono costanti riferimenti a sezioni precedenti. Cos\u00ec, in caso di un danneggiamento di una parte dei dati compressi, perdiamo non solo il blocco di dati non compressi ad esso legato, ma anche tutti quelli successivi.<\/p>\n<p><\/p>\n<p>Esistono approcci per risolvere questo problema:<\/p>\n<p><\/p>\n<ol>\n<li>Prevenire la comparsa del problema \u2014 aggiungere ridondanza ai dati compressi che permetta di individuare e correggere errori; di questo ne parleremo pi\u00f9 tardi;<\/li>\n<li>Minimizzare le conseguenze in caso di insorgenza del problema<br \/>\nAbbiamo gi\u00e0 detto in precedenza che \u00e8 possibile comprimere ogni blocco di dati in modo indipendente, e in questo caso il problema si risolve da solo (il danneggiamento dei dati di un blocco porter\u00e0 alla perdita dei dati solo di quel blocco). Tuttavia, questo \u00e8 un caso estremo, in cui la compressione dei dati diventa inefficace. L'estremo opposto: utilizzare i nostri 4 MB di chip come un unico archivio, il che ci dar\u00e0 un'ottima compressione, ma conseguenze disastrose in caso di danneggiamento dei dati.<br \/>\n<em>S\u00ec, \u00e8 necessaria una compromesso in termini di affidabilit\u00e0. Ma bisogna ricordare che stiamo sviluppando un formato di archiviazione dei dati per la memoria non volatile con un BER estremamente basso e un periodo dichiarato di conservazione dei dati di 20 anni.<\/em><\/li>\n<\/ol>\n<p><\/p>\n<p>Durante gli esperimenti ho scoperto che le perdite di livello di compressione pi\u00f9 o meno significative iniziano con blocchi di dati compressi di dimensioni inferiori a 10 KB.<br \/>\n\u00c8 stato menzionato in precedenza che la memoria utilizzata ha un'organizzazione a pagine, non vedo motivi per cui non si dovrebbe utilizzare la corrispondenza 'una pagina - un blocco di dati compressi'.<\/p>\n<p><\/p>\n<p>Quindi, la dimensione minima ragionevole della pagina \u00e8 di 16 KB (con un margine per le informazioni di servizio). Tuttavia, una pagina cos\u00ec piccola impone sostanziali limitazioni sulla dimensione massima della registrazione.<\/p>\n<p><\/p>\n<p>Anche se al momento non prevedo registrazioni superiori a qualche kilobyte in forma compressa, ho deciso di utilizzare pagine di dimensioni 32 KB (in totale ci saranno 128 pagine per chip).<\/p>\n<p><\/p>\n<p><strong>Sommario:<\/strong><\/p>\n<p><\/p>\n<ul>\n<li>I dati vengono memorizzati in forma compressa utilizzando zlib (deflate);<\/li>\n<li>Per ogni registrazione impostiamo Z_SYNC_FLUSH;<\/li>\n<li>Ogni registrazione compressa viene ridimensionata nei byte finali <em>(ad esempio, 0x00, 0x00, 0xff, 0xff)<\/em>; nell'intestazione indichiamo quanti byte abbiamo ridotto;<\/li>\n<li>I dati sono memorizzati in pagine da 32 KB; all'interno della pagina c'\u00e8 un flusso continuo di dati compressi; su ogni pagina la compressione ricomincia da capo.<\/li>\n<\/ul>\n<p><\/p>\n<p>E prima di terminare con la compressione, vorrei sottolineare che i dati compressi ci danno solo pochi byte per registrazione, quindi \u00e8 estremamente importante non gonfiare le informazioni di servizio, ogni byte conta qui.<\/p>\n<p><\/p>\n<h3 id=\"hranenie-zagolovkov-dannyh\">Memorizzazione degli header dei dati<\/h3>\n<p><\/p>\n<p>Poich\u00e9 abbiamo registrazioni di lunghezza variabile, dobbiamo in qualche modo determinare la posizione\/le frontiere delle registrazioni.<\/p>\n<p><\/p>\n<p>Conosco tre approcci:<\/p>\n<p><\/p>\n<ol>\n<li>Tutte le registrazioni sono memorizzate in un flusso continuo, all'inizio c'\u00e8 l'header della registrazione che contiene la lunghezza, e poi la registrazione stessa.<br \/>\nIn questa variante, sia gli header che i dati possono avere lunghezza variabile.<br \/>\nIn sostanza, otteniamo una lista concatenata, che viene utilizzata ovunque;<\/li>\n<li>Gli header e le registrazioni stesse sono memorizzati in flussi separati.<br \/>\nUtilizzando header di lunghezza fissa, otteniamo che la corruzione di un header non influisce sugli altri.<br \/>\nUn approccio simile \u00e8 utilizzato, ad esempio, in molti sistemi di file;<\/li>\n<li>Le registrazioni sono memorizzate in un flusso continuo, il confine della registrazione \u00e8 definito da un certo marcatore (simbolo\/ sequenza di simboli che sono vietati all'interno dei blocchi di dati). Se all'interno di una registrazione appare un marcatore, allora lo sostituiamo con una certa sequenza (lo scappiamo).<br \/>\nUn approccio simile \u00e8 utilizzato, ad esempio, nel protocollo PPP.<\/li>\n<\/ol>\n<p><\/p>\n<p>Facciamo un esempio.<\/p>\n<p><\/p>\n<p>Opzione 1:<br \/>\n<img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/e5a9676ca21eaca07e64ddf9fcd9f2eb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nQui \u00e8 tutto molto semplice: conoscendo la lunghezza della registrazione possiamo calcolare l'indirizzo del prossimo header. In questo modo ci spostiamo tra gli header fino a incontrare un'area riempita di 0xff (spazio libero) o la fine della pagina.<\/p>\n<p><\/p>\n<p>Opzione 2:<br \/>\n<img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/4fed4860fb659ab6042be9aeeb5c041a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nA causa della lunghezza variabile dei record, non possiamo anticipare quanti record (e quindi intestazioni) avremo bisogno per pagina. Possiamo distribuire le intestazioni e i dati su pagine diverse, ma preferisco un altro approccio: posizioniamo sia le intestazioni (di dimensione fissa) all'inizio della pagina, sia i dati (di lunghezza variabile) alla fine. Non appena si \"incontrano\" (non c'\u00e8 spazio sufficiente per un nuovo record), consideriamo questa pagina completa.<\/p>\n<p><\/p>\n<p>Opzione 3:<br \/>\n<img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/dfb64009aa7781fcb8a47423ff4160c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNon c'\u00e8 bisogno di mantenere nel titolo la lunghezza o altre informazioni sulla posizione dei dati; bastano i marcatori che indicano i confini delle registrazioni. Tuttavia, i dati devono essere elaborati durante la scrittura\/lettura.<br \/>\nCome marcatore, userei 0xff (con cui \u00e8 riempita la pagina dopo l'erase); in questo modo, l'area libera non sar\u00e0 interpretata come dati.<\/p>\n<p><\/p>\n<p>Tabella comparativa:<\/p>\n<p><\/p>\n<p>Opzione 1<br \/>\nOpzione 2<br \/>\nOpzione 3<\/p>\n<p><strong>Resistenza agli errori<\/strong><br \/>\n&#8212;<br \/>\n+<br \/>\n+<\/p>\n<p><strong>Compattezza<\/strong><br \/>\n+<br \/>\n&#8212;<br \/>\n+<\/p>\n<p><strong>Difficolt\u00e0 di implementazione<\/strong><br \/>\n*<br \/>\n**<br \/>\n**<\/p>\n<p><\/p>\n<p>La variante 1 ha un grave difetto: se uno dei titoli \u00e8 danneggiato, si distrugge l'intera catena successiva. Le altre varianti consentono di recuperare una parte dei dati anche in caso di danni diffusi.<br \/>\nTuttavia, \u00e8 opportuno ricordare che abbiamo deciso di memorizzare i dati in forma compressa. In ogni caso, perdiamo tutti i dati sulla pagina dopo un record \"corrotto\", quindi anche se il segno in tabella \u00e8 negativo, non lo consideriamo.<\/p>\n<p><\/p>\n<p>Compattezza:<\/p>\n<p><\/p>\n<ul>\n<li>nella prima variante dobbiamo memorizzare solo la lunghezza nel titolo; utilizzando interi di lunghezza variabile, nella maggior parte dei casi possiamo limitarci a un byte;<\/li>\n<li>nella seconda variante dobbiamo memorizzare l'indirizzo iniziale e la lunghezza; la registrazione deve avere dimensione fissa, la stimo in 4 byte per registrazione (due byte per l'offset e due byte per la lunghezza);<\/li>\n<li>nella terza variante \u00e8 sufficiente un solo carattere per indicare l'inizio della registrazione, pi\u00f9 la registrazione stessa aumenter\u00e0 di 1-2% a causa dell'escaping. In generale, si ha un parit\u00e0 approssimativa con la prima variante.<\/li>\n<\/ul>\n<p><\/p>\n<p>Inizialmente ho considerato la seconda variante come principale (e ho persino scritto una sua implementazione). Ho rinunciato a essa solo quando ho deciso definitivamente di utilizzare la compressione.<\/p>\n<p><\/p>\n<p><em>Forse, un giorno, utilizzer\u00f2 effettivamente una tale soluzione. Ad esempio, se dovessi gestire la memorizzazione di dati per una nave che viaggia tra la Terra e Marte \u2014 ci sarebbero totalmente altri requisiti in termini di affidabilit\u00e0, radiazioni spaziali, ...<\/em><\/p>\n<p><\/p>\n<p>Quanto alla terza variante: le ho dato due stelle per la difficolt\u00e0 di implementazione semplicemente perch\u00e9 non mi piace occuparmi dell'escaping, della modifica della lunghezza in corso e simili. S\u00ec, forse \u00e8 un pregiudizio, ma il codice dovr\u00f2 scriverlo io \u2014 perch\u00e9 costringermi a fare qualcosa che non mi piace.<\/p>\n<p><\/p>\n<p><strong>Sommario:<\/strong> scegliamo la modalit\u00e0 di memorizzazione come catene \"intestazione di lunghezza - dati di lunghezza variabile\" per la sua efficienza e semplicit\u00e0 di implementazione.<\/p>\n<p><\/p>\n<h3 id=\"ispolzovanie-bitovyh-poley-dlya-kontrolya-uspeshnosti-operaciy-zapisi\">Utilizzo dei campi bit per il controllo del successo delle operazioni di scrittura<\/h3>\n<p><\/p>\n<p>Non ricordo gi\u00e0 pi\u00f9 dove ho visto l'idea, ma \u00e8 circa cos\u00ec:<br \/>\nPer ogni registrazione riserviamo alcuni bit per memorizzare i flag.<br \/>\n<em>Come abbiamo detto prima, dopo l'erase tutti i bit sono impostati su 1, e possiamo cambiare 1 in 0, ma non viceversa.<\/em> Quindi, per \"flag non impostato\" utilizziamo 1, per \"flag impostato\" \u2014 0.<\/p>\n<p><\/p>\n<p>Ecco come pu\u00f2 apparire l'inserimento di una registrazione di lunghezza variabile nella flash:<\/p>\n<p><\/p>\n<ol>\n<li>Impostiamo il flag \"scrittura della lunghezza iniziata\";<\/li>\n<li>Scriviamo la lunghezza;<\/li>\n<li>Impostiamo il flag \"scrittura dei dati iniziata\";<\/li>\n<li>Scriviamo i dati;<\/li>\n<li>Impostiamo il flag \"scrittura completata\".<\/li>\n<\/ol>\n<p><\/p>\n<p>Inoltre, avremo un flag \"si \u00e8 verificato un errore\", per un totale di 4 flag bit.<\/p>\n<p><\/p>\n<p>In questo caso abbiamo due stati stabili \"1111\" \u2014 scrittura non iniziata e \"1000\" \u2014 scrittura eseguita con successo; nel caso di un'interruzione imprevista del processo di scrittura otterremo stati intermedi, che poi saremo in grado di rilevare e gestire.<\/p>\n<p><\/p>\n<p>L'approccio \u00e8 interessante, ma protegge solo da un'improvvisa interruzione di corrente e da simili malfunzionamenti, che \u00e8 certamente importante, tuttavia non \u00e8 l'unica (e nemmeno la principale) causa di possibili guasti.<\/p>\n<p><\/p>\n<p><strong>Sommario:<\/strong> Andiamo avanti nella ricerca di una buona soluzione.<\/p>\n<p><\/p>\n<h3 id=\"kontrolnye-summy\">Checksum<\/h3>\n<p><\/p>\n<p>Le checksum offrono anche la possibilit\u00e0 di accertarsi (con una probabilit\u00e0 sufficiente) che stiamo leggendo esattamente ci\u00f2 che doveva essere scritto. E, a differenza dei campi bit trattati sopra, esse funzionano sempre.<\/p>\n<p><\/p>\n<p>Se consideriamo l'elenco delle potenziali fonti di problemi di cui abbiamo parlato sopra, la checksum \u00e8 in grado di rilevare un errore indipendentemente dalla sua origine <em>(eccezion fatta, forse, per alieni malintenzionati \u2014 essi possono falsificare anche la checksum)<\/em>.<\/p>\n<p><\/p>\n<p>Quindi, se il nostro obiettivo \u00e8 verificare che i dati siano intatti, le checksum sono un\u2019ottima idea.<\/p>\n<p><\/p>\n<p>La scelta dell'algoritmo per il calcolo della checksum non ha sollevato problemi \u2014 CRC. Da un lato, le propriet\u00e0 matematiche permettono di rilevare errori di alcuni tipi al 100%, dall'altro \u2014 su dati casuali, in genere, questo algoritmo mostra una probabilit\u00e0 di collisione non molto superiore al limite teorico. <img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/bf9cca3564db7d9d03d3ce49642d3a71.jpg\" style=\"display:block;margin: 0 auto;\" \/>. Anche se non \u00e8 l'algoritmo pi\u00f9 veloce e non sempre il migliore in termini di numero di collisioni, ha una qualit\u00e0 molto importante: nei test che ho svolto non ho trovato schemi su cui fallisse chiaramente. La stabilit\u00e0 \u00e8 la qualit\u00e0 principale in questo caso.<\/p>\n<p><\/p>\n<p>Esempio di ricerca approfondita: <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo.html\">parte 1<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo2.html\">parte 2<\/a><\/noindex> <em>(link a narod.ru, scusate)<\/em>.<\/p>\n<p><\/p>\n<p>Tuttavia, il compito di scegliere una somma di controllo non \u00e8 finito; il CRC \u00e8 un intero insieme di somme di controllo. \u00c8 necessario decidere sulla lunghezza e poi scegliere un polinomio.<\/p>\n<p><\/p>\n<p>La scelta della lunghezza della somma di controllo non \u00e8 una questione semplice, come sembra a prima vista.<\/p>\n<p><\/p>\n<p>Illustro:<br \/>\nSupponiamo che ci sia una probabilit\u00e0 di errore in ogni byte <img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/89a9be2edf2a43cc9115eba37fa02fc8.jpg\" style=\"display:block;margin: 0 auto;\" \/> e una somma di controllo ideale; calcoliamo il numero medio di errori su un milione di registrazioni:<\/p>\n<p><\/p>\n<p>Dati, byte<br \/>\nSomma di controllo, byte<br \/>\nErrori non rilevati<br \/>\nFalsi allarmi di errore<br \/>\nTotale risposte errate<\/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>Sembra semplice: scegli la lunghezza della somma di controllo in base alla lunghezza dei dati protetti, con un minimo di risposte errate, e il gioco \u00e8 fatto.<\/p>\n<p><\/p>\n<p>Tuttavia, con somme di controllo corte si presenta un problema: sebbene esse rilevino bene gli errori di bit singoli, possono con una probabilit\u00e0 piuttosto alta accettare come valide dati completamente casuali. Su Habr \u00e8 gi\u00e0 stato pubblicato un articolo che descrive <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/428746\/\">il problema nella vita reale<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Quindi, per rendere praticamente impossibile una corrispondenza casuale della somma di controllo, \u00e8 necessario utilizzare somme di controllo di almeno 32 bit <em>(per lunghezze superiori a 64 bit si usano generalmente funzioni hash crittografiche)<\/em>.<\/p>\n<p><\/p>\n<p>Nonostante io abbia scritto in precedenza che bisogna risparmiare spazio con tutti i mezzi, alla fine utilizzeremo una somma di controllo a 32 bit (16 bit sono pochi, la probabilit\u00e0 di collisione \u00e8 superiore allo 0,01%; e 24 bit, come si suol dire, non funzionano n\u00e9 da una parte n\u00e9 dall'altra).<\/p>\n<p><\/p>\n<p>Qui potrebbe sorgere una obiezione: abbiamo risparmiato ogni byte nella scelta della compressione, solo per dare 4 byte subito? Non era meglio non comprimere e non aggiungere la somma di controllo? Certo che no, l'assenza di compressione <em>non implica<\/em>, che il controllo dell'integrit\u00e0 non ci sia utile.<\/p>\n<p><\/p>\n<p>Non inventeremo la bicicletta nella scelta del polinomio, ma utilizzeremo il popolare CRC-32C.<br \/>\nQuesto codice rileva 6 errori di bit in pacchetti fino a 22 byte (forse il caso pi\u00f9 comune per noi), 4 errori di bit in pacchetti fino a 655 byte (anch'esso un caso frequente per noi), 2 o qualsiasi numero dispari di errori di bit in pacchetti di qualsiasi lunghezza ragionevole.<\/p>\n<p>\n<b class=\"spoiler_title\">Se qualcuno \u00e8 interessato ai dettagli<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cyclic_redundancy_check\">Articolo di Wikipedia<\/a><\/noindex> su CRC.<\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0x8f6e37a0_len.txt\">Parametri del codice crc-32c<\/a><\/noindex> in <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/crc\/notes.html\">sito di Kupman<\/a><\/noindex> \u2014 forse il principale esperto di CRC al mondo.<\/p>\n<p><\/p>\n<p>In <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/networks\/dsn02\/dsn02_koopman.pdf\">il suo articolo<\/a><\/noindex> c'\u00e8 <noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0xfa567d89_len.txt\">un altro codice interessante<\/a><\/noindex>, che fornisce parametri leggermente migliori per le lunghezze di pacchetto a noi rilevanti, ma non ho trovato la differenza significativa, e mi sento abbastanza competente da scegliere un codice personalizzato invece di uno standard e ben studiato.<\/p>\n<p><\/p>\n<p>Inoltre, poich\u00e9 i nostri dati sono compressi, sorge la domanda: calcolare il checksum dei dati compressi o non compressi?<\/p>\n<p><\/p>\n<p>Argomenti \"a favore\" del calcolo del checksum dei dati non compressi:<\/p>\n<p><\/p>\n<ul>\n<li>alla fine dobbiamo controllare l'integrit\u00e0 dei dati memorizzati \u2014 ecco che lo verifichiamo direttamente (e contemporaneamente saranno controllati possibili errori nell'implementazione della compressione\/decompressione, danneggiamenti causati da memoria danneggiata, ecc.);<\/li>\n<li>l'algoritmo deflate in zlib ha un'implementazione abbastanza matura e <em>non dovrebbe<\/em> pu\u00f2 fallire con dati in ingresso \"corrotti\", inoltre spesso \u00e8 in grado di rilevare autonomamente errori nel flusso di ingresso, riducendo la probabilit\u00e0 complessiva di non rilevamento di errori (ho eseguito un test invertendo un singolo bit in un breve record, zlib ha rilevato l'errore circa un terzo delle volte).<\/li>\n<\/ul>\n<p><\/p>\n<p>Argomenti \"contro\" il calcolo del checksum dei dati non compressi:<\/p>\n<p><\/p>\n<ul>\n<li>Il CRC \u00e8 \"affilato\" specificamente per errori bit per bit poco numerosi, caratteristici della memoria flash (un errore bit in un flusso compresso pu\u00f2 provocare una grande modifica nel flusso di uscita, in cui, teoricamente, possiamo \"catturare\" una collisione);<\/li>\n<li>non mi piace molto l'idea di passare al decompressore dati potenzialmente danneggiati, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cvedetails.com\/vulnerability-list\/vendor_id-72\/product_id-1820\/GNU-Zlib.html\">chi lo sa<\/a><\/noindex>, come reagir\u00e0.<\/li>\n<\/ul>\n<p><\/p>\n<p>In questo progetto ho deciso di allontanarmi dalla pratica comune di memorizzare il checksum dei dati non compressi.<\/p>\n<p><\/p>\n<p><strong>Sommario:<\/strong> utilizziamo CRC-32C, calcolando il checksum dai dati nella forma in cui vengono registrati nella flash (dopo la compressione).<\/p>\n<p><\/p>\n<h3 id=\"izbytochnost\">Ridondanza<\/h3>\n<p><\/p>\n<p>L'uso della codifica ridondante non consente, ovviamente, di escludere la perdita di dati; tuttavia, pu\u00f2 ridurre notevolmente (spesso di diversi ordini di grandezza) la probabilit\u00e0 di perdita di dati irreversibile.<\/p>\n<p><\/p>\n<p>Possiamo utilizzare diversi tipi di ridondanza per correggere gli errori.<br \/>\nI codici di Hamming possono correggere errori di bit singoli, i codici Reed-Solomon sono simbolici, mentre pi\u00f9 copie di dati insieme a checksum o codifiche simili a RAID-6 possono aiutare a ripristinare i dati anche in caso di danni estesi.<br \/>\nInizialmente ero favorevole a un ampio utilizzo della codifica resistente agli errori, ma poi ho capito che prima di tutto era necessario avere un'idea degli errori da cui vogliamo proteggerci e poi scegliere la codifica.<\/p>\n<p><\/p>\n<p>Abbiamo gi\u00e0 detto che gli errori devono essere rilevati il prima possibile. In quali momenti possiamo incorrere in errori?<\/p>\n<p><\/p>\n<ol>\n<li>Registrazione incompleta (per qualche motivo, durante la scrittura, l'alimentazione \u00e8 andata via, Raspberry \u00e8 andato in crash, ...)<br \/>\nPurtroppo, in caso di un tale errore, rimane solo da ignorare le registrazioni non valide e considerare i dati persi;<\/li>\n<li>Errori di scrittura (per qualche motivo, nella memoria flash \u00e8 stato registrato qualcosa di diverso da quello che si intendeva registrare)<br \/>\nPossiamo rilevare immediatamente tali errori se effettuiamo una lettura di controllo subito dopo la scrittura;<\/li>\n<li>Distorsione dei dati nella memoria durante la conservazione;<\/li>\n<li>Errori di lettura<br \/>\nPer correggerli, \u00e8 sufficiente ripetere pi\u00f9 volte la lettura in caso di disaccordo del checksum.<\/li>\n<\/ol>\n<p><\/p>\n<p>Cio\u00e8, solo gli errori di terzo tipo (corruzione spontanea dei dati durante la conservazione) non possono essere corretti senza codifica resistente agli errori. Si ritiene che tali errori siano estremamente improbabili.<\/p>\n<p><\/p>\n<p><strong>Sommario:<\/strong> Si \u00e8 deciso di rinunciare alla codifica ridondante, ma se l'esperienza mostrer\u00e0 l'erroneit\u00e0 di questa decisione, torneremo a considerare la questione (con statistiche accumulate sui guasti che permetteranno di scegliere il tipo ottimale di codifica).<\/p>\n<p><\/p>\n<h3 id=\"prochee\">Altro<\/h3>\n<p><\/p>\n<p>Naturalmente, il formato dell'articolo non consente di giustificare ogni bit nel formato <em>(e io ho gi\u00e0 esaurito le forze)<\/em>, quindi passer\u00f2 rapidamente ad alcuni punti non trattati in precedenza.<\/p>\n<p><\/p>\n<ul>\n<li>Deciso di rendere tutte le pagine \"equivalenti\"<br \/>\nCio\u00e8 non ci saranno pagine speciali con metadati, flussi separati, ecc., ma un flusso unico che riscrive tutte le pagine in sequenza.<br \/>\nQuesto garantisce un'usura uniforme delle pagine, l'assenza di un unico punto di guasto e, semplicemente, \u00e8 gradito;<\/li>\n<li>\u00c8 necessario prevedere la versioning del formato.<br \/>\nUn formato senza numero di versione nell'intestazione \u00e8 male!<br \/>\nBasta aggiungere nel'intestazione della pagina un campo con un certo Magic Number (firma), che indicher\u00e0 la versione del formato utilizzato. <em>(non penso che ce ne saranno anche solo una decina nella pratica)<\/em>;<\/li>\n<li>Utilizzare per le registrazioni (che sono moltissime) un'intestazione di lunghezza variabile, cercando di mantenerla a 1 byte nella maggior parte dei casi;<\/li>\n<li>Per codificare la lunghezza dell'intestazione e la lunghezza della parte tagliata della registrazione compressa, utilizzare codici binari di lunghezza variabile.<\/li>\n<\/ul>\n<p><\/p>\n<p>\u00c8 stato molto utile <noindex><a rel=\"nofollow\" href=\"https:\/\/planetcalc.com\/2481\/\">il generatore online<\/a><\/noindex> di codici Huffman. Letteralmente in pochi minuti sono riuscito a trovare i codici di lunghezza variabile necessari.<\/p>\n<p><\/p>\n<h1 id=\"anchorformatanchoropisanie-formata-hraneniya-dannyh\"><noindex><a rel=\"nofollow\" name=\"format\"><\/a><\/noindex>Descrizione del formato di memorizzazione dei dati<\/h1>\n<p><\/p>\n<h2 id=\"byte-order\">Byte order<\/h2>\n<p><\/p>\n<p>I campi di dimensioni superiori a un byte sono memorizzati in formato big-endian (ordine dei byte di rete), cio\u00e8 0x1234 \u00e8 registrato come 0x12, 0x34.<\/p>\n<p><\/p>\n<h2 id=\"delenie-na-stranicy\">Divisione in pagine<\/h2>\n<p><\/p>\n<p>Tutta la memoria flash \u00e8 suddivisa in pagine di dimensione uguale.<\/p>\n<p><\/p>\n<p>La dimensione della pagina per impostazione predefinita \u00e8 di 32 KB, ma non superiore a 1\/4 della dimensione totale del chip di memoria (per un chip da 4 MB si ottengono 128 pagine).<\/p>\n<p><\/p>\n<p>Ogni pagina memorizza dati indipendentemente dalle altre (cio\u00e8 i dati di una pagina non rimandano ai dati di un'altra pagina).<\/p>\n<p><\/p>\n<p>Tutte le pagine sono numerate in ordine naturale (in ordine crescente degli indirizzi), a partire dal numero 0 (la pagina zero inizia con l'indirizzo 0, la prima con 32 KB, la seconda con 64 KB, ecc.)<\/p>\n<p><\/p>\n<p>Il chip di memoria \u00e8 utilizzato come buffer circolare (ring buffer), quindi prima la scrittura avviene nella pagina numero 0, poi nella pagina numero 1, &#8230;, quando l'ultima pagina \u00e8 piena, inizia un nuovo ciclo e la scrittura prosegue dalla pagina zero.<\/p>\n<p><\/p>\n<h2 id=\"vnutri-stranicy\">All'interno della pagina<\/h2>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/39b45f83dc46bb2fd7081ed2a0b638c9.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAll'inizio della pagina si trova un'intestazione di 4 byte, poi il checksum dell'intestazione (CRC-32C), dopodich\u00e9 sono memorizzate le voci nel formato &#171; intestazione, dati, checksum &#187;.<\/p>\n<p><\/p>\n<p>L'intestazione della pagina (nello schema di colore verde sporco) \u00e8 composta da:<\/p>\n<p><\/p>\n<ul>\n<li>un campo Magic Number di due byte (che \u00e8 anche l'indicatore della versione del formato)<br \/>\nper la versione attuale del formato \u00e8 considerato come <code>0xed00 \u2295 numero di pagina<\/code>;<\/li>\n<li>contro il contatore a due byte &#171;Versione della pagina&#187; (numero di ciclo di riscrittura della memoria).<\/li>\n<\/ul>\n<p><\/p>\n<p>Le registrazioni nella pagina vengono memorizzate in forma compressa (viene utilizzato l'algoritmo deflate). Tutte le registrazioni su una pagina vengono compresse in un unico flusso (viene utilizzato un dizionario comune); su ogni nuova pagina la compressione ricomincia da capo. Ci\u00f2 significa che per la decompresione di qualsiasi registrazione sono necessarie tutte le registrazioni precedenti su quella pagina (e solo su quella).<\/p>\n<p><\/p>\n<p>Ogni registrazione verr\u00e0 compressa con il flag Z_SYNC_FLUSH, e alla fine del flusso compresso si troveranno 4 byte 0x00, 0x00, 0xff, 0xff, preceduti, forse, da uno o due byte zero.<br \/>\nQuesta sequenza (di lunghezza 4, 5 o 6 byte) viene scartata durante la scrittura nella memoria flash.<\/p>\n<p><\/p>\n<p>L'intestazione della registrazione consiste in 1, 2 o 3 byte che memorizzano:<\/p>\n<p><\/p>\n<ul>\n<li>un bit (T), che indica il tipo di registrazione: 0 \u2014 contesto, 1 \u2014 registro;<\/li>\n<li>campo di lunghezza variabile (S) da 1 a 7 bit, che definisce la lunghezza dell'intestazione e il &#171;codice finale&#187; che deve essere aggiunto alla registrazione per l'espansione;<\/li>\n<li>la lunghezza della registrazione (L).<\/li>\n<\/ul>\n<p><\/p>\n<p>Tabella dei valori S:<\/p>\n<p><\/p>\n<p>S<br \/>\nLunghezza dell'intestazione, byte<br \/>\nScartato durante la scrittura, byte<\/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>Ho provato a illustrare, non so quanto sia chiaro:<br \/>\n<img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/8c9b739eec5af4429395c32b4ba4044a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIn giallo \u00e8 indicato il campo T, in bianco il campo S, in verde L (lunghezza dei dati compressi in byte), in blu i dati compressi, in rosso i byte finali dei dati compressi che non vengono scritti nella memoria flash.<\/p>\n<p><\/p>\n<p>Pertanto, le intestazioni delle registrazioni della lunghezza pi\u00f9 comune (fino a 63+5 byte in forma compressa) potranno essere salvate in un solo byte.<\/p>\n<p><\/p>\n<p>Dopo ogni registrazione viene memorizzata una somma di controllo CRC-32C, il cui valore iniziale (init) \u00e8 l'inverso della somma di controllo precedente.<\/p>\n<p><\/p>\n<p><em>Il CRC ha la propriet\u00e0 della &#171;continuit\u00e0&#187;, funziona (pi\u00f9 o meno l'inversione dei bit nel processo) con questa formula: <img decoding=\"async\" alt=\"La mia implementazione di un buffer circolare nella memoria NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/b194d6a3d18ae2f4ae5d4054a93c0ea3.jpg\" style=\"display:block;margin: 0 auto;\" \/>.<br \/>\nQuindi, di fatto, calcoliamo il CRC di tutti i byte precedenti delle intestazioni e dei dati su questa pagina.<\/em><\/p>\n<p><\/p>\n<p>Immediatamente dopo la somma di controllo si trova l'intestazione della registrazione successiva.<\/p>\n<p><\/p>\n<p>L'intestazione \u00e8 progettata in modo che il suo primo byte sia sempre diverso da 0x00 e 0xff (se invece del primo byte dell'intestazione troviamo 0xff, significa che si tratta di un'area non utilizzata; 0x00 segnala un errore).<\/p>\n<p><\/p>\n<h2 id=\"primernye-algoritmy\">Algoritmi approssimativi<\/h2>\n<p><\/p>\n<h3 id=\"chtenie-iz-flesh-pamyati\">Lettura dalla memoria flash<\/h3>\n<p><\/p>\n<p>Qualsiasi lettura avviene con verifica della somma di controllo.<br \/>\nSe il checksum non corrisponde, la lettura viene ripetuta pi\u00f9 volte nella speranza di riuscire a leggere i dati corretti.<\/p>\n<p><\/p>\n<p><em>(questo ha senso, Linux non memorizza nella cache la lettura dalla NOR Flash, verificato)<\/em><\/p>\n<p><\/p>\n<h3 id=\"zapis-v-flesh-pamyat\">Scrittura nella memoria flash<\/h3>\n<p><\/p>\n<p>Stiamo scrivendo i dati.<br \/>\nLi stiamo leggendo.<\/p>\n<p><\/p>\n<p>Se i dati letti non corrispondono a quelli scritti, riempiamo l'area di zeri e segnaliamo un errore.<\/p>\n<p><\/p>\n<h3 id=\"podgotovka-novoy-mikroshemy-k-rabote\">Preparazione del nuovo chip per il lavoro<\/h3>\n<p><\/p>\n<p>Per l'inizializzazione, viene scritto nell'ultima (in realt\u00e0 zero) pagina un'intestazione con la versione 1.<br \/>\nDopo di ci\u00f2, in questa pagina viene scritto il contesto iniziale (contiene UUID della macchina e impostazioni predefinite). <\/p>\n<p><\/p>\n<p>Tutto, la memoria flash \u00e8 pronta per il lavoro.<\/p>\n<p><\/p>\n<h3 id=\"zagruzka-avtomata\">Caricamento della macchina<\/h3>\n<p><\/p>\n<p>Durante il caricamento vengono letti i primi 8 byte di ogni pagina (intestazione + CRC); le pagine con un Magic Number sconosciuto o CRC errato vengono ignorate.<br \/>\nDalle &#171;pagine corrette&#187; vengono selezionate le pagine con la versione massima, da esse viene prelevata la pagina con il numero pi\u00f9 alto.<br \/>\nSi legge la prima voce, si verifica la correttezza del CRC, \u00e8 presente il flag &#171;contesto&#187;. Se tutto \u00e8 in ordine, questa pagina \u00e8 considerata attuale. Se no, si torna a quella precedente, fino a trovare una pagina &#171;viva&#187;.<br \/>\nnella pagina trovata leggiamo tutte le voci, quelle con il flag &#171;contesto&#187; vengono applicate.<br \/>\nSalviamo il dizionario zlib (necessario per la scrittura supplementare in questa pagina).<\/p>\n<p><\/p>\n<p>Tutto, caricamento completato, contesto ripristinato, si pu\u00f2 lavorare.<\/p>\n<p><\/p>\n<h3 id=\"dobavlenie-zapisi-v-zhurnal\">Aggiunta di una registrazione al giornale<\/h3>\n<p><\/p>\n<p>Comprimiamo la registrazione con il dizionario corretto, specificando Z_SYNC_FLUSH. Verifichiamo se la registrazione compressa pu\u00f2 essere collocata nella pagina attuale.<br \/>\nSe non ci sta (o ci sono stati errori di CRC nella pagina) iniziamo una nuova pagina (vedi sotto).<br \/>\nScriviamo la registrazione e il CRC. Se si verifica un errore, iniziamo una nuova pagina.<\/p>\n<p><\/p>\n<h3 id=\"novaya-stranica\">Nuova pagina<\/h3>\n<p><\/p>\n<p>Selezioniamo una pagina libera con il numero minimo (consideriamo libera una pagina con un checksum errato nell'intestazione o con una versione inferiore a quella attuale). Se non ci sono tali pagine, selezioniamo la pagina con il numero minimo tra quelle che hanno la versione uguale a quella attuale.<br \/>\nEffettuiamo un erase sulla pagina selezionata. Confrontiamo il contenuto con 0xff. Se qualcosa non va, prendiamo la prossima pagina libera, e cos\u00ec via.<br \/>\nNella pagina cancellata scriviamo l'intestazione, la prima registrazione \u00e8 lo stato corrente del contesto, la successiva \u00e8 la registrazione non scritta del giornale (se disponibile).<\/p>\n<p><\/p>\n<h1 id=\"primenimost-formata\">Applicabilit\u00e0 del formato<\/h1>\n<p><\/p>\n<p>A mio avviso, \u00e8 stato realizzato un buon formato per la memorizzazione di qualsiasi flusso di informazioni pi\u00f9 o meno comprimibile (testo semplice, JSON, MessagePack, CBOR, forse protobuf) nella NOR Flash.<\/p>\n<p><\/p>\n<p>Ovviamente, il formato \u00e8 &#171;ottimizzato&#187; per SLC NOR Flash.<\/p>\n<p><\/p>\n<p>Non dovrebbe essere utilizzato con supporti ad alto BER, come NAND o MLC NOR <em>(esiste davvero una memoria del genere in vendita? Ho trovato solo riferimenti in studi sui codici di correzione)<\/em>.<\/p>\n<p><\/p>\n<p>Inoltre, non dovrebbe essere utilizzato con dispositivi che hanno il proprio FTL: USB flash, SD, MicroSD, ecc. <em>(per tale memoria ho creato un formato con dimensione pagina di 512 byte, una firma all'inizio di ogni pagina e numeri di registrazione unici \u2014 a volte da una flash &#171;glitchata&#187; \u00e8 stato possibile recuperare tutti i dati tramite una semplice lettura sequenziale)<\/em>.<\/p>\n<p><\/p>\n<p>A seconda delle esigenze, il formato pu\u00f2 essere utilizzato senza modifiche su chiavette da 128Kbit (16Kb) a 1Gbit (128Mb). Se lo si desidera, pu\u00f2 essere usato anche su chip di dimensioni maggiori, ma probabilmente \u00e8 necessario correggere la dimensione della pagina. <em>(Ma qui sorge gi\u00e0 la questione della sostenibilit\u00e0 economica, il prezzo della NOR Flash di grande capacit\u00e0 non \u00e8 allettante)<\/em>.<\/p>\n<p><\/p>\n<p>Se a qualcuno il formato \u00e8 sembrato interessante e desidera utilizzarlo in un progetto open source \u2014 scrivete, cercher\u00f2 di trovare tempo, sistemare il codice e condividerlo su github.<\/p>\n<p><\/p>\n<h1 id=\"zaklyuchenie\">Conclusione<\/h1>\n<p><\/p>\n<p>Come vediamo, alla fine il formato si \u00e8 rivelato semplice <em>e persino noioso<\/em>.<\/p>\n<p><\/p>\n<p>Nell'articolo \u00e8 difficile riflettere l'evoluzione del mio punto di vista, ma credetemi: inizialmente volevo creare qualcosa di complesso, indistruttibile, capace di sopravvivere anche dopo un'esplosione nucleare nelle vicinanze. Tuttavia, la ragione (spero) ha comunque prevalso e gradualmente le priorit\u00e0 si sono spostate verso la semplicit\u00e0 e la compattezza.<\/p>\n<p><\/p>\n<p>\u00c8 possibile che mi sia sbagliato? Certo. Potrebbe benissimo risultare, ad esempio, che abbiamo acquistato un lotto di chip di scarsa qualit\u00e0. Oppure per qualche altro motivo l'attrezzatura non soddisfer\u00e0 le aspettative di affidabilit\u00e0.<\/p>\n<p><\/p>\n<p>Ho un piano per questo caso? Penso che dopo aver letto l'articolo non dubitate che ci sia un piano. E non uno solo.<\/p>\n<p><\/p>\n<p>Se vogliamo essere pi\u00f9 seri, il formato \u00e8 stato sviluppato sia come variante operativa che come &#171;prova&#187;.<\/p>\n<p><\/p>\n<p>Al momento, sulla mia scrivania, tutto funziona normalmente, letteralmente nei prossimi giorni la soluzione sar\u00e0 implementata <em>(circa)<\/em> su un centinaio di dispositivi, vedremo come si comporta in &#171;operazioni di combattimento&#187; (spero che il formato consenta di rilevare in modo affidabile i guasti; cos\u00ec sar\u00e0 possibile raccogliere statistiche complete). Tra qualche mese si potranno fare delle conclusioni. <em>(e se non va bene \u2014 anche prima)<\/em>.<\/p>\n<p><\/p>\n<p>Se alla fine dell'uso dovessero emergere problemi seri e fosse necessaria un'ulteriore lavorazione, ne scriver\u00f2 sicuramente.<\/p>\n<p><\/p>\n<h1 id=\"literatura\">Letteratura<\/h1>\n<p><\/p>\n<p>Non volevo redigere un lungo e noioso elenco di lavori utilizzati, alla fine il Google ce l'hanno tutti.<\/p>\n<p><\/p>\n<p>Qui ho deciso di lasciare l'elenco delle scoperte che mi sono sembrate particolarmente interessanti, ma man mano sono state inserite direttamente nel testo dell'articolo, e nell'elenco \u00e8 rimasto un solo punto:<\/p>\n<p><\/p>\n<ol>\n<li>Utilit\u00e0 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/madler\/infgen\/\">infgen<\/a><\/noindex> da autore zlib. \u00c8 in grado di visualizzare in modo comprensibile il contenuto degli archivi deflate\/zlib\/gzip. Se ti trovi a dover capire la struttura interna del formato deflate (o gzip) \u2014 lo consiglio vivamente.<\/li>\n<\/ol>\n<p>Fonte: <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.1.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\/it\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"it_IT\" \/>\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\/it\/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\udd47La mia implementazione del buffer circolare in NOR flash | ProHoster","description":"Precedenti Storia Ci sono distributori automatici sviluppati internamente. All'interno Raspberry Pi e un po' di circuiti su una scheda separata.","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/53751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=53751"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/53751\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=53751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=53751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=53751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}