Mia efektivigo de ringa bufro en NOR-fulmo

antaŭhistorio

Estas vendiloj de nia propra dezajno. Ene de la Raspberry Pi kaj iom da drataro sur aparta tabulo. Monero-akceptanto, bilet-akceptanto, bankterminalo estas konektitaj... Ĉio estas regata de memskribita programo. La tuta laborhistorio estas skribita en protokolon sur flash drive (MicroSD), kiu tiam estas transdonita per la Interreto (uzante USB-modemon) al la servilo, kie ĝi estas konservita en datumbazo. Vendaj informoj estas ŝarĝitaj en 1c, ekzistas ankaŭ simpla retinterfaco por monitorado, ktp.

Tio estas, la ĵurnalo estas esenca - por kontado (enspezo, vendo, ktp.), monitorado (ĉiuspecaj malsukcesoj kaj aliaj fortaj cirkonstancoj); Ĉi tio, oni povus diri, estas ĉiuj informoj, kiujn ni havas pri ĉi tiu maŝino.

problemo

Ekbriloj montras sin tre nefidindaj aparatoj. Ili malsukcesas kun enviinda reguleco. Ĉi tio kondukas al kaj maŝina malfunkcio kaj (se ial la protokolo ne povus esti transdonita interrete) al datumperdo.

Ĉi tio ne estas la unua sperto de uzado de poŝmemoriloj, antaŭ tio estis alia projekto kun pli ol cent aparatoj, kie la revuo estis konservita sur USB-memoriloj, estis ankaŭ problemoj pri fidindeco, foje la nombro de tiuj, kiuj malsukcesis en monato estis en la dekoj. Ni provis malsamajn poŝmemorojn, inkluzive de markitaj kun SLC-memoro, kaj iuj modeloj estas pli fidindaj ol aliaj, sed anstataŭigi poŝmemorojn ne radikale solvis la problemon.

Singardemo Longlegu! Se vi ne interesiĝas pri "kial", sed nur pri "kiel", vi povas iri rekte En la fino artikoloj.

decido

La unua afero, kiu venas al la menso, estas: forlasi MicroSD, instali, ekzemple, SSD, kaj ekbruligi de ĝi. Teorie ebla, verŝajne, sed relative multekosta, kaj ne tiom fidinda (aldonas USB-SATA-adaptilo; malsukcesaj statistikoj por buĝetaj SSD-oj ankaŭ ne estas kuraĝigaj).

USB HDD ankaŭ ne aspektas kiel aparte alloga solvo.

Sekve, ni venis al ĉi tiu opcio: lasu lanĉadon de MicroSD, sed uzu ilin en nurlegebla reĝimo, kaj konservu la operacian protokolon (kaj aliajn informojn unikajn al aparta aparataro - seria numero, sensilkalibradoj, ktp.) ie aliloke. .

La temo de nurlegebla FS por framboj jam estis studita interne kaj ekstere, mi ne pritraktos efektivigajn detalojn en ĉi tiu artikolo (sed se estas intereso, eble mi skribos mini-artikolon pri tiu ĉi temo). La sola punkto, kiun mi ŝatus rimarki, estas, ke kaj de persona sperto kaj de recenzoj de tiuj, kiuj jam efektivigis ĝin, estas gajno de fidindeco. Jes, estas neeble tute forigi paneojn, sed signife redukti ilian frekvencon estas tute ebla. Kaj la kartoj iĝas unuigitaj, kio faras anstataŭigon multe pli facila por servopersonaro.

Aparataro

Ne estis aparta dubo pri la elekto de memortipo - NOR Flash.
Argumentoj:

  • simpla konekto (plej ofte la SPI-buso, kiun vi jam havas sperton uzante, do ne estas antaŭvidataj aparataj problemoj);
  • ridinda prezo;
  • norma operacia protokolo (la efektivigo estas jam en la Linukso-kerno, se vi deziras, vi povas preni trian, kiu ankaŭ ĉeestas, aŭ eĉ skribi vian propran, feliĉe ĉio estas simpla);
  • fidindeco kaj rimedo:
    de tipa datenfolio: datenoj estas stokitaj dum 20 jaroj, 100000 forviŝcikloj por ĉiu bloko;
    de triaj fontoj: ekstreme malalta BER, postulas neniun bezonon de eraraj korektkodoj (kelkaj verkoj konsideras ECC por NOR, sed kutime ili ankoraŭ signifas MLC NOR; tio ankaŭ okazas).

Ni taksu la postulojn por volumeno kaj rimedo.

Mi ŝatus, ke la datumoj estu konservitaj dum kelkaj tagoj. Ĉi tio estas necesa por ke en kazo de iuj problemoj pri komunikado, la vendohistorio ne perdiĝu. Ni koncentriĝos sur 5 tagoj, dum ĉi tiu periodo (eĉ konsiderante semajnfinojn kaj feriojn) la problemo povas esti solvita.

Ni nuntempe kolektas ĉirkaŭ 100 kb da protokoloj tage (3-4 mil enskriboj), sed iom post iom ĉi tiu cifero kreskas - la detalo pliiĝas, novaj eventoj estas aldonitaj. Krome, foje estas eksplodoj (iu sensilo komencas spami kun falsaj pozitivoj, ekzemple). Ni kalkulos por 10 mil rekordoj po 100 bajtoj - megabajtoj ĉiutage.

Entute eliras 5MB da puraj (bone kunpremitaj) datumoj. Pli al ili (malglata takso) 1MB da servaj datumoj.

Tio estas, ni bezonas 8MB-peceton se ni ne uzas kunpremadon, aŭ 4MB se ni uzas ĝin. Sufiĉe realismaj nombroj por ĉi tiu tipo de memoro.

Koncerne la rimedon: se ni planas, ke la tuta memoro estos reverkita ne pli ol unufoje ĉiun 5 tagojn, tiam pli ol 10 jarojn da servo ni ricevas malpli ol mil reverkciklojn.
Mi memorigu vin, ke la fabrikanto promesas cent mil.

Iom pri NOR vs NAND

Hodiaŭ, kompreneble, NAND-memoro estas multe pli populara, sed mi ne uzus ĝin por ĉi tiu projekto: NAND, male al NOR, nepre postulas la uzon de erarkorektaj kodoj, tabelo de malbonaj blokoj ktp., kaj ankaŭ la krurojn de NAND-blatoj kutime multe pli.

La malavantaĝoj de NOR inkluzivas:

  • malgranda volumo (kaj, sekve, alta prezo por megabajto);
  • malalta komunika rapido (plejparte pro la fakto, ke oni uzas serian interfacon, kutime SPI aŭ I2C);
  • malrapida forigo (depende de la blokgrandeco, ĝi daŭras de frakcio de sekundo ĝis pluraj sekundoj).

Ŝajnas, ke estas nenio kritika por ni, do ni daŭrigas.

Se la detaloj estas interesaj, la mikrocirkvito estas elektita ĉe25df321a (tamen tio estas negrava, ekzistas multaj analogoj sur la merkato, kiuj estas kongruaj en pinout kaj komandsistemo; eĉ se ni volas instali mikrocirkviton de malsama fabrikanto kaj/aŭ malsama grandeco, ĉio funkcios sen ŝanĝi la kodo).

