{"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\/fr\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","title":{"rendered":"Ma mise en \u0153uvre d'un tampon circulaire dans la m\u00e9moire flash NOR","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h1 id=\"predystoriya\">Contexte<\/h1>\n<p><\/p>\n<p>Nous avons des distributeurs automatiques d\u00e9velopp\u00e9s en interne. \u00c0 l'int\u00e9rieur, un Raspberry Pi et un peu de c\u00e2blage sur une carte s\u00e9par\u00e9e. Ils sont connect\u00e9s \u00e0 un acceptateur de pi\u00e8ces, un acceptateur de billets, un terminal bancaire\u2026 Tout est g\u00e9r\u00e9 par un logiciel sur mesure. L'historique de fonctionnement est enregistr\u00e9 dans un journal sur une cl\u00e9 USB (MicroSD), qui est ensuite transf\u00e9r\u00e9 via Internet (via un modem USB) vers un serveur, o\u00f9 il est stock\u00e9 dans une base de donn\u00e9es. Les informations sur les ventes sont charg\u00e9es dans 1C, et il existe \u00e9galement une interface web simple pour le suivi, etc. <\/p>\n<p><\/p>\n<p>Donc, le journal est absolument n\u00e9cessaire \u2014 pour la comptabilit\u00e9 (avec les recettes, les ventes, etc.), et pour le monitoring (toutes sortes de pannes et autres circonstances impr\u00e9vues); c'est, on peut dire, toute l'information que nous avons sur ce distributeur automatique. <\/p>\n<p><\/p>\n<h1 id=\"problema\">Le probl\u00e8me<\/h1>\n<p><\/p>\n<p>Les cl\u00e9s USB se r\u00e9v\u00e8lent \u00eatre des dispositifs tr\u00e8s peu fiables. Elles tombent r\u00e9guli\u00e8rement en panne. Cela entra\u00eene \u00e0 la fois des temps d'arr\u00eat pour les distributeurs et (si pour une raison quelconque le journal ne pouvait pas \u00eatre transmis en ligne) des pertes de donn\u00e9es.<\/p>\n<p><\/p>\n<p><em>Ce n'est pas la premi\u00e8re fois que nous utilisons des cl\u00e9s USB, il y avait auparavant un autre projet avec plus d'une centaine d'appareils, o\u00f9 le journal \u00e9tait stock\u00e9 sur des cl\u00e9s USB, il y avait aussi des probl\u00e8mes de fiabilit\u00e9, et parfois le nombre de pannes par mois atteignait des dizaines. Nous avons essay\u00e9 diff\u00e9rentes cl\u00e9s, y compris des mod\u00e8les de marques avec de la m\u00e9moire SLC, certaines mod\u00e8les \u00e9tant plus fiables que d'autres, mais le remplacement des cl\u00e9s USB n'a pas r\u00e9solu le probl\u00e8me de mani\u00e8re radicale.<\/em><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <\/p>\n<p><strong>Attention !<\/strong> Longread ! Si vous n'\u00eates pas int\u00e9ress\u00e9 par le \u00abpourquoi\u00bb, mais uniquement par le \u00abcomment\u00bb, vous pouvez y aller tout de suite. <noindex><a rel=\"nofollow\" href=\"#format\">\u00e0 la fin<\/a><\/noindex> articles.<\/p>\n<p><\/p>\n<h1 id=\"reshenie\">Solution<\/h1>\n<p><\/p>\n<p>La premi\u00e8re chose qui me vient \u00e0 l'esprit : renoncer au MicroSD, opter par exemple pour un SSD, et d\u00e9marrer depuis l\u00e0. Th\u00e9oriquement, c'est possible, mais relativement cher, et ce n'est pas si fiable (un adaptateur USB-SATA est ajout\u00e9 ; les statistiques de d\u00e9faillance des SSD bon march\u00e9 ne sont pas tr\u00e8s r\u00e9jouissantes non plus).<\/p>\n<p><\/p>\n<p>Un disque dur USB ne semble pas non plus \u00eatre une solution particuli\u00e8rement attrayante.<\/p>\n<p><\/p>\n<p>C'est pourquoi nous avons opt\u00e9 pour cette solution : conserver le d\u00e9marrage \u00e0 partir du MicroSD, mais les utiliser en mode lecture seule, et stocker le journal de travail (et d'autres informations uniques pour chaque appareil \u2014 num\u00e9ro de s\u00e9rie, \u00e9talonnages des capteurs, etc.) ailleurs. <\/p>\n<p><\/p>\n<p>Le sujet des syst\u00e8mes de fichiers en lecture seule pour Raspberry Pi a d\u00e9j\u00e0 \u00e9t\u00e9 \u00e9tudi\u00e9 en profondeur, je ne m'attarderai pas sur les d\u00e9tails de mise en \u0153uvre dans cet article. <em>(mais si cela vous int\u00e9resse, peut-\u00eatre que j'\u00e9crirai un mini-article \u00e0 ce sujet)<\/em>. Le seul point \u00e0 noter est qu'en fonction de mon exp\u00e9rience personnelle et des retours d'exp\u00e9rience de ceux qui l'ont d\u00e9j\u00e0 int\u00e9gr\u00e9, il y a des gains en fiabilit\u00e9. Oui, il est impossible de se d\u00e9barrasser compl\u00e8tement des pannes, mais il est tout \u00e0 fait r\u00e9aliste de r\u00e9duire consid\u00e9rablement leur fr\u00e9quence. De plus, les cartes deviennent unifi\u00e9es, ce qui facilite visiblement le remplacement pour le personnel de maintenance.<\/p>\n<p><\/p>\n<h2 id=\"apparatnaya-chast\">Partie mat\u00e9rielle<\/h2>\n<p><\/p>\n<p>Il n'y avait pas de doutes particuliers sur le type de m\u00e9moire \u2014 NOR Flash.<br \/>\nArguments : <\/p>\n<p><\/p>\n<ul>\n<li>connexion simple (g\u00e9n\u00e9ralement un bus SPI, dont l'exp\u00e9rience d'utilisation est d\u00e9j\u00e0 en place, donc aucun probl\u00e8me \u00abmat\u00e9riel\u00bb \u00e0 pr\u00e9voir);<\/li>\n<li>prix d\u00e9risoire ;<\/li>\n<li>protocole de fonctionnement standard (une impl\u00e9mentation existe d\u00e9j\u00e0 dans le noyau Linux, si souhait\u00e9 on peut prendre une externe, qui existe aussi, ou m\u00eame \u00e9crire la sienne, c'est assez simple) ;<\/li>\n<li>fiabilit\u00e9 et endurance :<br \/>\nd'apr\u00e8s une fiche technique typique : les donn\u00e9es sont conserv\u00e9es pendant 20 ans, 100000 cycles d'effacement pour chaque bloc ;<br \/>\nd'apr\u00e8s des sources externes : BER tr\u00e8s bas, aucune n\u00e9cessit\u00e9 de codes de correction d'erreurs postul\u00e9e <em>(dans certains travaux, l'ECC pour NOR est envisag\u00e9, mais en g\u00e9n\u00e9ral on parle l\u00e0 de MLC NOR, ce genre de cas existe \u00e9galement)<\/em>.<\/li>\n<\/ul>\n<p><\/p>\n<p>Estimation des exigences en volume et en ressources.<\/p>\n<p><\/p>\n<p>Nous souhaitons que les donn\u00e9es soient conserv\u00e9es de mani\u00e8re garantie pendant plusieurs jours. Cela est n\u00e9cessaire pour que, en cas de probl\u00e8me de connexion, l'historique des ventes ne soit pas perdu. Nous viserons une p\u00e9riode de 5 jours, durant laquelle <em>(m\u00eame en tenant compte des week-ends et des jours f\u00e9ri\u00e9s)<\/em> on peut r\u00e9soudre le probl\u00e8me.<\/p>\n<p><\/p>\n<p>Actuellement, nous accumulons environ 100 Ko de journaux par jour (3-4 milliers d'entr\u00e9es), mais ce chiffre augmente progressivement \u2014 la granularit\u00e9 augmente, de nouveaux \u00e9v\u00e9nements sont ajout\u00e9s. De plus, il y a parfois des pics (un capteur commence \u00e0 spammer avec de fausses activations, par exemple). Nous allons calculer sur 10 000 entr\u00e9es de 100 octets \u2014 un m\u00e9gaoctet par jour.<\/p>\n<p><\/p>\n<p>Au total, cela donne 5 Mo de donn\u00e9es brutes (bien compressibles). \u00c0 cela s'ajoute <em>(estimation brute)<\/em> 1 Mo de donn\u00e9es de service.<\/p>\n<p><\/p>\n<p>Donc, nous avons besoin d'une puce de 8 Mo si nous ne sommes pas en mesure d'utiliser la compression, ou de 4 Mo si nous l'utilisons. Des chiffres tout \u00e0 fait r\u00e9els pour ce type de m\u00e9moire.<\/p>\n<p><\/p>\n<p>Quant aux ressources : si nous pr\u00e9voyons que la m\u00e9moire sera compl\u00e8tement r\u00e9\u00e9crite pas plus d'une fois tous les 5 jours, alors pour 10 ans de service, nous obtenons moins de mille cycles d'\u00e9criture.<br \/>\nJe rappelle que le fabricant promet cent mille.<\/p>\n<p>\n<b class=\"spoiler_title\">Un peu sur NOR vs NAND<\/b><\/p>\n<p>Aujourd'hui, la m\u00e9moire NAND est bien plus populaire, mais pour ce projet, je ne l'utiliserais pas : la NAND, contrairement \u00e0 la NOR, n\u00e9cessite toujours des codes de correction d'erreurs, des tableaux de blocs d\u00e9fectueux, etc., et les puces NAND ont g\u00e9n\u00e9ralement beaucoup plus de broches.<\/p>\n<p><\/p>\n<p>Les inconv\u00e9nients de la NOR incluent :<\/p>\n<p><\/p>\n<ul>\n<li>petit volume (et donc un prix par m\u00e9gaoctet \u00e9lev\u00e9);<\/li>\n<li>vitesse de transfert relativement faible (en grande partie \u00e0 cause de l'utilisation d'une interface s\u00e9rie, g\u00e9n\u00e9ralement SPI ou I2C);<\/li>\n<li>effacement lent (qui prend de quelques fractions de seconde \u00e0 plusieurs secondes selon la taille du bloc).<\/li>\n<\/ul>\n<p><\/p>\n<p>Il n'y a rien de critique pour nous, donc continuons.<\/p>\n<p><\/p>\n<p>Pour ceux qui sont int\u00e9ress\u00e9s par les d\u00e9tails, le circuit int\u00e9gr\u00e9 s\u00e9lectionn\u00e9 est <noindex><a rel=\"nofollow\" href=\"https:\/\/www.adestotech.com\/wp-content\/uploads\/doc3686.pdf\">at25df321a<\/a><\/noindex> <em>(ce qui n'est d'ailleurs pas important, il y a des milliers d'analogues sur le march\u00e9, compatibles en termes de brochage et de jeu de commandes ; m\u00eame si nous voulions installer un circuit int\u00e9gr\u00e9 d'un autre fabricant et\/ou d'une autre capacit\u00e9, tout fonctionnerait sans modification du code)<\/em>.<\/p>\n<p><\/p>\n<p>J'utilise le pilote int\u00e9gr\u00e9 au noyau Linux, sur Raspberry gr\u00e2ce \u00e0 la prise en charge des overlays de l'arbre des p\u00e9riph\u00e9riques, c'est tr\u00e8s simple \u2014 il suffit de placer l'overlay compil\u00e9 dans \/boot\/overlays et de modifier l\u00e9g\u00e8rement \/boot\/config.txt.<\/p>\n<p>\n<b class=\"spoiler_title\">Exemple de fichier dts<\/b><\/p>\n<p>Honn\u00eatement, je ne suis pas s\u00fbr que cela soit \u00e9crit sans erreurs, mais \u00e7a fonctionne.<\/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\">Et une ligne suppl\u00e9mentaire dans config.txt<\/b><\/p>\n<pre><code class=\"plaintext\">dtoverlay=at25:spimaxfrequency=50000000<\/code><\/pre>\n<p><\/p>\n<p>Je ne vais pas d\u00e9crire la connexion du circuit int\u00e9gr\u00e9 au Raspberry Pi. D'une part, je ne suis pas un sp\u00e9cialiste en \u00e9lectronique, d'autre part \u2014 c'est tr\u00e8s simple m\u00eame pour moi : le circuit int\u00e9gr\u00e9 n'a que 8 broches, dont nous avons besoin de la terre, de l'alimentation, et de SPI (CS, SI, SO, SCK); les niveaux correspondent \u00e0 ceux du Raspberry Pi, aucun c\u00e2blage suppl\u00e9mentaire n'est n\u00e9cessaire \u2014 il suffit de connecter les 6 broches indiqu\u00e9es.<\/p>\n<p><\/p>\n<h2 id=\"postanovka-zadachi\">D\u00e9finition du probl\u00e8me<\/h2>\n<p><\/p>\n<p>Comme d'habitude, la d\u00e9finition du probl\u00e8me passe par plusieurs it\u00e9rations, je pense qu'il est temps pour une autre. Alors, arr\u00eatons-nous, rassemblons ce qui a d\u00e9j\u00e0 \u00e9t\u00e9 \u00e9crit, et clarifions les d\u00e9tails qui restent dans l'ombre.<\/p>\n<p><\/p>\n<p>Ainsi, nous avons d\u00e9cid\u00e9 que le journal serait stock\u00e9 dans la m\u00e9moire Flash SPI NOR.<\/p>\n<p>\n<b class=\"spoiler_title\">Qu'est-ce que la m\u00e9moire Flash NOR pour ceux qui ne savent pas<\/b><\/p>\n<p>C'est une m\u00e9moire non volatile avec laquelle on peut effectuer trois op\u00e9rations :<\/p>\n<p><\/p>\n<ol>\n<li>Lecture :<br \/>\nLa lecture classique : nous donnons une adresse et lisons autant d'octets que n\u00e9cessaire;<\/li>\n<li>\u00c9criture :<br \/>\nL'\u00e9criture dans la m\u00e9moire NOR flash ressemble \u00e0 une \u00e9criture classique, mais elle a une particularit\u00e9 : on ne peut changer un 1 en 0, mais seulement l'inverse. Par exemple, si 0x55 \u00e9tait stock\u00e9 dans la cellule de m\u00e9moire, apr\u00e8s l'\u00e9criture de 0x0f, elle contiendra 0x05. <em>(voir le tableau ci-dessous)<\/em>;<\/li>\n<li>Effacer :<br \/>\n\u00c9videmment, nous devons \u00e9galement \u00eatre capables d'effectuer l'op\u00e9ration inverse \u2014 changer un 0 en 1, c'est pr\u00e9cis\u00e9ment \u00e0 cela que sert l'op\u00e9ration d'effacement. Contrairement aux deux premi\u00e8res op\u00e9rations, elle op\u00e8re non pas sur des octets mais sur des blocs (le bloc d'effacement minimal dans la puce choisie est de 4 Ko). L'effacement d\u00e9truit l'ensemble du bloc et c'est le seul moyen de changer un 0 en 1. Par cons\u00e9quent, lors du travail avec la m\u00e9moire flash, il est souvent n\u00e9cessaire d'aligner les structures de donn\u00e9es \u00e0 la fronti\u00e8re du bloc d'effacement.<br \/>\n\u00c9criture dans la m\u00e9moire NOR Flash :<\/li>\n<\/ol>\n<p><\/p>\n<p>Donn\u00e9es binaires<\/p>\n<p><strong>Il y avait<\/strong><br \/>\n<code>01010101<\/code><\/p>\n<p><strong>\u00c9crit<\/strong><br \/>\n<code>00001111<\/code><\/p>\n<p><strong>Il est devenu<\/strong><br \/>\n<code>00000101<\/code><\/p>\n<p><\/p>\n<p>Le journal lui-m\u00eame repr\u00e9sente une s\u00e9quence d'entr\u00e9es de longueur variable. La longueur typique d'une entr\u00e9e est d'environ 30 octets (bien que parfois il y ait des entr\u00e9es de plusieurs kilo-octets). <em>Dans ce cas, nous travaillons avec elles simplement comme avec un ensemble d'octets, mais, si cela vous int\u00e9resse, CBOR est utilis\u00e9 \u00e0 l'int\u00e9rieur des enregistrements.<\/em><\/p>\n<p><\/p>\n<p>En plus du journal, nous devons stocker certaines informations de \u00abconfiguration\u00bb, qu'elles soient mises \u00e0 jour ou non : un ID de l'appareil, des calibrations de capteurs, un drapeau \u00abl'appareil est temporairement hors service\u00bb, etc.<br \/>\nCes informations se pr\u00e9sentent sous la forme d'un ensemble d'enregistrements key-value, \u00e9galement stock\u00e9 en CBOR. Nous n'avons pas beaucoup de ces informations (un maximum de quelques kilo-octets), elles ne sont pas mises \u00e0 jour fr\u00e9quemment.<br \/>\nNous l'appellerons d\u00e9sormais le contexte.<\/p>\n<p><\/p>\n<p>Si l'on se rem\u00e9more le d\u00e9but de cet article, il est tr\u00e8s important d'assurer la fiabilit\u00e9 du stockage des donn\u00e9es et, si possible, un fonctionnement ininterrompu m\u00eame en cas de d\u00e9faillances mat\u00e9rielles ou de corruption des donn\u00e9es.<\/p>\n<p><\/p>\n<p>Quelles sources de probl\u00e8mes peut-on envisager ?<\/p>\n<p><\/p>\n<ul>\n<li>Coupure de courant pendant les op\u00e9rations write\/erase. C'est du domaine de \u00abcontre la loi, il n'y a pas de recours\u00bb.<br \/>\nInformations de <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\">la discussion<\/a><\/noindex> sur stackexchange : lors d'une coupure de courant pendant le travail avec la flash, tant erase (mise \u00e0 1) que write (mise \u00e0 0) entra\u00eenent un comportement ind\u00e9fini : les donn\u00e9es peuvent \u00eatre \u00e9crites, partiellement \u00e9crites (par exemple, nous avons transmis 10 octets \/ 80 bits, mais seulement 45 bits ont pu \u00eatre enregistr\u00e9s), il n'est pas exclu qu'une partie des bits soit dans un \u00e9tat \u00abinterm\u00e9diaire\u00bb (la lecture peut donner soit 0, soit 1);<\/li>\n<li>Erreurs de la m\u00e9moire flash elle-m\u00eame.<br \/>\nLe BER, bien que tr\u00e8s bas, ne peut pas \u00eatre nul ;<\/li>\n<li>Erreurs sur le bus<br \/>\nLes donn\u00e9es transmises par SPI ne sont pas prot\u00e9g\u00e9es et peuvent subir des erreurs de bits individuelles ainsi que des erreurs de synchronisation \u2014 perte ou insertion de bits (ce qui entra\u00eene des distorsions massives des donn\u00e9es) ;<\/li>\n<li>Autres erreurs\/sabots<br \/>\nErreurs dans le code, \u00abbugs\u00bb de Raspberry, intervention d'extraterrestres\u2026<\/li>\n<\/ul>\n<p><\/p>\n<p>J'ai formul\u00e9 des exigences dont je pense qu'elles sont n\u00e9cessaires pour assurer la fiabilit\u00e9 :<\/p>\n<p><\/p>\n<ul>\n<li>les enregistrements doivent \u00eatre enregistr\u00e9s en m\u00e9moire flash imm\u00e9diatement, l'enregistrement diff\u00e9r\u00e9 n'est pas envisag\u00e9 ; - si une erreur survient, elle doit \u00eatre d\u00e9tect\u00e9e et g\u00e9r\u00e9e le plus t\u00f4t possible ; - le syst\u00e8me doit pouvoir restaurer son fonctionnement apr\u00e8s une erreur.<br \/>\n<em>(exemple de la vie \u00abcomment cela ne devrait pas \u00eatre\u00bb, avec lequel je pense que tout le monde a \u00e9t\u00e9 confront\u00e9 : apr\u00e8s un red\u00e9marrage d'urgence, le syst\u00e8me de fichiers \u00abs'est corrompu\u00bb et le syst\u00e8me d'exploitation ne d\u00e9marre pas)<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"idei-podhody-razmyshleniya\">Id\u00e9es, approches, r\u00e9flexions<\/h2>\n<p><\/p>\n<p>Lorsque j'ai commenc\u00e9 \u00e0 r\u00e9fl\u00e9chir \u00e0 cette t\u00e2che, une multitude d'id\u00e9es a travers\u00e9 mon esprit, par exemple :<\/p>\n<p><\/p>\n<ul>\n<li>utiliser la compression de donn\u00e9es ;<\/li>\n<li>utiliser des structures de donn\u00e9es astucieuses, par exemple stocker les en-t\u00eates des enregistrements s\u00e9par\u00e9ment des enregistrements eux-m\u00eames, afin qu'en cas d'erreur dans l'un des enregistrements, les autres puissent \u00eatre lus sans probl\u00e8me ;<\/li>\n<li>utiliser des champs bits pour contr\u00f4ler l'ach\u00e8vement de l'enregistrement en cas de coupure d'alimentation ;<\/li>\n<li>conserver des sommes de contr\u00f4le pour tout et n'importe quoi ;<\/li>\n<li>utiliser une sorte de codage r\u00e9sistant aux erreurs.<\/li>\n<\/ul>\n<p><\/p>\n<p>Certaines de ces id\u00e9es ont \u00e9t\u00e9 utilis\u00e9es, d'autres ont \u00e9t\u00e9 mises de c\u00f4t\u00e9. Faisons-les dans l'ordre.<\/p>\n<p><\/p>\n<h3 id=\"szhatie-dannyh\">Compression des donn\u00e9es<\/h3>\n<p><\/p>\n<p>Les \u00e9v\u00e9nements que nous enregistrons dans le journal sont relativement homog\u00e8nes et r\u00e9p\u00e9titifs (\u00abnous avons lanc\u00e9 une pi\u00e8ce de 5 roubles\u00bb, \u00abnous avons appuy\u00e9 sur le bouton pour rendre la monnaie\u00bb, ...). Par cons\u00e9quent, la compression devrait s'av\u00e9rer assez efficace.<\/p>\n<p><\/p>\n<p>Les frais g\u00e9n\u00e9raux de compression sont insignifiants (notre processeur est suffisamment puissant, m\u00eame le premier Pi avait un c\u0153ur \u00e0 700 MHz, les mod\u00e8les actuels en ont plusieurs \u00e0 plus d'un gigahertz), la vitesse d'\u00e9change avec le stockage est faible (quelques m\u00e9gaoctets par seconde), et la taille des enregistrements est petite. Bref, si la compression affecte les performances, ce ne sera que de mani\u00e8re positive. <em>(absolument non critique, je constate simplement)<\/em>. De plus, nous n'avons pas un v\u00e9ritable embedded, mais un Linux ordinaire \u2014 donc la mise en \u0153uvre ne devrait pas n\u00e9cessiter beaucoup d'efforts (il suffit de lier la biblioth\u00e8que et d'utiliser quelques fonctions de celle-ci).<\/p>\n<p><\/p>\n<p>Un extrait de journal d'un appareil en fonctionnement a \u00e9t\u00e9 pris (1,7 Mo, 70 000 enregistrements) et d'abord v\u00e9rifi\u00e9 pour sa compressibilit\u00e9 \u00e0 l'aide de gzip, lz4, lzop, bzip2, xz, zstd disponibles sur l'ordinateur.<\/p>\n<p><\/p>\n<ul>\n<li>gzip, xz, zstd ont montr\u00e9 des r\u00e9sultats proches (40 Ko).<br \/>\nIl est surprenant que le tendance xz ait montr\u00e9 ici des performances au niveau de gzip ou zstd;<\/li>\n<li>lzip avec les param\u00e8tres par d\u00e9faut a donn\u00e9 un r\u00e9sultat l\u00e9g\u00e8rement moins bon;<\/li>\n<li>lz4 et lzop ont montr\u00e9 des r\u00e9sultats plut\u00f4t m\u00e9diocres (150 Ko);<\/li>\n<li>bzip2 a donn\u00e9 un r\u00e9sultat \u00e9tonnamment bon (18 Ko).<\/li>\n<\/ul>\n<p><\/p>\n<p>Ainsi, les donn\u00e9es se compressent tr\u00e8s bien.<br \/>\nDonc (si nous ne trouvons pas de d\u00e9fauts fatals) la compression sera l\u00e0 ! Juste parce que plus de donn\u00e9es pourront tenir sur la m\u00eame cl\u00e9 USB.<\/p>\n<p><\/p>\n<p>R\u00e9fl\u00e9chissons aux inconv\u00e9nients.<\/p>\n<p><\/p>\n<p>Le premier probl\u00e8me : nous avons d\u00e9j\u00e0 convenu que chaque enregistrement doit \u00eatre imm\u00e9diatement \u00e9crit sur la cl\u00e9. En g\u00e9n\u00e9ral, un compresseur accumule les donn\u00e9es du flux d'entr\u00e9e jusqu'\u00e0 ce qu'il juge qu'il est temps d'\u00e9crire dans le flux de sortie. Nous avons besoin d'obtenir imm\u00e9diatement un bloc de donn\u00e9es compress\u00e9es et de les enregistrer dans une m\u00e9moire non volatile.<\/p>\n<p><\/p>\n<p>Je vois trois solutions :<\/p>\n<p><\/p>\n<ol>\n<li>Compresser chaque enregistrement \u00e0 l'aide de la compression par dictionnaire au lieu des algorithmes mentionn\u00e9s ci-dessus.<br \/>\nC'est une option tout \u00e0 fait fonctionnelle, mais je ne l'aime pas. Pour obtenir un niveau de compression \u00e0 peu pr\u00e8s acceptable, le dictionnaire doit \u00eatre \u00abtaille\u00bb pour des donn\u00e9es sp\u00e9cifiques, tout changement entra\u00eenera une chute catastrophique du niveau de compression. Oui, le probl\u00e8me peut \u00eatre r\u00e9solu en cr\u00e9ant une nouvelle version du dictionnaire, mais c'est une vraie douleur \u2014 nous devrons stocker toutes les versions du dictionnaire ; dans chaque enregistrement, nous devrons indiquer avec quelle version du dictionnaire il a \u00e9t\u00e9 compress\u00e9\u2026<\/li>\n<li>Compresser chaque enregistrement avec des algorithmes \u00abclassiques\u00bb, mais ind\u00e9pendamment les uns des autres.<br \/>\nLes algorithmes de compression examin\u00e9s ne sont pas con\u00e7us pour fonctionner avec des enregistrements de cette taille (dizaines d'octets), le taux de compression sera clairement inf\u00e9rieur \u00e0 1 (c'est-\u00e0-dire une augmentation du volume des donn\u00e9es au lieu de compression);<\/li>\n<li>Faire un FLUSH apr\u00e8s chaque enregistrement.<br \/>\nDe nombreuses biblioth\u00e8ques de compression prennent en charge le FLUSH. C'est une commande (ou un param\u00e8tre de la proc\u00e9dure de compression) qui, une fois re\u00e7ue, fait en sorte que le compresseur g\u00e9n\u00e8re un flux compress\u00e9 permettant de restaurer <strong>tout<\/strong> les donn\u00e9es non compress\u00e9es qui ont d\u00e9j\u00e0 \u00e9t\u00e9 re\u00e7ues. Une sorte d'analogue <code>sync<\/code> dans les syst\u00e8mes de fichiers ou <code>commit<\/code> dans sql.<br \/>\nCe qui est important, c'est que les op\u00e9rations de compression suivantes pourront utiliser le dictionnaire accumul\u00e9 et le taux de compression ne sera pas aussi affect\u00e9 que dans l'option pr\u00e9c\u00e9dente.<\/li>\n<\/ol>\n<p><\/p>\n<p>Je pense qu'il est \u00e9vident que j'ai choisi la troisi\u00e8me option, examinons-la plus en d\u00e9tail.<\/p>\n<p><\/p>\n<p>Trouv\u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.bolet.org\/~pornin\/deflate-flush.html\">un excellent article<\/a><\/noindex> sur FLUSH dans zlib.<\/p>\n<p><\/p>\n<p>J'ai effectu\u00e9 un test inspir\u00e9 d'un article, j'ai pris 70 000 enregistrements d'un journal d'un appareil r\u00e9el, avec une taille de page de 60 Ko <em>(Nous reviendrons sur la taille de la page)<\/em> j'ai obtenu :<\/p>\n<p><\/p>\n<p>Donn\u00e9es d'origine<br \/>\nCompression gzip -9 (sans FLUSH)<br \/>\nzlib avec Z_PARTIAL_FLUSH<br \/>\nzlib avec Z_SYNC_FLUSH<\/p>\n<p><strong>Volume, Ko<\/strong><br \/>\n1692<br \/>\n40<br \/>\n352<br \/>\n604<\/p>\n<p><\/p>\n<p>\u00c0 premi\u00e8re vue, le co\u00fbt apport\u00e9 par FLUSH semble excessif, cependant en r\u00e9alit\u00e9 nos choix ne sont pas nombreux \u2014 soit ne pas compresser du tout, soit compresser (et de mani\u00e8re tr\u00e8s efficace) en utilisant FLUSH. N'oublions pas que nous avons 70 000 enregistrements, le surco\u00fbt apport\u00e9 par Z_PARTIAL_FLUSH est de seulement 4-5 octets par enregistrement. Et le taux de compression s'est av\u00e9r\u00e9 presque de 5:1, ce qui est plus qu'un excellent r\u00e9sultat.<\/p>\n<p>\n<b class=\"spoiler_title\">Cela peut sembler surprenant, mais en r\u00e9alit\u00e9 Z_SYNC_FLUSH \u2014 est une m\u00e9thode plus efficace de faire un FLUSH.<\/b><\/p>\n<p>Dans le cas de l'utilisation de Z_SYNC_FLUSH, les 4 derniers octets de chaque enregistrement seront toujours 0x00, 0x00, 0xff, 0xff. Et si nous les connaissons, nous pouvons ne pas les stocker, donc la taille finale ne fait que 324 Ko.<\/p>\n<p><\/p>\n<p>Dans l'article auquel je fais r\u00e9f\u00e9rence, il y a une explication :<\/p>\n<p><\/p>\n<blockquote><p>Un nouveau bloc de type 0 avec un contenu vide est ajout\u00e9.<\/p>\n<p>Un bloc de type 0 avec un contenu vide se compose de :<\/p>\n<ul>\n<li>l'en-t\u00eate de bloc de trois bits ;<\/li>\n<li>de 0 \u00e0 7 bits \u00e9gaux \u00e0 z\u00e9ro, pour atteindre l'alignement des octets ;<\/li>\n<li>la s\u00e9quence de quatre octets 00 00 FF FF.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Comme il n'est pas difficile de le remarquer, dans le dernier bloc avant ces 4 octets, il y a de 3 \u00e0 10 bits \u00e0 z\u00e9ro. Cependant, la pratique a montr\u00e9 qu'il y a en fait au moins 10 bits \u00e0 z\u00e9ro.<\/p>\n<p><\/p>\n<p>Il s'av\u00e8re que de si courts blocs de donn\u00e9es sont g\u00e9n\u00e9ralement (toujours ?) cod\u00e9s avec un bloc de type 1 (bloc fixe), qui se termine obligatoirement par 7 bits \u00e0 z\u00e9ro, ce qui nous donne entre 10 et 17 bits \u00e0 z\u00e9ro garantis (et les autres seront \u00e0 z\u00e9ro avec une probabilit\u00e9 d'environ 50%).<\/p>\n<p><\/p>\n<p>Donc, sur les donn\u00e9es de test, dans 100 % des cas, avant 0x00, 0x00, 0xff, 0xff, il y a un octet \u00e0 z\u00e9ro, et dans plus d'un tiers des cas \u2014 deux octets \u00e0 z\u00e9ro. <em>(peut-\u00eatre est-ce parce que j'utilise un CBOR binaire, et lors de l'utilisation de JSON textuel, on rencontrerait plus fr\u00e9quemment des blocs de type 2 \u2014 blocs dynamiques, de sorte qu'on rencontrerait des blocs sans octets suppl\u00e9mentaires \u00e0 z\u00e9ro avant 0x00, 0x00, 0xff, 0xff)<\/em>.<\/p>\n<p><\/p>\n<p>Ainsi, sur les donn\u00e9es de test existantes, on peut se conformer \u00e0 moins de 250 Ko de donn\u00e9es compress\u00e9es.<\/p>\n<p><\/p>\n<p>On peut encore \u00e9conomiser un peu en jonglant avec les bits : maintenant, nous ignorons la pr\u00e9sence de plusieurs bits nuls \u00e0 la fin du bloc, et quelques bits au d\u00e9but du bloc ne changent \u00e9galement pas...<br \/>\nMais ici, j'ai pris la d\u00e9cision de m'arr\u00eater, sinon, \u00e0 ce rythme, je pourrais finir par d\u00e9velopper mon propre archiveur.<\/p>\n<p><\/p>\n<p>En r\u00e9sum\u00e9, j'ai obtenu de mes donn\u00e9es de test 3 \u00e0 4 octets par \u00e9criture, et le taux de compression a d\u00e9pass\u00e9 6:1. Honn\u00eatement, je ne m'attendais pas \u00e0 un tel r\u00e9sultat, \u00e0 mon avis, tout ce qui est meilleur que 2:1 est d\u00e9j\u00e0 un r\u00e9sultat justifiant l'utilisation de la compression.<\/p>\n<p><\/p>\n<p>Tout va bien, mais zlib (deflate) reste un algorithme de compression archa\u00efque, m\u00e9ritant et un peu d\u00e9mod\u00e9. D\u00e9j\u00e0, le fait qu'il utilise les 32 Ko les plus r\u00e9cents du flux de donn\u00e9es non compress\u00e9es semble \u00e9trange aujourd'hui (c'est-\u00e0-dire que si un bloc de donn\u00e9es ressemble beaucoup \u00e0 ce qui \u00e9tait dans le flux d'entr\u00e9e il y a 40 Ko, il commencera \u00e0 \u00eatre compress\u00e9 \u00e0 nouveau, plut\u00f4t que de faire r\u00e9f\u00e9rence \u00e0 une entr\u00e9e pr\u00e9c\u00e9dente). Dans les archiveurs modernes \u00e0 la mode, la taille du dictionnaire est souvent mesur\u00e9e en m\u00e9gaoctets, et non en kilo-octets.<\/p>\n<p><\/p>\n<p>Donc, nous continuons notre mini-recherche sur les archiveurs.<\/p>\n<p><\/p>\n<p>Le suivant \u00e0 \u00eatre test\u00e9 a \u00e9t\u00e9 bzip2 (rappelons-le, sans FLUSH il a montr\u00e9 un taux de compression fantastique, presque 100:1). Malheureusement, avec FLUSH, il a montr\u00e9 de tr\u00e8s mauvais r\u00e9sultats, la taille des donn\u00e9es compress\u00e9es \u00e9tait sup\u00e9rieure \u00e0 celle des donn\u00e9es non compress\u00e9es.<\/p>\n<p>\n<b class=\"spoiler_title\">Mes hypoth\u00e8ses sur les raisons de l'\u00e9chec<\/b><\/p>\n<p>Libbz2 n'offre qu'une seule option de flush, qui semble nettoyer le dictionnaire (analogue de Z_FULL_FLUSH dans zlib), il est difficile de parler d'une compression efficace apr\u00e8s cela.<\/p>\n<p><\/p>\n<p>Et enfin, le dernier test\u00e9 a \u00e9t\u00e9 zstd. Selon les param\u00e8tres, il compresse soit au niveau de gzip, mais beaucoup plus rapidement, soit mieux que gzip.<\/p>\n<p><\/p>\n<p>Malheureusement, avec FLUSH, il s'est montr\u00e9 \u00abpas tr\u00e8s bon\u00bb : la taille des donn\u00e9es compress\u00e9es \u00e9tait d'environ 700 Ko.<\/p>\n<p><\/p>\n<p>Je <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/facebook\/zstd\/issues\/900\">j'ai pos\u00e9 la question<\/a><\/noindex> sur la page du projet sur github, et j'ai re\u00e7u une r\u00e9ponse indiquant qu'on doit s'attendre \u00e0 environ 10 octets de donn\u00e9es de service pour chaque bloc de donn\u00e9es compress\u00e9es, ce qui est proche des r\u00e9sultats obtenus, on ne pourra jamais \u00e9galer deflate.<\/p>\n<p><\/p>\n<p>J'ai d\u00e9cid\u00e9 d'arr\u00eater mes exp\u00e9riences avec les archiveurs (rappelons que xz, lzip, lzo, lz4 n'ont pas \u00e9t\u00e9 performants lors des tests sans FLUSH, et je n'ai pas voulu envisager des algorithmes de compression plus exotiques).<\/p>\n<p><\/p>\n<p>Retournons aux probl\u00e8mes d'archivage.<\/p>\n<p><\/p>\n<p>Le deuxi\u00e8me probl\u00e8me (comme on le dit en termes d'ordre et non de signification) est que les donn\u00e9es compress\u00e9es se pr\u00e9sentent sous la forme d'un flux unique, contenant constamment des r\u00e9f\u00e9rences \u00e0 des sections pr\u00e9c\u00e9dentes. Ainsi, si une partie des donn\u00e9es compress\u00e9es est endommag\u00e9e, nous perdons non seulement le bloc de donn\u00e9es non compress\u00e9es qui y est li\u00e9, mais \u00e9galement tous les suivants.<\/p>\n<p><\/p>\n<p>Il existe plusieurs approches pour r\u00e9soudre ce probl\u00e8me :<\/p>\n<p><\/p>\n<ol>\n<li>Pr\u00e9venir l'apparition du probl\u00e8me en ajoutant une redondance aux donn\u00e9es compress\u00e9es, ce qui permettra de d\u00e9tecter et de corriger les erreurs ; nous en parlerons plus tard;<\/li>\n<li>Minimiser les cons\u00e9quences en cas de probl\u00e8me<br \/>\nNous avons d\u00e9j\u00e0 mentionn\u00e9 pr\u00e9c\u00e9demment que chaque bloc de donn\u00e9es peut \u00eatre compress\u00e9 ind\u00e9pendamment, ce qui r\u00e9soudrait le probl\u00e8me (la corruption des donn\u00e9es d'un bloc entra\u00eenerait la perte de donn\u00e9es uniquement pour ce bloc). Cependant, c'est un cas extr\u00eame o\u00f9 la compression des donn\u00e9es serait inefficace. L'autre extr\u00eame : utiliser les 4 Mo de notre puce comme une seule archive, ce qui donnerait une excellente compression, mais des cons\u00e9quences catastrophiques en cas de corruption des donn\u00e9es.<br \/>\n<em>Oui, un compromis en termes de fiabilit\u00e9 est n\u00e9cessaire. Mais il faut garder \u00e0 l'esprit que nous d\u00e9veloppons un format de stockage des donn\u00e9es pour une m\u00e9moire non volatile avec un TBER extr\u00eamement bas et une dur\u00e9e de conservation des donn\u00e9es d\u00e9clar\u00e9e de 20 ans.<\/em><\/li>\n<\/ol>\n<p><\/p>\n<p>Au cours de mes exp\u00e9riences, j'ai d\u00e9couvert que des pertes de niveau de compression plus ou moins perceptibles commencent avec des blocs de donn\u00e9es compress\u00e9es d'une taille inf\u00e9rieure \u00e0 10 Ko.<br \/>\nIl a \u00e9t\u00e9 pr\u00e9c\u00e9demment mentionn\u00e9 que la m\u00e9moire utilis\u00e9e a une organisation par pages, je ne vois pas de raisons pour lesquelles il ne faudrait pas utiliser la correspondance \u00abune page \u2014 un bloc de donn\u00e9es compress\u00e9es\u00bb.<\/p>\n<p><\/p>\n<p>C'est-\u00e0-dire que la taille minimale raisonnable d'une page est de 16 Ko (avec une r\u00e9serve pour les informations de service). Cependant, une si petite taille de page impose des restrictions substantielles sur la taille maximale de l'enregistrement.<\/p>\n<p><\/p>\n<p>Bien que je n'anticipe pas encore d'enregistrements plus grands qu'un kilooctet en version compress\u00e9e, j'ai d\u00e9cid\u00e9 d'utiliser des pages de 32 Ko (ce qui donne un total de 128 pages par puce).<\/p>\n<p><\/p>\n<p><strong>R\u00e9sum\u00e9 :<\/strong><\/p>\n<p><\/p>\n<ul>\n<li>Nous stockons les donn\u00e9es compress\u00e9es \u00e0 l'aide de zlib (deflate);<\/li>\n<li>Pour chaque enregistrement, nous d\u00e9finissons Z_SYNC_FLUSH;<\/li>\n<li>Pour chaque enregistrement compress\u00e9, nous coupons les octets finaux <em>(par exemple, 0x00, 0x00, 0xff, 0xff)<\/em>; dans l'en-t\u00eate, nous indiquons combien d'octets nous avons coup\u00e9s;<\/li>\n<li>Les donn\u00e9es sont stock\u00e9es par pages de 32 Ko; \u00e0 l'int\u00e9rieur de la page, nous avons un flux continu de donn\u00e9es compress\u00e9es; \u00e0 chaque page, nous recommen\u00e7ons la compression.<\/li>\n<\/ul>\n<p><\/p>\n<p>Et, avant de terminer avec la compression, je voudrais souligner que nous n'obtenons qu'une poign\u00e9e d'octets compress\u00e9s par enregistrement, il est donc crucial de ne pas gonfler les informations de service, chaque octet compte ici.<\/p>\n<p><\/p>\n<h3 id=\"hranenie-zagolovkov-dannyh\">Stockage des en-t\u00eates de donn\u00e9es<\/h3>\n<p><\/p>\n<p>Comme nous avons des enregistrements de longueur variable, nous devons trouver un moyen de d\u00e9terminer l'emplacement \/ les limites des enregistrements.<\/p>\n<p><\/p>\n<p>Je connais trois approches :<\/p>\n<p><\/p>\n<ol>\n<li>Tous les enregistrements sont stock\u00e9s dans un flux continu, d'abord l'en-t\u00eate de l'enregistrement, contenant la longueur, puis l'enregistrement lui-m\u00eame.<br \/>\nDans ce cas, les en-t\u00eates et les donn\u00e9es peuvent avoir une longueur variable.<br \/>\nEn gros, nous obtenons une liste cha\u00een\u00e9e qui est utilis\u00e9e \u00e0 profusion.<\/li>\n<li>Les en-t\u00eates et les enregistrements eux-m\u00eames sont stock\u00e9s dans des flux s\u00e9par\u00e9s.<br \/>\nEn utilisant des en-t\u00eates de longueur fixe, nous garantissons qu'une corruption d'un en-t\u00eate n'affecte pas les autres.<br \/>\nUne telle approche est utilis\u00e9e, par exemple, dans de nombreux syst\u00e8mes de fichiers.<\/li>\n<li>Les enregistrements sont stock\u00e9s dans un flux continu, la limite de l'enregistrement est d\u00e9finie par un certain marqueur (symbole \/ s\u00e9quence de symboles qui sont interdits \u00e0 l'int\u00e9rieur des blocs de donn\u00e9es). Si un marqueur appara\u00eet dans l'enregistrement, nous le rempla\u00e7ons par une certaine s\u00e9quence (nous l'\u00e9chappons).<br \/>\nUne telle approche est utilis\u00e9e, par exemple, dans le protocole PPP.<\/li>\n<\/ol>\n<p><\/p>\n<p>Je vais illustrer.<\/p>\n<p><\/p>\n<p>Option 1 :<br \/>\n<img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/e5a9676ca21eaca07e64ddf9fcd9f2eb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIci, c'est tr\u00e8s simple : en connaissant la longueur de l'enregistrement, nous pouvons calculer l'adresse du prochain en-t\u00eate. Ainsi, nous avan\u00e7ons \u00e0 travers les en-t\u00eates jusqu'\u00e0 ce que nous rencontrions une zone remplie de 0xff (zone libre) ou la fin de la page.<\/p>\n<p><\/p>\n<p>Option 2 :<br \/>\n<img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/4fed4860fb659ab6042be9aeeb5c041a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn raison de la longueur variable des enregistrements, nous ne pouvons pas dire \u00e0 l'avance combien d'enregistrements (et donc de titres) nous aurons besoin sur une page. Nous pourrions r\u00e9partir les titres et les donn\u00e9es sur diff\u00e9rentes pages, mais je pr\u00e9f\u00e8re une autre approche : nous pla\u00e7ons \u00e0 la fois les titres (de taille fixe) et les donn\u00e9es (de longueur variable) sur la m\u00eame page, cependant, les titres commencent en haut de la page, tandis que les donn\u00e9es commencent en bas. D\u00e8s qu'ils 'se rencontrent' (l'espace libre n'est pas suffisant pour un nouvel enregistrement), nous consid\u00e9rons cette page comme remplie.<\/p>\n<p><\/p>\n<p>Option 3 :<br \/>\n<img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/dfb64009aa7781fcb8a47423ff4160c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIl n'est pas n\u00e9cessaire de stocker la longueur ou d'autres informations sur la disposition des donn\u00e9es dans l'en-t\u00eate, il suffit d'utiliser des marqueurs pour indiquer les fronti\u00e8res des enregistrements. Cependant, les donn\u00e9es doivent \u00eatre trait\u00e9es lors de l'\u00e9criture\/lecture.<br \/>\nComme marqueur, j'utiliserais 0xff (qui remplit la page apr\u00e8s une \u00e9radication), de cette fa\u00e7on, la zone libre ne sera d\u00e9finitivement pas interpr\u00e9t\u00e9e comme des donn\u00e9es.<\/p>\n<p><\/p>\n<p>Tableau comparatif :<\/p>\n<p><\/p>\n<p>Option 1<br \/>\nOption 2<br \/>\nOption 3<\/p>\n<p><strong>R\u00e9silience aux erreurs<\/strong><br \/>\n\u2014<br \/>\n+<br \/>\n+<\/p>\n<p><strong>Compacit\u00e9<\/strong><br \/>\n+<br \/>\n\u2014<br \/>\n+<\/p>\n<p><strong>Complexit\u00e9 de mise en \u0153uvre<\/strong><br \/>\n*<br \/>\n**<br \/>\n**<\/p>\n<p><\/p>\n<p>L'option 1 a un d\u00e9faut fatal : si l'un des en-t\u00eates est endommag\u00e9, toute la cha\u00eene suivante est d\u00e9truite. Les autres options permettent de r\u00e9cup\u00e9rer une partie des donn\u00e9es m\u00eame en cas de dommages massifs.<br \/>\nMais il convient de rappeler que nous avons d\u00e9cid\u00e9 de stocker les donn\u00e9es de mani\u00e8re compress\u00e9e, et de toute fa\u00e7on, nous perdons toutes les donn\u00e9es sur la page apr\u00e8s un enregistrement 'corrompu', donc m\u00eame si la table indique un moins, nous ne le prenons pas en compte.<\/p>\n<p><\/p>\n<p>Compacit\u00e9 :<\/p>\n<p><\/p>\n<ul>\n<li>Dans le premier cas, nous devons stocker uniquement la longueur dans l'en-t\u00eate, en utilisant une variable de longueur enti\u00e8re, nous pouvons g\u00e9n\u00e9ralement nous contenter d'un octet ;<\/li>\n<li>Dans le deuxi\u00e8me cas, nous devons stocker l'adresse de d\u00e9part et la longueur ; l'enregistrement doit avoir une taille fixe, j'estime cela \u00e0 4 octets par enregistrement (deux octets pour le d\u00e9calage et deux octets pour la longueur) ;<\/li>\n<li>La troisi\u00e8me option n\u00e9cessite seulement un caract\u00e8re pour d\u00e9signer le d\u00e9but de l'enregistrement, de plus, l'enregistrement lui-m\u00eame augmentera de 1 \u00e0 2 % \u00e0 cause de l'\u00e9chappement. Dans l'ensemble, il y a une parit\u00e9 approximative avec la premi\u00e8re option.<\/li>\n<\/ul>\n<p><\/p>\n<p>\u00c0 l'origine, j'ai consid\u00e9r\u00e9 la deuxi\u00e8me option comme principale (et m\u00eame j'ai \u00e9crit une impl\u00e9mentation). Je ne m'en suis d\u00e9tour\u00e9 qu'une fois que j'ai d\u00e9cid\u00e9 d'utiliser la compression.<\/p>\n<p><\/p>\n<p><em>Peut-\u00eatre qu'un jour j'utiliserai cette option. Par exemple, si je dois g\u00e9rer le stockage des donn\u00e9es pour un vaisseau spatiale en route entre la Terre et Mars \u2013 les exigences en mati\u00e8re de fiabilit\u00e9 sont compl\u00e8tement diff\u00e9rentes, les radiations spatiales, ...<\/em><\/p>\n<p><\/p>\n<p>Quant \u00e0 la troisi\u00e8me option : je lui ai donn\u00e9 deux \u00e9toiles pour la complexit\u00e9 de mise en \u0153uvre simplement parce que je n'aime pas me compliquer avec l'\u00e9chappement, le changement de longueur en cours de traitement, etc. Oui, c'est peut-\u00eatre biais\u00e9, mais c'est moi qui vais devoir \u00e9crire le code \u2014 pourquoi me forcer \u00e0 faire quelque chose que je n'aime pas.<\/p>\n<p><\/p>\n<p><strong>R\u00e9sum\u00e9 :<\/strong> Nous choisissons l'option de stockage sous forme de cha\u00eenes 'titre de longueur \u2013 donn\u00e9es de longueur variable' en raison de son efficacit\u00e9 et de sa simplicit\u00e9 d'impl\u00e9mentation.<\/p>\n<p><\/p>\n<h3 id=\"ispolzovanie-bitovyh-poley-dlya-kontrolya-uspeshnosti-operaciy-zapisi\">Utilisation de champs de bits pour contr\u00f4ler le succ\u00e8s des op\u00e9rations d'\u00e9criture<\/h3>\n<p><\/p>\n<p>Je ne me souviens d\u00e9j\u00e0 plus o\u00f9 j'ai vu l'id\u00e9e, mais cela ressemble \u00e0 peu pr\u00e8s \u00e0 ceci :<br \/>\nPour chaque enregistrement, nous assignons quelques bits pour stocker des drapeaux.<br \/>\n<em>Comme nous l'avons dit pr\u00e9c\u00e9demment, apr\u00e8s l'effacement, tous les bits sont remplis de 1, et nous pouvons changer le 1 en 0, mais pas l'inverse.<\/em> Donc pour 'flag non \u00e9tabli', nous utilisons 1, pour 'flag \u00e9tabli' \u2013 0.<\/p>\n<p><\/p>\n<p>Voici \u00e0 quoi pourrait ressembler l'enregistrement d'une variable de longueur dans la flash :<\/p>\n<p><\/p>\n<ol>\n<li>Nous d\u00e9finissons le drapeau \u00ab l'enregistrement de la longueur a commenc\u00e9 \u00bb ;<\/li>\n<li>Nous enregistrons la longueur ;<\/li>\n<li>Nous d\u00e9finissons le drapeau \u00ab l'enregistrement des donn\u00e9es a commenc\u00e9 \u00bb ;<\/li>\n<li>Nous enregistrons les donn\u00e9es ;<\/li>\n<li>Nous d\u00e9finissons le drapeau \u00ab l'enregistrement est termin\u00e9 \u00bb.<\/li>\n<\/ol>\n<p><\/p>\n<p>De plus, nous aurons un drapeau \u00ab une erreur s'est produite \u00bb, soit 4 drapeaux de bits au total.<\/p>\n<p><\/p>\n<p>Dans ce cas, nous avons deux \u00e9tats stables \u00ab 1111 \u00bb \u2014 l'enregistrement n'a pas commenc\u00e9 et \u00ab 1000 \u00bb \u2014 l'enregistrement a r\u00e9ussi ; en cas d'interruption impr\u00e9vue du processus d'enregistrement, nous obtiendrons des \u00e9tats interm\u00e9diaires que nous pourrons ensuite d\u00e9tecter et traiter.<\/p>\n<p><\/p>\n<p>L'approche est int\u00e9ressante, mais elle ne prot\u00e8ge que contre une coupure de courant soudaine et des pannes similaires, ce qui est bien s\u00fbr important, mais ce n'est pas la seule (et m\u00eame pas la principale) cause possible de pannes.<\/p>\n<p><\/p>\n<p><strong>R\u00e9sum\u00e9 :<\/strong> continuons \u00e0 chercher une bonne solution.<\/p>\n<p><\/p>\n<h3 id=\"kontrolnye-summy\">Sommes de contr\u00f4le<\/h3>\n<p><\/p>\n<p>Les sommes de contr\u00f4le permettent \u00e9galement de s'assurer (avec une probabilit\u00e9 suffisante) que nous lisons exactement ce qui devait \u00eatre enregistr\u00e9. Et, contrairement aux champs de bits mentionn\u00e9s ci-dessus, elles fonctionnent toujours.<\/p>\n<p><\/p>\n<p>Si nous examinons la liste des sources potentielles de probl\u00e8mes que nous avons discut\u00e9es ci-dessus, la somme de contr\u00f4le peut d\u00e9tecter une erreur ind\u00e9pendamment de son origine <em>(\u00e0 l'exception peut-\u00eatre des extraterrestres malveillants \u2014 ceux-ci peuvent falsifier la somme de contr\u00f4le \u00e9galement)<\/em>.<\/p>\n<p><\/p>\n<p>Ainsi, si notre objectif est de v\u00e9rifier que les donn\u00e9es sont intactes, les sommes de contr\u00f4le sont une excellente id\u00e9e.<\/p>\n<p><\/p>\n<p>Le choix de l'algorithme de calcul de la somme de contr\u00f4le ne posait pas de questions \u2014 CRC. D'une part, les propri\u00e9t\u00e9s math\u00e9matiques permettent de d\u00e9tecter \u00e0 100 % certaines erreurs, d'autre part \u2014 sur des donn\u00e9es al\u00e9atoires, cet algorithme montre g\u00e9n\u00e9ralement une probabilit\u00e9 de collisions pas beaucoup plus \u00e9lev\u00e9e que la limite th\u00e9orique. <img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/bf9cca3564db7d9d03d3ce49642d3a71.jpg\" style=\"display:block;margin: 0 auto;\" \/>. Bien que ce ne soit pas l'algorithme le plus rapide et qu'il ne minimise pas toujours le nombre de collisions, il a une qualit\u00e9 tr\u00e8s importante : dans les tests que j'ai rencontr\u00e9s, il n'y avait pas de motifs sur lesquels il \u00e9chouait clairement. La stabilit\u00e9 est la principale qualit\u00e9 dans ce cas.<\/p>\n<p><\/p>\n<p>Exemple d'une \u00e9tude approfondie : <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo.html\">partie 1<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo2.html\">partie 2<\/a><\/noindex> <em>(liens vers narod.ru, d\u00e9sol\u00e9)<\/em>.<\/p>\n<p><\/p>\n<p>Cependant, la t\u00e2che de choisir une somme de contr\u00f4le n'est pas termin\u00e9e, CRC est une famille enti\u00e8re de sommes de contr\u00f4le. Il faut se d\u00e9cider sur la longueur, puis choisir le polyn\u00f4me.<\/p>\n<p><\/p>\n<p>Le choix de la longueur de la somme de contr\u00f4le n'est pas aussi simple qu'il y para\u00eet au premier abord.<\/p>\n<p><\/p>\n<p>Je vais illustrer :<br \/>\nSupposons que nous avons une probabilit\u00e9 d'erreur pour chaque octet <img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/89a9be2edf2a43cc9115eba37fa02fc8.jpg\" style=\"display:block;margin: 0 auto;\" \/> et une somme de contr\u00f4le id\u00e9ale, calculons le nombre moyen d'erreurs sur un million d'enregistrements :<\/p>\n<p><\/p>\n<p>Donn\u00e9es, octet<br \/>\nSomme de contr\u00f4le, octet<br \/>\nErreurs non d\u00e9tect\u00e9es<br \/>\nFaux positifs<br \/>\nTotal des fausses alertes<\/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>Il semblerait que ce soit simple \u2014 choisissez la longueur de la somme de contr\u00f4le selon la longueur des donn\u00e9es prot\u00e9g\u00e9es avec un minimum de fausses alertes \u2014 et le tour est jou\u00e9.<\/p>\n<p><\/p>\n<p>Cependant, avec des sommes de contr\u00f4le courtes, un probl\u00e8me se pose : bien qu'elles d\u00e9tectent bien les erreurs de bit uniques, elles peuvent, avec une probabilit\u00e9 assez \u00e9lev\u00e9e, accepter des donn\u00e9es compl\u00e8tement al\u00e9atoires comme valides. Un article a d\u00e9j\u00e0 \u00e9t\u00e9 publi\u00e9 sur Habr d\u00e9crivant <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/428746\/\">le probl\u00e8me dans la vie r\u00e9elle<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Ainsi, pour rendre une co\u00efncidence de somme de contr\u00f4le pratiquement impossible, il faut utiliser des sommes de contr\u00f4le d'une longueur d'au moins 32 bits <em>(pour des longueurs sup\u00e9rieures \u00e0 64 bits, on utilise g\u00e9n\u00e9ralement des fonctions de hachage cryptographiques)<\/em>.<\/p>\n<p><\/p>\n<p>Bien que j'aie pr\u00e9c\u00e9demment \u00e9crit qu'il faut \u00e9conomiser de l'espace par tous les moyens, nous allons n\u00e9anmoins utiliser une somme de contr\u00f4le de 32 bits (16 bits est insuffisant, la probabilit\u00e9 de collision d\u00e9passe 0,01 % ; et 24 bits, comme on dit, ne m\u00e8ne nulle part).<\/p>\n<p><\/p>\n<p>Une objection pourrait surgir : avons-nous vraiment \u00e9conomis\u00e9 chaque octet lors du choix de la compression pour donner maintenant 4 octets d'un coup ? N'\u00e9tait-il pas mieux de ne pas compresser et de ne pas ajouter de somme de contr\u00f4le ? Bien s\u00fbr que non, l'absence de compression <em>ne signifie pas<\/em>, que la v\u00e9rification d'int\u00e9grit\u00e9 n'est pas n\u00e9cessaire.<\/p>\n<p><\/p>\n<p>Nous ne r\u00e9inventerons pas la roue pour le choix du polyn\u00f4me, et nous prendrons le populaire CRC-32C.<br \/>\nCe code d\u00e9tecte 6 erreurs de bits sur des paquets jusqu'\u00e0 22 octets (peut-\u00eatre le cas le plus fr\u00e9quent pour nous), 4 erreurs de bits sur des paquets jusqu'\u00e0 655 octets (\u00e9galement un cas fr\u00e9quent pour nous), 2 ou tout nombre impair d'erreurs de bits sur des paquets de toute longueur raisonnable.<\/p>\n<p>\n<b class=\"spoiler_title\">Si cela int\u00e9resse quelqu'un<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cyclic_redundancy_check\">Article de Wikip\u00e9dia<\/a><\/noindex> sur le CRC.<\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0x8f6e37a0_len.txt\">Les param\u00e8tres du code crc-32c<\/a><\/noindex> sur <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/crc\/notes.html\">sur le site de Kupman<\/a><\/noindex> \u2014 peut-\u00eatre le principal sp\u00e9cialiste du CRC sur la plan\u00e8te.<\/p>\n<p><\/p>\n<p>Dans <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/networks\/dsn02\/dsn02_koopman.pdf\">dans son article<\/a><\/noindex> Il existe <noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0xfa567d89_len.txt\">un autre code int\u00e9ressant<\/a><\/noindex>, offrant des param\u00e8tres l\u00e9g\u00e8rement meilleurs pour les longueurs de paquets qui nous concernent, mais je n'ai pas jug\u00e9 la diff\u00e9rence significative, et je me sens suffisamment comp\u00e9tent pour choisir un code personnalis\u00e9 plut\u00f4t que standard et bien \u00e9tudi\u00e9.<\/p>\n<p><\/p>\n<p>De plus, \u00e9tant donn\u00e9 que nos donn\u00e9es sont compress\u00e9es, se pose la question : devrions-nous calculer la somme de contr\u00f4le \u00e0 partir des donn\u00e9es compress\u00e9es ou non compress\u00e9es ?<\/p>\n<p><\/p>\n<p>Arguments 'pour' le calcul de la somme de contr\u00f4le des donn\u00e9es non compress\u00e9es :<\/p>\n<p><\/p>\n<ul>\n<li>nous devons finalement v\u00e9rifier l'int\u00e9grit\u00e9 des donn\u00e9es stock\u00e9es \u2014 c'est ce que nous v\u00e9rifions directement (cela permettra \u00e9galement de v\u00e9rifier les \u00e9ventuelles erreurs dans l'impl\u00e9mentation de la compression\/d\u00e9compression, les corruptions caus\u00e9es par une m\u00e9moire d\u00e9fectueuse, etc.) ;<\/li>\n<li>l'algorithme deflate dans zlib a une impl\u00e9mentation suffisamment mature et <em>ne devrait pas<\/em> Il \u00e9choue avec des donn\u00e9es d'entr\u00e9e 'corrompues', de plus, il est souvent capable de d\u00e9tecter lui-m\u00eame les erreurs dans le flux d'entr\u00e9e, r\u00e9duisant ainsi la probabilit\u00e9 totale de non-detection d'erreur (j'ai effectu\u00e9 un test en inversant un seul bit dans un enregistrement court, zlib a d\u00e9tect\u00e9 l'erreur environ un tiers du temps).<\/li>\n<\/ul>\n<p><\/p>\n<p>Arguments 'contre' le calcul de la somme de contr\u00f4le des donn\u00e9es non compress\u00e9es :<\/p>\n<p><\/p>\n<ul>\n<li>Le CRC est 'optimis\u00e9' pr\u00e9cis\u00e9ment pour les erreurs de bits peu fr\u00e9quentes, qui sont typiques de la m\u00e9moire flash (une erreur de bit dans un flux compress\u00e9 peut entra\u00eener un changement massif du flux de sortie, sur lequel, en th\u00e9orie, nous pourrions 'attraper' une collision);<\/li>\n<li>je n'aime pas trop l'id\u00e9e de transmettre des donn\u00e9es potentiellement corrompues au d\u00e9compresseur, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cvedetails.com\/vulnerability-list\/vendor_id-72\/product_id-1820\/GNU-Zlib.html\">qui sait<\/a><\/noindex>, comment il r\u00e9agira.<\/li>\n<\/ul>\n<p><\/p>\n<p>Dans ce projet, j'ai d\u00e9cid\u00e9 de m'\u00e9loigner de la pratique courante de stockage de la somme de contr\u00f4le des donn\u00e9es non compress\u00e9es.<\/p>\n<p><\/p>\n<p><strong>R\u00e9sum\u00e9 :<\/strong> nous utilisons CRC-32C, et nous calculons la somme de contr\u00f4le \u00e0 partir des donn\u00e9es tel qu'elles sont enregistr\u00e9es dans la flash (apr\u00e8s compression).<\/p>\n<p><\/p>\n<h3 id=\"izbytochnost\">Redondance<\/h3>\n<p><\/p>\n<p>L'utilisation de la redondance de code ne permet pas, bien s\u00fbr, d'\u00e9liminer compl\u00e8tement la perte de donn\u00e9es, mais elle peut consid\u00e9rablement (souvent de plusieurs ordres de grandeur) r\u00e9duire la probabilit\u00e9 de pertes de donn\u00e9es irr\u00e9cup\u00e9rables.<\/p>\n<p><\/p>\n<p>Nous pouvons utiliser diff\u00e9rents types de redondance pour corriger les erreurs.<br \/>\nLes codes de Hamming peuvent corriger des erreurs de bit uniques, les codes de Reed-Solomon sont symboliques, et plusieurs copies de donn\u00e9es accompagn\u00e9es de sommes de contr\u00f4le ou un codage tel que RAID-6 peuvent aider \u00e0 r\u00e9cup\u00e9rer des donn\u00e9es m\u00eame en cas de dommages massifs.<br \/>\nAu d\u00e9part, j'\u00e9tais en faveur d'une utilisation \u00e9tendue du codage r\u00e9sistant aux erreurs, mais j'ai ensuite r\u00e9alis\u00e9 qu'il fallait d'abord comprendre contre quelles erreurs nous voulons nous prot\u00e9ger, puis choisir le codage.<\/p>\n<p><\/p>\n<p>Nous avons dit pr\u00e9c\u00e9demment qu'il fallait d\u00e9tecter les erreurs le plus t\u00f4t possible. \u00c0 quels moments pouvons-nous \u00eatre confront\u00e9s \u00e0 des erreurs ?<\/p>\n<p><\/p>\n<ol>\n<li>Un enregistrement inachev\u00e9 (pour diverses raisons, l'alimentation a \u00e9t\u00e9 coup\u00e9e au moment de l'\u00e9criture, le Raspberry a plant\u00e9, ...)<br \/>\nH\u00e9las, dans le cas d'une telle erreur, il ne reste qu'\u00e0 ignorer les enregistrements invalides et \u00e0 consid\u00e9rer les donn\u00e9es comme perdues ;<\/li>\n<li>Erreurs d'\u00e9criture (pour une raison quelconque, ce qui a \u00e9t\u00e9 \u00e9crit dans la m\u00e9moire flash n'est pas ce qui a \u00e9t\u00e9 demand\u00e9)<br \/>\nNous pouvons imm\u00e9diatement d\u00e9tecter de telles erreurs si nous effectuons une lecture de contr\u00f4le juste apr\u00e8s l'\u00e9criture ;<\/li>\n<li>Distorsion des donn\u00e9es en m\u00e9moire au cours du stockage ;<\/li>\n<li>Erreurs de lecture<br \/>\nPour la correction, il suffit, en cas de d\u00e9saccord des sommes de contr\u00f4le, de r\u00e9p\u00e9ter plusieurs fois la lecture.<\/li>\n<\/ol>\n<p><\/p>\n<p>Ainsi, seules les erreurs de troisi\u00e8me type (corruption spontan\u00e9e des donn\u00e9es au stockage) ne peuvent \u00eatre corrig\u00e9es sans codage r\u00e9sistant aux erreurs. Il semble que des erreurs de ce type soient n\u00e9anmoins extr\u00eamement peu probables.<\/p>\n<p><\/p>\n<p><strong>R\u00e9sum\u00e9 :<\/strong> Il a \u00e9t\u00e9 d\u00e9cid\u00e9 d'abandonner le codage redondant, mais si l'exploitation montre que cette d\u00e9cision est erron\u00e9e, alors il sera n\u00e9cessaire de revenir sur la question (avec des statistiques accumul\u00e9es sur les pannes, ce qui permettra de choisir le type de codage optimal).<\/p>\n<p><\/p>\n<h3 id=\"prochee\">Autre<\/h3>\n<p><\/p>\n<p>\u00c9videmment, le format de l'article ne permet pas de justifier chaque bit dans le format <em>(et je suis d\u00e9j\u00e0 \u00e0 court de forces)<\/em>, donc je vais bri\u00e8vement couvrir certains points qui n'ont pas \u00e9t\u00e9 abord\u00e9s auparavant.<\/p>\n<p><\/p>\n<ul>\n<li>Il a \u00e9t\u00e9 d\u00e9cid\u00e9 de rendre toutes les pages '\u00e9galement valables'<br \/>\nCela signifie qu'il n'y aura pas de pages sp\u00e9ciales avec des m\u00e9tadonn\u00e9es, des flux distincts, etc., mais un flux unique qui r\u00e9\u00e9crit toutes les pages \u00e0 tour de r\u00f4le.<br \/>\nCela garantit une usure uniforme des pages, l'absence d'un point de d\u00e9faillance unique, et c'est tout simplement agr\u00e9able.<\/li>\n<li>Il est imp\u00e9ratif de pr\u00e9voir la version du format.<br \/>\nUn format sans num\u00e9ro de version dans l'en-t\u00eate est une mauvaise id\u00e9e !<br \/>\nIl suffit d'ajouter dans l'en-t\u00eate de la page un champ avec un Magic Number (signature), qui indiquera la version du format utilis\u00e9. <em>(je ne pense pas qu'il y en ait m\u00eame une dizaine en pratique)<\/em>;<\/li>\n<li>Utilisez un en-t\u00eate de longueur variable pour les enregistrements (qui sont tr\u00e8s nombreux), en essayant de le faire d'une longueur de 1 octet pour la plupart des cas.<\/li>\n<li>Pour coder la longueur de l'en-t\u00eate et la longueur de la partie tronqu\u00e9e de l'enregistrement compress\u00e9, utilisez des codes binaires de longueur variable.<\/li>\n<\/ul>\n<p><\/p>\n<p>A beaucoup aid\u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/planetcalc.com\/2481\/\">g\u00e9n\u00e9rateur en ligne<\/a><\/noindex> de codes de Huffman. En quelques minutes, j'ai pu trouver les codes de longueur variable n\u00e9cessaires.<\/p>\n<p><\/p>\n<h1 id=\"anchorformatanchoropisanie-formata-hraneniya-dannyh\"><noindex><a rel=\"nofollow\" name=\"format\"><\/a><\/noindex>Description du format de stockage des donn\u00e9es<\/h1>\n<p><\/p>\n<h2 id=\"byte-order\">Ordre des octets<\/h2>\n<p><\/p>\n<p>Les champs dont la taille d\u00e9passe un octet sont stock\u00e9s en format big-endian (ordre des octets r\u00e9seau), c'est-\u00e0-dire que 0x1234 est enregistr\u00e9 sous la forme 0x12, 0x34.<\/p>\n<p><\/p>\n<h2 id=\"delenie-na-stranicy\">Division en pages<\/h2>\n<p><\/p>\n<p>Toute la m\u00e9moire flash est divis\u00e9e en pages de taille \u00e9gale.<\/p>\n<p><\/p>\n<p>La taille de la page par d\u00e9faut est de 32 Ko, mais pas plus d'un quart de la taille totale de la puce de m\u00e9moire (pour une puce de 4 Mo, cela repr\u00e9sente 128 pages).<\/p>\n<p><\/p>\n<p>Chaque page stocke des donn\u00e9es ind\u00e9pendamment des autres (c'est-\u00e0-dire que les donn\u00e9es d'une page ne r\u00e9f\u00e9rencent pas les donn\u00e9es d'une autre page).<\/p>\n<p><\/p>\n<p>Toutes les pages sont num\u00e9rot\u00e9es dans un ordre naturel (dans l'ordre croissant des adresses), en commen\u00e7ant par le num\u00e9ro 0 (la page z\u00e9ro commence \u00e0 l'adresse 0, la premi\u00e8re \u00e0 32 Ko, la seconde \u00e0 64 Ko, etc.).<\/p>\n<p><\/p>\n<p>La puce m\u00e9moire est utilis\u00e9e comme un tampon circulaire (ring buffer), c'est-\u00e0-dire que l'\u00e9criture commence \u00e0 la page num\u00e9ro 0, puis va \u00e0 la page num\u00e9ro 1, et ainsi de suite. Lorsque nous remplissons la derni\u00e8re page, un nouveau cycle commence et l'\u00e9criture se poursuit \u00e0 partir de la page z\u00e9ro.<\/p>\n<p><\/p>\n<h2 id=\"vnutri-stranicy\">\u00c0 l'int\u00e9rieur de la page<\/h2>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/39b45f83dc46bb2fd7081ed2a0b638c9.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAu d\u00e9but de la page se trouve un en-t\u00eate de 4 octets, suivi d'un contr\u00f4le de somme d'en-t\u00eate (CRC-32C), puis les enregistrements sont stock\u00e9s au format \u00ab en-t\u00eate, donn\u00e9es, contr\u00f4le de somme \u00bb.<\/p>\n<p><\/p>\n<p>L'en-t\u00eate de la page (en vert sale sur le sch\u00e9ma) se compose de :<\/p>\n<p><\/p>\n<ul>\n<li>un champ de deux octets Magic Number (qui est la marque de version du format)<br \/>\npour la version actuelle du format, il est consid\u00e9r\u00e9 comme <code>0xed00 \u2295 num\u00e9ro de page<\/code>;<\/li>\n<li>un compteur \u00e0 deux octets \u00ab Version de la page \u00bb (num\u00e9ro du cycle de r\u00e9\u00e9criture de la m\u00e9moire).<\/li>\n<\/ul>\n<p><\/p>\n<p>Les enregistrements sur la page sont stock\u00e9s sous forme compress\u00e9e (l'algorithme deflate est utilis\u00e9). Tous les enregistrements sur une m\u00eame page sont compress\u00e9s dans un seul flux (un dictionnaire commun est utilis\u00e9), et \u00e0 chaque nouvelle page, la compression recommence \u00e0 z\u00e9ro. Cela signifie que pour d\u00e9compresser n'importe quel enregistrement, il faut tous les enregistrements pr\u00e9c\u00e9dents de cette page (et seulement de celle-ci).<\/p>\n<p><\/p>\n<p>Chaque enregistrement sera compress\u00e9 avec le drapeau Z_SYNC_FLUSH, ce qui laisse \u00e0 la fin du flux compress\u00e9 4 octets 0x00, 0x00, 0xff, 0xff, \u00e9ventuellement pr\u00e9c\u00e9d\u00e9s d'un ou deux octets nuls.<br \/>\nCette s\u00e9quence (d'une longueur de 4, 5 ou 6 octets) est \u00e9cart\u00e9e lors de l'\u00e9criture dans la m\u00e9moire flash.<\/p>\n<p><\/p>\n<p>L'en-t\u00eate de l'enregistrement se compose de 1, 2 ou 3 octets, stockant :<\/p>\n<p><\/p>\n<ul>\n<li>un bit (T), indiquant le type d'enregistrement : 0 \u2014 contexte, 1 \u2014 journal ;<\/li>\n<li>un champ de longueur variable (S) de 1 \u00e0 7 bits, d\u00e9terminant la longueur de l'en-t\u00eate et du \u00ab suffixe \u00bb \u00e0 ajouter \u00e0 l'enregistrement pour le d\u00e9ballage ;<\/li>\n<li>la longueur de l'enregistrement (L).<\/li>\n<\/ul>\n<p><\/p>\n<p>Tableau des valeurs S :<\/p>\n<p><\/p>\n<p>S<br \/>\nLongueur de l'en-t\u00eate, octets<br \/>\n\u00c9cart\u00e9 lors de l'\u00e9criture, octet<\/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>J'ai essay\u00e9 d'illustrer, je ne sais pas \u00e0 quel point c'est clair :<br \/>\n<img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/8c9b739eec5af4429395c32b4ba4044a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn jaune, le champ T, en blanc, le champ S, en vert L (longueur des donn\u00e9es compress\u00e9es en octets), en bleu, les donn\u00e9es compress\u00e9es, en rouge, les octets finaux des donn\u00e9es compress\u00e9es qui ne sont pas \u00e9crits dans la m\u00e9moire flash.<\/p>\n<p><\/p>\n<p>Ainsi, les en-t\u00eates des enregistrements de longueur la plus courante (jusqu'\u00e0 63+5 octets en version compress\u00e9e) peuvent \u00eatre enregistr\u00e9s en un seul octet.<\/p>\n<p><\/p>\n<p>Apr\u00e8s chaque enregistrement se trouve un contr\u00f4le de somme CRC-32C, dont la valeur initiale (init) est l'inversion de la somme de contr\u00f4le pr\u00e9c\u00e9dente.<\/p>\n<p><\/p>\n<p><em>Le CRC a la propri\u00e9t\u00e9 de \u00ab continuit\u00e9 \u00bb, il fonctionne (plus ou moins l'inversion des bits pendant le processus) selon cette formule : <img decoding=\"async\" alt=\"Ma mise en \u0153uvre d&#039;un tampon circulaire dans la m\u00e9moire flash NOR\" src=\"\/wp-content\/uploads\/2019\/12\/b194d6a3d18ae2f4ae5d4054a93c0ea3.jpg\" style=\"display:block;margin: 0 auto;\" \/>.<br \/>\nC'est-\u00e0-dire que nous calculons en fait le CRC de tous les octets pr\u00e9c\u00e9dents des en-t\u00eates et des donn\u00e9es sur cette page.<\/em><\/p>\n<p><\/p>\n<p>Juste apr\u00e8s la somme de contr\u00f4le se trouve l'en-t\u00eate du prochain enregistrement.<\/p>\n<p><\/p>\n<p>L'en-t\u00eate est construit de mani\u00e8re \u00e0 ce que son premier octet soit toujours diff\u00e9rent de 0x00 et 0xff (si nous rencontrons 0xff \u00e0 la place du premier octet de l'en-t\u00eate, cela signifie que c'est une zone inutilis\u00e9e ; 0x00 signale une erreur).<\/p>\n<p><\/p>\n<h2 id=\"primernye-algoritmy\">Algorithmes approximatifs<\/h2>\n<p><\/p>\n<h3 id=\"chtenie-iz-flesh-pamyati\">Lecture \u00e0 partir de la m\u00e9moire flash<\/h3>\n<p><\/p>\n<p>Toute lecture se fait avec v\u00e9rification de la somme de contr\u00f4le.<br \/>\nSi la somme de contr\u00f4le ne correspond pas, la lecture est r\u00e9p\u00e9t\u00e9e plusieurs fois dans l'espoir de lire des donn\u00e9es correctes.<\/p>\n<p><\/p>\n<p><em>(cela a du sens, Linux ne met pas en cache la lecture de la m\u00e9moire Flash NOR, v\u00e9rifi\u00e9)<\/em><\/p>\n<p><\/p>\n<h3 id=\"zapis-v-flesh-pamyat\">\u00c9criture dans la m\u00e9moire Flash<\/h3>\n<p><\/p>\n<p>Nous \u00e9crivons des donn\u00e9es.<br \/>\nNous les lisons.<\/p>\n<p><\/p>\n<p>Si les donn\u00e9es lues ne correspondent pas \u00e0 celles \u00e9crites, nous remplissons la zone de z\u00e9ros et signalons une erreur.<\/p>\n<p><\/p>\n<h3 id=\"podgotovka-novoy-mikroshemy-k-rabote\">Pr\u00e9paration d'une nouvelle puce \u00e0 la fonction<\/h3>\n<p><\/p>\n<p>Pour l'initialisation, un en-t\u00eate avec la version 1 est \u00e9crit sur la premi\u00e8re (c'est-\u00e0-dire la z\u00e9ro) page.<br \/>\nApr\u00e8s cela, le contexte initial (contient l'UUID de la machine et les param\u00e8tres par d\u00e9faut) est \u00e9crit dans cette page. <\/p>\n<p><\/p>\n<p>C'est bon, la m\u00e9moire Flash est pr\u00eate \u00e0 l'emploi.<\/p>\n<p><\/p>\n<h3 id=\"zagruzka-avtomata\">Chargement de la machine<\/h3>\n<p><\/p>\n<p>Lors du chargement, les premiers 8 octets de chaque page (en-t\u00eate + CRC) sont lus, les pages avec un Magic Number inconnu ou un CRC incorrect sont ignor\u00e9es.<br \/>\nParmi les pages \u00ab correctes \u00bb, celles avec la version maximale sont s\u00e9lectionn\u00e9es, et on prend celle qui a le num\u00e9ro le plus \u00e9lev\u00e9.<br \/>\nLa premi\u00e8re entr\u00e9e est lue, la validit\u00e9 du CRC et la pr\u00e9sence du drapeau \u00ab contexte \u00bb sont v\u00e9rifi\u00e9es. Si tout est normal, cette page est consid\u00e9r\u00e9e comme actuelle. Sinon, nous revenons \u00e0 la pr\u00e9c\u00e9dente jusqu'\u00e0 ce que nous trouvions une page \u00ab active \u00bb.<br \/>\nSur la page trouv\u00e9e, nous lisons toutes les entr\u00e9es, celles avec le drapeau \u00ab contexte \u00bb sont appliqu\u00e9es.<br \/>\nNous sauvegardons le dictionnaire zlib (il sera n\u00e9cessaire pour des \u00e9critures suppl\u00e9mentaires dans cette page).<\/p>\n<p><\/p>\n<p>C'est bon, le chargement est termin\u00e9, le contexte est restaur\u00e9, nous pouvons travailler.<\/p>\n<p><\/p>\n<h3 id=\"dobavlenie-zapisi-v-zhurnal\">Ajout d'une entr\u00e9e au journal<\/h3>\n<p><\/p>\n<p>Nous compressons l'entr\u00e9e avec le bon dictionnaire, en utilisant Z_SYNC_FLUSH. Nous v\u00e9rifions si l'entr\u00e9e compress\u00e9e tient sur la page actuelle.<br \/>\nSi elle ne tient pas (ou s'il y a eu des erreurs de CRC sur la page), nous commen\u00e7ons une nouvelle page (voir ci-dessous).<br \/>\nNous \u00e9crivons l'entr\u00e9e et le CRC. En cas d'erreur, nous commen\u00e7ons une nouvelle page.<\/p>\n<p><\/p>\n<h3 id=\"novaya-stranica\">Nouvelle page<\/h3>\n<p><\/p>\n<p>Nous choisissons une page libre avec le num\u00e9ro minimal (nous consid\u00e9rons comme libre une page avec une somme de contr\u00f4le incorrecte dans l'en-t\u00eate ou une version inf\u00e9rieure \u00e0 la version actuelle). S'il n'y a pas de telles pages, nous choisissons la page avec le num\u00e9ro minimal ayant la version \u00e9gale \u00e0 la version actuelle.<br \/>\nNous mettons la page s\u00e9lectionn\u00e9e en mode effacement. Nous comparons le contenu avec 0xff. Si quelque chose ne va pas, nous prenons la page libre suivante, etc.<br \/>\nSur la page effac\u00e9e, nous \u00e9crivons l'en-t\u00eate, la premi\u00e8re entr\u00e9e contient l'\u00e9tat actuel du contexte, la suivante l'entr\u00e9e de journal non \u00e9crite (s'il y en a).<\/p>\n<p><\/p>\n<h1 id=\"primenimost-formata\">Applicabilit\u00e9 du format<\/h1>\n<p><\/p>\n<p>\u00c0 mon avis, c'est un bon format pour stocker tout flux d'informations compressibles (texte simple, JSON, MessagePack, CBOR, peut-\u00eatre protobuf) dans de la NOR Flash.<\/p>\n<p><\/p>\n<p>Bien s\u00fbr, le format est \u00ab con\u00e7u \u00bb pour la m\u00e9moire SLC NOR Flash.<\/p>\n<p><\/p>\n<p>Il ne faut pas l'utiliser avec des supports ayant un BER \u00e9lev\u00e9, comme NAND ou MLC NOR. <em>(y a-t-il de la m\u00e9moire de ce type en vente ? Je n'ai vu que des r\u00e9f\u00e9rences dans des travaux sur les codes de correction)<\/em>.<\/p>\n<p><\/p>\n<p>D'autant plus qu'il ne faut pas l'utiliser avec des dispositifs ayant leur propre FTL : USB flash, SD, MicroSD, etc. <em>(pour ce type de m\u00e9moire, j'ai cr\u00e9\u00e9 un format avec une taille de page de 512 octets, une signature au d\u00e9but de chaque page et des num\u00e9ros d'enregistrements uniques \u2014 parfois, en proc\u00e9dant \u00e0 une lecture s\u00e9quentielle, j'ai pu r\u00e9cup\u00e9rer toutes les donn\u00e9es d'une cl\u00e9 USB \u00ab bogu\u00e9e \u00bb.)<\/em>.<\/p>\n<p><\/p>\n<p>Selon les t\u00e2ches, le format peut \u00eatre utilis\u00e9 sans modifications sur des cl\u00e9s USB de 128Kbit (16Ko) \u00e0 1Gbit (128Mo). Si d\u00e9sir\u00e9, il peut \u00e9galement \u00eatre utilis\u00e9 sur des puces de plus grande capacit\u00e9, mais il faudra probablement ajuster la taille de la page. <em>(Mais l\u00e0 se pose d\u00e9j\u00e0 la question de la rentabilit\u00e9 \u00e9conomique, le prix de la NOR Flash de grande capacit\u00e9 n'est pas r\u00e9jouissant)<\/em>.<\/p>\n<p><\/p>\n<p>Si ce format semble int\u00e9ressant \u00e0 quelqu'un et qu'il souhaite l'utiliser dans un projet open source \u2014 \u00e9crivez-moi, je ferai de mon mieux pour trouver du temps, peaufiner le code et le publier sur github.<\/p>\n<p><\/p>\n<h1 id=\"zaklyuchenie\">Conclusion<\/h1>\n<p><\/p>\n<p>Comme nous le voyons, au final, le format s'est av\u00e9r\u00e9 simple. <em>et m\u00eame ennuyeux.<\/em>.<\/p>\n<p><\/p>\n<p>Dans l'article, il est difficile de refl\u00e9ter l'\u00e9volution de mon point de vue, mais croyez-moi : au d\u00e9part, je voulais cr\u00e9er quelque chose de complexe, d'invuln\u00e9rable, capable de survivre m\u00eame apr\u00e8s une explosion nucl\u00e9aire \u00e0 proximit\u00e9. Cependant, la raison (j'esp\u00e8re) a finalement triomph\u00e9 et mes priorit\u00e9s se sont progressivement d\u00e9plac\u00e9es vers la simplicit\u00e9 et la compacit\u00e9.<\/p>\n<p><\/p>\n<p>Est-il possible que je me sois tromp\u00e9 ? Oui, bien s\u00fbr. Il se peut par exemple que nous ayons achet\u00e9 un lot de puces de mauvaise qualit\u00e9. Ou pour une autre raison, l'\u00e9quipement ne r\u00e9pondra pas aux attentes en mati\u00e8re de fiabilit\u00e9.<\/p>\n<p><\/p>\n<p>Ai-je un plan pour ce cas ? Je pense qu'apr\u00e8s avoir lu l'article, vous ne doutez pas que j'ai un plan. Et m\u00eame plusieurs.<\/p>\n<p><\/p>\n<p>Si l'on aborde ce sujet de mani\u00e8re plus s\u00e9rieuse, le format a \u00e9t\u00e9 d\u00e9velopp\u00e9 \u00e0 la fois comme une option de travail et comme un \u00ab projet pilote \u00bb.<\/p>\n<p><\/p>\n<p>\u00c0 ce jour, tout fonctionne normalement sur la table, litt\u00e9ralement dans les jours \u00e0 venir, la solution sera d\u00e9ploy\u00e9e. <em>(environ)<\/em> sur une centaine d'appareils, nous verrons ce qui se passe en \u00ab conditions r\u00e9elles \u00bb (esp\u00e9rons que le format permet de d\u00e9tecter les pannes de mani\u00e8re fiable, afin que nous puissions collecter des statistiques compl\u00e8tes). Dans quelques mois, nous pourrons tirer des conclusions. <em>(et si la chance ne sourit pas, alors m\u00eame plus t\u00f4t)<\/em>.<\/p>\n<p><\/p>\n<p>Si \u00e0 l'issue de l'utilisation des probl\u00e8mes s\u00e9rieux apparaissent et que des ajustements sont n\u00e9cessaires, je n'h\u00e9siterai pas \u00e0 en parler.<\/p>\n<p><\/p>\n<h1 id=\"literatura\">Litt\u00e9rature<\/h1>\n<p><\/p>\n<p>Je n'avais pas envie de dresser une longue liste ennuyeuse de travaux utilis\u00e9s, apr\u00e8s tout, tout le monde a Google.<\/p>\n<p><\/p>\n<p>Ici, j'ai d\u00e9cid\u00e9 de laisser une liste de d\u00e9couvertes que j'ai trouv\u00e9es particuli\u00e8rement int\u00e9ressantes, mais progressivement, elles ont \u00e9t\u00e9 int\u00e9gr\u00e9es directement dans le texte de l'article, et il ne reste qu'un seul \u00e9l\u00e9ment dans la liste :<\/p>\n<p><\/p>\n<ol>\n<li>Utilitaire <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/madler\/infgen\/\">infgen<\/a><\/noindex> de l'auteur zlib. Il peut afficher le contenu des archives deflate\/zlib\/gzip de mani\u00e8re compr\u00e9hensible. Si vous devez vous pencher sur la structure interne du format deflate (ou gzip), je le recommande vivement.<\/li>\n<\/ol>\n<p>Source : <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\/fr\/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=\"fr_FR\" \/>\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\/fr\/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\udd47Ma r\u00e9alisation d'un tampon circulaire en NOR flash | ProHoster","description":"Contexte Il s'agit de distributeurs automatiques d\u00e9velopp\u00e9s en interne. \u00c0 l'int\u00e9rieur, un Raspberry Pi et un peu de c\u00e2blage sur une plaque s\u00e9par\u00e9e.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","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\/fr\/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\/fr\/wp-json\/wp\/v2\/posts\/53751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=53751"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/53751\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=53751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=53751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=53751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}