{"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\/pt\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","title":{"rendered":"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h1 id=\"predystoriya\">Antecedentes<\/h1>\n<p><\/p>\n<p>Temos m\u00e1quinas de venda autom\u00e1tica desenvolvidas internamente. Dentro delas est\u00e3o Raspberry Pi e um pouco de hardware em uma placa separada. Est\u00e3o conectados a um validador de moedas, um validador de notas, um terminal banc\u00e1rio... Tudo \u00e9 controlado por um programa desenvolvido internamente. Todo o hist\u00f3rico de opera\u00e7\u00e3o \u00e9 gravado em um registro em um pen drive (MicroSD), que depois \u00e9 transmitido pela internet (usando um modem USB) para um servidor, onde \u00e9 armazenado em um banco de dados. As informa\u00e7\u00f5es de vendas s\u00e3o carregadas no 1C, e tamb\u00e9m existe uma interface web simples para monitoramento, etc. <\/p>\n<p><\/p>\n<p>Ou seja, o registro \u00e9 vital \u2014 para contabilidade (eletrificando a receita, vendas e assim por diante), monitoramento (diversas falhas e outros imprevistos); tudo isso \u00e9, de certa forma, toda a informa\u00e7\u00e3o que temos sobre esta m\u00e1quina. <\/p>\n<p><\/p>\n<h1 id=\"problema\">O problema<\/h1>\n<p><\/p>\n<p>Os pen drives se mostram como dispositivos muito pouco confi\u00e1veis. Eles falham com uma regularidade impressionante. Isso resulta tanto em inatividade das m\u00e1quinas quanto (se, por algum motivo, o registro n\u00e3o puder ser transmitido online) em perda de dados.<\/p>\n<p><\/p>\n<p><em>Este n\u00e3o \u00e9 o primeiro experimento com pen drives, antes houve outro projeto com mais de cem dispositivos, onde o registro era armazenado em pen drives USB, e l\u00e1 tamb\u00e9m houve problemas de confiabilidade, \u00e0s vezes o n\u00famero de falhas por m\u00eas chegava a dezenas. Testamos v\u00e1rias marcas de pen drives, incluindo modelos de mem\u00f3ria SLC, alguns modelos s\u00e3o mais confi\u00e1veis que outros, mas a troca dos pen drives n\u00e3o resolveu o problema de forma radical.<\/em><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <\/p>\n<p><strong>Aten\u00e7\u00e3o!<\/strong> Longread! Se voc\u00ea n\u00e3o est\u00e1 interessado no 'porqu\u00ea', mas apenas no 'como', pode ir direto ao ponto. <noindex><a rel=\"nofollow\" href=\"#format\">para o final<\/a><\/noindex> artigo.<\/p>\n<p><\/p>\n<h1 id=\"reshenie\">Solu\u00e7\u00e3o<\/h1>\n<p><\/p>\n<p>A primeira ideia que vem \u00e0 mente: abandonar o MicroSD, instalar, por exemplo, um SSD, e inicializar a partir dele. Teoricamente \u00e9 poss\u00edvel, talvez, mas relativamente caro e n\u00e3o t\u00e3o confi\u00e1vel (adiciona-se um adaptador USB-SATA; a estat\u00edstica de falhas para SSDs de baixo custo tamb\u00e9m n\u00e3o \u00e9 animadora).<\/p>\n<p><\/p>\n<p>Discos r\u00edgidos USB tamb\u00e9m n\u00e3o parecem uma solu\u00e7\u00e3o muito atraente.<\/p>\n<p><\/p>\n<p>Portanto, chegamos a esta op\u00e7\u00e3o: manter a inicializa\u00e7\u00e3o a partir do MicroSD, mas us\u00e1-los em modo somente leitura, e armazenar o registro de opera\u00e7\u00e3o (e outras informa\u00e7\u00f5es \u00fanicas para cada hardware \u2014 n\u00famero de s\u00e9rie, calibra\u00e7\u00f5es de sensores, etc.) em outro lugar. <\/p>\n<p><\/p>\n<p>O tema de sistemas de arquivos somente leitura para a Raspberry Pi j\u00e1 foi bastante estudado, n\u00e3o vou me aprofundar nos detalhes de implementa\u00e7\u00e3o neste artigo. <em>(mas se houver interesse \u2014 talvez eu escreva um mini-artigo sobre isso)<\/em>. Um ponto que gostaria de ressaltar: tanto pela experi\u00eancia pessoal quanto pelas avalia\u00e7\u00f5es de quem j\u00e1 implementou, h\u00e1 um ganho em confiabilidade. Sim, \u00e9 imposs\u00edvel se livrar completamente das falhas, mas \u00e9 bem vi\u00e1vel reduzir a frequ\u00eancia delas. Al\u00e9m disso, os cart\u00f5es est\u00e3o se tornando padronizados, o que simplifica bastante a substitui\u00e7\u00e3o para a equipe de manuten\u00e7\u00e3o.<\/p>\n<p><\/p>\n<h2 id=\"apparatnaya-chast\">Hardware<\/h2>\n<p><\/p>\n<p>N\u00e3o houve grandes d\u00favidas sobre o tipo de mem\u00f3ria \u2014 NOR Flash.<br \/>\nArgumentos: <\/p>\n<p><\/p>\n<ul>\n<li>conex\u00e3o simples (freq\u00fcentemente um barramento SPI, cuja experi\u00eancia de uso j\u00e1 existe, ent\u00e3o n\u00e3o se prev\u00ea problemas 'de hardware');<\/li>\n<li>pre\u00e7o acess\u00edvel;<\/li>\n<li>protocolo padr\u00e3o de opera\u00e7\u00e3o (a implementa\u00e7\u00e3o j\u00e1 existe no n\u00facleo Linux, se desejado, \u00e9 poss\u00edvel usar solu\u00e7\u00f5es de terceiros, que tamb\u00e9m est\u00e3o dispon\u00edveis, ou at\u00e9 mesmo escrever a sua, visto que tudo \u00e9 simples);<\/li>\n<li>confiabilidade e durabilidade:<br \/>\ndo datasheet t\u00edpico: os dados s\u00e3o armazenados por 20 anos, 100.000 ciclos de apagamento para cada bloco;<br \/>\nde fontes externas: taxa de erro muito baixa, postulando a aus\u00eancia de necessidade de c\u00f3digos de corre\u00e7\u00e3o de erros <em>(em alguns trabalhos se considera ECC para NOR, mas geralmente se refere a MLC NOR, que tamb\u00e9m existe)<\/em>.<\/li>\n<\/ul>\n<p><\/p>\n<p>Vamos estimar os requisitos de volume e durabilidade.<\/p>\n<p><\/p>\n<p>Gostaria que os dados fossem garantidamente mantidos por v\u00e1rios dias. Isso \u00e9 necess\u00e1rio para que, em caso de qualquer problema de comunica\u00e7\u00e3o, a hist\u00f3ria das vendas n\u00e3o seja perdida. Vamos nos orientar para 5 dias, nesse per\u00edodo <em>(mesmo considerando fins de semana e feriados)<\/em> \u00e9 poss\u00edvel resolver o problema.<\/p>\n<p><\/p>\n<p>Atualmente, acumulamos cerca de 100 KB de registro por dia (3-4 mil entradas), por\u00e9m esse n\u00famero est\u00e1 crescendo gradualmente \u2014 a detalha\u00e7\u00e3o est\u00e1 aumentando, novos eventos est\u00e3o sendo adicionados. Al\u00e9m disso, \u00e0s vezes ocorrem picos (algum sensor come\u00e7a a disparar falsos positivos, por exemplo). Vamos considerar 10 mil entradas de 100 bytes \u2014 1 megabyte por dia.<\/p>\n<p><\/p>\n<p>No total, temos 5 MB de dados limpos (bem compact\u00e1veis). A isso ainda <em>(estimativa grosseira)<\/em> 1 MB de dados de servi\u00e7o.<\/p>\n<p><\/p>\n<p>Ou seja, precisamos de um chip de 8 MB se n\u00e3o usarmos compress\u00e3o, ou de 4 MB se utilizarmos. N\u00fameros bastante razo\u00e1veis para esse tipo de mem\u00f3ria.<\/p>\n<p><\/p>\n<p>Quanto \u00e0 durabilidade: se planejarmos que a mem\u00f3ria seja reescrita no m\u00e1ximo uma vez a cada 5 dias, ao longo de 10 anos de opera\u00e7\u00e3o, teremos menos de mil ciclos de regrava\u00e7\u00e3o.<br \/>\nLembro que o fabricante promete cem mil.<\/p>\n<p>\n<b class=\"spoiler_title\">Um pouco sobre NOR vs NAND<\/b><\/p>\n<p>Hoje, certamente, a mem\u00f3ria NAND \u00e9 muito mais popular, mas para este projeto eu n\u00e3o a utilizaria: a NAND, ao contr\u00e1rio da NOR, exige obrigatoriamente o uso de c\u00f3digos de corre\u00e7\u00e3o de erros, tabelas de blocos defeituosos etc., e as fichas da NAND geralmente t\u00eam muitos mais pinos.<\/p>\n<p><\/p>\n<p>Quanto \u00e0s desvantagens da NOR, podemos apontar:<\/p>\n<p><\/p>\n<ul>\n<li>pequena capacidade (e, consequentemente, alto custo por megabyte);<\/li>\n<li>baixa velocidade de transfer\u00eancia (muito devido ao uso de uma interface serial, geralmente SPI ou I2C);<\/li>\n<li>apaga lento (dependendo do tamanho do bloco, pode levar de fra\u00e7\u00f5es de segundo a v\u00e1rios segundos).<\/li>\n<\/ul>\n<p><\/p>\n<p>Parece que n\u00e3o h\u00e1 nada cr\u00edtico para n\u00f3s, ent\u00e3o vamos continuar.<\/p>\n<p><\/p>\n<p>Se voc\u00ea estiver interessado nos detalhes, o chip escolhido foi <noindex><a rel=\"nofollow\" href=\"https:\/\/www.adestotech.com\/wp-content\/uploads\/doc3686.pdf\">at25df321a<\/a><\/noindex> <em>(no entanto, isso n\u00e3o \u00e9 essencial, h\u00e1 muitos an\u00e1logos no mercado, compat\u00edveis em pinagem e sistema de comandos; mesmo que decidamos usar um chip de outro fabricante e\/ou de outra capacidade, tudo funcionar\u00e1 sem altera\u00e7\u00e3o do c\u00f3digo)<\/em>.<\/p>\n<p><\/p>\n<p>Eu uso o driver embutido no n\u00facleo Linux, na Raspberry, gra\u00e7as ao suporte do device tree overlay, \u00e9 tudo muito simples \u2014 basta colocar o overlay compilado em \/boot\/overlays e modificar um pouco o \/boot\/config.txt.<\/p>\n<p>\n<b class=\"spoiler_title\">Exemplo de arquivo dts<\/b><\/p>\n<p>Honestamente, n\u00e3o tenho certeza se est\u00e1 escrito sem erros, mas funciona.<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">\/*\n * Device tree overlay for at25 at spi0.1\n *\/\n\n\/dts-v1\/;\n\/plugin\/;\n\n\/ {\n    compatible = \"brcm,bcm2835\", \"brcm,bcm2836\", \"brcm,bcm2708\", \"brcm,bcm2709\"; \n\n    \/* disable spi-dev for spi0.1 *\/\n    fragment@0 {\n        target = &lt;&amp;spi0&gt;;\n        __overlay__ {\n            status = \"okay\";\n            spidev@1{\n                status = \"disabled\";\n            };\n        };\n    };\n\n    \/* the spi config of the at25 *\/\n    fragment@1 {\n        target = &lt;&amp;spi0&gt;;\n        __overlay__ {\n            #address-cells = &lt;1&gt;;\n            #size-cells = &lt;0&gt;;\n            flash: m25p80@1 {\n                    compatible = \"atmel,at25df321a\";\n                    reg = &lt;1&gt;;\n                    spi-max-frequency = &lt;50000000&gt;;\n\n                    \/* default to false:\n                    m25p,fast-read ;\n                    *\/\n            };\n        };\n    };\n\n    __overrides__ {\n        spimaxfrequency = &lt;&amp;flash&gt;,\"spi-max-frequency:0\";\n        fastread = &lt;&amp;flash&gt;,\"m25p,fast-read?\";\n    };\n};<\/code><\/pre>\n<p>\n<b class=\"spoiler_title\">E mais uma linha no config.txt<\/b><\/p>\n<pre><code class=\"plaintext\">dtoverlay=at25:spimaxfrequency=50000000<\/code><\/pre>\n<p><\/p>\n<p>Vou omitir a descri\u00e7\u00e3o da conex\u00e3o do chip ao Raspberry Pi. Por um lado, n\u00e3o sou especialista em eletr\u00f4nica, por outro, \u00e9 tudo banal at\u00e9 para mim: o chip tem apenas 8 pinos, dos quais precisamos de terra, alimenta\u00e7\u00e3o, SPI (CS, SI, SO, SCK); os n\u00edveis coincidem com os do Raspberry Pi, n\u00e3o \u00e9 necess\u00e1rio nenhum circuito adicional \u2014 basta conectar os 6 pinos indicados.<\/p>\n<p><\/p>\n<h2 id=\"postanovka-zadachi\">Formula\u00e7\u00e3o do problema<\/h2>\n<p><\/p>\n<p>Como de costume, a defini\u00e7\u00e3o da tarefa passa por v\u00e1rias itera\u00e7\u00f5es, e acho que chegou a hora de mais uma. Portanto, vamos parar, reunir o que j\u00e1 foi escrito e esclarecer os detalhes que ainda est\u00e3o obscuros.<\/p>\n<p><\/p>\n<p>Assim, decidimos que o log ser\u00e1 armazenado na SPI NOR Flash.<\/p>\n<p>\n<b class=\"spoiler_title\">O que \u00e9 NOR Flash para quem n\u00e3o sabe<\/b><\/p>\n<p>\u00c9 uma mem\u00f3ria n\u00e3o vol\u00e1til, com a qual podemos realizar tr\u00eas opera\u00e7\u00f5es:<\/p>\n<p><\/p>\n<ol>\n<li>Leitura:<br \/>\nA mais comum leitura: passamos o endere\u00e7o e lemos quantos bytes precisamos;<\/li>\n<li>Grava\u00e7\u00e3o:<br \/>\nA grava\u00e7\u00e3o na mem\u00f3ria NOR flash parece normal, mas tem uma particularidade: s\u00f3 \u00e9 poss\u00edvel mudar 1 para 0, mas n\u00e3o o contr\u00e1rio. Por exemplo, se tivermos 0x55 armazenado na c\u00e9lula de mem\u00f3ria, ap\u00f3s gravar 0x0f, ela conter\u00e1 0x05. <em>(veja a tabela um pouco abaixo)<\/em>;<\/li>\n<li>Apagar:<br \/>\nNaturalmente, tamb\u00e9m precisamos ser capazes de realizar a opera\u00e7\u00e3o inversa \u2014 mudar 0 para 1; para isso existe a opera\u00e7\u00e3o de apagar. Ao contr\u00e1rio das duas primeiras, ela opera em blocos, n\u00e3o em bytes (o bloco m\u00ednimo de apagamento na microcircuito escolhido \u00e9 de 4 kb). Apagar destr\u00f3i o bloco inteiro e \u00e9 a \u00fanica maneira de mudar 0 para 1. Portanto, ao trabalhar com mem\u00f3ria flash, muitas vezes \u00e9 necess\u00e1rio alinhar as estruturas de dados na borda do bloco de apagar.<br \/>\nGrava\u00e7\u00e3o na NOR Flash:<\/li>\n<\/ol>\n<p><\/p>\n<p>Dados bin\u00e1rios<\/p>\n<p><strong>Foi<\/strong><br \/>\n<code>01010101<\/code><\/p>\n<p><strong>Gravado<\/strong><br \/>\n<code>00001111<\/code><\/p>\n<p><strong>Tornou-se<\/strong><br \/>\n<code>00000101<\/code><\/p>\n<p><\/p>\n<p>O pr\u00f3prio log representa uma sequ\u00eancia de grava\u00e7\u00f5es de comprimento vari\u00e1vel. O comprimento t\u00edpico de uma grava\u00e7\u00e3o \u00e9 de cerca de 30 bytes (embora, \u00e0s vezes, ocorram grava\u00e7\u00f5es com v\u00e1rios kilobytes de comprimento). <em>Neste caso, lidamos com eles simplesmente como um conjunto de bytes, mas, se houver interesse, dentro das grava\u00e7\u00f5es \u00e9 utilizado CBOR.<\/em><\/p>\n<p><\/p>\n<p>Al\u00e9m do registro, precisamos armazenar algumas informa\u00e7\u00f5es 'de configura\u00e7\u00e3o', tanto atualiz\u00e1veis quanto n\u00e3o: algum ID do dispositivo, calibra\u00e7\u00f5es de sensores, sinalizador 'dispositivo temporariamente desligado', etc.<br \/>\nEssas informa\u00e7\u00f5es consistem em um conjunto de grava\u00e7\u00f5es key-value, tamb\u00e9m armazenadas em CBOR. Temos uma quantidade relativamente pequena dessa informa\u00e7\u00e3o (no m\u00e1ximo alguns kilobytes), que n\u00e3o \u00e9 atualizada com frequ\u00eancia.<br \/>\nNo futuro, nos referiremos a isso como contexto.<\/p>\n<p><\/p>\n<p>Se lembrarmos de como come\u00e7ou este artigo, \u00e9 extremamente importante garantir a confiabilidade do armazenamento de dados e, sempre que poss\u00edvel, a opera\u00e7\u00e3o cont\u00ednua, mesmo em caso de falhas de hardware\/danos aos dados.<\/p>\n<p><\/p>\n<p>Que fontes de problemas podemos considerar?<\/p>\n<p><\/p>\n<ul>\n<li>Desligamento de energia durante opera\u00e7\u00f5es de grava\u00e7\u00e3o\/apagamento. Isso \u00e9 do tipo 'n\u00e3o h\u00e1 rem\u00e9dio contra ferro velho'.<br \/>\nInforma\u00e7\u00e3o 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\">a discuss\u00e3o<\/a><\/noindex> no stackexchange: durante o desligamento de energia ao trabalhar com flash, tanto o apagar (defini\u00e7\u00e3o em 1) quanto o gravar (defini\u00e7\u00e3o em 0) levam a um comportamento indefinido: os dados podem ser gravados, gravados parcialmente (digamos, enviamos 10 bytes\/80 bits, mas s\u00f3 conseguimos gravar 45 bits), n\u00e3o se pode excluir que parte dos bits fique em um estado 'intermedi\u00e1rio' (a leitura pode resultar em 0 ou 1);<\/li>\n<li>Erros da pr\u00f3pria mem\u00f3ria flash.<br \/>\nO BER, embora muito baixo, n\u00e3o pode ser igual a zero;<\/li>\n<li>Erros na linha de transmiss\u00e3o.<br \/>\nOs dados transmitidos por SPI n\u00e3o est\u00e3o protegidos de forma alguma, podem ocorrer tanto erros de bit isolados quanto erros de sincroniza\u00e7\u00e3o \u2014 perda ou inser\u00e7\u00e3o de bits (o que leva a distor\u00e7\u00f5es massivas de dados);<\/li>\n<li>Outros erros\/falhas<br \/>\nErros no c\u00f3digo, 'bugs' do Raspberry, intromiss\u00e3o de alien\u00edgenas...<\/li>\n<\/ul>\n<p><\/p>\n<p>Eu formulei os requisitos que, na minha opini\u00e3o, s\u00e3o necess\u00e1rios para garantir a confiabilidade:<\/p>\n<p><\/p>\n<ul>\n<li>as grava\u00e7\u00f5es devem ser armazenadas na mem\u00f3ria flash imediatamente, grava\u00e7\u00e3o em atraso n\u00e3o \u00e9 considerada; - se um erro ocorrer, ele deve ser detectado e tratado o mais cedo poss\u00edvel; - o sistema deve ser capaz de se recuperar ap\u00f3s erros.<br \/>\n<em>(exemplo da vida real 'como n\u00e3o deve ser', com o qual, acredito, todos n\u00f3s j\u00e1 nos deparamos: ap\u00f3s uma reinicializa\u00e7\u00e3o for\u00e7ada, o sistema de arquivos ficou 'corrompido' e o sistema operacional n\u00e3o inicializa).<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"idei-podhody-razmyshleniya\">Ideias, abordagens, reflex\u00f5es<\/h2>\n<p><\/p>\n<p>Quando comecei a pensar sobre essa tarefa, muitas ideias passaram pela minha mente, por exemplo:<\/p>\n<p><\/p>\n<ul>\n<li>usar compress\u00e3o de dados;<\/li>\n<li>usar estruturas de dados engenhosas, por exemplo, armazenar cabe\u00e7alhos de registros separadamente dos pr\u00f3prios registros, para que, em caso de erro em algum registro, seja poss\u00edvel ler os demais sem problemas;<\/li>\n<li>usar campos de bits para controlar a conclus\u00e3o do registro durante a queda de energia;<\/li>\n<li>armazenar somas de verifica\u00e7\u00e3o para tudo e todos;<\/li>\n<li>usar alguma variedade de codifica\u00e7\u00e3o de corre\u00e7\u00e3o de erros.<\/li>\n<\/ul>\n<p><\/p>\n<p>Parte dessas ideias foi utilizada, e parte foi decidida por descartar. Vamos por partes.<\/p>\n<p><\/p>\n<h3 id=\"szhatie-dannyh\">Compress\u00e3o de dados<\/h3>\n<p><\/p>\n<p>Os eventos que registramos no log s\u00e3o bastante homog\u00eaneos e repetitivos ('jogou uma moeda de 5 reais', 'pressionou o bot\u00e3o para liberar o troco', ...). Portanto, a compress\u00e3o deve se mostrar bastante eficaz.<\/p>\n<p><\/p>\n<p>Os custos de compress\u00e3o n\u00e3o s\u00e3o significativos (nosso processador \u00e9 bastante poderoso, mesmo no primeiro Pi havia um n\u00facleo com frequ\u00eancia de 700MHz, nos modelos atuais v\u00e1rios n\u00facleos com frequ\u00eancia acima de um gigahertz), a velocidade de troca com o armazenamento \u00e9 baixa (v\u00e1rios megabytes por segundo), o tamanho dos registros \u00e9 pequeno. Em geral, se a compress\u00e3o impactar a performance, isso ser\u00e1 apenas de forma positiva. <em>(absolutamente n\u00e3o cr\u00edtico, apenas constatando)<\/em>. Al\u00e9m disso, n\u00e3o temos um verdadeiro embedded, mas sim um Linux comum \u2014 ent\u00e3o, a implementa\u00e7\u00e3o n\u00e3o deve exigir muito esfor\u00e7o (basta vincular a biblioteca e usar algumas fun\u00e7\u00f5es dela).<\/p>\n<p><\/p>\n<p>Foi retirada uma parte do log de um dispositivo em funcionamento (1,7MB, 70 mil registros) e, para come\u00e7ar, foi verificada a compressibilidade usando gzip, lz4, lzop, bzip2, xz, zstd dispon\u00edveis no computador.<\/p>\n<p><\/p>\n<ul>\n<li>gzip, xz, zstd mostraram resultados pr\u00f3ximos (40KB).<br \/>\nSurpreendeu-me que o popular xz se comportou aqui no n\u00edvel do gzip ou zstd;<\/li>\n<li>lzip com configura\u00e7\u00f5es padr\u00e3o deu um resultado ligeiramente pior;<\/li>\n<li>lz4 e lzop mostraram um resultado n\u00e3o muito bom (150KB);<\/li>\n<li>bzip2 apresentou um resultado surpreendentemente bom (18KB).<\/li>\n<\/ul>\n<p><\/p>\n<p>Assim, os dados s\u00e3o comprimidos muito bem.<br \/>\nPortanto, (se n\u00e3o encontrarmos falhas fatais) a compress\u00e3o ser\u00e1 uma realidade! Apenas porque mais dados caber\u00e3o no mesmo pen-drive.<\/p>\n<p><\/p>\n<p>Vamos pensar sobre as desvantagens.<\/p>\n<p><\/p>\n<p>O primeiro problema: j\u00e1 concordamos que cada registro deve ser imediatamente gravado no pen-drive. Normalmente, um compressor acumula dados do fluxo de entrada at\u00e9 decidir que \u00e9 hora de gravar na sa\u00edda. Precisamos obter imediatamente um bloco de dados comprimidos e salv\u00e1-lo na mem\u00f3ria n\u00e3o vol\u00e1til.<\/p>\n<p><\/p>\n<p>Vejo tr\u00eas caminhos:<\/p>\n<p><\/p>\n<ol>\n<li>Comprimir cada registro usando compress\u00e3o por dicion\u00e1rio em vez dos algoritmos acima mencionados.<br \/>\nUma op\u00e7\u00e3o vi\u00e1vel, mas que n\u00e3o me agrada. Para garantir um n\u00edvel de compress\u00e3o mais ou menos aceit\u00e1vel, o dicion\u00e1rio deve ser 'afinado' para dados espec\u00edficos, qualquer altera\u00e7\u00e3o resultar\u00e1 em uma queda catastr\u00f3fica no n\u00edvel de compress\u00e3o. Sim, o problema \u00e9 resolvido criando uma nova vers\u00e3o do dicion\u00e1rio, mas isso \u00e9 uma dor de cabe\u00e7a \u2014 precisaremos armazenar todas as vers\u00f5es do dicion\u00e1rio; em cada registro teremos que indicar com qual vers\u00e3o do dicion\u00e1rio ele foi comprimido...<\/li>\n<li>Comprimir cada registro com algoritmos 'cl\u00e1ssicos', mas independentemente uns dos outros.<br \/>\nOs algoritmos de compress\u00e3o considerados n\u00e3o s\u00e3o adequados para trabalhar com registros desse tamanho (dezenas de bytes), a taxa de compress\u00e3o ser\u00e1 claramente menor que 1 (ou seja, o volume de dados aumentar\u00e1 em vez de diminuir);<\/li>\n<li>Fazer FLUSH ap\u00f3s cada registro.<br \/>\nEm muitas bibliotecas de compress\u00e3o, h\u00e1 suporte para FLUSH. Este \u00e9 um comando (ou par\u00e2metro para o procedimento de compress\u00e3o), que, ao ser recebido, faz com que o compressor forme um fluxo comprimido de tal forma que seja poss\u00edvel recuperar <strong>tudo<\/strong> os dados n\u00e3o comprimidos que j\u00e1 foram recebidos. Tal an\u00e1logo <code>sync<\/code> em sistemas de arquivos ou <code>commit<\/code> em sql.<br \/>\nO que \u00e9 importante, as opera\u00e7\u00f5es de compress\u00e3o subsequentes poder\u00e3o usar o dicion\u00e1rio acumulado e o grau de compress\u00e3o n\u00e3o sofrer\u00e1 tanto quanto na vers\u00e3o anterior.<\/li>\n<\/ol>\n<p><\/p>\n<p>Acho que \u00e9 \u00f3bvio que escolhi a terceira op\u00e7\u00e3o, vamos nos aprofundar mais nela.<\/p>\n<p><\/p>\n<p>Encontrado <noindex><a rel=\"nofollow\" href=\"https:\/\/www.bolet.org\/~pornin\/deflate-flush.html\">\u00f3tima mat\u00e9ria<\/a><\/noindex> sobre FLUSH no zlib.<\/p>\n<p><\/p>\n<p>Fiz um teste inspirado em um artigo, peguei 70 mil registros de logs de um dispositivo real, com um tamanho de p\u00e1gina de 60KB <em>(sobre o tamanho da p\u00e1gina ainda voltaremos)<\/em> obtive:<\/p>\n<p><\/p>\n<p>Dados de origem<br \/>\nCompress\u00e3o gzip -9 (sem FLUSH)<br \/>\nzlib com Z_PARTIAL_FLUSH<br \/>\nzlib com Z_SYNC_FLUSH<\/p>\n<p><strong>Volume, KB<\/strong><br \/>\n1692<br \/>\n40<br \/>\n352<br \/>\n604<\/p>\n<p><\/p>\n<p>\u00c0 primeira vista, o custo trazido pelo FLUSH parece excessivamente alto, no entanto, na verdade temos uma escolha limitada \u2014 ou n\u00e3o comprimir de forma alguma, ou comprimir (de forma bastante eficaz) com FLUSH. N\u00e3o devemos esquecer que temos 70 mil registros, a redund\u00e2ncia criada pelo Z_PARTIAL_FLUSH \u00e9 de apenas 4-5 bytes por registro. E a taxa de compress\u00e3o resultou em quase 5:1, que \u00e9 um resultado mais do que excelente.<\/p>\n<p>\n<b class=\"spoiler_title\">Pode parecer surpreendente, mas na verdade Z_SYNC_FLUSH \u2014 \u00e9 uma maneira mais eficiente de fazer FLUSH.<\/b><\/p>\n<p>No caso de usar Z_SYNC_FLUSH, os \u00faltimos 4 bytes de cada registro ser\u00e3o sempre 0x00, 0x00, 0xff, 0xff. E se soubermos disso \u2014 podemos n\u00e3o armazen\u00e1-los, assim o tamanho final acaba sendo apenas 324KB.<\/p>\n<p><\/p>\n<p>No artigo ao qual me refiro, h\u00e1 uma explica\u00e7\u00e3o:<\/p>\n<p><\/p>\n<blockquote><p>Um novo bloco de tipo 0 com conte\u00fados vazios \u00e9 anexado.<\/p>\n<p>Um bloco de tipo 0 com conte\u00fados vazios consiste em:<\/p>\n<ul>\n<li>o cabe\u00e7alho de bloco de tr\u00eas bits;<\/li>\n<li>0 a 7 bits iguais a zero, para alcan\u00e7ar alinhamento de byte;<\/li>\n<li>a sequ\u00eancia de quatro bytes 00 00 FF FF.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Como \u00e9 f\u00e1cil notar, no \u00faltimo bloco antes desses 4 bytes existem de 3 a 10 bits nulos. No entanto, a pr\u00e1tica mostrou que os bits nulos na verdade s\u00e3o no m\u00ednimo 10.<\/p>\n<p><\/p>\n<p>Aparentemente, blocos de dados t\u00e3o curtos normalmente (sempre?) s\u00e3o codificados usando um bloco do tipo 1 (bloco fixo), que deve sempre terminar com 7 bits nulos, totalizando garantidamente 10-17 bits nulos (e os restantes ser\u00e3o nulos com uma probabilidade de cerca de 50%).<\/p>\n<p><\/p>\n<p>Assim, nos dados de teste, em 100% dos casos antes de 0x00, 0x00, 0xff, 0xff aparece um byte nulo, e em mais de um ter\u00e7o dos casos \u2014 dois bytes nulos. <em>(talvez o problema seja que estou usando CBOR bin\u00e1rio, enquanto ao usar JSON textual eu encontraria mais frequentemente blocos do tipo 2 \u2014 bloco din\u00e2mico, que consequentemente incluiria blocos sem bytes nulos adicionais antes de 0x00, 0x00, 0xff, 0xff)<\/em>.<\/p>\n<p><\/p>\n<p>Portanto, com os dados de teste dispon\u00edveis, \u00e9 poss\u00edvel manter menos de 250KB de dados comprimidos.<\/p>\n<p><\/p>\n<p>\u00c9 poss\u00edvel economizar um pouco mais ao brincar com os bits: agora estamos ignorando a presen\u00e7a de v\u00e1rios bits zerados no final do bloco, e v\u00e1rios bits no in\u00edcio do bloco tamb\u00e9m n\u00e3o mudam\u2026<br \/>\nMas aqui tomei a decis\u00e3o de parar, sen\u00e3o, nesse ritmo, acabaria desenvolvendo meu pr\u00f3prio compactador.<\/p>\n<p><\/p>\n<p>No total, consegui de meus dados de teste 3-4 bytes por grava\u00e7\u00e3o, a taxa de compress\u00e3o foi superior a 6:1. Vou ser honesto: n\u00e3o esperava esse resultado, na minha opini\u00e3o, qualquer coisa melhor que 2:1 j\u00e1 justifica o uso da compress\u00e3o.<\/p>\n<p><\/p>\n<p>Tudo \u00f3timo, mas o zlib (deflate) ainda \u00e9 um algoritmo de compress\u00e3o antiquado e um pouco ultrapassado. Apenas o fato de que o dicion\u00e1rio usa os \u00faltimos 32KB do fluxo de dados n\u00e3o compactados j\u00e1 parece estranho hoje (ou seja, se algum bloco de dados for muito semelhante ao que estava no fluxo de entrada h\u00e1 40KB, ele come\u00e7ar\u00e1 a ser compactado novamente, e n\u00e3o far\u00e1 refer\u00eancia \u00e0 entrada anterior). Em compactadores modernos, o tamanho do dicion\u00e1rio \u00e9 frequentemente medido em megabytes, n\u00e3o em kilobytes.<\/p>\n<p><\/p>\n<p>Portanto, vamos continuar nossa mini-investiga\u00e7\u00e3o em compactadores.<\/p>\n<p><\/p>\n<p>O pr\u00f3ximo testado foi o bzip2 (lembro que, sem FLUSH, mostrou uma taxa de compress\u00e3o fant\u00e1stica, quase 100:1). Infelizmente, com FLUSH, seu desempenho foi muito ruim, o tamanho dos dados compactados foi maior que o dos dados n\u00e3o compactados.<\/p>\n<p>\n<b class=\"spoiler_title\">Minhas suposi\u00e7\u00f5es sobre as causas do fracasso<\/b><\/p>\n<p>Libbz2 oferece apenas uma op\u00e7\u00e3o de flush, que, aparentemente, limpa o dicion\u00e1rio (semelhante ao Z_FULL_FLUSH no zlib), portanto, n\u00e3o se pode esperar uma compress\u00e3o eficiente ap\u00f3s isso.<\/p>\n<p><\/p>\n<p>E por \u00faltimo, testei o zstd. Dependendo dos par\u00e2metros, ele comprime ou no n\u00edvel do gzip, mas muito mais r\u00e1pido, ou melhor que gzip.<\/p>\n<p><\/p>\n<p>Infelizmente, com FLUSH ele se mostrou 'n\u00e3o t\u00e3o bom': o tamanho dos dados comprimidos saiu em torno de 700Kb.<\/p>\n<p><\/p>\n<p>Eu <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/facebook\/zstd\/issues\/900\">fiz uma pergunta<\/a><\/noindex> na p\u00e1gina do projeto no github, obtive a resposta de que devemos esperar at\u00e9 10 bytes de dados auxiliares para cada bloco de dados compactados, o que est\u00e1 perto dos resultados obtidos, n\u00e3o ser\u00e1 poss\u00edvel alcan\u00e7ar o deflate.<\/p>\n<p><\/p>\n<p>Decidi parar os experimentos com compactadores (lembro que xz, lzip, lzo, lz4 n\u00e3o se sa\u00edram bem na fase de teste sem FLUSH, e n\u00e3o me dediquei a considerar algoritmos de compress\u00e3o mais ex\u00f3ticos).<\/p>\n<p><\/p>\n<p>Voltando aos problemas de arquivamento.<\/p>\n<p><\/p>\n<p>O segundo problema (como se diz em ordem, e n\u00e3o em significado) \u00e9 que os dados comprimidos representam um \u00fanico fluxo, onde h\u00e1 constantemente refer\u00eancias a trechos anteriores. Assim, ao danificar alguma parte dos dados comprimidos, perdemos n\u00e3o apenas o bloco de dados descomprimidos a ele relacionado, mas tamb\u00e9m todos os subsequentes.<\/p>\n<p><\/p>\n<p>Existem abordagens para resolver esse problema:<\/p>\n<p><\/p>\n<ol>\n<li>Prevenir o surgimento do problema \u2014 adicionar redund\u00e2ncia aos dados comprimidos que permita identificar e corrigir erros; sobre isso falaremos mais tarde;<\/li>\n<li>Minimizar as consequ\u00eancias em caso de ocorr\u00eancia do problema.<br \/>\nJ\u00e1 mencionamos anteriormente que \u00e9 poss\u00edvel comprimir cada bloco de dados de forma independente, o que faria o problema desaparecer por conta pr\u00f3pria (a corrup\u00e7\u00e3o de dados de um bloco levaria \u00e0 perda de dados apenas desse bloco). No entanto, esse \u00e9 um caso extremo, onde a compress\u00e3o de dados seria ineficaz. A outra extremidade: usar os 4MB de nosso chip como um \u00fanico arquivo, o que nos daria uma compress\u00e3o excelente, mas consequ\u00eancias catastr\u00f3ficas em caso de corrup\u00e7\u00e3o de dados.<br \/>\n<em>Sim, \u00e9 necess\u00e1rio um compromisso em termos de confiabilidade. Mas \u00e9 preciso lembrar que estamos desenvolvendo um formato de armazenamento para mem\u00f3ria n\u00e3o vol\u00e1til com BER extremamente baixo e prazo de armazenamento de dados de 20 anos.<\/em><\/li>\n<\/ol>\n<p><\/p>\n<p>Durante os experimentos, descobri que as perdas significativas no n\u00edvel de compress\u00e3o come\u00e7am em blocos de dados comprimidos com menos de 10KB.<br \/>\nFoi mencionado anteriormente que a mem\u00f3ria utilizada tem uma organiza\u00e7\u00e3o em p\u00e1ginas, n\u00e3o vejo raz\u00f5es para n\u00e3o usar a correspond\u00eancia 'uma p\u00e1gina \u2014 um bloco de dados comprimidos'.<\/p>\n<p><\/p>\n<p>Ou seja, o tamanho m\u00ednimo razo\u00e1vel da p\u00e1gina \u00e9 16KB (com margem para informa\u00e7\u00f5es administrativas). No entanto, um tamanho de p\u00e1gina t\u00e3o pequeno imp\u00f5e limita\u00e7\u00f5es significativas ao tamanho m\u00e1ximo do registro.<\/p>\n<p><\/p>\n<p>Embora eu ainda n\u00e3o preveja registros maiores do que um quilobyte em forma comprimida, decidi usar p\u00e1ginas de 32KB (o que resulta em 128 p\u00e1ginas no chip).<\/p>\n<p><\/p>\n<p><strong>Resumo:<\/strong><\/p>\n<p><\/p>\n<ul>\n<li>Os dados s\u00e3o armazenados comprimidos usando zlib (deflate);<\/li>\n<li>Para cada registro, estabelecemos Z_SYNC_FLUSH;<\/li>\n<li>Cortamos os bytes finais de cada registro comprimido <em>(por exemplo, 0x00, 0x00, 0xff, 0xff)<\/em>; no cabe\u00e7alho, especificamos quantos bytes cortamos;<\/li>\n<li>Os dados s\u00e3o armazenados em p\u00e1ginas de 32KB; dentro da p\u00e1gina, h\u00e1 um fluxo cont\u00ednuo de dados comprimidos; em cada p\u00e1gina, a compress\u00e3o come\u00e7a do zero.<\/li>\n<\/ul>\n<p><\/p>\n<p>E, antes de finalizar a compress\u00e3o, gostaria de ressaltar que temos apenas alguns bytes comprimidos por registro, portanto, \u00e9 extremamente importante n\u00e3o inflar as informa\u00e7\u00f5es de controle, cada byte conta aqui.<\/p>\n<p><\/p>\n<h3 id=\"hranenie-zagolovkov-dannyh\">Armazenamento de cabe\u00e7alhos de dados<\/h3>\n<p><\/p>\n<p>Como temos registros de comprimento vari\u00e1vel, precisamos de alguma forma de determinar a localiza\u00e7\u00e3o\/fronteira dos registros.<\/p>\n<p><\/p>\n<p>Eu conhe\u00e7o tr\u00eas abordagens:<\/p>\n<p><\/p>\n<ol>\n<li>Todos os registros s\u00e3o armazenados em um fluxo cont\u00ednuo, primeiro vem o cabe\u00e7alho do registro, que cont\u00e9m o comprimento, e depois o pr\u00f3prio registro.<br \/>\nNesse caso, tanto os cabe\u00e7alhos quanto os dados podem ter comprimento vari\u00e1vel.<br \/>\nEssencialmente, temos uma lista encadeada que \u00e9 amplamente utilizada;<\/li>\n<li>Os cabe\u00e7alhos e os pr\u00f3prios registros s\u00e3o armazenados em fluxos separados.<br \/>\nUsando cabe\u00e7alhos de comprimento fixo, garantimos que a corrup\u00e7\u00e3o de um cabe\u00e7alho n\u00e3o afete os outros.<br \/>\nAbordagem semelhante \u00e9 usada, por exemplo, em muitos sistemas de arquivos;<\/li>\n<li>Os registros s\u00e3o armazenados em um fluxo cont\u00ednuo, e a fronteira do registro \u00e9 determinada por um determinado marcador (s\u00edmbolo\/sequ\u00eancia de s\u00edmbolos que s\u00e3o proibidos dentro dos blocos de dados). Se um marcador for encontrado dentro do registro, ele \u00e9 substitu\u00eddo por uma determinada sequ\u00eancia (\u00e9 escapado).<br \/>\nAbordagem semelhante \u00e9 utilizada, por exemplo, no protocolo PPP.<\/li>\n<\/ol>\n<p><\/p>\n<p>Vou ilustrar.<\/p>\n<p><\/p>\n<p>Op\u00e7\u00e3o 1:<br \/>\n<img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/e5a9676ca21eaca07e64ddf9fcd9f2eb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAqui \u00e9 muito simples: sabendo o comprimento do registro, podemos calcular o endere\u00e7o do pr\u00f3ximo cabe\u00e7alho. Assim, avan\u00e7amos pelos cabe\u00e7alhos at\u00e9 encontrarmos uma \u00e1rea preenchida com 0xff (\u00e1rea livre) ou o final da p\u00e1gina.<\/p>\n<p><\/p>\n<p>Op\u00e7\u00e3o 2:<br \/>\n<img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/4fed4860fb659ab6042be9aeeb5c041a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nDevido ao comprimento vari\u00e1vel da entrada, n\u00e3o podemos prever quantas entradas (e, portanto, cabe\u00e7alhos) precisaremos em uma p\u00e1gina. Podemos distribuir os cabe\u00e7alhos e os dados por diferentes p\u00e1ginas, mas prefiro uma abordagem diferente: colocamos tanto os cabe\u00e7alhos (de tamanho fixo) quanto os dados (de comprimento vari\u00e1vel) na mesma p\u00e1gina, com os cabe\u00e7alhos come\u00e7ando do topo e os dados vindo do fundo. Assim que eles se 'encontrarem' (o espa\u00e7o livre n\u00e3o for suficiente para uma nova entrada), consideramos essa p\u00e1gina preenchida.<\/p>\n<p><\/p>\n<p>Op\u00e7\u00e3o 3:<br \/>\n<img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/dfb64009aa7781fcb8a47423ff4160c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nN\u00e3o h\u00e1 necessidade de armazenar no cabe\u00e7alho o comprimento ou qualquer outra informa\u00e7\u00e3o sobre a localiza\u00e7\u00e3o dos dados; apenas marcadores indicando os limites dos registros s\u00e3o suficientes. No entanto, os dados precisam ser processados ao gravar\/leitura.<br \/>\nComo marcador, eu usaria 0xff (que preenche a p\u00e1gina ap\u00f3s a exclus\u00e3o), dessa forma a \u00e1rea livre n\u00e3o ser\u00e1 interpretada como dados.<\/p>\n<p><\/p>\n<p>Tabela comparativa:<\/p>\n<p><\/p>\n<p>Op\u00e7\u00e3o 1<br \/>\nOp\u00e7\u00e3o 2<br \/>\nOp\u00e7\u00e3o 3<\/p>\n<p><strong>Resist\u00eancia a erros<\/strong><br \/>\n\u2014<br \/>\n+<br \/>\n+<\/p>\n<p><strong>Compacta\u00e7\u00e3o<\/strong><br \/>\n+<br \/>\n\u2014<br \/>\n+<\/p>\n<p><strong>Complexidade de implementa\u00e7\u00e3o<\/strong><br \/>\n*<br \/>\n**<br \/>\n**<\/p>\n<p><\/p>\n<p>A op\u00e7\u00e3o 1 tem uma desvantagem fatal: ao danificar algum dos cabe\u00e7alhos, toda a cadeia subsequente \u00e9 destru\u00edda. As outras op\u00e7\u00f5es permitem recuperar parte dos dados mesmo em caso de danos em massa.<br \/>\nMas aqui \u00e9 apropriado lembrar que decidimos armazenar os dados em formato compactado, e de qualquer maneira perdemos todos os dados na p\u00e1gina ap\u00f3s a entrada 'corrompida', ent\u00e3o mesmo que na tabela exista um sinal negativo, n\u00e3o o levamos em conta.<\/p>\n<p><\/p>\n<p>Compacta\u00e7\u00e3o:<\/p>\n<p><\/p>\n<ul>\n<li>Na primeira op\u00e7\u00e3o, precisamos armazenar no cabe\u00e7alho apenas o comprimento; se usarmos inteiros de comprimento vari\u00e1vel, na maioria dos casos podemos nos contentar com um byte;<\/li>\n<li>Na segunda op\u00e7\u00e3o, precisamos armazenar o endere\u00e7o inicial e o comprimento; o registro deve ter um tamanho fixo, que eu avalio em 4 bytes por registro (dois bytes para deslocamento e dois bytes para comprimento);<\/li>\n<li>A terceira op\u00e7\u00e3o precisa de apenas um s\u00edmbolo para indicar o in\u00edcio do registro, mais o pr\u00f3prio registro que, devido \u00e0 necessidade de escape, crescer\u00e1 em 1-2%. Em geral, a paridade aproximada com a primeira op\u00e7\u00e3o.<\/li>\n<\/ul>\n<p><\/p>\n<p>Inicialmente, considerei a segunda op\u00e7\u00e3o como a principal (e at\u00e9 escrevi uma implementa\u00e7\u00e3o). Desisti dela apenas quando decidi definitivamente usar compress\u00e3o.<\/p>\n<p><\/p>\n<p><em>Talvez, algum dia, eu realmente utilize uma variante como essa. Por exemplo, se eu tiver que lidar com o armazenamento de dados para uma nave que viaja entre a Terra e Marte \u2014 com requisitos de confiabilidade completamente diferentes, radia\u00e7\u00e3o espacial, ...<\/em><\/p>\n<p><\/p>\n<p>Quanto \u00e0 terceira op\u00e7\u00e3o: eu dei duas estrelas pela complexidade de implementa\u00e7\u00e3o simplesmente porque n\u00e3o gosto de lidar com escape, mudan\u00e7a de comprimento no processo, etc. Sim, talvez de forma tendenciosa, mas eu terei que escrever o c\u00f3digo \u2014 por que me for\u00e7ar a fazer algo que n\u00e3o gosto?<\/p>\n<p><\/p>\n<p><strong>Resumo:<\/strong> Optamos pelo armazenamento em forma de cadeias 'cabe\u00e7alho com comprimento \u2014 dados de comprimento vari\u00e1vel' devido \u00e0 efici\u00eancia e simplicidade de implementa\u00e7\u00e3o.<\/p>\n<p><\/p>\n<h3 id=\"ispolzovanie-bitovyh-poley-dlya-kontrolya-uspeshnosti-operaciy-zapisi\">O uso de campos de bits para controlar o sucesso das opera\u00e7\u00f5es de grava\u00e7\u00e3o<\/h3>\n<p><\/p>\n<p>J\u00e1 n\u00e3o me lembro onde eu vi a ideia, mas funciona mais ou menos assim:<br \/>\nPara cada grava\u00e7\u00e3o, reservamos alguns bits para armazenar as flags.<br \/>\n<em>Como mencionamos anteriormente, ap\u00f3s o erase, todos os bits s\u00e3o preenchidos com 1, e podemos alterar 1 para 0, mas n\u00e3o o contr\u00e1rio.<\/em> Portanto, para 'flag n\u00e3o definido' usamos 1, e para 'flag definido' \u2014 0.<\/p>\n<p><\/p>\n<p>Assim pode parecer o armazenamento de uma grava\u00e7\u00e3o de comprimento vari\u00e1vel na flash:<\/p>\n<p><\/p>\n<ol>\n<li>Definimos a flag \"grava\u00e7\u00e3o de comprimento iniciada\";<\/li>\n<li>Gravamos o comprimento;<\/li>\n<li>Definimos a flag \"grava\u00e7\u00e3o de dados iniciada\";<\/li>\n<li>Gravamos os dados;<\/li>\n<li>Definimos a flag \"grava\u00e7\u00e3o finalizada\".<\/li>\n<\/ol>\n<p><\/p>\n<p>Al\u00e9m disso, teremos uma flag \"ocorreu um erro\", totalizando 4 flags de bits.<\/p>\n<p><\/p>\n<p>Nesse caso, temos dois estados est\u00e1veis \"1111\" \u2014 grava\u00e7\u00e3o n\u00e3o iniciada e \"1000\" \u2014 grava\u00e7\u00e3o bem-sucedida; em caso de interrup\u00e7\u00e3o inesperada do processo de grava\u00e7\u00e3o, obteremos estados intermedi\u00e1rios, que depois poderemos detectar e processar.<\/p>\n<p><\/p>\n<p>A abordagem \u00e9 interessante, mas ela protege apenas contra a inesperada falha de energia e interrup\u00e7\u00f5es semelhantes, o que, claro, \u00e9 importante, mas n\u00e3o \u00e9 a \u00fanica (e nem a principal) raz\u00e3o para poss\u00edveis falhas.<\/p>\n<p><\/p>\n<p><strong>Resumo:<\/strong> Vamos avan\u00e7ar na busca por uma boa solu\u00e7\u00e3o.<\/p>\n<p><\/p>\n<h3 id=\"kontrolnye-summy\">Somas de verifica\u00e7\u00e3o<\/h3>\n<p><\/p>\n<p>As somas de verifica\u00e7\u00e3o tamb\u00e9m permitem garantir (com uma probabilidade razo\u00e1vel) que estamos lendo exatamente o que deveria ter sido gravado. E, ao contr\u00e1rio dos campos de bits discutidos acima, elas funcionam sempre.<\/p>\n<p><\/p>\n<p>Se considerarmos a lista de potenciais fontes de problemas que discutimos acima, uma soma de verifica\u00e7\u00e3o pode identificar um erro independentemente de sua origem <em>(exceto, talvez, por alien\u00edgenas maliciosos \u2014 eles podem falsificar at\u00e9 mesmo a soma de verifica\u00e7\u00e3o)<\/em>.<\/p>\n<p><\/p>\n<p>Portanto, se nosso objetivo \u00e9 verificar se os dados est\u00e3o \u00edntegros, as somas de verifica\u00e7\u00e3o s\u00e3o uma excelente ideia.<\/p>\n<p><\/p>\n<p>A escolha do algoritmo para c\u00e1lculo da soma de verifica\u00e7\u00e3o n\u00e3o suscitou d\u00favidas \u2014 CRC. Por um lado, as propriedades matem\u00e1ticas permitem capturar 100% dos erros de certos tipos, por outro \u2014 em dados aleat\u00f3rios, esse algoritmo geralmente mostra uma probabilidade de colis\u00f5es n\u00e3o muito maior do que o limite te\u00f3rico. <img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/bf9cca3564db7d9d03d3ce49642d3a71.jpg\" style=\"display:block;margin: 0 auto;\" \/>. Embora este n\u00e3o seja o algoritmo mais r\u00e1pido e nem sempre o que minimiza o n\u00famero de colis\u00f5es, ele possui uma qualidade muito importante: nos testes que encontrei, n\u00e3o surgiram padr\u00f5es nos quais ele falhasse claramente. A estabilidade \u00e9 a principal qualidade neste caso.<\/p>\n<p><\/p>\n<p>Exemplo de uma pesquisa abrangente: <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo.html\">parte 1<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo2.html\">parte 2<\/a><\/noindex> <em>(links para narod.ru, desculpe)<\/em>.<\/p>\n<p><\/p>\n<p>No entanto, a tarefa de escolher um checksum n\u00e3o est\u00e1 conclu\u00edda; o CRC \u00e9 uma fam\u00edlia inteira de checksums. \u00c9 necess\u00e1rio determinar o comprimento e, em seguida, escolher um polin\u00f4mio.<\/p>\n<p><\/p>\n<p>A escolha do comprimento do checksum n\u00e3o \u00e9 uma quest\u00e3o t\u00e3o simples quanto parece \u00e0 primeira vista.<\/p>\n<p><\/p>\n<p>Vou ilustrar:<br \/>\nVamos considerar a probabilidade de erro em cada byte <img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/89a9be2edf2a43cc9115eba37fa02fc8.jpg\" style=\"display:block;margin: 0 auto;\" \/> e o checksum ideal, vamos calcular o n\u00famero m\u00e9dio de erros em um milh\u00e3o de registros:<\/p>\n<p><\/p>\n<p>Dados, byte<br \/>\nChecksum, byte<br \/>\nErro n\u00e3o detectado<br \/>\nFalsos positivos de erro<br \/>\nTotal de falhas incorretas<\/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>Parece simples \u2013 escolha o comprimento do checksum com base no comprimento dos dados protegidos, minimizando as falhas incorretas \u2013 e est\u00e1 resolvido.<\/p>\n<p><\/p>\n<p>No entanto, com checksums curtos, surge o problema: embora eles detectem bem erros bit a bit, podem com uma probabilidade bastante alta considerar como v\u00e1lidos dados completamente aleat\u00f3rios. J\u00e1 houve um artigo no Habr que descrevia <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/428746\/\">o problema na vida real<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Portanto, para tornar uma coincid\u00eancia aleat\u00f3ria de checksum praticamente imposs\u00edvel, precisa-se usar checksums de 32 bits ou mais <em>(para comprimentos superiores a 64 bits, normalmente utilizam-se fun\u00e7\u00f5es hash criptogr\u00e1ficas)<\/em>.<\/p>\n<p><\/p>\n<p>Apesar de eu ter escrito anteriormente que devemos economizar espa\u00e7o a todo custo, vamos optar por um checksum de 32 bits (16 bits \u00e9 pouco, a probabilidade de colis\u00e3o \u00e9 maior que 0,01%; e 24 bits, como dizem, n\u00e3o chega e n\u00e3o sai).<\/p>\n<p><\/p>\n<p>Pode surgir uma obje\u00e7\u00e3o: n\u00e3o economizamos cada byte ao escolher a compress\u00e3o para agora entregar 4 bytes de uma s\u00f3 vez? N\u00e3o seria melhor n\u00e3o comprimir e n\u00e3o adicionar checksum? Claro que n\u00e3o, a falta de compress\u00e3o <em>n\u00e3o significa<\/em>, que a verifica\u00e7\u00e3o de integridade n\u00e3o \u00e9 necess\u00e1ria.<\/p>\n<p><\/p>\n<p>Quanto \u00e0 escolha do polin\u00f4mio, n\u00e3o vamos inventar a roda, e sim pegar o CRC-32C, que \u00e9 popular atualmente.<br \/>\nEste c\u00f3digo detecta 6 erros de bit em pacotes de at\u00e9 22 bytes (talvez o caso mais comum para n\u00f3s), 4 erros de bit em pacotes de at\u00e9 655 bytes (tamb\u00e9m um caso comum para n\u00f3s), 2 ou qualquer n\u00famero \u00edmpar de erros de bit em pacotes de qualquer comprimento razo\u00e1vel.<\/p>\n<p>\n<b class=\"spoiler_title\">Se algu\u00e9m estiver interessado em detalhes<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cyclic_redundancy_check\">artigo da Wikip\u00e9dia<\/a><\/noindex> sobre CRC.<\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0x8f6e37a0_len.txt\">Par\u00e2metros do c\u00f3digo crc-32c<\/a><\/noindex> para <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/crc\/notes.html\">site de Kupman<\/a><\/noindex> \u2013 talvez o principal especialista em CRC do planeta.<\/p>\n<p><\/p>\n<p>Em <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/networks\/dsn02\/dsn02_koopman.pdf\">seu artigo<\/a><\/noindex> tem <noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0xfa567d89_len.txt\">outro c\u00f3digo interessante<\/a><\/noindex>, que fornece par\u00e2metros ligeiramente melhores para os comprimentos de pacotes que nos interessam, mas n\u00e3o considerei a diferen\u00e7a significativa, e me sinto suficientemente competente para escolher um c\u00f3digo personalizado em vez de um padr\u00e3o bem pesquisado.<\/p>\n<p><\/p>\n<p>Al\u00e9m disso, como nossos dados est\u00e3o comprimidos, surge a quest\u00e3o: devemos calcular a soma de verifica\u00e7\u00e3o de dados comprimidos ou n\u00e3o comprimidos?<\/p>\n<p><\/p>\n<p>Argumentos 'a favor' da contagem do checksum de dados n\u00e3o compactados:<\/p>\n<p><\/p>\n<ul>\n<li>precisamos, em \u00faltima an\u00e1lise, verificar a integridade do armazenamento de dados \u2013 \u00e9 isso que estamos verificando diretamente (ao mesmo tempo, tamb\u00e9m ser\u00e3o verificados poss\u00edveis erros na implementa\u00e7\u00e3o de compress\u00e3o\/descompress\u00e3o, danos causados por mem\u00f3ria com defeito, etc.);<\/li>\n<li>o algoritmo deflate em zlib possui uma implementa\u00e7\u00e3o bastante madura e <em>n\u00e3o deve<\/em> ele pode falhar com 'dados de entrada incorretos', al\u00e9m disso, muitas vezes, ele consegue detectar erros no fluxo de entrada por conta pr\u00f3pria, reduzindo a probabilidade geral de n\u00e3o detec\u00e7\u00e3o de erro (realizei um teste invertendo um \u00fanico bit em uma entrada curta, e o zlib detectou o erro aproximadamente em um ter\u00e7o dos casos).<\/li>\n<\/ul>\n<p><\/p>\n<p>Argumentos 'contra' a contagem do checksum de dados n\u00e3o compactados:<\/p>\n<p><\/p>\n<ul>\n<li>O CRC \u00e9 'especializado' para erros de bit raros, que s\u00e3o caracter\u00edsticos da mem\u00f3ria flash (um erro de bit em um fluxo compactado pode resultar em uma mudan\u00e7a massiva no fluxo de sa\u00edda, onde, teoricamente, podemos 'capturar' uma colis\u00e3o);<\/li>\n<li>n\u00e3o gosto muito da ideia de passar dados potencialmente corrompidos ao descompretor, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cvedetails.com\/vulnerability-list\/vendor_id-72\/product_id-1820\/GNU-Zlib.html\">quem sabe<\/a><\/noindex>, como ele reagir\u00e1.<\/li>\n<\/ul>\n<p><\/p>\n<p>Neste projeto, decidi me afastar da pr\u00e1tica comum de armazenar a soma de verifica\u00e7\u00e3o de dados n\u00e3o comprimidos.<\/p>\n<p><\/p>\n<p><strong>Resumo:<\/strong> usamos CRC-32C, calculamos a soma de verifica\u00e7\u00e3o dos dados na forma em que s\u00e3o gravados na flash (ap\u00f3s a compress\u00e3o).<\/p>\n<p><\/p>\n<h3 id=\"izbytochnost\">Redund\u00e2ncia<\/h3>\n<p><\/p>\n<p>O uso de codifica\u00e7\u00e3o redundante n\u00e3o elimina, \u00e9 claro, a perda de dados, no entanto, pode reduzir significativamente (muitas vezes em v\u00e1rias ordens de grandeza) a probabilidade de perda irrevers\u00edvel de dados.<\/p>\n<p><\/p>\n<p>Podemos usar diferentes tipos de redund\u00e2ncia para corrigir erros.<br \/>\nOs c\u00f3digos de Hamming podem corrigir erros individuais de bits, os c\u00f3digos de Reed-Solomon s\u00e3o simb\u00f3licos; v\u00e1rias c\u00f3pias de dados juntamente com somas de verifica\u00e7\u00e3o, ou codifica\u00e7\u00f5es como RAID-6 podem ajudar a recuperar dados mesmo em caso de danos massivos.<br \/>\nInicialmente, eu estava inclinado a usar amplamente a codifica\u00e7\u00e3o resistente a falhas, mas depois percebi que primeiro precisamos ter uma ideia de quais erros queremos nos proteger, e depois escolher a codifica\u00e7\u00e3o.<\/p>\n<p><\/p>\n<p>Dissemos anteriormente que os erros devem ser detectados o mais r\u00e1pido poss\u00edvel. Em que momentos podemos enfrentar erros?<\/p>\n<p><\/p>\n<ol>\n<li>Uma entrada incompleta (por algum motivo, a energia foi cortada no momento da grava\u00e7\u00e3o, o Raspberry travou, ...)<br \/>\nInfelizmente, em caso de tal erro, s\u00f3 resta ignorar os registros inv\u00e1lidos e considerar os dados perdidos;<\/li>\n<li>Erros de grava\u00e7\u00e3o (por algum motivo, o que foi gravado na mem\u00f3ria flash n\u00e3o \u00e9 o que deveria ter sido gravado)<br \/>\nPodemos detectar esses erros imediatamente se fizermos uma leitura de verifica\u00e7\u00e3o logo ap\u00f3s a grava\u00e7\u00e3o;<\/li>\n<li>Distor\u00e7\u00e3o de dados na mem\u00f3ria durante o armazenamento;<\/li>\n<li>Erros de leitura<br \/>\nPara corrigir, basta repetir a leitura algumas vezes em caso de discrep\u00e2ncia na soma de verifica\u00e7\u00e3o.<\/li>\n<\/ol>\n<p><\/p>\n<p>Ou seja, apenas erros do terceiro tipo (corruptela espont\u00e2nea de dados durante o armazenamento) n\u00e3o podem ser corrigidos sem codifica\u00e7\u00e3o resistente a falhas. Acredita-se que tais erros s\u00e3o extremamente improv\u00e1veis.<\/p>\n<p><\/p>\n<p><strong>Resumo:<\/strong> decidiu-se abrir m\u00e3o da codifica\u00e7\u00e3o redundante, mas, se a opera\u00e7\u00e3o mostrar a falha dessa decis\u00e3o, ser\u00e1 poss\u00edvel reconsiderar a quest\u00e3o (com a estat\u00edstica j\u00e1 acumulada sobre falhas, que permitir\u00e1 escolher o tipo de codifica\u00e7\u00e3o ideal).<\/p>\n<p><\/p>\n<h3 id=\"prochee\">Outros<\/h3>\n<p><\/p>\n<p>\u00c9 claro que o formato do artigo n\u00e3o permite justificar cada bit no formato <em>(e j\u00e1 estou sem for\u00e7as)<\/em>, portanto, passarei brevemente por alguns pontos que n\u00e3o foram abordados anteriormente.<\/p>\n<p><\/p>\n<ul>\n<li>Decidimos tornar todas as p\u00e1ginas 'igualmente dignas'<br \/>\nOu seja, n\u00e3o haver\u00e1 p\u00e1ginas especiais com metadados, fluxos separados, etc., mas sim um \u00fanico fluxo que reescreve todas as p\u00e1ginas sequencialmente.<br \/>\nIsso garante um desgaste uniforme das p\u00e1ginas, a aus\u00eancia de um \u00fanico ponto de falha, e simplesmente agrada;<\/li>\n<li>\u00c9 imprescind\u00edvel considerar a versionabilidade do formato.<br \/>\nUm formato sem n\u00famero de vers\u00e3o no cabe\u00e7alho \u00e9 um mal!<br \/>\nBasta adicionar um campo no cabe\u00e7alho da p\u00e1gina com um n\u00famero m\u00e1gico (assinatura), que indicar\u00e1 a vers\u00e3o do formato utilizado. <em>(n\u00e3o acho que na pr\u00e1tica haver\u00e1 at\u00e9 dez deles)<\/em>;<\/li>\n<li>Usar um cabe\u00e7alho de comprimento vari\u00e1vel para os registros (que s\u00e3o muitos), tentando em grande parte dos casos mant\u00ea-lo com comprimento de 1 byte;<\/li>\n<li>Para codificar o comprimento do cabe\u00e7alho e o comprimento da parte cortada do registro comprimido, utilizar c\u00f3digos bin\u00e1rios de comprimento vari\u00e1vel.<\/li>\n<\/ul>\n<p><\/p>\n<p>Ajudou muito <noindex><a rel=\"nofollow\" href=\"https:\/\/planetcalc.com\/2481\/\">o gerador online<\/a><\/noindex> de c\u00f3digos de Huffman. Em quest\u00e3o de minutos, consegui encontrar os c\u00f3digos de comprimento vari\u00e1vel necess\u00e1rios.<\/p>\n<p><\/p>\n<h1 id=\"anchorformatanchoropisanie-formata-hraneniya-dannyh\"><noindex><a rel=\"nofollow\" name=\"format\"><\/a><\/noindex>Descri\u00e7\u00e3o do formato de armazenamento de dados<\/h1>\n<p><\/p>\n<h2 id=\"byte-order\">Ordem dos bytes<\/h2>\n<p><\/p>\n<p>Os campos que excedem um byte de tamanho s\u00e3o armazenados em formato big-endian (ordem de bytes da rede), ou seja, 0x1234 \u00e9 gravado como 0x12, 0x34.<\/p>\n<p><\/p>\n<h2 id=\"delenie-na-stranicy\">Divis\u00e3o em p\u00e1ginas<\/h2>\n<p><\/p>\n<p>Toda a mem\u00f3ria flash est\u00e1 dividida em p\u00e1ginas de tamanho igual.<\/p>\n<p><\/p>\n<p>O tamanho da p\u00e1gina por padr\u00e3o \u00e9 32KB, mas n\u00e3o mais do que 1\/4 do tamanho total do chip de mem\u00f3ria (para um chip de 4MB isso resulta em 128 p\u00e1ginas).<\/p>\n<p><\/p>\n<p>Cada p\u00e1gina armazena dados de forma independente das outras (ou seja, os dados de uma p\u00e1gina n\u00e3o referenciam os dados de outra p\u00e1gina).<\/p>\n<p><\/p>\n<p>Todas as p\u00e1ginas est\u00e3o numeradas em ordem natural (em ordem crescente de endere\u00e7os), come\u00e7ando pelo n\u00famero 0 (a p\u00e1gina zero come\u00e7a no endere\u00e7o 0, a primeira \u2014 no 32KB, a segunda \u2014 no 64KB, e assim por diante).<\/p>\n<p><\/p>\n<p>O chip de mem\u00f3ria \u00e9 usado como um buffer c\u00edclico (ring buffer), ou seja, a grava\u00e7\u00e3o come\u00e7a na p\u00e1gina n\u00famero 0, depois na p\u00e1gina n\u00famero 1, &#8230;, quando preenchermos a \u00faltima p\u00e1gina, um novo ciclo come\u00e7a e a grava\u00e7\u00e3o continua na p\u00e1gina zero.<\/p>\n<p><\/p>\n<h2 id=\"vnutri-stranicy\">Dentro da p\u00e1gina<\/h2>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/39b45f83dc46bb2fd7081ed2a0b638c9.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNo in\u00edcio da p\u00e1gina, h\u00e1 um cabe\u00e7alho de 4 bytes, seguido da soma de verifica\u00e7\u00e3o do cabe\u00e7alho (CRC-32C), e depois armazenam-se registros no formato &#171;cabe\u00e7alho, dados, soma de verifica\u00e7\u00e3o&#187;.<\/p>\n<p><\/p>\n<p>O cabe\u00e7alho da p\u00e1gina (na ilustra\u00e7\u00e3o em verde sujo) consiste em:<\/p>\n<p><\/p>\n<ul>\n<li>um campo de dois bytes chamado Magic Number (que tamb\u00e9m \u00e9 um sinal de vers\u00e3o do formato)<br \/>\npara a vers\u00e3o atual do formato, ele \u00e9 considerado como <code>0xed00 \u2295 n\u00famero da p\u00e1gina<\/code>;<\/li>\n<li>um contador de dois bytes &#171;Vers\u00e3o da p\u00e1gina&#187; (n\u00famero do ciclo de reescrita da mem\u00f3ria).<\/li>\n<\/ul>\n<p><\/p>\n<p>As entradas na p\u00e1gina s\u00e3o armazenadas de forma compactada (usando o algoritmo deflate). Todas as entradas em uma p\u00e1gina s\u00e3o comprimidas em um \u00fanico fluxo (utilizando um dicion\u00e1rio comum), e a compress\u00e3o em cada nova p\u00e1gina come\u00e7a do zero. Ou seja, para descomprimir qualquer entrada, s\u00e3o necess\u00e1rias todas as entradas anteriores dessa p\u00e1gina (e apenas dessa).<\/p>\n<p><\/p>\n<p>Cada entrada ser\u00e1 comprimida com a flag Z_SYNC_FLUSH, resultando em 4 bytes 0x00, 0x00, 0xff, 0xff no final do fluxo comprimido, possivelmente precedidos por um ou dois bytes nulos.<br \/>\nDescartamos essa sequ\u00eancia (com comprimento de 4, 5 ou 6 bytes) ao gravar na mem\u00f3ria flash.<\/p>\n<p><\/p>\n<p>O cabe\u00e7alho da entrada consiste em 1, 2 ou 3 bytes, armazenando:<\/p>\n<p><\/p>\n<ul>\n<li>um bit (T) que indica o tipo da entrada: 0 \u2014 contexto, 1 \u2014 log;<\/li>\n<li>um campo de comprimento vari\u00e1vel (S) de 1 a 7 bits, que define o comprimento do cabe\u00e7alho e &#171;cauda&#187; que deve ser adicionada ao registro para descompacta\u00e7\u00e3o;<\/li>\n<li>o comprimento da entrada (L).<\/li>\n<\/ul>\n<p><\/p>\n<p>Tabela de valores S:<\/p>\n<p><\/p>\n<p>S<br \/>\nComprimento do cabe\u00e7alho, bytes<br \/>\nDescartado ao gravar, byte<\/p>\n<p><code>0<\/code><br \/>\n1<br \/>\n5 (<code>00 00 00 ff ff<\/code>)<\/p>\n<p><code>10<\/code><br \/>\n1<br \/>\n6 (<code>00 00 00 00 ff ff<\/code>)<\/p>\n<p><code>110<\/code><br \/>\n2<br \/>\n4 (<code>00 00 ff ff<\/code>)<\/p>\n<p><code>1110<\/code><br \/>\n2<br \/>\n5 (<code>00 00 00 ff ff<\/code>)<\/p>\n<p><code>11110<\/code><br \/>\n2<br \/>\n6 (<code>00 00 00 00 ff ff<\/code>)<\/p>\n<p><code>1111100<\/code><br \/>\n3<br \/>\n4 (<code>00 00 ff ff<\/code>)<\/p>\n<p><code>1111101<\/code><br \/>\n3<br \/>\n5 (<code>00 00 00 ff ff<\/code>)<\/p>\n<p><code>1111110<\/code><br \/>\n3<br \/>\n6 (<code>00 00 00 00 ff ff<\/code>)<\/p>\n<p><\/p>\n<p>Tentei ilustrar, n\u00e3o sei qu\u00e3o claro ficou:<br \/>\n<img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/8c9b739eec5af4429395c32b4ba4044a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nO campo T est\u00e1 marcado em amarelo, o campo S em branco, L (comprimento dos dados comprimidos em bytes) em verde, os dados comprimidos em azul, os bytes finais dos dados comprimidos que n\u00e3o s\u00e3o gravados na mem\u00f3ria flash em vermelho.<\/p>\n<p><\/p>\n<p>Assim, os cabe\u00e7alhos das entradas mais comuns (at\u00e9 63+5 bytes em formato comprimido) poder\u00e3o ser gravados em um \u00fanico byte.<\/p>\n<p><\/p>\n<p>Ap\u00f3s cada entrada, \u00e9 armazenada uma soma de verifica\u00e7\u00e3o CRC-32C, que usa como valor inicial (init) o valor invertido da soma de verifica\u00e7\u00e3o anterior.<\/p>\n<p><\/p>\n<p><em>O CRC tem a propriedade da &#171;persist\u00eancia&#187;, opera (mais ou menos com a invers\u00e3o de bits durante o processo) a seguinte f\u00f3rmula: <img decoding=\"async\" alt=\"Minha implementa\u00e7\u00e3o de buffer circular em NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/b194d6a3d18ae2f4ae5d4054a93c0ea3.jpg\" style=\"display:block;margin: 0 auto;\" \/>.<br \/>\nOu seja, na pr\u00e1tica, estamos calculando o CRC de todos os bytes anteriores dos cabe\u00e7alhos e dados nessa p\u00e1gina.<\/em><\/p>\n<p><\/p>\n<p>Imediatamente ap\u00f3s a soma de verifica\u00e7\u00e3o, encontra-se o cabe\u00e7alho da pr\u00f3xima entrada.<\/p>\n<p><\/p>\n<p>O cabe\u00e7alho \u00e9 constru\u00eddo de tal maneira que seu primeiro byte seja sempre diferente de 0x00 e 0xff (se encontrarmos 0xff em vez do primeiro byte do cabe\u00e7alho, isso significa que \u00e9 uma \u00e1rea n\u00e3o utilizada; 0x00 sinaliza um erro).<\/p>\n<p><\/p>\n<h2 id=\"primernye-algoritmy\">Algoritmos aproximados<\/h2>\n<p><\/p>\n<h3 id=\"chtenie-iz-flesh-pamyati\">Leitura da mem\u00f3ria flash<\/h3>\n<p><\/p>\n<p>Qualquer leitura \u00e9 feita com verifica\u00e7\u00e3o de soma de verifica\u00e7\u00e3o.<br \/>\nSe a soma de verifica\u00e7\u00e3o n\u00e3o corresponder, a leitura ser\u00e1 repetida v\u00e1rias vezes na esperan\u00e7a de ler os dados corretos.<\/p>\n<p><\/p>\n<p><em>(isso faz sentido, o Linux n\u00e3o armazena em cache as leituras da NOR Flash, comprovado)<\/em><\/p>\n<p><\/p>\n<h3 id=\"zapis-v-flesh-pamyat\">Grava\u00e7\u00e3o na mem\u00f3ria flash<\/h3>\n<p><\/p>\n<p>Estamos gravando dados.<br \/>\nEstamos os lendo.<\/p>\n<p><\/p>\n<p>Se os dados lidos n\u00e3o corresponderem aos dados gravados, preenchemos a \u00e1rea com zeros e sinalizamos um erro.<\/p>\n<p><\/p>\n<h3 id=\"podgotovka-novoy-mikroshemy-k-rabote\">Preparando o novo chip para opera\u00e7\u00e3o<\/h3>\n<p><\/p>\n<p>Para a inicializa\u00e7\u00e3o, um cabe\u00e7alho com a vers\u00e3o 1 \u00e9 gravado na primeira (ou, mais precisamente, na p\u00e1gina zero).<br \/>\nDepois disso, um contexto inicial \u00e9 gravado nesta p\u00e1gina (cont\u00e9m o UUID do dispositivo e configura\u00e7\u00f5es padr\u00e3o). <\/p>\n<p><\/p>\n<p>Tudo pronto, a mem\u00f3ria flash est\u00e1 pronta para opera\u00e7\u00e3o.<\/p>\n<p><\/p>\n<h3 id=\"zagruzka-avtomata\">Carregamento do dispositivo<\/h3>\n<p><\/p>\n<p>Durante o carregamento, os primeiros 8 bytes de cada p\u00e1gina (cabe\u00e7alho + CRC) s\u00e3o lidos, e p\u00e1ginas com um n\u00famero m\u00e1gico desconhecido ou CRC inv\u00e1lido s\u00e3o ignoradas.<br \/>\nDas &#171;p\u00e1ginas &#187;corretas&#187;, s\u00e3o selecionadas as p\u00e1ginas com a m\u00e1xima vers\u00e3o, da qual se pega a p\u00e1gina com o maior n\u00famero.<br \/>\nLemos o primeiro registro, verificamos a corre\u00e7\u00e3o do CRC e a presen\u00e7a do sinalizador &#171;contexto&#187;. Se tudo estiver correto, essa p\u00e1gina \u00e9 considerada atual. Se n\u00e3o, retrocedemos para a anterior, at\u00e9 encontrar uma p\u00e1gina &#171;viva&#187;.<br \/>\nNa p\u00e1gina encontrada, lemos todos os registros, aqueles com o sinalizador &#171;contexto&#187; s\u00e3o aplicados.<br \/>\nSalvamos o dicion\u00e1rio zlib (ser\u00e1 necess\u00e1rio para escrev\u00ea-lo novamente nesta p\u00e1gina).<\/p>\n<p><\/p>\n<p>Isso \u00e9 tudo, o carregamento foi conclu\u00eddo, o contexto foi restaurado, \u00e9 poss\u00edvel trabalhar.<\/p>\n<p><\/p>\n<h3 id=\"dobavlenie-zapisi-v-zhurnal\">Adicionando uma entrada ao log<\/h3>\n<p><\/p>\n<p>Comprimimos a entrada com o dicion\u00e1rio correto, especificando Z_SYNC_FLUSH. Verificamos se a entrada comprimida cabe na p\u00e1gina atual.<br \/>\nSe n\u00e3o couber (ou se houver erros de CRC na p\u00e1gina), come\u00e7amos uma nova p\u00e1gina (veja abaixo).<br \/>\nGravamos a entrada e o CRC. Se ocorrer um erro, come\u00e7amos uma nova p\u00e1gina.<\/p>\n<p><\/p>\n<h3 id=\"novaya-stranica\">Nova p\u00e1gina<\/h3>\n<p><\/p>\n<p>Escolhemos uma p\u00e1gina livre com o menor n\u00famero (consideramos livre uma p\u00e1gina com uma soma de verifica\u00e7\u00e3o errada no cabe\u00e7alho ou com uma vers\u00e3o inferior \u00e0 atual). Se n\u00e3o houver tais p\u00e1ginas, escolhemos a p\u00e1gina com o menor n\u00famero que tenha a vers\u00e3o igual \u00e0 atual.<br \/>\nRealizamos o erase na p\u00e1gina selecionada. Comparamos o conte\u00fado com 0xff. Se algo estiver errado, pegamos a pr\u00f3xima p\u00e1gina livre, e assim por diante.<br \/>\nNa p\u00e1gina apagada, gravamos o cabe\u00e7alho, com o estado atual do contexto como a primeira entrada e, em seguida, a entrada do log n\u00e3o gravada (se houver).<\/p>\n<p><\/p>\n<h1 id=\"primenimost-formata\">Aplicabilidade do formato<\/h1>\n<p><\/p>\n<p>Na minha opini\u00e3o, este \u00e9 um formato razo\u00e1vel para armazenar qualquer fluxo de informa\u00e7\u00f5es que seja minimamente comprim\u00edvel (texto simples, JSON, MessagePack, CBOR, possivelmente protobuf) em NOR Flash.<\/p>\n<p><\/p>\n<p>Certamente, o formato \u00e9 &#171;adaptado&#187; para SLC NOR Flash.<\/p>\n<p><\/p>\n<p>N\u00e3o deve ser utilizado com dispositivos que possuem um BER alto, como NAND ou MLC NOR. <em>(existe realmente mem\u00f3ria desse tipo \u00e0 venda? s\u00f3 encontrei men\u00e7\u00f5es em trabalhos sobre c\u00f3digos de corre\u00e7\u00e3o)<\/em>.<\/p>\n<p><\/p>\n<p>Ainda mais, n\u00e3o deve ser usado com dispositivos que possuem seu pr\u00f3prio FTL: USB flash, SD, MicroSD, etc. <em>(para essa mem\u00f3ria eu criei um formato com tamanho de p\u00e1gina de 512 bytes, uma assinatura no in\u00edcio de cada p\u00e1gina e n\u00fameros de registro \u00fanicos \u2014 \u00e0s vezes, de um flash &#171;travado&#187; foi poss\u00edvel recuperar todos os dados apenas com leitura sequencial)<\/em>.<\/p>\n<p><\/p>\n<p>Dependendo das tarefas, o formato pode ser usado sem altera\u00e7\u00f5es em flash de 128Kbits (16Kbytes) at\u00e9 1Gbit (128Mbytes). Se desejado, tamb\u00e9m pode ser utilizado em chips de maior capacidade, mas provavelmente \u00e9 necess\u00e1rio ajustar o tamanho da p\u00e1gina. <em>(Mas aqui j\u00e1 surge a quest\u00e3o da viabilidade econ\u00f4mica, o pre\u00e7o da NOR Flash em grande capacidade n\u00e3o \u00e9 animador.)<\/em>.<\/p>\n<p><\/p>\n<p>Se algu\u00e9m achou o formato interessante e quiser us\u00e1-lo em um projeto aberto \u2014 escreva, tentarei encontrar tempo para organizar o c\u00f3digo e public\u00e1-lo no github.<\/p>\n<p><\/p>\n<h1 id=\"zaklyuchenie\">Conclus\u00e3o<\/h1>\n<p><\/p>\n<p>Como podemos ver, no final o formato acabou sendo simples. <em>E at\u00e9 mesmo mon\u00f3tono.<\/em>.<\/p>\n<p><\/p>\n<p>Na artigo, \u00e9 dif\u00edcil refletir a evolu\u00e7\u00e3o do meu ponto de vista, mas acredite: inicialmente eu queria criar algo sofisticado, indestrut\u00edvel, capaz de sobreviver mesmo ap\u00f3s uma explos\u00e3o nuclear nas proximidades. No entanto, a raz\u00e3o (espero) acabou prevalecendo e, gradualmente, as prioridades se deslocaram para a simplicidade e compacta\u00e7\u00e3o.<\/p>\n<p><\/p>\n<p>Pode ser que eu esteja errado? Sim, claro. \u00c9 bem poss\u00edvel, por exemplo, que tenhamos adquirido um lote de chips de baixa qualidade. Ou, por algum outro motivo, o equipamento n\u00e3o atenda \u00e0s expectativas de confiabilidade.<\/p>\n<p><\/p>\n<p>Tenho um plano para esse caso? Eu acho que, ap\u00f3s ler o artigo, voc\u00ea n\u00e3o duvida de que h\u00e1 um plano. E n\u00e3o s\u00f3 um.<\/p>\n<p><\/p>\n<p>Se levarmos um pouco mais a s\u00e9rio, o formato foi desenvolvido tanto como uma op\u00e7\u00e3o funcional quanto como um &#171;teste&#187;.<\/p>\n<p><\/p>\n<p>At\u00e9 o momento, tudo est\u00e1 funcionando normalmente na mesa, e em breve a solu\u00e7\u00e3o ser\u00e1 implantada. <em>(aproximadamente)<\/em> em cem dispositivos, vamos ver o que acontece na &#171;explora\u00e7\u00e3o de combate&#187; (felizmente, espero que o formato permita detectar falhas de forma confi\u00e1vel; assim, poder\u00e1 ser compilada uma estat\u00edstica completa). Em alguns meses, poder\u00e1 haver conclus\u00f5es. <em>(e se n\u00e3o tiver sorte \u2014 antes disso)<\/em>.<\/p>\n<p><\/p>\n<p>Se, ap\u00f3s o uso, surgirem problemas s\u00e9rios e forem necess\u00e1rias melhorias, com certeza vou mencionar isso.<\/p>\n<p><\/p>\n<h1 id=\"literatura\">Literatura<\/h1>\n<p><\/p>\n<p>N\u00e3o queria fazer uma longa lista entediante de trabalhos utilizados, afinal, o Google est\u00e1 acess\u00edvel a todos.<\/p>\n<p><\/p>\n<p>Decidi manter uma lista de descobertas que me pareceram particularmente interessantes, mas gradualmente elas foram incorporadas diretamente no texto do artigo, e na lista ficou apenas um item:<\/p>\n<p><\/p>\n<ol>\n<li>Utilit\u00e1rio <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/madler\/infgen\/\">infgen<\/a><\/noindex> de autoria de zlib. \u00c9 capaz de exibir o conte\u00fado de arquivos deflate\/zlib\/gzip de forma compreens\u00edvel. Se voc\u00ea precisa entender a estrutura interna do formato deflate (ou gzip) \u2014 recomendo fortemente.<\/li>\n<\/ol>\n<p>Fonte: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/479044\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f \u0415\u0441\u0442\u044c \u0442\u043e\u0440\u0433\u043e\u0432\u044b\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u044b \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0412\u043d\u0443\u0442\u0440\u0438 Raspberry Pi \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0432\u044f\u0437\u043a\u0438 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u0435. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u043c\u043e\u043d\u0435\u0442\u043e\u043f\u0440\u0438\u0451\u043c\u043d\u0438\u043a, \u043a\u0443\u043f\u044e\u0440\u043e\u043f\u0440\u0438\u0451\u043c\u043d\u0438\u043a, \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0439 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u2026 \u0423\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u0441\u0435\u043c \u0441\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u0430\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430. \u0412\u0441\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0432 \u0436\u0443\u0440\u043d\u0430\u043b \u043d\u0430 \u0444\u043b\u0435\u0448\u043a\u0435 (MicroSD), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0442\u043e\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 (\u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e USB-\u043c\u043e\u0434\u0435\u043c\u0430) \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440, \u0442\u0430\u043c \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0411\u0414. \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u0440\u043e\u0434\u0430\u0436\u0430\u0445 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0432 1\u0441, \u0442\u0430\u043a\u0436\u0435 \u0435\u0441\u0442\u044c [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-53751","post","type-post","status-publish","format-standard","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.3 - 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\/pt\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"pt_PT\" \/>\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\/pt\/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\udd47Minha implementa\u00e7\u00e3o de um buffer circular em NOR flash | ProHoster","description":"Hist\u00f3ria de fundo Existem m\u00e1quinas de venda autom\u00e1tica de nossa pr\u00f3pria concep\u00e7\u00e3o. Dentro delas, um Raspberry Pi e um pouco de fia\u00e7\u00e3o em uma placa separada.","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pt_PT","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\/pt\/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\/pt\/wp-json\/wp\/v2\/posts\/53751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=53751"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/53751\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=53751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=53751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=53751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}