Mi uzas la ŝoforon enkonstruitan en la Linukso-kerno; ĉe Raspberry, dank' al subteno de la arbkovraĵo de aparatoj, ĉio estas tre simpla - vi devas meti la kompilitan superkovron en /boot/overlays kaj iomete modifi /boot/config.txt.

Ekzemplo dts dosiero

Verdire, mi ne certas, ke ĝi estas skribita sen eraroj, sed ĝi funkcias.

/*
 * 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?";
    };
};

Kaj alia linio en config.txt

dtoverlay=at25:spimaxfrequency=50000000

Mi preterlasos la priskribon pri konekto de la blato al la Raspberry Pi. Unuflanke mi ne estas fakulo pri elektroniko, aliflanke ĉio ĉi tie estas banala eĉ por mi: la mikrocirkvito havas nur 8 krurojn, el kiuj ni bezonas grundon, potencon, SPI (CS, SI, SO, SCK). ); la niveloj estas la samaj kiel tiuj de la Raspberry Pi, ne necesas plia kablado - nur konekti la indikitajn 6 pinglojn.

Formulado de la problemo

Kiel kutime, la problemo-deklaro trairas plurajn ripetojn, kaj ŝajnas al mi, ke estas tempo por la sekva. Do ni ĉesu, kunigu tion, kio estas jam skribita, kaj klarigu la detalojn, kiuj restas en la ombro.

Do, ni decidis, ke la protokolo estos konservita en SPI NOR Flash.

Kio estas NOR Flash por tiuj, kiuj ne scias?

Ĉi tio estas nevolatila memoro per kiu vi povas fari tri operaciojn:

  1. Legado:
    La plej ofta legado: ni transdonas la adreson kaj legas tiom da bajtoj kiom ni bezonas;
  2. Rekordo:
    Skribi al NOR-fulmo aspektas kiel regula, sed ĝi havas unu proprecon: oni povas ŝanĝi nur 1 al 0, sed ne inverse. Ekzemple, se ni havis 0x55 en memorĉelo, tiam post skribado 0x0f al ĝi, 0x05 jam estos konservita tie (vidu tabelon tuj malsupre);
  3. Viŝi:
    Kompreneble, ni devas povi fari la kontraŭan operacion - ŝanĝi 0 al 1, ĝuste por tio estas la viŝa operacio. Male al la unuaj du, ĝi funkcias ne per bajtoj, sed kun blokoj (la minimuma forviŝbloko en la elektita blato estas 4kb). Forigi detruas la tutan blokon kaj estas la nura maniero ŝanĝi 0 al 1. Tial, kiam vi laboras kun fulmmemoro, vi ofte devas vicigi datumstrukturojn al la viŝa bloklimo.
    Registrado en NOR Flash:

Binaraj datumoj

Estis
01010101

Registrita
00001111

Fariĝis
00000101

La protokolo mem reprezentas sekvencon de registroj de varia longo. La tipa longo de rekordo estas proksimume 30 bajtoj (kvankam rekordoj kiuj longas plurajn kilobajtojn foje okazas). En ĉi tiu kazo, ni laboras kun ili simple kiel aro de bajtoj, sed, se vi interesiĝas, CBOR estas uzata ene de la registroj.

Krom la protokolo, ni devas konservi iujn informojn pri "agordo", ambaŭ ĝisdatigitaj kaj ne: certa aparato-identigilo, sensilo-kalibradoj, "aparato estas provizore malŝaltita" flago, ktp.
Ĉi tiu informo estas aro de ŝlosilvaloraj rekordoj, ankaŭ konservitaj en CBOR. Ni ne havas multajn ĉi tiujn informojn (kelkaj kilobajtoj maksimume), kaj ĝi estas malofte ĝisdatigita.
En kio sekvas ni nomos ĝin kunteksto.

Se ni memoras, kie ĉi tiu artikolo komenciĝis, estas tre grave certigi fidindan datumstokadon kaj, se eble, daŭran funkciadon eĉ en kazo de aparataj misfunkciadoj/datumkoruptado.

Kiujn fontojn de problemoj oni povas konsideri?

  • Malŝaltu dum skribaj/forigitaj operacioj. Ĉi tio estas el la kategorio "ne ekzistas ruzo kontraŭ levstango."
    Informoj de diskutoj sur stackexchange: kiam la potenco estas malŝaltita dum laborado per fulmo, kaj viŝado (agordita al 1) kaj skribi (agordita al 0) kondukas al nedifinita konduto: datumoj povas esti skribitaj, parte skribitaj (diru, ni transdonis 10 bajtojn/80 bitojn). , sed ankoraŭ ne nur 45 bitoj povas esti skribitaj), estas ankaŭ eble ke kelkaj el la bitoj estos en "meza" stato (legado povas produkti kaj 0 kaj 1);
  • Eraroj en la memoro mem.
    BER, kvankam tre malalta, ne povas esti egala al nulo;
  • Bus-eraroj
    Datenoj elsenditaj per SPI neniel estas protektitaj; kaj unubitaj eraroj kaj sinkronigaj eraroj povas okazi - perdo aŭ enmeto de bitoj (kiu kondukas al amasa datuma misprezento);
  • Aliaj eraroj/problemoj
    Eraroj en la kodo, Frambo-problemoj, eksterterana interfero...

Mi formulis la postulojn, kies plenumo, laŭ mi, estas necesa por certigi fidindecon:

  • rekordoj devas tuj eniri en fulmmemoron, malfruaj skribaĵoj ne estas konsiderataj; - se okazas eraro, ĝi devas esti detektita kaj prilaborita kiel eble plej frue; - la sistemo devas, se eble, resaniĝi de eraroj.
    (ekzemplo el la vivo "kiel ĝi ne devus esti", kiun mi opinias ĉiuj renkontis: post urĝa rekomenco, la dosiersistemo estas "rompita" kaj la operaciumo ne ekfunkciigas)

Ideoj, aliroj, pripensoj

Kiam mi ekpensis pri ĉi tiu problemo, trafluis mian kapon multaj ideoj, ekzemple:

  • uzi datuman kunpremadon;
  • uzu lertajn datumstrukturojn, ekzemple, konservante rekordajn kapliniojn aparte de la rekordoj mem, tiel ke se estas eraro en iu rekordo, vi povas legi la ceterajn senprobleme;
  • uzu bitajn kampojn por kontroli la finaĵon de registrado kiam la potenco estas malŝaltita;
  • stoki ĉeksumojn por ĉio;
  • uzu ian bru-rezistan kodigon.

Kelkaj el tiuj ideoj estis uzitaj, dum aliaj estis deciditaj esti prirezignitaj. Ni iru en ordo.

Kunpremo de datumoj

La eventoj mem, kiujn ni registras en la ĵurnalo, estas sufiĉe similaj kaj ripeteblaj ("ĵetis moneron de 5 rubloj", "premis la butonon por doni ŝanĝon", ...). Tial kunpremado devus esti sufiĉe efika.

La kunpremado estas sensignifa (nia procesoro estas sufiĉe potenca, eĉ la unua Pi havis unu kernon kun ofteco de 700 MHz, nunaj modeloj havas plurajn kernojn kun ofteco de pli ol gigaherco), la kurzo kun la stokado estas malalta (pluraj megabajtoj je sekundo), la grandeco de la rekordoj estas malgranda. Ĝenerale, se kunpremado havas efikon al agado, ĝi estos nur pozitiva. (absolute senkritika, nur deklarante). Krome, ni ne havas veran enigitan, sed regulan Linukso - do la efektivigo ne postulu multe da peno (sufiĉas nur ligi la bibliotekon kaj uzi plurajn funkciojn de ĝi).

Peco de la protokolo estis prenita el funkcianta aparato (1.7 MB, 70 mil enskriboj) kaj unue kontrolita por kunpremebleco per gzip, lz4, lzop, bzip2, xz, zstd disponeblaj en la komputilo.

  • gzip, xz, zstd montris similajn rezultojn (40Kb).
    Mi surpriziĝis, ke la moda xz montriĝis ĉi tie je la nivelo de gzip aŭ zstd;
  • lzip kun defaŭltaj agordoj donis iomete pli malbonajn rezultojn;
  • lz4 kaj lzop montris ne tre bonajn rezultojn (150Kb);
  • bzip2 montris surprize bonan rezulton (18Kb).

Do, la datumoj estas kunpremitaj tre bone.
Do (se ni ne trovas fatalajn difektojn) estos kunpremado! Simple ĉar pli da datumoj povas konveni sur la sama poŝmemoro.

Ni pensu pri la malavantaĝoj.

Unua problemo: ni jam konsentis, ke ĉiu disko devas tuj ekbrili. Tipe, la arkivisto kolektas datumojn de la eniga fluo ĝis ĝi decidas, ke estas tempo skribi dum la semajnfino. Ni devas tuj ricevi kunpremitan blokon de datumoj kaj konservi ĝin en nevolatila memoro.

Mi vidas tri manierojn:

  1. Kunpremu ĉiun registron uzante vortaran kunpremadon anstataŭ la algoritmojn diskutitajn supre.
    Ĝi estas tute funkcia opcio, sed mi ne ŝatas ĝin. Por certigi pli-malpli decan nivelon de kunpremado, la vortaro devas esti "tajlorita" al specifaj datumoj; ĉiu ŝanĝo kondukos al la kunpremadnivelo katastrofe fali. Jes, la problemo povas esti solvita kreante novan version de la vortaro, sed tio estas kapdoloro - ni devos konservi ĉiujn versiojn de la vortaro; en ĉiu enskribo ni devos indiki per kiu versio de la vortaro ĝi estis kunpremita...
  2. Kunpremu ĉiun rekordon per "klasikaj" algoritmoj, sed sendepende de la aliaj.
    La kunpremaj algoritmoj konsiderataj ne estas dezajnitaj por labori kun rekordoj de ĉi tiu grandeco (dekoj da bajtoj), la kunprema proporcio klare estos malpli ol 1 (tio estas, pliigante la datuman volumon anstataŭ kunpremi);
  3. Faru FLUSH post ĉiu registrado.
    Multaj kunpremaj bibliotekoj havas subtenon por FLUSH. Ĉi tio estas komando (aŭ parametro al la kunpremadproceduro), ricevinte, kiun la arĥivisto formas kunpremitan fluon tiel ke ĝi povas esti uzata por restarigi ĉiuj nekunpremitaj datumoj kiuj jam estis ricevitaj. Tia analogo sync en dosiersistemoj aŭ commit en sql.
    Gravas, ke postaj kunpremaj operacioj povos uzi la amasigitan vortaron kaj la kunprema proporcio ne suferos tiom kiom en la antaŭa versio.

Mi pensas, ke estas evidente, ke mi elektis la trian opcion, ni rigardu ĝin pli detale.

Trovita bonega artikolo pri FLUSH en zlib.

Mi faris genuan teston bazitan sur la artikolo, prenis 70 mil protokolojn de vera aparato, kun paĝa grandeco de 60Kb (Ni revenos al paĝa grandeco poste) ricevis:

Fontaj datumoj
Kunpremo gzip -9 (sen FLUSH)
zlib kun Z_PARTIAL_FLUSH
zlib kun Z_SYNC_FLUSH

Volumo, KB
1692
40
352
604

Unuavide, la prezo kontribuita de FLUSH estas troe alta, sed fakte ni havas malmulte da elekto - aŭ tute ne kunpremi, aŭ kunpremi (kaj tre efike) per FLUSH. Ni ne devas forgesi, ke ni havas 70 mil registrojn, la redundo enkondukita de Z_PARTIAL_FLUSH estas nur 4-5 bajtoj por registro. Kaj la kunprema proporcio montriĝis preskaŭ 5:1, kio estas pli ol bonega rezulto.

Ĝi povas veni kiel surprizo, sed Z_SYNC_FLUSH estas fakte pli efika maniero fari FLUSH.

Kiam vi uzas Z_SYNC_FLUSH, la lastaj 4 bajtoj de ĉiu eniro ĉiam estos 0x00, 0x00, 0xff, 0xff. Kaj se ni konas ilin, tiam ni ne devas konservi ilin, do la fina grandeco estas nur 324Kb.

La artikolo, al kiu mi ligis, havas klarigon:

Nova tipo 0 bloko kun malplena enhavo estas almetita.

Tipo 0-bloko kun malplena enhavo konsistas el:

  • la tri-bita blokkapo;
  • 0 ĝis 7 bitoj egala al nulo, por atingi bajtan vicigon;
  • la kvar-bajta sekvenco 00 00 FF FF.

Kiel vi povas facile vidi, en la lasta bloko antaŭ ĉi tiuj 4 bajtoj estas de 3 ĝis 10 nul bitoj. Tamen, praktiko montris ke ekzistas fakte almenaŭ 10 nul bitoj.

Montriĝas, ke tiaj mallongaj datumblokoj estas kutime (ĉiam?) kodigitaj per bloko de tipo 1 (fiksa bloko), kiu nepre finiĝas per 7 nulbitoj, donante entute 10-17 garantiitajn nulbitojn (kaj la ceteraj faros). estu nulo kun probableco de ĉirkaŭ 50%).

Do, pri testaj datumoj, en 100% de kazoj estas unu nul bajto antaŭ 0x00, 0x00, 0xff, 0xff, kaj en pli ol triono de kazoj estas du nul bajtoj. (eble la fakto estas, ke mi uzas duuman CBOR, kaj kiam oni uzas tekston JSON, blokoj de tipo 2 - dinamika bloko estus pli oftaj, respektive, blokoj sen aldonaj nulaj bajtoj antaŭ ol 0x00, 0x00, 0xff, 0xff estus renkontitaj).

Entute, uzante la disponeblajn testajn datumojn, eblas kongrui en malpli ol 250Kb da kunpremitaj datumoj.

Vi povas ŝpari iom pli farante iom ĵongladon: nuntempe ni ignoras la ĉeeston de kelkaj nulaj bitoj ĉe la fino de la bloko, kelkaj bitoj ĉe la komenco de la bloko ankaŭ ne ŝanĝiĝas...
Sed tiam mi fortevolas decidon ĉesi, alie ĉi-rapide mi povus fini evoluigi mian propran arkiviston.

Entute, el miaj testaj datumoj mi ricevis 3-4 bajtojn per skribo, la kunprema proporcio montriĝis pli ol 6:1. Mi estos honesta: mi ne atendis tian rezulton; laŭ mi, io pli bona ol 2:1 jam estas rezulto, kiu pravigas la uzon de kunpremado.

Ĉio estas en ordo, sed zlib (malŝveligi) estas ankoraŭ arkaika, meritita kaj iomete malnovmoda kunprema algoritmo. La nura fakto, ke la lastaj 32Kb de la nekunpremita datumfluo estas uzata kiel vortaro, aspektas strange hodiaŭ (tio estas, se iu datumbloko estas tre simila al tio, kio estis en la eniga fluo antaŭ 40Kb, tiam ĝi komencos esti arkivita denove, kaj ne raportos al antaŭa okazo). En modaj modernaj arkivistoj, la vortargrandeco ofte estas mezurita en megabajtoj prefere ol kilobajtoj.

Do ni daŭrigas nian mini-studon de arkivistoj.

Poste ni testis bzip2 (memoru, sen FLUSH ĝi montris mirindan kunpremoproporcion de preskaŭ 100:1). Bedaŭrinde, ĝi funkciis tre malbone kun FLUSH; la grandeco de la kunpremitaj datumoj montriĝis pli granda ol la nekunpremitaj datumoj.

Miaj supozoj pri la kialoj de la fiasko

Libbz2 ofertas nur unu fluopcion, kiu ŝajnas malplenigi la vortaron (analoge al Z_FULL_FLUSH en zlib); oni ne parolas pri efika kunpremado post tio.

Kaj la lasta testita estis zstd. Depende de la parametroj, ĝi kunpremas aŭ je la nivelo de gzip, sed multe pli rapide, aŭ pli bone ol gzip.

Ve, kun FLUSH ĝi ne tre bone funkciis: la grandeco de la kunpremitaj datumoj estis ĉirkaŭ 700Kb.

Я demandis demandon sur la github-paĝo de la projekto, mi ricevis respondon, ke vi devas kalkuli je ĝis 10 bajtoj da servaj datumoj por ĉiu bloko de kunpremitaj datumoj, kiuj estas proksimaj al la rezultoj akiritaj; ne estas maniero atingi deflate.

Mi decidis ĉesi ĉe ĉi tiu punkto en miaj eksperimentoj kun arkivistoj (mi memorigu al vi, ke xz, lzip, lzo, lz4 ne montriĝis eĉ en la testa stadio sen FLUSH, kaj mi ne konsideris pli ekzotikajn kunpremajn algoritmojn).

Ni revenu al arkivaj problemoj.

La dua (kiel oni diras en ordo, ne en valoro) problemo estas, ke la kunpremitaj datumoj estas unuopa fluo, en kiu estas konstante referencoj al antaŭaj sekcioj. Tiel, se sekcio de kunpremitaj datumoj estas difektita, ni perdas ne nur la rilatan blokon de nekunpremitaj datumoj, sed ankaŭ ĉiujn postajn.

Estas aliro por solvi ĉi tiun problemon:

  1. Malhelpi la problemon okazi - aldonu redundon al la kunpremitaj datumoj, kio permesos al vi identigi kaj korekti erarojn; pri tio ni parolos poste;
  2. Minimumu la konsekvencojn se okazas problemo
    Ni jam diris antaŭe, ke vi povas kunpremi ĉiun datumblokon sendepende, kaj la problemo malaperos per si mem (damaĝo al la datumoj de unu bloko kondukos al la perdo de datumoj nur por ĉi tiu bloko). Tamen, ĉi tio estas ekstrema kazo en kiu datumkunpremo estos neefika. La kontraŭa ekstremo: uzu ĉiujn 4MB de nia blato kiel ununuran arkivon, kiu donos al ni bonegan kunpremadon, sed katastrofajn sekvojn en kazo de datumkorupto.
    Jes, kompromiso necesas rilate fidindecon. Sed ni devas memori, ke ni disvolvas datumstokan formaton por nevolatila memoro kun ekstreme malalta BER kaj deklarita datuma konserva periodo de 20 jaroj.

Dum la eksperimentoj, mi malkovris, ke pli-malpli rimarkindaj perdoj en la kunprema nivelo komenciĝas ĉe blokoj de kunpremitaj datumoj malpli ol 10 KB en grandeco.
Antaŭe oni menciis, ke la uzata memoro estas paĝigita; mi ne vidas kialon, kial la korespondado "unu paĝo - unu bloko de kunpremitaj datumoj" ne estu uzata.

Tio estas, la minimuma racia paĝa grandeco estas 16Kb (kun rezervo por servaj informoj). Tamen, tia malgranda paĝa grandeco trudas signifajn limigojn al la maksimuma rekorda grandeco.

Kvankam mi ankoraŭ ne atendas diskojn pli grandajn ol kelkaj kilobajtoj en kunpremita formo, mi decidis uzi 32Kb-paĝojn (por entute 128 paĝoj po blato).

Resumo:

  • Ni stokas datumojn kunpremitajn per zlib (deflate);
  • Por ĉiu eniro ni starigas Z_SYNC_FLUSH;
  • Por ĉiu kunpremita registro, ni tranĉas la postajn bajtojn (ekz. 0x00, 0x00, 0xff, 0xff); en la kaplinio ni indikas kiom da bajtoj ni fortranĉis;
  • Ni konservas datumojn en 32Kb-paĝoj; estas ununura fluo de kunpremitaj datumoj ene de la paĝo; Sur ĉiu paĝo ni denove komencas kunpremadon.

Kaj, antaŭ ol fini kun kunpremado, mi ŝatus atentigi vin pri tio, ke ni havas nur kelkajn bajtojn da kunpremitaj datumoj po rekordo, do estas ege grave ne ŝveligi la servajn informojn, ĉiu bajto valoras ĉi tie.

Stokado de Datumaj Kapoj

Ĉar ni havas rekordojn de varia longo, ni devas iel determini la lokigon/limojn de rekordoj.

Mi konas tri alirojn:

  1. Ĉiuj rekordoj estas stokitaj en kontinua fluo, unue estas rekorda kaplinio enhavanta la longon, kaj poste la rekordo mem.
    En ĉi tiu formado, kaj titoloj kaj datenoj povas esti de varia longo.
    Esence, ni ricevas unuope ligitan liston, kiu estas uzata la tutan tempon;
  2. Titoloj kaj la rekordoj mem estas konservitaj en apartaj fluoj.
    Uzante kapliniojn de konstanta longo, ni certigas, ke damaĝo al unu kaplinio ne influas la aliajn.
    Simila aliro estas uzata, ekzemple, en multaj dosiersistemoj;
  3. Rekordoj estas stokitaj en kontinua fluo, la rekorda limo estas determinita per certa markilo (karaktero/sekvenco de signoj kiuj estas malpermesitaj ene de datumblokoj). Se estas markilo ene de la registro, tiam ni anstataŭigas ĝin per iu sekvenco (eskap ĝin).
    Simila aliro estas uzata, ekzemple, en la PPP-protokolo.

Mi ilustros.

Eblo 1:
Mia efektivigo de ringa bufro en NOR-fulmo
Ĉio estas tre simpla ĉi tie: sciante la longecon de la registro, ni povas kalkuli la adreson de la sekva kaplinio. Do ni moviĝas tra la titoloj ĝis ni renkontas areon plenigitan per 0xff (libera areo) aŭ la fino de la paĝo.

Eblo 2:
Mia efektivigo de ringa bufro en NOR-fulmo
Pro la varia rekordlongo, ni ne povas anticipe diri kiom da rekordoj (kaj do titoloj) ni bezonos po paĝo. Vi povas disvastigi la titolojn kaj la datumojn mem tra malsamaj paĝoj, sed mi preferas alian aliron: ni metas kaj la kapojn kaj la datumojn sur unu paĝon, sed la kaplinioj (de konstanta grandeco) venas de la komenco de la paĝo, kaj la datumoj (de varia longo) venas de la fino. Tuj kiam ili "renkontiĝos" (ne estas sufiĉe libera spaco por nova eniro), ni konsideras ĉi tiun paĝon kompleta.

Eblo 3:
Mia efektivigo de ringa bufro en NOR-fulmo
Ne necesas konservi la longon aŭ aliajn informojn pri la loko de la datumoj en la kaplinio; markiloj indikante la limojn de la rekordoj sufiĉas. Tamen, la datumoj devas esti prilaboritaj dum skribado/legado.
Mi uzus 0xff kiel markilon (kiu plenigas la paĝon post viŝado), do la libera areo certe ne estos traktata kiel datumoj.

Kompara tabelo:

Eblo 1
Eblo 2
Eblo 3

Erartoleremo
-
+
+

Komplikeco
+
-
+

Komplekseco de efektivigo
*
**
**

Opcio 1 havas mortigan difekton: se iu el la kaplinioj estas difektita, la tuta posta ĉeno estas detruita. La ceteraj opcioj permesas reakiri iujn datumojn eĉ en kazo de amasa damaĝo.
Sed ĉi tie taŭgas memori, ke ni decidis stoki la datumojn en kunpremita formo, kaj do ni perdas ĉiujn datumojn sur la paĝo post "rompita" rekordo, do kvankam estas minuso en la tabelo, ni ne perdas. konsideru ĝin.

Kompakteco:

  • en la unua opcio, ni devas konservi nur la longon en la kaplinio; se ni uzas variablo-longajn entjerojn, tiam en la plej multaj kazoj ni povas sukcesi per unu bajto;
  • en la dua opcio ni devas konservi la komencan adreson kaj longecon; la rekordo devas esti konstanta grandeco, mi taksas 4 bajtojn po rekordo (du bajtoj por la ofseto, kaj du bajtoj por la longo);
  • la tria opcio bezonas nur unu signon por indiki la komencon de la registrado, plus la registrado mem pliiĝos je 1-2% pro ŝirmado. Ĝenerale, proksimume egaleco kun la unua opcio.

Komence, mi konsideris la duan opcion kiel la ĉefan (kaj eĉ skribis la efektivigon). Mi forlasis ĝin nur kiam mi finfine decidis uzi kunpremadon.

Eble iam mi ankoraŭ uzos similan opcion. Ekzemple, se mi devas trakti datumstokadon por ŝipo vojaĝanta inter Tero kaj Marso, estos tute malsamaj postuloj por fidindeco, kosma radiado, ...

Koncerne la tria opcio: mi donis al ĝi du stelojn pro la malfacileco de efektivigo simple ĉar mi ne ŝatas fuŝi kun ŝirmado, ŝanĝi la longon en la procezo, ktp. Jes, eble mi estas partia, sed mi devos skribi la kodon - kial devigi vin fari ion, kion vi ne ŝatas.

Resumo: Ni elektas la stokan opcion en la formo de ĉenoj "kapo kun longo - datumoj de ŝanĝiĝema longo" pro efikeco kaj facileco de efektivigo.

Uzante Bitajn Kampojn por Monitori la Sukceson de Skribaj Operacioj

Mi ne memoras nun, kie mi ricevis la ideon, sed ĝi aspektas kiel ĉi tio:
Por ĉiu eniro, ni asignas plurajn bitojn por stoki flagojn.
Kiel ni diris antaŭe, post viŝado ĉiuj bitoj estas plenigitaj per 1s, kaj ni povas ŝanĝi 1 al 0, sed ne inverse. Do por "la flago ne estas metita" ni uzas 1, por "la flago estas metita" ni uzas 0.

Jen kiel povus aspekti meti variablo-longan rekordon en fulmon:

  1. Agordu la flagon "longa registrado komenciĝis";
  2. Registri la longon;
  3. Agordu la flagon "datumregistrado komenciĝis";
  4. Ni registras datumojn;
  5. Metu la flagon "registrado finita".

Krome, ni havos flagon de "eraro okazis", por entute 4 bitaj flagoj.

En ĉi tiu kazo, ni havas du stabilajn statojn "1111" - registrado ne komenciĝis kaj "1000" - registrado estis sukcesa; okaze de neatendita interrompo de la registra procezo, ni ricevos mezajn statojn, kiujn ni tiam povas detekti kaj prilabori.

La aliro estas interesa, sed ĝi nur protektas kontraŭ subitaj elektropaneoj kaj similaj misfunkciadoj, kio, kompreneble, estas grava, sed ĉi tio estas malproksime de la sola (aŭ eĉ la ĉefa) kialo de eblaj fiaskoj.

Resumo: Ni pluiru serĉante bonan solvon.

Kontrolsumoj

Kontrolsumoj ankaŭ ebligas certigi (kun akceptebla probableco) ke ni legas ĝuste tion, kio devus esti skribita. Kaj, male al la bitkampoj diskutitaj supre, ili ĉiam funkcias.

Se ni konsideras la liston de eblaj fontoj de problemoj, kiujn ni diskutis supre, tiam la ĉeksumo kapablas rekoni eraron sendepende de ĝia origino. (krom, eble, por malicaj eksterteranoj - ili ankaŭ povas forĝi la ĉeksumon).

Do se nia celo estas kontroli, ke la datumoj estas sendifektaj, ĉeksumoj estas bonega ideo.

La elekto de algoritmo por kalkuli la ĉeksumon ne levis demandojn - CRC. Unuflanke, matematikaj ecoj ebligas kapti certajn specojn de eraroj 100%; aliflanke, sur hazardaj datumoj ĉi tiu algoritmo kutime montras la probablecon de kolizioj ne multe pli grandan ol la teoria limo. Mia efektivigo de ringa bufro en NOR-fulmo. Ĝi eble ne estas la plej rapida algoritmo, nek ĉiam la minimuma laŭ la nombro da kolizioj, sed ĝi havas tre gravan kvaliton: en la provoj, kiujn mi renkontis, ne estis ŝablonoj, en kiuj ĝi klare malsukcesis. Stabileco estas la ĉefa kvalito en ĉi tiu kazo.

Ekzemplo de volumetra studo: parto 1, parto 2 (ligiloj al narod.ru, pardonu).

Tamen, la tasko elekti kontrolsumon ne estas kompleta; CRC estas tuta familio de kontrolsumoj. Vi devas decidi pri la longo, kaj poste elekti polinomon.

Elekti la ĉeksumlongon ne estas tiel simpla demando kiel ŝajnas unuavide.

Lasu min ilustri:
Ni havu la probablecon de eraro en ĉiu bajto Mia efektivigo de ringa bufro en NOR-fulmo kaj ideala kontrolsumo, ni kalkulu la averaĝan nombron da eraroj per miliono da registroj:

Data, bajto
Checksum, bajto
Nerimarkitaj eraroj
Malveraj erardetektoj
Totalaj falsaj pozitivoj

1
0
1000
0
1000

1
1
4
999
1003

1
2
≈0
1997
1997

1
4
≈0
3990
3990

10
0
9955
0
9955

10
1
39
990
1029

10
2
≈0
1979
1979

10
4
≈0
3954
3954

1000
0
632305
0
632305

1000
1
2470
368
2838

1000
2
10
735
745

1000
4
≈0
1469
1469

Ŝajnus, ke ĉio estas simpla - depende de la longeco de la datumoj protektataj, elektu la longon de la ĉeksumo kun minimumo da malĝustaj pozitivoj - kaj la lertaĵo estas en la sako.

Tamen, problemo ekestas kun mallongaj ĉeksumoj: kvankam ili kapablas detekti unubitajn erarojn, ili povas kun sufiĉe alta probableco akcepti tute hazardajn datumojn kiel ĝustajn. Jam estis artikolo pri Habré priskribanta problemo en la reala vivo.

Tial, por igi hazardan kontrolsumon preskaŭ neebla, vi devas uzi kontrolsumojn kiuj estas 32 bitoj aŭ pli longaj. (por longoj pli grandaj ol 64 bitoj, kriptografaj hashfunkcioj estas kutime uzitaj).

Malgraŭ tio, ke mi skribis pli frue, ke ni devas ŝpari spacon ĉiurimede, ni ankoraŭ uzos 32-bitan ĉeksumon (16 bitoj ne sufiĉas, la probablo de kolizio estas pli ol 0.01%; kaj 24 bitoj, ĉar ili diru, estas nek ĉi tie nek tie).

Obĵeto povas esti ĉi tie: ĉu ni ŝparis ĉiun bajton elektante kunpremadon por nun doni 4 bajtojn samtempe? Ĉu ne estus pli bone ne kunpremi aŭ aldoni ĉeksumon? Kompreneble ne, neniu kunpremo ne signifas, ke ni ne bezonas integreckontroladon.

Elektinte polinomon, ni ne reinventos la radon, sed prenos la nun popularan CRC-32C.
Ĉi tiu kodo detektas 6-bit-erarojn sur pakaĵoj ĝis 22 bajtoj (eble la plej ofta kazo por ni), 4-bit-eraroj sur pakaĵoj ĝis 655 bajtoj (ankaŭ ofta kazo por ni), 2 aŭ ajna nepara nombro da bitaj eraroj sur pakaĵoj de ajna racia longeco.

Se iu interesiĝas pri la detaloj

Vikipedia artikolo pri CRC.

Kodo-parametroj crc-32c sur Koopman retejo — eble la plej elstara CRC-specialisto sur la planedo.

В lia artikolo estas alia interesa kodo, kiu provizas iomete pli bonajn parametrojn por la pakaĵetlongoj, kiuj rilatas al ni, sed mi ne konsideris la diferencon grava, kaj mi estis sufiĉe kompetenta por elekti kutiman kodon anstataŭ la norman kaj bone esploritan.

Ankaŭ, ĉar niaj datumoj estas kunpremitaj, aperas la demando: ĉu ni kalkulu la kontrolsumon de kunpremitaj aŭ nekunpremitaj datumoj?

Argumentoj en favoro de kalkulado de la ĉeksumo de nekunpremitaj datenoj:

  • Ni finfine devas kontroli la sekurecon de datumstokado - do ni kontrolas ĝin rekte (samtempe oni kontrolos eventualajn erarojn en la efektivigo de kunpremado/malkunpremado, damaĝoj kaŭzitaj de rompita memoro ktp.);
  • La malŝveliga algoritmo en zlib havas sufiĉe maturan efektivigon kaj ne devus falu kun "malrektaj" enirdatenoj; krome, ĝi ofte povas sendepende detekti erarojn en la enigfluo, reduktante la totalan probablecon de nedetektado de eraro (farita testo kun inversigo de ununura bito en mallonga rekordo, zlib detektis eraron. en proksimume triono de kazoj).

Argumentoj kontraŭ kalkulado de la ĉeksumo de nekunpremitaj datenoj:

  • CRC estas "tajlorita" specife por la malmultaj bitaj eraroj, kiuj estas karakterizaj de fulmmemoro (eta eraro en kunpremita fluo povas kaŭzi masivan ŝanĝon en la produktaĵfluo, sur kiu, pure teorie, ni povas "kapti" kolizion);
  • Mi ne tre ŝatas la ideon transdoni eble rompitajn datumojn al la malkompresoro, Kiu sciaskiel li reagos.

En ĉi tiu projekto, mi decidis deflankiĝi de la ĝenerale akceptita praktiko konservi ĉeksumon de nekunpremitaj datumoj.

Resumo: Ni uzas CRC-32C, ni kalkulas la ĉeksumon el la datumoj en la formo, en kiu ili estas skribitaj por ekbrili (post kunpremado).

Redundo

La uzo de redunda kodigo kompreneble ne forigas datumperdon, tamen ĝi povas signife (ofte je multaj grandordoj) redukti la verŝajnecon de nereakirebla datumperdo.

Ni povas uzi malsamajn specojn de redundo por korekti erarojn.
Hamming-kodoj povas korekti unubitajn erarojn, Reed-Solomon-karakteraj kodoj, multoblaj kopioj de datumoj kombinitaj kun kontrolsumoj, aŭ kodigoj kiel RAID-6 povas helpi reakiri datumojn eĉ en la okazo de masiva korupto.
Komence, mi estis engaĝita al la disvastigita uzo de erarrezista kodigo, sed tiam mi rimarkis, ke ni unue devas havi ideon pri kiaj eraroj ni volas protekti nin, kaj poste elekti kodigon.

Ni diris pli frue, ke eraroj devas esti kaptitaj kiel eble plej rapide. En kiuj punktoj ni povas renkonti erarojn?

  1. Nefinita registrado (pro iu kialo dum registrado la potenco estis malŝaltita, la Frambo frostiĝis, ...)
    Ve, en la okazo de tia eraro, restas nur ignori nevalidajn registrojn kaj konsideri la datumojn perditaj;
  2. Skribu erarojn (ial tio, kio estis skribita al la fulmmemoro, ne estis tio, kio estis skribita)
    Ni povas tuj detekti tiajn erarojn se ni faras teston legi tuj post registrado;
  3. Misformo de datumoj en memoro dum stokado;
  4. Legaj eraroj
    Por korekti ĝin, se la ĉeksumo ne kongruas, sufiĉas ripeti la legadon plurfoje.

Tio estas, nur eraroj de la tria tipo (spontanea korupto de datumoj dum stokado) ne povas esti korektitaj sen erarrezista kodigo. Ŝajnas, ke tiaj eraroj ankoraŭ estas ege neverŝajnaj.

Resumo: oni decidis forlasi redundan kodigon, sed se operacio montras la eraron de ĉi tiu decido, tiam revenu al konsidero de la afero (kun jam amasigitaj statistikoj pri misfunkciadoj, kiuj permesos elekti la optimuman tipon de kodado).

Aliaj

Kompreneble, la formato de la artikolo ne permesas al ni pravigi ĉiun pecon en la formato (kaj miaj fortoj jam elĉerpiĝis), do mi mallonge trarigardos kelkajn punktojn ne tuŝitajn antaŭe.

  • Oni decidis fari ĉiujn paĝojn "egalaj"
    Tio estas, ne estos specialaj paĝoj kun metadatenoj, apartaj fadenoj ktp., sed anstataŭe ununura fadeno, kiu reverkas ĉiujn paĝojn laŭvice.
    Ĉi tio certigas eĉ eluziĝon sur la paĝoj, neniun ununuran punkton de fiasko, kaj mi nur ŝatas ĝin;
  • Nepras provizi version de la formato.
    Formato sen versio-numero en la kaplinio estas malbona!
    Sufiĉas aldoni kampon kun certa Magia Nombro (subskribo) al la paĝokapo, kiu indikos la version de la uzata formato. (Mi ne pensas, ke praktike estos eĉ dekduo da ili);
  • Uzu variablo-longan kaplinion por registroj (el kiuj estas multaj), provante fari ĝin 1 bajto longa en la plej multaj kazoj;
  • Por kodi la longon de la kaplinio kaj la longon de la tranĉita parto de la kunpremita rekordo, uzu variablo-longajn binarajn kodojn.

Multe helpis interreta generatoro Huffman-kodoj. En nur kelkaj minutoj ni povis elekti la postulatajn varialongajn kodojn.

Priskribo de datumstokado formato

Byte ordo

Kampoj pli grandaj ol unu bajto estas stokitaj en big-endian formato (reta bajto-ordo), tio estas, 0x1234 estas skribita kiel 0x12, 0x34.

Paĝigo

Ĉiu fulmmemoro estas dividita en paĝojn de egala grandeco.

La defaŭlta paĝgrandeco estas 32Kb, sed ne pli ol 1/4 de la totala grandeco de la memorpeto (por 4MB blato, 128 paĝoj estas akiritaj).

Ĉiu paĝo konservas datumojn sendepende de la aliaj (tio estas, datumoj sur unu paĝo ne referencas datumojn sur alia paĝo).

Ĉiuj paĝoj estas numeritaj en natura ordo (en kreskanta ordo de adresoj), komencante per numero 0 (paĝo nulo komenciĝas je adreso 0, la unua paĝo komenciĝas je 32Kb, la dua paĝo komenciĝas je 64Kb, ktp.)

La memorpeceto estas uzata kiel cikla bufro (ringa bufro), tio estas, unue skribo iras al paĝo numero 0, poste numero 1, ..., kiam ni plenigas la lastan paĝon, nova ciklo komenciĝas kaj registrado daŭras de paĝo nulo. .

Ene de la paĝo

Mia efektivigo de ringa bufro en NOR-fulmo
Komence de la paĝo, 4-bajta paĝa kaplinio estas stokita, tiam kapa ĉeksumo (CRC-32C), tiam rekordoj estas stokitaj en la formato "kapo, datumoj, ĉeksumo".

La paĝotitolo (malpura verda en la diagramo) konsistas el:

  • dubajta Magia Nombro-kampo (ankaŭ signo de la formatversio)
    por la nuna versio de la formato ĝi estas kalkulita kiel 0xed00 ⊕ номер страницы;
  • dubajta nombrilo “Paĝa versio” (memor-reskriba ciklonumero).

Enskriboj sur la paĝo estas konservitaj en kunpremita formo (la malŝveliga algoritmo estas uzata). Ĉiuj rekordoj sur unu paĝo estas kunpremitaj en unu fadeno (komuna vortaro estas uzata), kaj sur ĉiu nova paĝo kunpremo komenciĝas denove. Tio estas, por malkunpremi ajnan registron, ĉiuj antaŭaj registroj de ĉi tiu paĝo (kaj nur ĉi tiu) estas postulataj.

Ĉiu rekordo estos kunpremita per la flago Z_SYNC_FLUSH, kaj ĉe la fino de la kunpremita fluo estos 4 bajtoj 0x00, 0x00, 0xff, 0xff, eble antaŭitaj de unu aŭ du pliaj nulbajtoj.
Ni forĵetas ĉi tiun sinsekvon (4, 5 aŭ 6 bajtojn longa) kiam oni skribas al fulmmemoro.

La rekorda kaplinio estas 1, 2 aŭ 3 bajtoj konservante:

  • unu bito (T) indikante la tipon de registro: 0 - kunteksto, 1 - log;
  • varia longokampo (S) de 1 ĝis 7 bitoj, difinante la longon de la kaplinio kaj la "vosto" kiuj devas esti aldonitaj al la rekordo por malkunpremo;
  • rekordlongo (L).

S valortabelo:

S
Longo de kaplinio, bajtoj
Forĵetita sur skribado, bajto

0
1
5 (00 00 00 ff ff)

10
1
6 (00 00 00 00 ff ff)

110
2
4 (00 00 ff ff)

1110
2
5 (00 00 00 ff ff)

11110
2
6 (00 00 00 00 ff ff)

1111100
3
4 (00 00 ff ff)

1111101
3
5 (00 00 00 ff ff)

1111110
3
6 (00 00 00 00 ff ff)

Mi provis ilustri, mi ne scias kiom klare ĝi rezultis:
Mia efektivigo de ringa bufro en NOR-fulmo
Flava ĉi tie indikas la T-kampon, blanka la S-kampon, verda L (la longo de la kunpremitaj datumoj en bajtoj), blua la kunpremitaj datumoj, ruĝa la finaj bajtoj de la kunpremitaj datumoj, kiuj ne estas skribitaj al fulmmemoro.

Tiel, ni povas skribi rekordajn kapliniojn de la plej ofta longo (ĝis 63+5 bajtoj en kunpremita formo) en unu bajto.

Post ĉiu rekordo, CRC-32C ĉeksumo estas stokita, en kiu la inversa valoro de la antaŭa ĉeksumo estas utiligita kiel la komenca valoro (init).

CRC havas la posedaĵon de "daŭro", la sekva formulo funkcias (pli aŭ minus bita inversio en la procezo): Mia efektivigo de ringa bufro en NOR-fulmo.
Tio estas, fakte, ni kalkulas la CRC de ĉiuj antaŭaj bajtoj de kaplinioj kaj datumoj sur ĉi tiu paĝo.

Rekte sekvante la kontrolsumon estas la kaplinio de la sekva rekordo.

La kaplinio estas desegnita tiel, ke ĝia unua bajto ĉiam diferencas de 0x00 kaj 0xff (se anstataŭ la unua bajto de la kaplinio ni renkontas 0xff, tiam tio signifas, ke tio estas neuzata areo; 0x00 signalas eraron).

Ekzemplaj Algoritmoj

Legado el Flash Memoro

Ĉiu legado venas kun ĉeksumo.
Se la ĉeksumo ne kongruas, la legado estas ripetita plurfoje kun la espero legi la ĝustajn datumojn.

(tio havas sencon, Linukso ne kaŝmemorigas legadojn de NOR Flash, provite)

Skribu al fulmmemoro

Ni registras la datumojn.
Ni legu ilin.

Se la legitaj datumoj ne kongruas kun la skribitaj datumoj, ni plenigas la areon per nuloj kaj signalas eraron.

Preparante novan mikrocirkviton por funkciado

Por komencado, kaplinio kun versio 1 estas skribita al la unua (aŭ prefere nula) paĝo.
Post tio, la komenca kunteksto estas skribita al ĉi tiu paĝo (enhavas la UUID de la maŝino kaj defaŭltajn agordojn).

Jen ĝi, la fulmmemoro estas preta por uzo.

Ŝargante la maŝinon

Dum ŝarĝo, la unuaj 8 bajtoj de ĉiu paĝo (kapo + CRC) estas legitaj, paĝoj kun nekonata Magia Numero aŭ malĝusta CRC estas ignorataj.
El la "ĝustaj" paĝoj, paĝoj kun la maksimuma versio estas elektitaj, kaj la paĝo kun la plej alta nombro estas prenita de ili.
La unua rekordo estas legita, la ĝusteco de la CRC kaj la ĉeesto de la "kunteksto" flago estas kontrolitaj. Se ĉio estas bona, ĉi tiu paĝo estas konsiderata aktuala. Se ne, ni reiru al la antaŭa ĝis ni trovas "vivan" paĝon.
kaj sur la trovita paĝo ni legas ĉiujn registrojn, tiujn, kiujn ni uzas kun la flago "kunteksto".
Konservu la zlib-vortaron (ĝi estos bezonata por aldoni al ĉi tiu paĝo).

Jen ĝi, la elŝuto estas kompleta, la kunteksto estas restarigita, vi povas labori.

Aldono de Ĵurnalo Enskribo

Ni kunpremas la rekordon per la ĝusta vortaro, specifante Z_SYNC_FLUSH. Ni vidas ĉu la kunpremita rekordo taŭgas sur la nuna paĝo.
Se ĝi ne taŭgas (aŭ estis CRC-eraroj sur la paĝo), komencu novan paĝon (vidu sube).
Ni skribas la rekordon kaj CRC. Se eraro okazas, komencu novan paĝon.

Nova paĝo

Ni elektas senpagan paĝon kun la minimuma nombro (ni konsideras senpagan paĝon kiel paĝon kun malĝusta ĉeksumo en la kaplinio aŭ kun versio malpli ol la nuna). Se ne ekzistas tiaj paĝoj, elektu la paĝon kun la minimuma nombro el tiuj, kiuj havas version egalan al la nuna.
Ni forigas la elektitan paĝon. Ni kontrolas la enhavon per 0xff. Se io estas malĝusta, prenu la sekvan senpagan paĝon, ktp.
Ni skribas kaplinion sur la forigita paĝo, la unua enskribo estas la nuna stato de la kunteksto, la sekva estas la neskribita protokolo (se ekzistas).

Formata aplikebleco

Laŭ mi, ĝi montriĝis bona formato por konservi pli-malpli kunpremeblajn informfluojn (plaĉa teksto, JSON, MessagePack, CBOR, eble protobuf) en NOR Flash.

Kompreneble, la formato estas "tajlorita" por SLC NOR Flash.

Ĝi ne estu uzata kun altaj BER-amaskomunikiloj kiel NAND aŭ MLC NOR (ĉu tia memoro eĉ haveblas por vendo? Mi nur vidis ĝin menciita en verkoj pri korektaj kodoj).

Krome, ĝi ne devus esti uzata kun aparatoj kiuj havas sian propran FTL: USB-fulmo, SD, MicroSD, ktp (por tia memoro mi kreis formaton kun paĝa grandeco de 512 bajtoj, subskribo komence de ĉiu paĝo kaj unikaj rekordaj numeroj - foje eblis reakiri ĉiujn datumojn de "malfunkcia" poŝmemoro per simpla sinsekva legado).

Depende de la taskoj, la formato povas esti uzata sen ŝanĝoj en poŝmemoriloj de 128Kbit (16Kb) ĝis 1Gbit (128MB). Se vi volas, vi povas uzi ĝin sur pli grandaj blatoj, sed vi verŝajne bezonas ĝustigi la paĝan grandecon (Sed ĉi tie jam aperas la demando pri ekonomia farebleco; la prezo por grandvoluma NOR Flash ne estas kuraĝiga).

Se iu trovas la formaton interesa kaj volas uzi ĝin en malfermita projekto, skribu, mi provos trovi la tempon, poluri la kodon kaj afiŝi ĝin sur github.

konkludo

Kiel vi povas vidi, finfine la formato montriĝis simpla kaj eĉ enuiga.

Estas malfacile reflekti la evoluon de mia vidpunkto en artikolo, sed kredu min: komence mi volis krei ion altnivelan, nedetrueblan, kapablan travivi eĉ nuklean eksplodon en proksima proksimeco. Tamen racio (mi esperas) ankoraŭ venkis kaj iom post iom prioritatoj ŝanĝiĝis al simpleco kaj kompakteco.

Ĉu eble mi eraris? Jes certa. Povas esti, ekzemple, ke ni aĉetis aron da malaltkvalitaj mikrocirkvitoj. Aŭ pro iu alia kialo la ekipaĵo ne renkontos fidindecajn atendojn.

Ĉu mi havas planon por ĉi tio? Mi pensas, ke leginte la artikolon vi ne dubas, ke ekzistas plano. Kaj eĉ ne sole.

En iom pli serioza noto, la formato estis evoluigita kaj kiel laboropcio kaj kiel "prova balono".

Nuntempe ĉio sur la tablo funkcias bone, laŭvorte la alian tagon la solvo estos deplojita (proksimume) sur centoj da aparatoj, ni vidu, kio okazas en "batala" operacio (feliĉe, mi esperas, ke la formato ebligas al vi fidinde detekti misfunkciadojn; do vi povas kolekti plenajn statistikojn). Post kelkaj monatoj eblos eltiri konkludojn (kaj se vi estas malbonŝanca, eĉ pli frue).

Se, surbaze de la rezultoj de uzo, seriozaj problemoj estas malkovritaj kaj plibonigoj necesas, tiam mi certe skribos pri ĝi.

Literaturo

Mi ne volis fari longan tedan liston de uzitaj verkoj; finfine ĉiuj havas Guglon.

Ĉi tie mi decidis lasi liston de rezultoj, kiuj ŝajnis al mi precipe interesaj, sed iom post iom ili migris rekte en la tekston de la artikolo, kaj unu ero restis en la listo:

  1. Utileco infgen de la aŭtoro zlib. Povas klare montri la enhavon de deflate/zlib/gzip-arkivoj. Se vi devas trakti la internan strukturon de la deflate (aŭ gzip) formato, mi tre rekomendas ĝin.

fonto: www.habr.com

Aldoni komenton