Mon implémentation de tampon en anneau dans NOR flash

Contexte


Il existe des distributeurs automatiques de notre propre conception. À l'intĂ©rieur du Raspberry Pi et un peu de cerclage sur une carte sĂ©parĂ©e. Un accepteur de piĂšces, un accepteur de billets, un terminal bancaire sont connectĂ©s ... Un programme auto-Ă©crit gĂšre tout. L'historique complet de l'Ɠuvre est Ă©crit dans le magazine sur une clĂ© USB (MicroSD), qui est ensuite transmis via Internet (Ă  l'aide d'un modem USB) au serveur, oĂč il est ajoutĂ© Ă  la base de donnĂ©es. Les informations de vente sont chargĂ©es en 1s, il y a aussi une interface web simple pour le suivi, etc.


C'est-à-dire que le magazine est vital - pour la comptabilité (les revenus, les ventes, etc.), le suivi (toutes sortes de défaillances et autres circonstances de force majeure); ceci, vous pouvez dire, toutes les informations que nous avons sur cette machine.


Le problĂšme


Les lecteurs flash se prĂ©sentent comme des appareils trĂšs peu fiables. Ils Ă©chouent avec une rĂ©gularitĂ© enviable. Cela entraĂźne Ă  la fois un arrĂȘt de la machine et (si pour une raison quelconque le journal n'a pas pu ĂȘtre transmis en ligne) une perte de donnĂ©es.


Ce n'est pas la premiĂšre expĂ©rience d'utilisation de lecteurs flash, avant qu'il y ait un autre projet avec plus d'une centaine d'appareils oĂč le magazine Ă©tait stockĂ© sur des lecteurs flash USB, il y avait aussi des problĂšmes de fiabilitĂ©, parfois le nombre de pannes par mois Ă©tait de l'ordre de la dizaine. Nous avons essayĂ© diffĂ©rents lecteurs flash, y compris ceux de marque sur la mĂ©moire SLC, et certains modĂšles sont plus fiables que d'autres, mais le remplacement des lecteurs flash n'a pas rĂ©solu radicalement le problĂšme.


Attention! Longrid! Si vous n'ĂȘtes pas intĂ©ressĂ© par le «pourquoi», mais uniquement par le «comment», vous pouvez immĂ©diatement aller Ă  la fin de l' article.


Solution


La premiÚre chose qui vient à l'esprit: abandonner MicroSD, mettre, par exemple, SSD et démarrer à partir de celui-ci. C'est théoriquement possible, probablement, mais relativement cher et pas si fiable (un adaptateur USB-SATA est ajouté; sur les SSD à petit budget, les statistiques de défaillance ne sont pas satisfaisantes non plus).


Le disque dur USB ne semble pas non plus une solution particuliĂšrement attrayante.


Par conséquent, nous sommes arrivés à cette option: laisser le téléchargement depuis MicroSD, mais les utiliser en mode lecture seule, et stocker le journal des opérations (et d'autres informations uniques à un matériel spécifique - numéro de série, étalonnages des capteurs, etc.) ailleurs.


Le sujet de la FS en lecture seule pour les framboises a dĂ©jĂ  Ă©tĂ© Ă©tudiĂ© Ă  plusieurs reprises, je ne m'attarderai pas sur les dĂ©tails de la mise en Ɠuvre dans cet article (mais s'il y a un intĂ©rĂȘt, j'Ă©crirai peut-ĂȘtre un mini-article sur ce sujet) . Le seul point que je veux noter: Ă  la fois par expĂ©rience personnelle et par des critiques qui ont dĂ©jĂ  mis en place un gain de fiabilitĂ©, c'est. Oui, il est impossible de se dĂ©barrasser complĂštement des pannes, mais il est tout Ă  fait possible de rĂ©duire considĂ©rablement leur frĂ©quence. Oui, et les cartes deviennent unifiĂ©es, ce qui simplifie considĂ©rablement le remplacement du personnel de maintenance.


Matériel informatique


Il n'y avait aucun doute sur le choix du type de mémoire - NOR Flash.
Arguments:


  • connexion simple (le plus souvent le bus SPI, l'expĂ©rience d'utilisation qui est dĂ©jĂ  lĂ , donc pas de problĂšmes "de fer" Ă  prĂ©voir);
  • prix ridicule;
  • protocole d'exploitation standard (l'implĂ©mentation est dĂ©jĂ  dans le noyau Linux, si vous le souhaitez, vous pouvez prendre un tiers, qui est Ă©galement prĂ©sent, ou mĂȘme Ă©crire le vĂŽtre, l'avantage est simple);
  • fiabilitĂ© et ressource:
    à partir d'une fiche technique typique: les données sont stockées pendant 20 ans, 100 000 cycles d'effacement pour chaque bloc;
    Ă  partir de sources tierces: BER extrĂȘmement bas, il est postulĂ© qu'il n'y a pas besoin de codes de correction d'erreur (dans certains articles, ECC pour NOR est pris en compte, mais gĂ©nĂ©ralement MLC NOR est lĂ , cela arrive) .

Estimons les besoins en volume et en ressources.


Je veux avoir la garantie de sauvegarder les donnĂ©es pendant plusieurs jours. Cela est nĂ©cessaire pour qu'en cas de problĂšme de connexion, l'historique des ventes ne soit pas perdu. Nous allons nous concentrer sur 5 jours, pendant cette pĂ©riode (mĂȘme en tenant compte des week-ends et jours fĂ©riĂ©s), nous pouvons rĂ©soudre le problĂšme.


Nous tapons maintenant environ 100 Ko de magazine par jour (3 à 4 000 enregistrements), mais progressivement ce chiffre augmente - les détails augmentent, de nouveaux événements sont ajoutés. De plus, il y a parfois des salves (certains capteurs commencent à envoyer du spam avec des faux positifs, par exemple). Nous calculerons 10 mille enregistrements de 100 octets - mégaoctets par jour.


Un total de 5 Mo de données propres (bien compressibles) sort. Ils ont également (estimation approximative) 1 Mo de données de service.


Autrement dit, nous avons besoin d'une puce de 8 Mo si vous n'utilisez pas la compression, ou de 4 Mo si vous l'utilisez. Des nombres bien réels pour ce type de mémoire.


En ce qui concerne la ressource: si nous prévoyons que la mémoire entiÚre ne sera réécrite pas plus d'une fois tous les 5 jours, alors en 10 ans de service, nous obtenons moins de mille cycles de réécriture.
Je me souviens, le constructeur promet cent mille.


Un peu sur NOR vs NAND

Aujourd'hui, bien sûr, la mémoire NAND est beaucoup plus populaire, mais pour ce projet, je ne l'utiliserais pas: NAND, contrairement à NOR, nécessite nécessairement l'utilisation de codes de correction d'erreur, une table de mauvais blocs, etc., et les jambes de puces NAND généralement beaucoup plus.


Les inconvénients de NOR incluent:


  • petit volume (et, par consĂ©quent, prix Ă©levĂ© par mĂ©gaoctet);
  • faible taux de change (en grande partie dĂ» au fait qu'une interface sĂ©rie est utilisĂ©e, gĂ©nĂ©ralement SPI ou I2C);
  • effacement lent (selon la taille du bloc, il faut compter des fractions de seconde Ă  plusieurs secondes).

Cela ne semble rien de critique pour nous, alors continuez.


Si les dĂ©tails sont intĂ©ressants, c'est la puce at25df321a qui a Ă©tĂ© choisie (cependant, c'est insignifiant, il y a beaucoup d'analogues sur le marchĂ© qui sont compatibles avec le brochage et le systĂšme de commande; mĂȘme si nous voulons mettre la puce d'un autre fabricant et / ou d'un autre volume, tout fonctionnera sans changer le code) .


J'utilise le pilote intégré au noyau Linux, sur Raspberry, grùce à la prise en charge de la superposition de l'arborescence des périphériques, tout est trÚs simple - vous devez mettre la superposition compilée dans / boot / overlays et modifier un peu /boot/config.txt.


Exemple de fichier dts

HonnĂȘtement, je ne sais pas ce qui est Ă©crit sans erreurs, mais ça marche.


/* * Device tree overlay for at25 at spi0.1 */ /dts-v1/; /plugin/; / { compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709"; /* disable spi-dev for spi0.1 */ fragment@0 { target = <&spi0>; __overlay__ { status = "okay"; spidev@1{ status = "disabled"; }; }; }; /* the spi config of the at25 */ fragment@1 { target = <&spi0>; __overlay__ { #address-cells = <1>; #size-cells = <0>; flash: m25p80@1 { compatible = "atmel,at25df321a"; reg = <1>; spi-max-frequency = <50000000>; /* default to false: m25p,fast-read ; */ }; }; }; __overrides__ { spimaxfrequency = <&flash>,"spi-max-frequency:0"; fastread = <&flash>,"m25p,fast-read?"; }; }; 

Et une autre ligne dans config.txt
 dtoverlay=at25:spimaxfrequency=50000000 

Je vais omettre la description de la connexion de la puce au Raspberry Pi. D'une part, je ne suis pas un expert en Ă©lectronique, d'autre part, tout est trivial mĂȘme pour moi: le microcircuit n'a que 8 jambes, dont nous avons besoin de masse, d'alimentation, SPI (CS, SI, SO, SCK); les niveaux coĂŻncident avec ceux du Raspberry Pi, aucune liaison supplĂ©mentaire n'est requise - il suffit de connecter les 6 contacts spĂ©cifiĂ©s.


ÉnoncĂ© du problĂšme


Comme d'habitude, la formulation du problĂšme passe par plusieurs itĂ©rations, il me semble que le moment est venu pour la suivante. ArrĂȘtons-nous donc, rassemblons ce qui a dĂ©jĂ  Ă©tĂ© Ă©crit et clarifions les dĂ©tails qui restent dans l'ombre.


Nous avons donc décidé que le journal serait stocké dans SPI NOR Flash.


Qu'est-ce que NOR Flash pour ceux qui ne connaissent pas

Il s'agit d'une mémoire non volatile avec laquelle vous pouvez effectuer trois opérations:


  1. Lecture:
    La lecture la plus courante: nous transmettons l'adresse et lisons autant d'octets que nécessaire;
  2. Record:
    L'écriture dans un flash NOR ressemble à un flash ordinaire, mais elle a une particularité: vous ne pouvez changer que 1 en 0, mais pas l'inverse. Par exemple, si nous avions 0x55 dans la cellule mémoire, alors aprÚs y avoir écrit 0x0f, 0x05 y sera déjà stocké (voir le tableau ci-dessous) ;
  3. Effacer:
    Bien sûr, nous devons également pouvoir effectuer l'opération inverse - changer 0 en 1, c'est pourquoi l'opération d'effacement existe. Contrairement aux deux premiers, il ne fonctionne pas en octets, mais en blocs (le bloc d'effacement minimum dans le microcircuit sélectionné est de 4 Ko). L'effacement détruit le bloc entier et c'est la seule façon de changer 0 à 1. Par conséquent, lorsque vous travaillez avec la mémoire flash, vous devez souvent aligner les structures de données sur la bordure du bloc d'effacement.
    Enregistrer en NOR Flash:

Données binaires
Était01010101
Enregistré00001111
Est devenu00000101

Le journal lui-mĂȘme reprĂ©sente une sĂ©quence d'enregistrements de longueur variable. Une longueur d'enregistrement typique est d'environ 30 octets (bien que des enregistrements de plusieurs kilo-octets se produisent parfois). Dans ce cas, nous travaillons avec eux comme un ensemble d'octets, mais, si vous ĂȘtes intĂ©ressĂ©, CBOR est utilisĂ© dans les enregistrements.


En plus du journal, nous devons stocker certaines informations de "réglage", qu'elles soient mises à jour ou non: un certain ID d'appareil, l'étalonnage du capteur, l'indicateur "l'appareil est temporairement désactivé", etc.
Ces informations sont un ensemble d'enregistrements de valeurs-clés, également stockés dans CBOR. Nous n'avons pas beaucoup de ces informations (quelques kilo-octets maximum), elles sont rarement mises à jour.
À l'avenir, nous l'appellerons contexte.


Si vous vous souvenez oĂč cet article a commencĂ©, il est trĂšs important de garantir la fiabilitĂ© du stockage des donnĂ©es et, si possible, un fonctionnement continu mĂȘme en cas de panne matĂ©rielle / corruption de donnĂ©es.


Quelles sources de problĂšmes peuvent ĂȘtre envisagĂ©es?


  • Mettez hors tension pendant les opĂ©rations d'Ă©criture / effacement. Il s'agit de la catĂ©gorie «contre la ferraille sans rĂ©ception».
    Informations issues de la discussion sur stackexchange: lorsque l'alimentation est coupĂ©e lorsque vous travaillez avec le flash, cet effacement (mise Ă  1), cette Ă©criture (mise Ă  0) conduisent Ă  un comportement indĂ©fini: les donnĂ©es peuvent ĂȘtre Ă©crites, partiellement Ă©crites (disons, nous avons transfĂ©rĂ© 10 octets / 80 bits , et seulement 45 bits ont rĂ©ussi Ă  ĂȘtre enregistrĂ©s), il est Ă©galement possible que certains des bits soient Ă  l'Ă©tat "intermĂ©diaire" (la lecture peut produire soit 0 soit 1);
  • Erreurs de la mĂ©moire flash elle-mĂȘme.
    Le BER, bien que trĂšs faible, ne peut pas ĂȘtre Ă©gal Ă  zĂ©ro;
  • Erreurs de bus
    Les données transmises via SPI ne sont en aucun cas protégées, cela peut trÚs bien se produire sous la forme d'erreurs sur un seul bit ou d'erreurs de synchronisation - perte ou insertion de bits (ce qui entraßne une distorsion massive des données);
  • Autres erreurs / Ă©checs
    Erreurs dans le code, "pépins" de framboise, intervention extraterrestre ...

J'ai formulé des exigences, dont la satisfaction, à mon avis, est nécessaire pour assurer la fiabilité:


  • Les enregistrements doivent ĂȘtre Ă©crits dans la mĂ©moire flash immĂ©diatement, l'enregistrement en attente n'est pas pris en compte; - si une erreur se produit, elle doit ĂȘtre dĂ©tectĂ©e et traitĂ©e dĂšs que possible; - le systĂšme doit, si possible, se remettre des erreurs.
    (un exemple tirĂ© de la vie "comme il ne devrait pas ĂȘtre", que tout le monde a rencontrĂ©, je pense: aprĂšs un redĂ©marrage d'urgence, le systĂšme de fichiers "s'est cassĂ©" et le systĂšme d'exploitation ne dĂ©marre pas)

Idées, approches, réflexions


Quand j'ai commencĂ© Ă  penser Ă  cette tĂąche, un tas d'idĂ©es m'a traversĂ© la tĂȘte, par exemple:


  • Utiliser la compression des donnĂ©es
  • Utilisez des structures de donnĂ©es dĂ©licates, par exemple, stockez les en-tĂȘtes d'enregistrement sĂ©parĂ©ment des enregistrements eux-mĂȘmes, de sorte que si une erreur se produit dans un enregistrement, vous pouvez lire le reste sans problĂšme
  • utiliser des champs de bits pour contrĂŽler l'intĂ©gralitĂ© de l'enregistrement lorsque l'alimentation est coupĂ©e;
  • stocker des sommes de contrĂŽle pour tout et tout;
  • utiliser une sorte de codage correcteur d'erreurs.

Certaines de ces idées ont été utilisées, d'autres ont été décidées à refuser. Allons dans l'ordre.


Compression des données


Les Ă©vĂ©nements que nous enregistrons dans le journal eux-mĂȘmes sont tout Ă  fait les mĂȘmes et rĂ©pĂ©tables ("jetĂ© une piĂšce de 5 roubles", "cliquĂ© sur le bouton de livraison de changement", ...). Par consĂ©quent, la compression devrait ĂȘtre assez efficace.


La surcharge pour la compression est insignifiante (le processeur que nous avons est assez puissant, mĂȘme sur le premier Pi il y avait un cƓur avec une frĂ©quence de 700 MHz, sur les modĂšles actuels il y avait plusieurs cƓurs avec une frĂ©quence de plus d'un gigahertz), la vitesse d'Ă©change avec le stockage est faible (plusieurs mĂ©gaoctets par seconde), la taille d'enregistrement est petite. En gĂ©nĂ©ral, si la compression affectera les performances, alors seulement positive (absolument non critique, simplement dĂ©clarĂ©e) . De plus, nous n'avons pas de vĂ©ritable Linux embarquĂ©, mais ordinaire - donc l'implĂ©mentation ne devrait pas nĂ©cessiter beaucoup d'efforts (il suffit de lier la bibliothĂšque et d'utiliser plusieurs fonctions).


Une partie du journal a été extraite du périphérique de travail (1,7 Mo, 70000 enregistrements) et pour commencer, sa compressibilité a été vérifiée à l'aide des gzip, lz4, lzop, bzip2, xz, zstd disponibles sur l'ordinateur.


  • gzip, xz, zstd ont montrĂ© des rĂ©sultats similaires (40 Ko).
    J'ai été surpris que le xz à la mode se soit montré ici au niveau gzip ou zstd;
  • lzip avec les paramĂštres par dĂ©faut a donnĂ© un rĂ©sultat lĂ©gĂšrement pire;
  • lz4 et lzop n'ont pas donnĂ© de trĂšs bons rĂ©sultats (150 Ko);
  • bzip2 a montrĂ© des rĂ©sultats Ă©tonnamment bons (18 Ko).

Les données sont donc trÚs bien compressées.
Donc (si nous ne trouvons pas de dĂ©fauts fatals) il devrait y avoir une compression! Tout simplement parce que plus de donnĂ©es tiendront sur le mĂȘme lecteur flash.


Réfléchissons aux défauts.


Premier problÚme: nous avons déjà convenu que chaque disque devrait immédiatement se mettre à clignoter. Habituellement, l'archiveur collecte les données du flux d'entrée jusqu'à ce qu'il décide qu'il est temps d'écrire sur la sortie. Nous devons immédiatement obtenir un bloc de données compressé et l'enregistrer dans une mémoire non volatile.


Je vois trois façons:


  1. Compressez chaque entrée en utilisant la compression du dictionnaire au lieu des algorithmes décrits ci-dessus.
    C'est une option qui fonctionne, mais je ne l'aime pas. Pour garantir un niveau de compression plus ou moins dĂ©cent, le dictionnaire doit ĂȘtre «affiné» pour des donnĂ©es spĂ©cifiques, tout changement entraĂźnera une baisse catastrophique du niveau de compression. Oui, le problĂšme est rĂ©solu en crĂ©ant une nouvelle version du dictionnaire, mais c'est un casse-tĂȘte - nous devrons stocker toutes les versions du dictionnaire; dans chaque entrĂ©e, nous devrons indiquer avec quelle version du dictionnaire il a Ă©tĂ© compressĂ© ...
  2. Compressez chaque entrée avec des algorithmes "classiques", mais indépendamment des autres.
    Les algorithmes de compression considérés ne sont pas conçus pour fonctionner avec des enregistrements de cette taille (dizaines d'octets), le coefficient de compression sera clairement inférieur à 1 (c'est-à-dire une augmentation de la quantité de données au lieu de la compression);
  3. Faites FLUSH aprĂšs chaque enregistrement.
    De nombreuses bibliothĂšques de compression prennent en charge FLUSH. Il s'agit d'une commande (ou paramĂštre de la procĂ©dure de compression), Ă  la rĂ©ception de laquelle l'archiveur gĂ©nĂšre un flux compressĂ© afin que toutes les donnĂ©es non compressĂ©es qui ont dĂ©jĂ  Ă©tĂ© reçues puissent ĂȘtre restaurĂ©es. Un tel analogue de sync dans les systĂšmes de fichiers ou de commit dans SQL.
    Ce qui est important, les opérations de compression suivantes pourront utiliser le dictionnaire accumulé et le taux de compression ne souffrira pas autant que dans la version précédente.

Je pense qu'il est Ă©vident que j'ai choisi la troisiĂšme option, approfondissons-la.


Il y avait un excellent article sur FLUSH dans zlib.


J'ai fait un test motivé basé sur l'article, pris 70 000 entrées de journal à partir d'un appareil réel, avec une taille de page de 60 Ko (nous reviendrons à la taille de la page) :


Données sourceCompression Gzip -9 (sans FLUSH)zlib avec Z_PARTIAL_FLUSHzlib avec Z_SYNC_FLUSH
Volume, Kb169240352604

À premiĂšre vue, le prix introduit par FLUSH est excessivement Ă©levĂ©, mais en rĂ©alitĂ© nous avons un mauvais choix - soit ne pas compresser du tout, soit compresser (et trĂšs efficacement) avec FLUSH. N'oubliez pas que nous avons 70 000 enregistrements, la redondance introduite par Z_PARTIAL_FLUSH n'est que de 4 Ă  5 octets par enregistrement. Et le taux de compression s'est avĂ©rĂ© ĂȘtre presque 5: 1, ce qui est plus qu'un excellent rĂ©sultat.


Cela peut sembler inattendu, mais en fait Z_SYNC_FLUSH est un moyen plus efficace de faire FLUSH

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 ne pouvons pas les stocker, donc la taille totale n'est que de 324 Ko.


L'article auquel je fais référence a une explication:


Un nouveau bloc de type 0 avec un contenu vide est ajouté.

Un bloc de type 0 avec un contenu vide se compose de:
  • l'en-tĂȘte de bloc Ă  trois bits;
  • 0 Ă  7 bits Ă©gaux Ă  zĂ©ro, pour rĂ©aliser l'alignement des octets;
  • la sĂ©quence de quatre octets 00 00 FF FF.

Comme vous pouvez le voir, dans le dernier bloc avant ces 4 octets vient de 3 à 10 bits zéro. Cependant, la pratique a montré que zéro bit est en fait au moins 10.


Il s'avÚre que ces blocs de données courts sont généralement (toujours?) Encodés à l'aide d'un bloc de type 1 (bloc fixe), qui se termine nécessairement par 7 bits zéro, nous obtenons donc 10-17 bits garantis zéro (et le reste sera nul avec une probabilité d'environ 50%).


Ainsi, sur les donnĂ©es de test, dans 100% des cas, avant 0x00, 0x00, 0xff, 0xff, il y a un octet zĂ©ro, et plus que dans le troisiĂšme cas, il y a deux octets zĂ©ro (peut-ĂȘtre que j'utilise du CBOR binaire, et lorsque j'utilise du texte CBOR JSON serait plus susceptible de rencontrer des blocs de type 2 - bloc dynamique, respectivement, les blocs se produiraient sans zĂ©ro octet supplĂ©mentaire avant 0x00, 0x00, 0xff, 0xff) .


Le total des données de test disponibles peut contenir moins de 250 Ko de données compressées.


Vous pouvez économiser un peu plus en jonglant avec les bits: maintenant nous ignorons la présence de plusieurs bits zéro à la fin du bloc, plusieurs bits au début du bloc ne changent pas non plus ...
Mais j'ai alors pris la ferme dĂ©cision d'arrĂȘter, sinon, Ă  un tel rythme, vous pouvez atteindre le dĂ©veloppement de votre archiveur.


Au total, j'ai obtenu 3-4 octets par enregistrement de mes donnĂ©es de test, le taux de compression Ă©tait supĂ©rieur Ă  6: 1. HonnĂȘtement, je ne comptais pas sur un tel rĂ©sultat, Ă  mon avis tout ce qui est meilleur que 2: 1 est dĂ©jĂ  un rĂ©sultat qui justifie l'utilisation de la compression.


Tout va bien, mais zlib (dĂ©gonfler) est toujours archaĂŻque algorithme de compression bien mĂ©ritĂ© et lĂ©gĂšrement dĂ©modĂ©. Le simple fait que les 32 derniers Ko du flux de donnĂ©es non compressĂ©s soient utilisĂ©s comme dictionnaire semble Ă©trange aujourd'hui (c'est-Ă -dire que si un bloc de donnĂ©es est trĂšs similaire Ă  ce qui Ă©tait dans le flux d'entrĂ©e de 40 Ko en arriĂšre, il recommencera Ă  ĂȘtre archivĂ©, mais ne sera pas voir l'entrĂ©e prĂ©cĂ©dente). Dans Ă  la mode Le dictionnaire de taille des archiveurs modernes est souvent mesurĂ© en mĂ©gaoctets plutĂŽt qu'en kilo-octets.


Nous continuons donc notre mini-Ă©tude des archiveurs.


Le prochain bzip2 a été testé (rappel, sans FLUSH, il a montré un taux de compression fantastique, presque 100: 1). Hélas, avec FLUSH cela se montrait trÚs mal, la taille des données compressées était supérieure à celle des données non compressées.


Mes hypothĂšses sur les raisons de l'Ă©chec

Libbz2 ne propose qu'une seule option de vidage, ce qui semble effacer le dictionnaire (similaire Ă  Z_FULL_FLUSH dans zlib), il n'y a aucune raison de parler d'une sorte de compression efficace.


Et zstd a Ă©tĂ© le dernier Ă  ĂȘtre testĂ©. Selon les paramĂštres, il se comprime soit au niveau gzip, mais beaucoup plus rapidement, ou gzip est meilleur.


Hélas, avec FLUSH, il s'est avéré «pas trÚs»: la taille des données compressées était d'environ 700 Ko.


J'ai posé une question sur la page du projet dans github, j'ai obtenu une réponse selon laquelle il vaut la peine de compter jusqu'à 10 octets de données de service pour chaque bloc de données compressées, ce qui est proche des résultats, la capture du dégonflage ne fonctionne pas.


J'ai dĂ©cidĂ© de m'arrĂȘter lĂ -dessus dans des expĂ©riences avec des archiveurs (je vous rappelle que xz, lzip, lzo, lz4 ne se sont pas montrĂ©s mĂȘme au stade des tests sans FLUSH, mais je n'ai pas considĂ©rĂ© d'algorithmes de compression plus exotiques).


Nous revenons aux problĂšmes d'archivage.


Le deuxiÚme problÚme (comme on dit dans l'ordre, mais pas en valeur) - les données compressées sont un flux unique dans lequel il est constamment envoyé vers les sections précédentes. Ainsi, lorsqu'une section de données compressées est endommagée, nous perdons non seulement le bloc de données non compressées qui lui est associé, mais aussi toutes les suivantes.


Il existe une approche pour résoudre ce problÚme:


  1. EmpĂȘchez l'apparition d'un problĂšme - ajoutez une redondance aux donnĂ©es compressĂ©es, ce qui permettra d'identifier et de corriger les erreurs; nous en parlerons plus tard;
  2. Minimisez les conséquences en cas de problÚme
    Nous avons dĂ©jĂ  dit prĂ©cĂ©demment qu'il est possible de compresser chaque bloc de donnĂ©es indĂ©pendamment, et le problĂšme disparaĂźtra de lui-mĂȘme (la corruption des donnĂ©es d'un bloc entraĂźnera la perte de donnĂ©es de ce bloc uniquement). Cependant, il s'agit d'un cas extrĂȘme dans lequel la compression des donnĂ©es sera inefficace. L'extrĂȘme opposĂ©: utilisez les 4 Mo de notre microcircuit comme une seule archive, ce qui nous donnera une excellente compression, mais des consĂ©quences catastrophiques en cas de corruption de donnĂ©es.
    Oui, un compromis est nĂ©cessaire en termes de fiabilitĂ©. Mais nous devons nous rappeler que nous dĂ©veloppons un format de stockage de donnĂ©es pour la mĂ©moire non volatile avec un BER extrĂȘmement bas et une pĂ©riode de stockage de donnĂ©es dĂ©clarĂ©e de 20 ans.

Au cours des expériences, j'ai constaté que des pertes plus ou moins notables dans le niveau de compression commencent sur des blocs de données compressés d'une taille inférieure à 10 Ko.
Il a été mentionné plus tÎt que la mémoire utilisée a une organisation de page, je ne vois aucune raison pour laquelle vous ne devriez pas utiliser la correspondance «une page - un bloc de données compressées».


Autrement dit, la taille de page minimale raisonnable est de 16 Ko (avec une marge pour les informations de service). Cependant, une taille de page aussi petite impose des restrictions importantes sur la taille d'enregistrement maximale.


Bien que je ne m'attende toujours pas à des enregistrements de plus d'unités de kilo-octets sous forme compressée, j'ai décidé d'utiliser des pages de 32 Ko (un total de 128 pages par puce).


Résumé:


  • Nous stockons les donnĂ©es compressĂ©es Ă  l'aide de zlib (dĂ©gonfler);
  • Pour chaque enregistrement, dĂ©finissez Z_SYNC_FLUSH;
  • Pour chaque enregistrement compressĂ©, nous supprimons les octets finaux (par exemple, 0x00, 0x00, 0xff, 0xff) ; dans l'en-tĂȘte indique le nombre d'octets que nous coupons;
  • Nous stockons les donnĂ©es dans des pages de 32 Ko; Ă  l'intĂ©rieur de la page, il y a un seul flux de donnĂ©es compressĂ©es; sur chaque page, nous recommençons la compression.

Et, avant de terminer la compression, je voudrais attirer l'attention sur le fait que nous n'obtenons que quelques octets de donnĂ©es d'Ă©criture, il est donc extrĂȘmement important de ne pas gonfler les informations de service, chaque octet est comptĂ©.


Stockage des en-tĂȘtes de donnĂ©es


Étant donnĂ© que nous avons des enregistrements de longueur variable, nous devons en quelque sorte dĂ©terminer l'emplacement / les limites des enregistrements.


Je connais trois approches:


  1. Tous les enregistrements sont stockĂ©s dans un flux continu, vient d'abord l'en-tĂȘte d'enregistrement contenant la longueur, puis l'enregistrement lui-mĂȘme.
    Dans ce mode de rĂ©alisation, les en-tĂȘtes et les donnĂ©es peuvent avoir une longueur variable.
    En fait, nous obtenons une liste à lien unique qui est utilisée tout le temps;
  2. Les en-tĂȘtes et les enregistrements eux-mĂȘmes sont stockĂ©s dans des flux sĂ©parĂ©s.
    En utilisant des en-tĂȘtes de longueur constante, nous nous assurons que les dommages Ă  un en-tĂȘte n'affectent pas le reste.
    Une approche similaire est utilisée, par exemple, dans de nombreux systÚmes de fichiers;
  3. Les enregistrements sont stockés dans un flux continu, la limite de l'enregistrement est déterminée par un marqueur (symbole / séquence de caractÚres, ce qui est / qui est interdit à l'intérieur des blocs de données). Si un marqueur est trouvé à l'intérieur de l'enregistrement, nous le remplaçons par une certaine séquence (échappons-le).
    Une approche similaire est utilisée, par exemple, dans le protocole PPP.

Je vais illustrer.


Option 1:
Option 1
Tout est trĂšs simple ici: connaissant la longueur de l'enregistrement, on peut calculer l'adresse de l'en-tĂȘte suivant. Nous passons donc Ă  travers les en-tĂȘtes jusqu'Ă  ce que nous rencontrions une rĂ©gion remplie de 0xff (rĂ©gion libre) ou la fin de la page.


Option 2:
Option 2
En raison de la longueur variable de l'enregistrement, nous ne pouvons pas dire Ă  l'avance le nombre d'enregistrements (et donc les en-tĂȘtes) par page dont nous avons besoin. Vous pouvez rĂ©partir les en-tĂȘtes et les donnĂ©es elles-mĂȘmes sur diffĂ©rentes pages, mais je prĂ©fĂšre une approche diffĂ©rente: nous plaçons les en-tĂȘtes et les donnĂ©es sur la mĂȘme page, cependant, les en-tĂȘtes (de taille constante) viennent du dĂ©but de la page et les donnĂ©es (de longueur variable) de la fin. DĂšs qu'ils se "rencontrent" (il n'y a pas assez d'espace libre pour un nouveau record) - nous considĂ©rons que cette page est pleine.


Option 3:
Option 3
Il n'est pas nĂ©cessaire de stocker dans l'en-tĂȘte la longueur ou d'autres informations sur l'emplacement des donnĂ©es, il y a suffisamment de marqueurs qui indiquent les limites des enregistrements. Cependant, les donnĂ©es doivent ĂȘtre traitĂ©es lors de l'Ă©criture / lecture.
En tant que marqueur, j'utiliserais 0xff (dont la page est remplie aprÚs l'effacement), donc la zone libre ne sera certainement pas traitée comme des données.


Tableau de comparaison:


Option 1Option 2Option 3
Tolérance aux erreurs-++
Compacité+-+
ComplexitĂ© de la mise en Ɠuvre*****

L'option 1 a un dĂ©faut fatal: si l'un des en-tĂȘtes est endommagĂ©, toute notre chaĂźne suivante est dĂ©truite. D'autres options vous permettent de rĂ©cupĂ©rer une partie des donnĂ©es mĂȘme en cas de dommages massifs.
Mais ici, il convient de rappeler que nous avons dĂ©cidĂ© de stocker les donnĂ©es sous forme compressĂ©e, et donc nous perdons toutes les donnĂ©es sur la page aprĂšs l'enregistrement "cassĂ©", donc mĂȘme s'il y a un moins dans le tableau, nous n'en tenons pas compte.


Compacité:


  • dans la premiĂšre version, nous devons stocker uniquement la longueur dans l'en-tĂȘte, si des entiers de longueur variable sont utilisĂ©s, alors dans la plupart des cas, nous pouvons le faire avec un octet;
  • dans la deuxiĂšme option, nous devons stocker l'adresse et la longueur de dĂ©part; l'enregistrement doit ĂȘtre de taille constante, j'estime 4 octets par enregistrement (deux octets par dĂ©calage et deux octets par longueur);
  • , - 1-2%. .

( ). , .


, - - . , , — , , ...


: , , .. , , , — , .


: " — " - .



, , :
.
, erase 1, 1 0, . " " 1, " " — 0.


flash:


  1. “ ”;
  2. ;
  3. “ ”;
  4. ;
  5. “ ”.

, “ ”, 4 .


“1111” — “1000” — ; , .


, , , , , ( ) .


: .



( ) , . , , .


, , ( , , — ) .


, , , — .


— CRC. , 100% , — 2−n . , , : , . — .


: 1 , 2 ( narod.ru, ) .


, CRC — . , .


, .


:
10−3 , :


,,
10100001000
1149991003
12≈019971997
14≈039903990
100995509955
101399901029
102≈019791979
104≈039543954
100006323050632305
1000124703682838
1000210735745
10004≈014691469

, — — .


, : , , . , .


, , 32 ( 64 -) .


, , , - 32- (16 , 0.01%; 24 , , ).


: , 4 ? ? , , .


, CRC-32C.
6 22 (, c), 4 655 ( ), 2 .


CRC.


crc-32c — , CRC .


, , , , .


, , : ?


"" :


  • — ( /, , ..);
  • deflate zlib "" , , , ( , zlib ).

"" :


  • CRC "" , - ( , , , "" );
  • , , .

.


: CRC-32C, , flash ( ).



, , , , ( ) .


, .
, - , RAID-6 .
, , , .


, . ?


  1. ( - , Raspberry, ...)
    , ;
  2. ( - flash- , )
    , ;
  3. ;

  4. .

( ) . , - .


: , , , ( , ).



, ( ) , , .


  • ""
    - , .., , .
    , , ;
  • .
    — !
    Magic Number (), ( , ) ;
  • ( ) , 1 ;
  • .

- . .



Byte order


, , big-endian (network byte order), 0x1234 0x12, 0x34.



- .


32, , 1/4 ( 4 128 ).


( ).


( ), 0 ( 0, — 32, — 64 ..)


(ring buffer), 0, 1, ..., , .




4- , (CRC-32C), ", , ".


( -) :


  • Magic Number ( — )
    0xed00 ⊕ ;
  • " " ( ).

( deflate). ( ), . ( ).


Z_SYNC_FLUSH, 4 0x00, 0x00, 0xff, 0xff, , , .
( 4, 5 6 ) -.


1, 2 3 , :


  • (T), : 0 — , 1 — ;
  • (S) 1 7 , "", ;
  • (L).

S:


S,,
015 ( 00 00 00 ff ff )
1016 ( 00 00 00 00 ff ff )
11024 ( 00 00 ff ff )
111025 ( 00 00 00 ff ff )
1111026 ( 00 00 00 00 ff ff )
111110034 ( 00 00 ff ff )
111110135 ( 00 00 00 ff ff )
111111036 ( 00 00 00 00 ff ff )

, , :

T, — S, L ( ), — , — , -.


, ( 63+5 ) .


CRC-32C, (init) .


CRC "", (- ) : CRC(init,A||B)=CRC(CRC(init,A),B) .
CRC .


.


, 0x00 0xff ( 0xff, ; 0x00 ).



-


.
— - .


( , Linux NOR Flash, )


-


.
.


— .



( ) 1.
( UUID ).


, - .



8 ( + CRC), Magic Number CRC .
"" , , .
, CRC, "". — . — , "" .
, , "" .
zlib ( ).


, , , .



, Z_SYNC_FLUSH., .
( CRC) — (. ).
CRC. — .



( ). — , .
erase. 0xff. - — , ..
, , — ( ).



, - ( , JSON, MessagePack, CBOR, , protobuf) NOR Flash.


, "" SLC NOR Flash.


BER, NAND MLC NOR ( ? ) .


, , FTL: USB flash, SD, MicroSD, etc ( 512 , — "" ) .


128 (16) 1 (128). , , , ( , NOR Flash ) .


- , — , , github.


Conclusion


, .


, : - , , . , () - .


, ? Oui bien sûr. , , . - .


? , , . .


, , " ".


, () , , "" (, , ; ). ( — ) .


, .


Littérature


, .


, , , :


  1. infgen zlib. deflate/zlib/gzip. deflate ( gzip) — .

Source: https://habr.com/ru/post/fr479044/


All Articles