{"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\/hr\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","title":{"rendered":"Moja implementacija kru\u017enog bafera u NOR flash","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h1 id=\"predystoriya\">Povijest<\/h1>\n<p><\/p>\n<p>Imamo automate za prodaju koje smo sami razvili. Unutra se nalazi Raspberry Pi i malo dodatne elektronike na odvojenoj plo\u010di. Povezani su nov\u010danik, aparati za nov\u010danice, bankovni terminal\u2026 Sve upravlja program koji smo sami napisali. Cijela povijest rada se zapisuje u dnevnik na fle\u0161 memoriji (MicroSD), koja se zatim prenosi putem interneta (pomo\u0107u USB modema) na server, gdje se pohranjuje u bazu podataka. Informacije o prodaji se u\u010ditavaju u 1C, a tako\u0111e postoji jednostavni web interfejs za pra\u0107enje i sli\u010dno. <\/p>\n<p><\/p>\n<p>Dakle, dnevnik je od su\u0161tinskog zna\u010daja \u2014 za evidenciju (tu su prihodi, prodaje itd.), pra\u0107enje (razne gre\u0161ke i druge nepredvi\u0111ene okolnosti); to je, mo\u017ee se re\u0107i, sve informacije koje imamo o ovom aparatu. <\/p>\n<p><\/p>\n<h1 id=\"problema\">Problem<\/h1>\n<p><\/p>\n<p>Fle\u0161 memorije se pokazale kao veoma nepouzdani ure\u0111aji. Redovno se kvare sa zavidnom pravilno\u0161\u0107u. To dovodi do zastoja automata, a tako\u0111e (ako iz nekog razloga dnevnik nije mogao da se prenese online) do gubitka podataka.<\/p>\n<p><\/p>\n<p><em>Ovo nije prvi put da koristimo fle\u0161 memorije, imali smo drugi projekat sa vi\u0161e od stotinu ure\u0111aja, gde je dnevnik bio pohranjen na USB fle\u0161 memorijama, tamo su tako\u0111e bile probleme sa pouzdano\u0161\u0107u, ponekad je broj onih koji su se pokvarili u mesecu dostizao desetine. Poku\u0161avali smo razne fle\u0161 memorije, uklju\u010duju\u0107i brendirane sa SLC memorijom, a neki modeli su pouzdaniji od drugih, ali zamena fle\u0161 memorija nije drasti\u010dno re\u0161ila problem.<\/em><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <\/p>\n<p><strong>Pa\u017enja!<\/strong> Longread! Ako vas ne zanima &#171;za\u0161to&#187;, a zanima vas samo &#171;kako&#187;, odmah mo\u017eete i\u0107i <noindex><a rel=\"nofollow\" href=\"#format\">na kraj<\/a><\/noindex> \u010dlanka.<\/p>\n<p><\/p>\n<h1 id=\"reshenie\">Re\u0161enje<\/h1>\n<p><\/p>\n<p>Prvo \u0161to mi pada na pamet: odustati od MicroSD, staviti, na primer, SSD i u\u010ditati po njemu. Teoretski je to mogu\u0107e, verovatno, ali relativno skupo i nije toliko pouzdano (dodaje se USB-SATA adapter; statistika otkaza i za jeftine SSD-ove tako\u0111e nije ohrabruju\u0107a).<\/p>\n<p><\/p>\n<p>USB HDD tako\u0111e ne izgleda kao posebno privla\u010dno re\u0161enje.<\/p>\n<p><\/p>\n<p>Zato smo do\u0161li do ovakvog re\u0161enja: ostaviti u\u010ditavanje sa MicroSD, ali koristiti ih u read-only re\u017eimu, a dnevnik rada (i druge informacije jedinstvene za odre\u0111eni ure\u0111aj \u2014 serijski broj, kalibracije senzora itd.) \u010duvati negde drugde. <\/p>\n<p><\/p>\n<p>Tema read-only datote\u010dnih sistema za Raspberry Pi je ve\u0107 detaljno istra\u017eena, ne\u0107u se zadr\u017eavati na detaljima implementacije u ovom \u010dlanku <em>(ali ako bude interesa \u2014 mo\u017eda \u0107u napisati mini-\u010dlanak na ovu temu)<\/em>. Jedna stvar koju \u017eelim napomenuti: kako iz osobnog iskustva, tako i iz recenzija onih koji su ve\u0107 implementirali, postoji dobitak u pouzdanosti. Da, potpuno uklanjanje kvarova nije mogu\u0107e, no zna\u010dajno smanjivanje njihove u\u010destalosti je itekako realno. Tako\u0111er, kartice postaju standardizirane, \u0161to znatno olak\u0161ava zamjenu za tehni\u010dko osoblje.<\/p>\n<p><\/p>\n<h2 id=\"apparatnaya-chast\">Hardverski dio<\/h2>\n<p><\/p>\n<p>Nije bilo posebnih dvojbi oko izbora tipa memorije \u2014 NOR Flash.<br \/>\nArgumenti: <\/p>\n<p><\/p>\n<ul>\n<li>jednostavno povezivanje (naj\u010de\u0161\u0107e SPI sabirnica, \u010dije iskustvo ve\u0107 imamo, pa se &#171;hardverski&#187; problemi ne o\u010dekuju);<\/li>\n<li>smije\u0161na cijena;<\/li>\n<li>standardni radni protokol (implementacija ve\u0107 postoji u Linux kernelu, ako \u017eelite, mo\u017eete koristiti tre\u0107e strane, koje tako\u0111er postoje, ili \u010dak napisati vlastitu, s obzirom da je sve jednostavno);<\/li>\n<li>pouzdano\u0161\u0107u i resursom:<br \/>\niz tipi\u010dne tehni\u010dke dokumentacije: podaci se \u010duvaju 20 godina, 100000 brisanja za svaku jedinicu;<br \/>\niz vanjskih izvora: izuzetno nizak BER, postulira se odsustvo potrebe za kodovima za ispravak gre\u0161aka. <em>(u nekim radovima razmatra se ECC za NOR, ali obi\u010dno se misli na MLC NOR, to se tako\u0111er doga\u0111a)<\/em>.<\/li>\n<\/ul>\n<p><\/p>\n<p>Procijenimo zahtjeve za volumen i resurs.<\/p>\n<p><\/p>\n<p>\u017delim da se podaci jam\u010deno \u010duvaju nekoliko dana. To je potrebno kako bi se, u slu\u010daju problema s vezom, povijest prodaje ne izgubila. Oslonit \u0107emo se na 5 dana, za to vrijeme <em>(\u010dak i uz dane vikenda i praznike)<\/em> mo\u017ee se rije\u0161iti problem.<\/p>\n<p><\/p>\n<p>Trenutno svaki dan zabilje\u017eimo oko 100kb dnevnika (3-4 tisu\u0107e zapisa), me\u0111utim postepeno ta brojka raste \u2014 pove\u0107ava se detaljnost, dodaju se novi sadr\u017eaji. Plus, ponekad se doga\u0111aju iznenadni skokovi (neki senzor po\u010dne stvarati la\u017ena o\u010ditavanja, na primjer). Izra\u010dunat \u0107emo na 10 tisu\u0107a zapisa po 100 bajta \u2014 megabajt dnevno.<\/p>\n<p><\/p>\n<p>Dakle, ukupno dolazimo do 5Mb \u010distih (dobro komprimiranih) podataka. K tome jo\u0161 <em>(gruba procjena)<\/em> 1Mb operativnih podataka.<\/p>\n<p><\/p>\n<p>To zna\u010di da nam treba \u010dip od 8Mb ako ne koristimo kompresiju, ili 4Mb ako je koristimo. Ovo su sasvim realne brojke za ovu vrstu memorije.<\/p>\n<p><\/p>\n<p>\u0160to se ti\u010de resursa: ako planiramo da \u0107e se memorija u cijelosti prepisivati ne \u010de\u0161\u0107e od svaka 5 dana, onda za 10 godina kori\u0161tenja dobivamo manje od tisu\u0107u ciklusa prepisivanja.<br \/>\nPodsje\u0107am, proizvo\u0111a\u010d jam\u010di sto tisu\u0107a.<\/p>\n<p>\n<b class=\"spoiler_title\">Nekoliko rije\u010di o NOR vs NAND<\/b><\/p>\n<p>Danas je naravno mnogo popularnija NAND memorija, no za ovaj projekt ne bih je koristio: NAND, za razliku od NOR, obavezno zahtijeva upotrebu kodova za ispravljanje gre\u0161aka, tablica neispravnih blokova itd., a i nogu na NAND \u010dipovima obi\u010dno ima mnogo vi\u0161e.<\/p>\n<p><\/p>\n<p>Kao nedostatke NOR-a mo\u017eemo navesti:<\/p>\n<p><\/p>\n<ul>\n<li>mali kapacitet (i, sukladno tome, visoku cijenu po megabajtu);<\/li>\n<li>niska brzina prijenosa (dost.djelomi\u010dno zbog kori\u0161tenja serijskog su\u010delja, obi\u010dno SPI ili I2C);<\/li>\n<li>sporo brisanje (ovisno o veli\u010dini bloka, mo\u017ee trajati od dijelova sekunde do nekoliko sekundi).<\/li>\n<\/ul>\n<p><\/p>\n<p>Naizgled nema ni\u0161ta kriti\u010dno za nas, pa nastavljamo.<\/p>\n<p><\/p>\n<p>Ako vas zanimaju detalji, odabrali smo \u010dip <noindex><a rel=\"nofollow\" href=\"https:\/\/www.adestotech.com\/wp-content\/uploads\/doc3686.pdf\">at25df321a<\/a><\/noindex> <em>(me\u0111utim, to nije presudno, na tr\u017ei\u0161tu ima mno\u0161tvo analoga, kompatibilnih po pinoutu i sustavu komandi; \u010dak i ako po\u017eelimo instalirati \u010dip drugog proizvo\u0111a\u010da i\/ili druge veli\u010dine, sve \u0107e raditi bez promjena u kodu)<\/em>.<\/p>\n<p><\/p>\n<p>Koristim ugra\u0111eni Linuxov drajver, na Raspberry Pi-u, zahvaljuju\u0107i podr\u0161ci za device tree overlay, sve je vrlo jednostavno \u2014 potrebno je staviti u \/boot\/overlays kompajliran overlay i malo izmijeniti \/boot\/config.txt.<\/p>\n<p>\n<b class=\"spoiler_title\">Primjer dts datoteke<\/b><\/p>\n<p>Iskreno, nisam siguran da je napisano bez gre\u0161aka, ali radi.<\/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\">I jo\u0161 jedan redak u config.txt<\/b><\/p>\n<pre><code class=\"plaintext\">dtoverlay=at25:spimaxfrequency=50000000<\/code><\/pre>\n<p><\/p>\n<p>Opi\u0161em samo kako se \u010dip povezuje s Raspberry Pi-om. S jedne strane, nisam stru\u010dnjak za elektroniku, s druge strane \u2014 ovdje je sve banalno \u010dak i za mene: \u010dip ima samo 8 pinova, od kojih su nam potrebni zemlja, napajanje, SPI (CS, SI, SO, SCK); razine se podudaraju s onima na Raspberry Pi-u, nikakva dodatna oprema nije potrebna \u2014 samo spojite navedenih 6 kontakata.<\/p>\n<p><\/p>\n<h2 id=\"postanovka-zadachi\">Postavljanje zadatka<\/h2>\n<p><\/p>\n<p>Kao obi\u010dno, formulacija zadatka prolazi kroz nekoliko iteracija, \u010dini mi se da je do\u0161lo vrijeme za jo\u0161 jednu. Dakle, zaustavimo se, okupljamo ono \u0161to je ve\u0107 napisano i razjasnimo preostale u sjeni.<\/p>\n<p><\/p>\n<p>Dakle, odlu\u010dili smo da \u0107e dnevnik biti pohranjen u SPI NOR Flash.<\/p>\n<p>\n<b class=\"spoiler_title\">\u0160to je NOR Flash za one koji ne znaju<\/b><\/p>\n<p>Ovo je nevoljno energijska memorija, s kojom mo\u017eemo izvoditi tri operacije:<\/p>\n<p><\/p>\n<ol>\n<li>\u010citanje:<br \/>\nNajobi\u010dnije \u010ditanje: \u0161aljemo adresu i \u010ditamo onoliko bajtova koliko nam treba;<\/li>\n<li>Pisanje:<br \/>\nUpis u NOR flash izgleda kao obi\u010dan, ali ima jednu posebnost: mogu\u0107e je samo mijenjati 1 u 0, ali ne obrnuto. Na primjer, ako smo u memorijskoj \u0107eliji imali 0x55, nakon \u0161to upi\u0161emo 0x0f, tamo \u0107e se ve\u0107 \u010duvati 0x05. <em>(vidi tabelu malo ni\u017ee)<\/em>;<\/li>\n<li>Brisanje:<br \/>\nNaravno, trebamo znati napraviti i obrnutu operaciju \u2014 mijenjati 0 u 1, upravo za to postoji operacija brisanja. Za razliku od prve dvije, ona operira ne bajtovima, ve\u0107 blokovima (minimalni blok za brisanje u odabranom \u010dipu je 4kB). Brisanje uni\u0161tava cijeli blok u cjelini i to je jedini na\u010din da se promijeni 0 u 1. Stoga, pri radu s flash memorijom \u010desto se mora izravnavati struktura podataka na granici bloka za brisanje.<br \/>\nUpis u NOR flash:<\/li>\n<\/ol>\n<p><\/p>\n<p>Binarni podaci<\/p>\n<p><strong>Bilo je<\/strong><br \/>\n<code>01010101<\/code><\/p>\n<p><strong>Upisano<\/strong><br \/>\n<code>00001111<\/code><\/p>\n<p><strong>Postalo je<\/strong><br \/>\n<code>00000101<\/code><\/p>\n<p><\/p>\n<p>Sam dnevnik predstavlja sekvencu zapisa varijabilne duljine. Tipi\u010dna duljina zapisa je oko 30 bajtova (iako ponekad dolazi do zapisa duljine nekoliko kilobajta). <em>U ovom slu\u010daju radimo s njima jednostavno kao s nizom bajtova, ali, ako je zanimljivo, unutar zapisa koristi se CBOR.<\/em><\/p>\n<p><\/p>\n<p>Osim dnevnika, trebamo pohraniti neku &#171;konfiguracijsku&#187; informaciju, kako a\u017euriranu, tako i ne: neki ID ure\u0111aja, kalibracije senzora, oznaku &#171;ure\u0111aj je privremeno isklju\u010den&#187;, itd.<br \/>\nOve informacije predstavljaju skup zapisa klju\u010d-vrijednost, tako\u0111er pohranjene u CBOR-u. Ove informacije nemamo puno (maksimalno nekoliko kilobajta), a\u017euriraju se rijetko.<br \/>\nU nastavku \u0107emo je zvati kontekst.<\/p>\n<p><\/p>\n<p>Ako se sjetimo odakle je po\u010dela ova pri\u010da, va\u017eno je osigurati pouzdanost pohrane podataka i, kada je to mogu\u0107e, neprekidni rad \u010dak i u slu\u010daju hardverskih kvarova\/povrede podataka.<\/p>\n<p><\/p>\n<p>Koje izvore problema mo\u017eemo razmotriti?<\/p>\n<p><\/p>\n<ul>\n<li>Isklju\u010divanje napajanja u trenutku operacija write\/erase. To je iz domene &#171;protu udarcu nema metode&#187;.<br \/>\nInformacije iz <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\">rasprave<\/a><\/noindex> na stackexchange: prilikom isklju\u010denja napajanja tijekom rada s flashom, i erase (postavljanje na 1) i write (postavljanje na 0) dovode do undefined behavior: podaci se mogu zapisati, djelomi\u010dno zapisati (recimo, poslali smo 10 bajtova\/80 bita, a uspjeli smo zapisati samo 45 bita), nije isklju\u010deno ni da \u0107e dio bitova biti u &#171;intermedijarnom&#187; stanju (\u010ditanje mo\u017ee dati i 0 i 1);<\/li>\n<li>Gre\u0161ke same flash memorije.<br \/>\nBER, iako vrlo mali, ne mo\u017ee biti jednak nuli;<\/li>\n<li>Gre\u0161ke po sabirnici<br \/>\nPodaci koji se prenose putem SPI nisu za\u0161ti\u0107eni i mogu do\u0107i do pojedina\u010dnih bitnih gre\u0161aka, kao i gre\u0161aka sinkronizacije \u2014 gubitak ili umetanje bitova (\u0161to mo\u017ee uzrokovati masovna o\u0161te\u0107enja podataka);<\/li>\n<li>Ostale gre\u0161ke\/prekidi<br \/>\nGre\u0161ke u kodu, &#171;gre\u0161ke&#187; Raspberry, uplitanje vanzemaljaca&#8230;<\/li>\n<\/ul>\n<p><\/p>\n<p>Definirao sam zahtjeve \u010dija ispunjenja smatram nu\u017enima za osiguranje pouzdanosti:<\/p>\n<p><\/p>\n<ul>\n<li>zapisi moraju odmah biti pohranjeni u flash memoriju, odgo\u0111ena pohrana nije opcija; - ako do\u0111e do gre\u0161ke, ona treba biti otkrivena i obra\u0111ena \u0161to je prije mogu\u0107e; - sustav bi trebao mo\u0107i obnoviti rad nakon gre\u0161aka.<br \/>\n<em>(primjer iz \u017eivota &#171;kako ne bi trebalo biti&#187;, s kojim se, mislim, svi susre\u0107u: nakon hitnog ponovnog pokretanja &#171;pokvarila&#187; se datote\u010dna struktura i operativni sustav se ne pokre\u0107e)<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"idei-podhody-razmyshleniya\">Ideje, pristupi, razmi\u0161ljanja<\/h2>\n<p><\/p>\n<p>Kada sam po\u010deo razmi\u0161ljati o ovom zadatku, kroz glavu su prolazile brojne ideje, na primjer:<\/p>\n<p><\/p>\n<ul>\n<li>koristiti kompresiju podataka;<\/li>\n<li>koristiti pametne strukture podataka, na primjer, odvojeno pohraniti zaglavlja zapisa od samih zapisa, tako da bi, u slu\u010daju gre\u0161ke u nekom zapisu, bez problema moglo pro\u010ditati ostale;<\/li>\n<li>koristiti bitne prozore za kontrolu dovr\u0161enosti zapisa prilikom gubitka napajanja;<\/li>\n<li>pohranjivati kontrolne zbrojeve za sve i sva\u0161ta;<\/li>\n<li>koristiti neku vrstu kodiranja otporne na smetnje.<\/li>\n<\/ul>\n<p><\/p>\n<p>Dio ovih ideja je kori\u0161ten, dok se od nekih odlu\u010dilo odustati. Idemo redom.<\/p>\n<p><\/p>\n<h3 id=\"szhatie-dannyh\">Kompresija podataka<\/h3>\n<p><\/p>\n<p>Sama doga\u0111anja koja evidentiramo u dnevniku su dovoljno jednoli\u010dna i ponavljaju\u0107a (&#171;bacio kovanicu od 5 kuna&#187;, &#171;pritisnuo gumb za isplatu sitni\u0161a&#187;, &#8230;). Stoga bi kompresija trebala biti dovoljno u\u010dinkovita.<\/p>\n<p><\/p>\n<p>Tro\u0161kovi kompresije su neznatni (procesor je dovoljno sna\u017ean, \u010dak i na prvom Pi imao je jedno jezgro s brzinom od 700MHz, na aktualnim modelima nekoliko jezgri s brzinom iznad gigaherca), brzina razmjene s pohranom nije visoka (nekoliko megabajta u sekundi), a veli\u010dina zapisa nije velika. Ukratko, ako kompresija i utje\u010de na performanse, onda samo pozitivno <em>(apsolutno nebitno, samo konstatiram)<\/em>. Plus, we don't have a real embedded system, but a regular Linux \u2014 so the implementation shouldn't require much effort (just link the library and use a few functions from it).<\/p>\n<p><\/p>\n<p>A piece of the log from a running device (1.7MB, 70 thousand entries) was taken and initially tested for compressibility using gzip, lz4, lzop, bzip2, xz, and zstd available on the computer.<\/p>\n<p><\/p>\n<ul>\n<li>gzip, xz, and zstd showed similar results (40KB).<br \/>\nI was surprised that the trendy xz performed at the level of gzip or zstd here;<\/li>\n<li>lzip with default settings yielded slightly worse results;<\/li>\n<li>lz4 and lzop showed not very good results (150KB);<\/li>\n<li>bzip2 surprisingly showed good results (18KB).<\/li>\n<\/ul>\n<p><\/p>\n<p>So, the data compresses very well.<br \/>\nTherefore, (if we don't find any fatal flaws) there will be compression! Simply because more data can fit on the same flash drive.<\/p>\n<p><\/p>\n<p>Let's consider the drawbacks.<\/p>\n<p><\/p>\n<p>The first problem: we already agreed that each entry must be immediately written to the flash. Usually, the compressor accumulates data from the input stream until it decides it's time to write to the output. However, we need to obtain a compressed block of data immediately and save it to non-volatile memory.<\/p>\n<p><\/p>\n<p>I see three possible ways:<\/p>\n<p><\/p>\n<ol>\n<li>Compress each entry using dictionary compression instead of the algorithms discussed above.<br \/>\nPotpuno funkcionalna opcija, ali nije mi draga. Da bi se osigurao barem prihvatljiv nivo kompresije, rje\u010dnik mora biti &#171;prilago\u0111en&#187; specifi\u010dnim podacima, svaka promjena \u0107e dovesti do toga da nivo kompresije katastofalno padne. Da, problem se rje\u0161ava stvaranjem nove verzije rje\u010dnika, ali to je glavobolja \u2014 morat \u0107emo pohraniti sve verzije rje\u010dnika; u svakom zapisu morat \u0107emo navesti s kojom je verzijom rje\u010dnika kompresiran&#8230;<\/li>\n<li>Kompresirati svaku stavku &#171;klasi\u010dnim&#187; algoritmima, ali neovisno o drugima.<br \/>\nThe considered compression algorithms are not designed to work with entries of such size (dozens of bytes), the compression ratio will clearly be less than 1 (meaning an increase in data volume instead of compression);<\/li>\n<li>Perform a FLUSH after each entry.<br \/>\nMany compression libraries have FLUSH support. This is a command (or parameter to the compression procedure) that, when received, causes the compressor to form a compressed stream such that it can restore <strong>svi<\/strong> the uncompressed data that has already been received. Such an analogy <code>synchronizacija<\/code> u datote\u010dnim sustavima ili <code>commit<\/code> u SQL-u.<br \/>\n\u0160to je va\u017eno, sljede\u0107e operacije kompresije mo\u0107i \u0107e koristiti akumulirani rje\u010dnik, a razina kompresije ne\u0107e trpjeti toliko kao u prethodnoj varijanti.<\/li>\n<\/ol>\n<p><\/p>\n<p>Mislim da je o\u010dito da sam odabrao tre\u0107u opciju, zadr\u017eimo se na njoj detaljnije.<\/p>\n<p><\/p>\n<p>Prona\u0111ena <noindex><a rel=\"nofollow\" href=\"https:\/\/www.bolet.org\/~pornin\/deflate-flush.html\">odli\u010dna \u010dlanka<\/a><\/noindex> o FLUSH-u u zlib-u.<\/p>\n<p><\/p>\n<p>Napravio sam test temeljen na \u010dlanku, uzeo sam 70 tisu\u0107a zapisa iz dnevnika stvarnog ure\u0111aja, s veli\u010dinom stranice od 60KB <em>(o veli\u010dini stranice \u0107emo se jo\u0161 vratiti)<\/em> dobio sam:<\/p>\n<p><\/p>\n<p>Izvorni podaci<br \/>\ngzip kompresija -9 (bez FLUSH-a)<br \/>\nzlib s Z_PARTIAL_FLUSH<br \/>\nzlib s Z_SYNC_FLUSH<\/p>\n<p><strong>Obujam, KB<\/strong><br \/>\n1692<br \/>\n40<br \/>\n352<br \/>\n604<\/p>\n<p><\/p>\n<p>Na prvi pogled, cijena koju donosi FLUSH mo\u017ee izgledati prevelika, no zapravo nemamo bogat izbor \u2014 ili ne komprimirati uop\u0107e, ili komprimirati (i to vrlo u\u010dinkovito) s FLUSH-om. Ne smijemo zaboraviti da imamo 70 tisu\u0107a zapisa, a vi\u0161ak koji donosi Z_PARTIAL_FLUSH iznosi samo 4-5 bajtova po zapisu. A omjer kompresije ispostavio se gotovo 5:1, \u0161to je vi\u0161e nego odli\u010dan rezultat.<\/p>\n<p>\n<b class=\"spoiler_title\">Mo\u017ee se \u010diniti iznena\u0111uju\u0107e, ali zapravo je Z_SYNC_FLUSH \u2014 u\u010dinkovitiji na\u010din za izvo\u0111enje FLUSH<\/b><\/p>\n<p>U slu\u010daju kori\u0161tenja Z_SYNC_FLUSH, 4 posljednja bajta svake stavke uvijek \u0107e biti 0x00, 0x00, 0xff, 0xff. Ako nam je to poznato \u2014 ne trebamo ih spremati, tako da kona\u010dna veli\u010dina iznosi samo 324KB.<\/p>\n<p><\/p>\n<p>U \u010dlanku na koji se pozivam, postoji obja\u0161njenje:<\/p>\n<p><\/p>\n<blockquote><p>Novi tip 0 blok s praznim sadr\u017eajem se dodaje.<\/p>\n<p>Tip 0 blok s praznim sadr\u017eajem sastoji se od:<\/p>\n<ul>\n<li>trosmjernog zaglavlja bloka;<\/li>\n<li>0 do 7 bitova jednakih nuli, za postizanje uskla\u0111enosti s bajtom;<\/li>\n<li>\u010detiri bajta sekvence 00 00 FF FF.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Kako se lako mo\u017ee primijetiti, u posljednjem bloku prije ovih 4 bajta se nalaze od 3 do 10 nula bitova. Me\u0111utim, praksa je pokazala da je nula bitova zapravo najmanje 10.<\/p>\n<p><\/p>\n<p>Ispostavilo se da se tako kratki podatkovni blokovi obi\u010dno (uvijek?) kodiraju pomo\u0107u bloka tipa 1 (fiksni blok), koji obavezno zavr\u0161ava s 7 nula bitova, \u0161to nam daje 10-17 zagarantovanih nula bitova (a ostali \u0107e biti nulti s vjerojatno\u0161\u0107u od oko 50%).<\/p>\n<p><\/p>\n<p>Dakle, na testnim podacima u 100% slu\u010dajeva ispred 0x00, 0x00, 0xff, 0xff dolazi jedan nulti bajt, a u vi\u0161e od jedne tre\u0107ine slu\u010dajeva \u2014 dva nulti bajta. <em>(mo\u017eda je stvar u tome da koristim binarni CBOR, a pri kori\u0161tenju tekstualnog JSON-a \u010de\u0161\u0107e bi se pojavljivali blokovi tipa 2 \u2014 dinami\u010dki blok, \u0161to bi odgovaralo blokovima bez dodatnih nulti bajtova ispred 0x00, 0x00, 0xff, 0xff).<\/em>.<\/p>\n<p><\/p>\n<p>Dakle, na dostupnim testnim podacima mogu\u0107e je smjestiti se ispod 250KB komprimiranih podataka.<\/p>\n<p><\/p>\n<p>Mo\u017eete jo\u0161 malo u\u0161tedjeti bave\u0107i se \u017eongliranjem bitova: sada ignoriramo prisutnost vi\u0161e nultih bitova na kraju bloka, nekoliko bitova na po\u010detku bloka tako\u0111er se ne mijenja...<br \/>\nNo, tada sam donio \u010dvrstu odluku da stanem, ina\u010de bih tim tempom mogao do\u0107i do razvoja vlastitog arhivatora.<\/p>\n<p><\/p>\n<p>Dakle, od svojih testnih podataka dobio sam 3-4 bajta po zapisu, a omjer kompresije bio je ve\u0107i od 6:1. Iskreno ka\u017eem: nisam se nadao takvom rezultatu, po mom mi\u0161ljenju sve \u0161to je bolje od 2:1 \u2014 ve\u0107 je rezultat koji opravdava kori\u0161tenje kompresije.<\/p>\n<p><\/p>\n<p>Sve je odli\u010dno, ali zlib (deflate) \u2014 ipak je arhai\u010dni zaslu\u017eni i pomalo staromodeni algoritam kompresije. Samo to \u0161to se kao rje\u010dnik koristi posljednjih 32Kb iz toka nekomprimirane podatke, danas izgleda \u010dudno (to jest, ako je neki blok podataka vrlo sli\u010dan onome \u0161to je bilo u ulaznom toku prije 40Kb, on \u0107e zapo\u010deti ponovo arhivirati, a ne \u0107e se referirati na prethodni unos). U modernim arhivatorima veli\u010dina rje\u010dnika se \u010de\u0161\u0107e mjeri u megabajtima, a ne kilobajtima.<\/p>\n<p><\/p>\n<p>Dakle, nastavljamo na\u0161e mini-istra\u017eivanje arhivatora.<\/p>\n<p><\/p>\n<p>Sljede\u0107i je bio isproban bzip2 (podsje\u0107am, bez FLUSH-a pokazao je fantasti\u010dan stupanj kompresije, gotovo 100:1). Na\u017ealost, s FLUSH-om pokazao se vrlo lo\u0161e, veli\u010dina komprimiranih podataka bila je ve\u0107a od nekomprimiranih.<\/p>\n<p>\n<b class=\"spoiler_title\">Moja pretpostavka o uzrocima neuspjeha<\/b><\/p>\n<p>Libbz2 nudi samo jednu opciju flush, koja, \u010dini se, \u010disti rje\u010dnik (analog Z_FULL_FLUSH u zlib), o nekakvoj u\u010dinkovitosti kompresije nakon toga ne mo\u017eemo govoriti.<\/p>\n<p><\/p>\n<p>I posljednji je isproban zstd. Ovisno o parametrima, komprimira ili na razini gzip-a, ali mnogo br\u017ee, ili bolje od gzip-a.<\/p>\n<p><\/p>\n<p>Na\u017ealost, s FLUSH pokazao se &#171;ne ba\u0161 dobro&#187;: veli\u010dina kompresiranih podataka iznosila je oko 700Kb.<\/p>\n<p><\/p>\n<p>Ja <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/facebook\/zstd\/issues\/900\">postavio sam pitanje<\/a><\/noindex> na stranici projekta na githubu, dobio sam odgovor da se mo\u017ee ra\u010dunati na do 10 bajta pomo\u0107nih podataka po svakom bloku komprimiranih podataka, \u0161to je blizu dobivenih rezultata, ne mo\u017eemo dosti\u0107i deflate.<\/p>\n<p><\/p>\n<p>Na tome sam odlu\u010dio stati u eksperimentima s arhivatorima (podsje\u0107am, xz, lzip, lzo, lz4 se nisu pokazali jo\u0161 u fazi testiranja bez FLUSH-a, a egzoti\u010dnije algoritme kompresije nisam razmatrao).<\/p>\n<p><\/p>\n<p>Vra\u0107amo se problemima arhiviranja.<\/p>\n<p><\/p>\n<p>Druga (kako se ka\u017ee po redu, a ne po zna\u010denju) problema je da su komprimirani podaci jedinstveni tok u kojem se stalno upu\u0107uje na prethodne dijelove. Na taj na\u010din, ako do\u0111e do o\u0161te\u0107enja nekog dijela komprimiranih podataka, gubimo ne samo povezani blok nekomprimiranih podataka, ve\u0107 i sve sljede\u0107e.<\/p>\n<p><\/p>\n<p>Postoji nekoliko pristupa rje\u0161avanju ovog problema:<\/p>\n<p><\/p>\n<ol>\n<li>Sprije\u010diti pojavu problema - dodati suvi\u0161nost u komprimirane podatke koja \u0107e omogu\u0107iti otkrivanje i ispravljanje gre\u0161aka; o tome \u0107emo razgovarati kasnije;<\/li>\n<li>Minimizirati posljedice u slu\u010daju pojave problema<br \/>\nVe\u0107 smo ranije spomenuli da se svaki blok podataka mo\u017ee komprimirati neovisno, \u010dime bi problem nestao (o\u0161te\u0107enje podataka jednog bloka dovelo bi do gubitka podataka samo tog bloka). Me\u0111utim, to je krajnji slu\u010daj, u kojem bi kompresija podataka bila neu\u010dinkovita. S druge strane, koristit \u0107emo svih 4MB na\u0161eg \u010dipa kao jedinstveni arhiv, \u0161to nam daje izvrsnu kompresiju, ali katastrofalne posljedice u slu\u010daju o\u0161te\u0107enja podataka.<br \/>\n<em>Da, potreban je kompromis s obzirom na pouzdanost. No, potrebno je imati na umu da razvijamo format za pohranu podataka za energijski neovisnu memoriju s izuzetno niskim BER-om i deklariranim rokom trajanja podataka od 20 godina.<\/em><\/li>\n<\/ol>\n<p><\/p>\n<p>Tijekom eksperimenta otkrio sam da primjetnije gubitke razine kompresije po\u010dinju na blokovima komprimiranih podataka veli\u010dine manje od 10KB.<br \/>\nRanije je spomenuto da kori\u0161tena memorija ima stranicu organizaciju, ne vidim razloga za\u0161to ne koristiti uskla\u0111ivanje &#171;jedna stranica \u2014 jedna blok kompresiranih podataka&#187;.<\/p>\n<p><\/p>\n<p>Dakle, minimalna razumna veli\u010dina stranice jednaka je 16KB (s rezervom za pomo\u0107ne informacije). Me\u0111utim, tako mala veli\u010dina stranice name\u0107e zna\u010dajna ograni\u010denja na maksimalnu veli\u010dinu zapisa.<\/p>\n<p><\/p>\n<p>Iako trenutno ne planiram zapise ve\u0107e od jednog kilobajta u komprimiranom obliku, odlu\u010dio sam koristiti stranice veli\u010dine 32KB (ukupno 128 stranica po \u010dipu).<\/p>\n<p><\/p>\n<p><strong>Sa\u017eetak:<\/strong><\/p>\n<p><\/p>\n<ul>\n<li>Podatke pohranjujemo komprimirane pomo\u0107u zlib (deflate);<\/li>\n<li>Za svaki zapis postavljamo Z_SYNC_FLUSH;<\/li>\n<li>Kod svake komprimirane evidencije orezujemo zavr\u0161ne bajtove <em>(na primjer, 0x00, 0x00, 0xff, 0xff)<\/em>; u zaglavlju navodimo koliko bajtova smo orezali;<\/li>\n<li>Podaci se pohranjuju u stranicama od 32 KB; unutar stranice postoji jedinstveni tok komprimiranih podataka; svaka stranica zapo\u010dinje kompresiju iznova.<\/li>\n<\/ul>\n<p><\/p>\n<p>I, prije nego \u0161to zavr\u0161imo s kompresijom, \u017eelio bih skrenuti pa\u017enju da imamo samo nekoliko bajtova komprimiranih podataka po zapisu, stoga je izuzetno va\u017eno ne pove\u0107avati pomo\u0107ne informacije, svaki bajt je ovdje va\u017ean.<\/p>\n<p><\/p>\n<h3 id=\"hranenie-zagolovkov-dannyh\">Pohrana zaglavlja podataka<\/h3>\n<p><\/p>\n<p>Budu\u0107i da imamo zapise varijabilne duljine, trebamo nekako odrediti raspored\/granice zapisa.<\/p>\n<p><\/p>\n<p>Poznajem tri pristupa:<\/p>\n<p><\/p>\n<ol>\n<li>Svi zapisi se pohranjuju u kontinuiranom toku, prvo ide zaglavlje zapisa koje sadr\u017ei duljinu, a zatim sam zapis.<br \/>\nU ovoj varijanti i zaglavlja i podaci mogu imati varijabilnu duljinu.<br \/>\nU su\u0161tini, dobijamo jednosmjernu listu koja se \u010desto koristi;<\/li>\n<li>Zaglavlja i sami zapisi pohranjuju se u odvojenim tokovima.<br \/>\nKoriste\u0107i zaglavlja fiksne duljine, osiguravamo da kvar jednog zaglavlja ne utje\u010de na ostale.<br \/>\nSli\u010dan pristup se koristi, na primjer, u mnogim datote\u010dnim sustavima;<\/li>\n<li>Zapisi se pohranjuju u kontinuiranom toku, granica zapisa odre\u0111uje se nekim markerom (simbolom\/sekvencijom simbola koji\/kvaliteti su zabranjeni unutar blokova podataka). Ako se unutar zapisa nai\u0111e na marker, zamjenjujemo ga s nekom sekvencijom (evidentiramo ga).<br \/>\nSli\u010dan pristup se koristi, na primjer, u PPP protokolu.<\/li>\n<\/ol>\n<p><\/p>\n<p>Ilustrirat \u0107u.<\/p>\n<p><\/p>\n<p>Opcija 1:<br \/>\n<img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/e5a9676ca21eaca07e64ddf9fcd9f2eb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nOvdje je sve vrlo jednostavno: znaju\u0107i duljinu zapisa, mo\u017eemo izra\u010dunati adresu sljede\u0107eg zaglavlja. Tako se kre\u0107emo po zaglavljima dok ne nai\u0111emo na podru\u010dje ispunjeno 0xff (slobodno podru\u010dje) ili kraj stranice.<\/p>\n<p><\/p>\n<p>Opcija 2:<br \/>\n<img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/4fed4860fb659ab6042be9aeeb5c041a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nZbog varijabilne duljine zapisa ne mo\u017eemo unaprijed re\u0107i koliko \u0107e nam zapisa (a time i naslova) biti potrebno po stranici. Mo\u017eemo razdvojiti naslove i podatke na razli\u010dite stranice, ali neki drugi pristup mi je dra\u017ei: i naslove i podatke smje\u0161tamo na jednu stranicu, no naslovi (fiksne veli\u010dine) idu od vrha stranice, a podaci (varijabilne duljine) \u2014 od dna. Kada se &#171;susretnu&#187; (slobodnog mjesta ne bude dovoljno za novi zapis) \u2014 smatramo da je ova stranica ispunjena.<\/p>\n<p><\/p>\n<p>Opcija 3:<br \/>\n<img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/dfb64009aa7781fcb8a47423ff4160c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNema potrebe za \u010duvanjem du\u017eine ili drugih informacija o lokaciji podataka u zaglavlju, dovoljni su oznake koje ozna\u010davaju granice zapisa. Me\u0111utim, podaci se moraju obra\u0111ivati prilikom pisanja\/\u010ditati.<br \/>\nKao oznaku bih koristio 0xff (koja je popunjena stranica nakon brisanja), tako da slobodan prostor definitivno ne\u0107e biti interpretiran kao podaci.<\/p>\n<p><\/p>\n<p>Usporedna tablica:<\/p>\n<p><\/p>\n<p>Opcija 1<br \/>\nOpcija 2<br \/>\nOpcija 3<\/p>\n<p><strong>Otpornost na gre\u0161ke<\/strong><br \/>\n\u2014<br \/>\n+<br \/>\n+<\/p>\n<p><strong>Kompaktnost<\/strong><br \/>\n+<br \/>\n\u2014<br \/>\n+<\/p>\n<p><strong>Slo\u017eenost implementacije<\/strong><br \/>\n*<br \/>\n**<br \/>\n**<\/p>\n<p><\/p>\n<p>Opcija 1 ima fatalni nedostatak: kada je o\u0161te\u0107en neki od zaglavlja, \u010ditav niz koji slijedi se uni\u0161tava. Ostale opcije omogu\u0107uju obnovu dijela podataka \u010dak i pri masovnim o\u0161te\u0107enjima.<br \/>\nNo, ovdje valja spomenuti da smo odlu\u010dili pohranjivati podatke u komprimiranom obliku, tako da gubimo sve podatke na stranici nakon &#171;pokvarene&#187; stavke, tako da, iako u tablici stoji minus, ne uzimamo ga u obzir.<\/p>\n<p><\/p>\n<p>Kompaktnost:<\/p>\n<p><\/p>\n<ul>\n<li>u prvoj opciji trebamo pohraniti samo du\u017einu u zaglavlju, ako koristimo cijele varijable du\u017eine, u ve\u0107ini slu\u010dajeva mo\u017eemo se ograni\u010diti na jedan bajt;<\/li>\n<li>u drugoj opciji trebamo pohraniti po\u010detnu adresu i du\u017einu; zapis bi trebao imati stalnu veli\u010dinu, procjenjujem na 4 bajta po zapisu (dva bajta na pomak, i dva bajta na du\u017einu);<\/li>\n<li>tre\u0107oj opciji dovoljna je samo jedna oznaka za ozna\u010davanje po\u010detka zapisa, plus sam zapis zbog eskapiranja \u0107e rasti za 1-2%. U cjelini, pribli\u017ena paritet s prvom opcijom.<\/li>\n<\/ul>\n<p><\/p>\n<p>Isprva sam razmatrao drugu opciju kao glavnu (\u010dak sam i napisao implementaciju). Odustao sam od nje tek kada sam kona\u010dno odlu\u010dio koristiti kompresiju.<\/p>\n<p><\/p>\n<p><em>Mo\u017eda \u0107u jednog dana zaista koristiti sli\u010dnu varijantu. Na primjer, ako budem morao pohranjivati podatke za brod koji plovi izme\u0111u Zemlje i Marsa \u2014 sasvim druga\u010diji zahtjevi za pouzdanost, svemirsko zra\u010denje, &#8230;<\/em><\/p>\n<p><\/p>\n<p>\u0160to se ti\u010de tre\u0107e opcije: dao sam joj dvije zvjezdice zbog slo\u017eenosti implementacije jednostavno zato \u0161to ne volim mije\u0161ati se s eskapiranjem, promjenom du\u017eine tijekom procesa i sli\u010dno. Da, mo\u017eda pristrano, ali kod moram pisati ja \u2014 za\u0161to se tjerati da radim ono \u0161to ne volim.<\/p>\n<p><\/p>\n<p><strong>Sa\u017eetak:<\/strong> biramo opciju pohrane u obliku nizova &#171;naslov s duljinom \u2014 podaci varijabilne duljine&#187; zbog u\u010dinkovitosti i jednostavnosti implementacije.<\/p>\n<p><\/p>\n<h3 id=\"ispolzovanie-bitovyh-poley-dlya-kontrolya-uspeshnosti-operaciy-zapisi\">Upotreba bitnih polja za kontrolu uspje\u0161nosti operacija pisanja.<\/h3>\n<p><\/p>\n<p>Ve\u0107 se ne sje\u0107am gdje sam vidio tu ideju, ali izgleda otprilike ovako:<br \/>\nZa svaki zapis izdvajamo nekoliko bitova za pohranu oznaka.<br \/>\n<em>Kao \u0161to smo ranije rekli, nakon brisanja svi bitovi su ispunjeni 1, a mo\u017eemo mijenjati 1 u 0, ali ne obrnuto.<\/em> Dakle, za &#171;zastavica nije postavljena&#187; koristimo 1, a za &#171;zastavica postavljena&#187; \u2014 0.<\/p>\n<p><\/p>\n<p>Ovako mo\u017ee izgledati smje\u0161taj zapisa varijabilne duljine u flash:<\/p>\n<p><\/p>\n<ol>\n<li>Postavljamo oznaku \"zapis duljine je zapo\u010deo\";<\/li>\n<li>Zapisujemo duljinu;<\/li>\n<li>Postavljamo oznaku \"zapis podataka je zapo\u010deo\";<\/li>\n<li>Zapisujemo podatke;<\/li>\n<li>Postavljamo oznaku \"zapis je zavr\u0161en\".<\/li>\n<\/ol>\n<p><\/p>\n<p>Osim toga, imat \u0107emo oznaku \"do\u0161lo je do pogre\u0161ke\", ukupno 4 bitne oznake.<\/p>\n<p><\/p>\n<p>U tom slu\u010daju imamo dva stabilna stanja \"1111\" \u2014 zapis nije zapo\u010deo i \"1000\" \u2014 zapis je uspje\u0161no pro\u0161ao; u slu\u010daju neo\u010dekivanog prekida procesa pisanja dobit \u0107emo me\u0111ustanja koja \u0107emo mo\u0107i otkriti i obraditi kasnije.<\/p>\n<p><\/p>\n<p>Pristup je zanimljiv, ali \u0161titi samo od iznenadnog isklju\u010divanja napajanja i sli\u010dnih kvarova, \u0161to je naravno va\u017eno, no to nije jedini (a ni glavni) uzrok mogu\u0107ih kvarova.<\/p>\n<p><\/p>\n<p><strong>Sa\u017eetak:<\/strong> Idemo dalje u potrazi za dobrom solucijom.<\/p>\n<p><\/p>\n<h3 id=\"kontrolnye-summy\">Kontrolne zbrojeve.<\/h3>\n<p><\/p>\n<p>Kontrolne zbrojeve tako\u0111er omogu\u0107uju da budemo sigurni (s dovoljno vjerojatno\u0161\u0107u) da \u010ditamo upravo ono \u0161to je trebalo biti zapisano. I, za razliku od gore spomenutih bitnih polja, oni uvijek rade.<\/p>\n<p><\/p>\n<p>Ako razmotrimo popis potencijalnih izvora problema o kojima smo govorili ranije, kontrolna zbroj mo\u017ee prepoznati pogre\u0161ku neovisno o njenom podrijetlu. <em>(osim, mo\u017eda, zlo\u0107udnih vanzemaljaca \u2014 oni tako\u0111er mogu krivotvoriti kontrolnu zbroj).<\/em>.<\/p>\n<p><\/p>\n<p>Dakle, ako je na\u0161 cilj provjeriti su cjeloviti, kontrolni zbroj je odli\u010dna ideja.<\/p>\n<p><\/p>\n<p>Odabir algoritma za izra\u010dun kontrolne zbroja nije izazivao pitanja \u2014 CRC. S jedne strane, matemati\u010dka svojstva omogu\u0107uju 100% otkrivanje pogre\u0161aka odre\u0111enih tipova, a s druge strane, u slu\u010dajnim podacima ovaj algoritam obi\u010dno pokazuje vjerojatnost kolizija neznatno ve\u0107u od teorijskog limita. <img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/bf9cca3564db7d9d03d3ce49642d3a71.jpg\" style=\"display:block;margin: 0 auto;\" \/>. Iako to nije najbr\u017ei algoritam, niti uvijek minimalan po broju kolizija, ima jednu vrlo va\u017enu osobinu: u testovima s kojima sam se susreo nisu se pojavili obrasci na kojima bi o\u010digledno propao. Stabilnost je najva\u017enija kvaliteta u ovom slu\u010daju.<\/p>\n<p><\/p>\n<p>Primjer opse\u017enog istra\u017eivanja: <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo.html\">dio 1<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/amsoftware.narod.ru\/algo2.html\">dio 2<\/a><\/noindex> <em>(linkovi na narod.ru, izvinite)<\/em>.<\/p>\n<p><\/p>\n<p>Me\u0111utim, zadatak odabira kontrolne zbrojke nije zavr\u0161en, CRC je \u010ditava obitelj kontrolnih zbrojki. Potrebno je odlu\u010diti o du\u017eini, a zatim odabrati polinom.<\/p>\n<p><\/p>\n<p>Odabir du\u017eine kontrolne zbrojke nije tako jednostavno pitanje kako se isprva mo\u017ee \u010diniti.<\/p>\n<p><\/p>\n<p>Ilustrirat \u0107u:<br \/>\nPretpostavimo da imamo vjerojatnost pogre\u0161ke u svakom bajtu <img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/89a9be2edf2a43cc9115eba37fa02fc8.jpg\" style=\"display:block;margin: 0 auto;\" \/> i idealnu kontrolnu zbrojku, izra\u010dunajmo prosje\u010dan broj pogre\u0161aka na milijun zapisa:<\/p>\n<p><\/p>\n<p>Podaci, bajt<br \/>\nKontrolna zbrojka, bajt<br \/>\nNeotkrivene pogre\u0161ke<br \/>\nLa\u017ena otkri\u0107a pogre\u0161aka<br \/>\nUkupno pogre\u0161nih aktivacija<\/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>\u010cinilo bi se da je sve jednostavno \u2014 odaberi du\u017einu kontrolne zbrojke prema du\u017eini za\u0161ti\u0107enih podataka s minimalnim pogre\u0161nim aktivacijama \u2014 i to je to.<\/p>\n<p><\/p>\n<p>Me\u0111utim, s kratkim kontrolnim zbrojkama javlja se problem: iako dobro otkrivaju pojedina\u010dne bitne pogre\u0161ke, mogu s dovoljno velikom vjerojatno\u0161\u0107u prihvatiti potpuno slu\u010dajne podatke kao ispravne. Na Habr\u00e9u je ve\u0107 bila \u010dlanak koji opisuje <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/428746\/\">problem u stvarnom \u017eivotu<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Stoga, kako bismo u\u010dinili slu\u010dajno podudaranje kontrolne zbrojke prakti\u010dki nemogu\u0107im, trebamo koristiti kontrolne zbrojke du\u017eine 32 bita i vi\u0161e <em>(za duljine ve\u0107e od 64 bita obi\u010dno se koriste kriptografske hash funkcije)<\/em>.<\/p>\n<p><\/p>\n<p>Iako sam ranije rekao da trebamo \u0161tediti prostor svim silama, ipak \u0107emo koristiti kontrolnu zbrojku od 32 bita (16 bita je premalo, vjerojatnost kolizije ve\u0107a od 0,01%; a 24 bita, kao \u0161to se ka\u017ee, ni tu ni tamo).<\/p>\n<p><\/p>\n<p>Ovdje bi se moglo postaviti pitanje: zar smo \u0161tedjeli svaki bajt prilikom odabira kompresije da bismo sada odrekli 4 bajta odjednom? nije li bolje bilo ne komprimirati i ne dodavati kontrolnu zbrojku? Naravno da nije, odsustvo kompresije <em>ne zna\u010di<\/em>, da nam provjera cjelovitosti nije potrebna.<\/p>\n<p><\/p>\n<p>O izboru polinoma ne\u0107emo izmi\u0161ljati bicikl, ve\u0107 \u0107emo uzeti trenutno popularni CRC-32C.<br \/>\nOvaj kod otkriva 6 bitnih gre\u0161aka u paketima do 22 bajta (verovatno naj\u010de\u0161\u0107i slu\u010daj za nas), 4 bitne gre\u0161ke u paketima do 655 bajta (tako\u0111e \u010dest slu\u010daj za nas), 2 ili bilo koji neparni broj bitnih gre\u0161aka u paketima bilo koje razumn\u0435 du\u017eine.<\/p>\n<p>\n<b class=\"spoiler_title\">Ako je nekome zanimljivo vi\u0161e detalja<\/b><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Cyclic_redundancy_check\">\u010dlanak na vikipediji<\/a><\/noindex> o CRC-u.<\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0x8f6e37a0_len.txt\">Parametri koda crc-32c<\/a><\/noindex> na <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/crc\/notes.html\">na sajtu Kupmana<\/a><\/noindex> \u2014 verovatno glavnog stru\u010dnjaka za CRC na planeti.<\/p>\n<p><\/p>\n<p>U <noindex><a rel=\"nofollow\" href=\"http:\/\/users.ece.cmu.edu\/~koopman\/networks\/dsn02\/dsn02_koopman.pdf\">u njegovom \u010dlanku<\/a><\/noindex> ima <noindex><a rel=\"nofollow\" href=\"https:\/\/users.ece.cmu.edu\/~koopman\/crc\/c32\/0xfa567d89_len.txt\">jo\u0161 jedan zanimljiv kod<\/a><\/noindex>, koji pru\u017ea malo bolje parametre za relevantne du\u017eine paketa, ali nisam smatrao razliku zna\u010dajnom, a sebe dovoljno kompetentnim da odaberem prilago\u0111eni kod umesto standardnog i dobro istra\u017eenog.<\/p>\n<p><\/p>\n<p>Tako\u0111e, s obzirom na to da su na\u0161i podaci kompresovani, postavlja se pitanje: da li ra\u010dunati kontrolnu sumu nad kompresovanim ili nekompresovanim podacima?<\/p>\n<p><\/p>\n<p>Argumenti &#171;za&#187; izra\u010dunavanje kontrolnog zbira nekomprimiranih podataka:<\/p>\n<p><\/p>\n<ul>\n<li>na kraju nam je potrebno da proverimo o\u010duvanje podataka \u2014 to direktno i proveravamo (pri tom \u0107e biti proverene i mogu\u0107e gre\u0161ke u implementaciji kompresije\/dekompresije, o\u0161te\u0107enja izazvana lo\u0161om memorijom itd.);<\/li>\n<li>algoritam deflate u zlib-u ima dovoljno zrelu implementaciju i <em>ne bi trebalo<\/em> padati pri &#171;krivim&#187; ulaznim podacima, \u0161tovi\u0161e, \u010desto je sposoban sam otkriti pogre\u0161ke u ulaznom toku, smanjuju\u0107i ukupnu vjerojatnost neotkrivanja pogre\u0161ke (provodio sam test s inverzijom jednog bita u kratkom zapisu, zlib je otkrio pogre\u0161ku u otprilike jednoj tre\u0107ini slu\u010dajeva).<\/li>\n<\/ul>\n<p><\/p>\n<p>Argumenti &#171;protiv&#187; izra\u010dunavanja kontrolnog zbira nekomprimiranih podataka:<\/p>\n<p><\/p>\n<ul>\n<li>CRC je &#171;prilago\u0111en&#187; upravo za nekoliko bitnih pogre\u0161aka koje su karakteristi\u010dne za flash memoriju (bitna pogre\u0161ka u komprimiranom toku mo\u017ee dati masovne promjene izlaznog toka, na kojem, \u010disto teoretski, mo\u017eemo &#171;uhvatiti&#187; koliziju);<\/li>\n<li>nisam posebno sre\u0107an s idejom da \u0161aljem dekompresoru potencijalno o\u0161te\u0107ene podatke, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cvedetails.com\/vulnerability-list\/vendor_id-72\/product_id-1820\/GNU-Zlib.html\">ko \u0107e znati<\/a><\/noindex>, kako \u0107e on reagovati.<\/li>\n<\/ul>\n<p><\/p>\n<p>U ovom projektu sam odlu\u010dio da odstupim od op\u0161teprihva\u0107ene prakse skladi\u0161tenja kontrolne sume nekompresovanih podataka.<\/p>\n<p><\/p>\n<p><strong>Sa\u017eetak:<\/strong> koristimo CRC-32C, kontrolnu sumu ra\u010dunamo od podataka u onom obliku u kojem se bele\u017ee u flash (posle kompresije).<\/p>\n<p><\/p>\n<h3 id=\"izbytochnost\">Redundancija<\/h3>\n<p><\/p>\n<p>Kori\u0161tenje redundantnog kodiranja, naravno, ne mo\u017ee isklju\u010diti gubitak podataka, no mo\u017ee zna\u010dajno (\u010desto za nekoliko redova) smanjiti vjerojatnost nepovratnog gubitka podataka.<\/p>\n<p><\/p>\n<p>Mo\u017eemo koristiti razli\u010dite vrste redundancije kako bismo ispravili gre\u0161ke.<br \/>\nHammingovi kodovi mogu ispraviti pojedina\u010dne gre\u0161ke na bitu, Reed-Solomon kodovi na razini simbola, vi\u0161e kopija podataka zajedno s kontrolnim zbrojevima ili kodiranje poput RAID-6 mo\u017ee pomo\u0107i u oporavku podataka \u010dak i u slu\u010daju masovnih o\u0161te\u0107enja.<br \/>\nIsprva sam bio sklon \u0161irokom kori\u0161tenju kodiranja otpornog na smetnje, no zatim sam shvatio da prvo trebamo imati predstavu o tome s kakvim gre\u0161kama se \u017eelimo za\u0161tititi, a potom odabrati kodiranje.<\/p>\n<p><\/p>\n<p>Ranije smo pri\u010dali o tome da gre\u0161ke treba otkriti \u0161to je br\u017ee mogu\u0107e. U kojim trenucima se mo\u017eemo susresti s gre\u0161kama?<\/p>\n<p><\/p>\n<ol>\n<li>Nedovr\u0161eni zapis (iz bilo kojeg razloga u trenutku pisanja isklju\u010dila se struja, Raspberry se zamrznuo, &#8230;)<br \/>\nNa\u017ealost, u slu\u010daju ovakve gre\u0161ke ostaje samo ignorirati neispravne zapise i smatrati podatke izgubljenima;<\/li>\n<li>Gre\u0161ke pri zapisivanju (iz raznih razloga u flash memoriju je zapisano ono \u0161to nije smjelo)<br \/>\nTakve gre\u0161ke mo\u017eemo odmah otkriti ako neposredno nakon zapisivanja napravimo kontrolno o\u010ditanje;<\/li>\n<li>Iskrivljenje podataka u memoriji tijekom pohrane;<\/li>\n<li>Gre\u0161ke pri o\u010ditanju<br \/>\nZa ispravljanje je dovoljno, u slu\u010daju neslaganja kontrolnog zbroja, nekoliko puta ponoviti o\u010ditanje.<\/li>\n<\/ol>\n<p><\/p>\n<p>Dakle, samo gre\u0161ke tre\u0107eg tipa (spontano propadanje podataka tijekom skladi\u0161tenja) ne mogu se ispraviti bez kodiranja otpornog na smetnje. \u010cini mi se da su takve gre\u0161ke ipak iznimno malo vjerojatne.<\/p>\n<p><\/p>\n<p><strong>Sa\u017eetak:<\/strong> odlu\u010deno je odustati od redundantnog kodiranja, no, ako eksploatacija poka\u017ee pogre\u0161nost ove odluke, vratit \u0107e se na razmatranje pitanja (s ve\u0107 akumuliranom statistikom o kvarovima koja \u0107e omogu\u0107iti odabir optimalne vrste kodiranja).<\/p>\n<p><\/p>\n<h3 id=\"prochee\">Ostalo<\/h3>\n<p><\/p>\n<p>Naravno, format \u010dlanka ne dopu\u0161ta obrazlaganje svake pojedinosti u formatu <em>(ionako su mi ve\u0107 ponestali snage)<\/em>, stoga \u0107u kratko pro\u0107i kroz neke to\u010dke koje nisu prethodno spomenute.<\/p>\n<p><\/p>\n<ul>\n<li>Odlu\u010deno je napraviti sve stranice &#171;ravnopravnima&#187;<br \/>\nTo zna\u010di da ne\u0107e biti nekih posebnih stranica s metapodacima, odvojenim tokovima itd., umjesto toga jedinstveni tok koji prepisuje sve stranice redom.<br \/>\nTo osigurava ravnomjernu potro\u0161nju stranica, izbjegava jedinstvenu ta\u010dku kvara i jednostavno se svi\u0111a;<\/li>\n<li>Obavezno je predvidjeti verzioniranje formata.<br \/>\nFormat bez broja verzije u zaglavlju je zlo!<br \/>\nDovoljno je dodati u zaglavlje stranice polje s nekim Magic Number (potpisom) koje \u0107e ukazivati na kori\u0161tenu verziju formata. <em>(ne mislim da \u0107e ih u praksi biti vi\u0161e od desetak)<\/em>;<\/li>\n<li>Koristiti za zapise (kojih ima jako puno) zaglavlje varijabilne du\u017eine, trude\u0107i se da ve\u0107inu slu\u010dajeva u\u010dinimo njegovu du\u017einu 1 bajt;<\/li>\n<li>Za kodiranje du\u017eine zaglavlja i du\u017eine skra\u0107ene komprimirane datoteke koristiti binarne kodove varijabilne du\u017eine.<\/li>\n<\/ul>\n<p><\/p>\n<p>Jako je pomoglo <noindex><a rel=\"nofollow\" href=\"https:\/\/planetcalc.com\/2481\/\">online generator<\/a><\/noindex> Huffmanovih kodova. Doslovno za nekoliko minuta uspjelo je odabrati potrebne kodove varijabilne du\u017eine.<\/p>\n<p><\/p>\n<h1 id=\"anchorformatanchoropisanie-formata-hraneniya-dannyh\"><noindex><a rel=\"nofollow\" name=\"format\"><\/a><\/noindex>Opis formata za pohranu podataka<\/h1>\n<p><\/p>\n<h2 id=\"byte-order\">Redoslijed bajtova<\/h2>\n<p><\/p>\n<p>Polja veli\u010dine ve\u0107ih od jednog bajta pohranjuju se u big-endian formatu (mre\u017eni redoslijed bajtova), to jest 0x1234 se zapisuje kao 0x12, 0x34.<\/p>\n<p><\/p>\n<h2 id=\"delenie-na-stranicy\">Podjela na stranice<\/h2>\n<p><\/p>\n<p>Sva flash memorija podijeljena je na stranice jednake veli\u010dine.<\/p>\n<p><\/p>\n<p>Zadana veli\u010dina stranice je 32KB, ali ne vi\u0161e od 1\/4 ukupne veli\u010dine \u010dipa memorije (za \u010dip od 4MB to daje 128 stranica).<\/p>\n<p><\/p>\n<p>Svaka stranica pohranjuje podatke neovisno od drugih (to jest podaci jedne stranice ne referenciraju podatke druge stranice).<\/p>\n<p><\/p>\n<p>Sve stranice su numerirane u prirodnom redu (po rastu\u0107im adresama), po\u010dinju\u0107i s brojem 0 (nulta stranica po\u010dinje s adresom 0, prva s 32KB, druga s 64KB itd.)<\/p>\n<p><\/p>\n<p>Memorijski \u010dip se koristi kao kru\u017eni spremnik (ring buffer), \u0161to zna\u010di da se zapis prvo upisuje u stranicu s brojem 0, zatim s brojem 1, &#8230;, kada napunimo posljednju stranicu, po\u010dinje novi ciklus i zapis se nastavlja s nultom stranicom.<\/p>\n<p><\/p>\n<h2 id=\"vnutri-stranicy\">Unutar stranice<\/h2>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/39b45f83dc46bb2fd7081ed2a0b638c9.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNa po\u010detku stranice se nalazi 4-bajtni zaglavlje stranice, zatim kontrolna suma zaglavlja (CRC-32C), a potom se pohranjuju zapisi u formatu &#171;zaglavlje, podaci, kontrolna suma&#187;.<\/p>\n<p><\/p>\n<p>Zaglavlje stranice (na dijagramu prljavo-zelene boje) sastoji se od:<\/p>\n<p><\/p>\n<ul>\n<li>dvobajtno polje Magic Number (tako\u0111er signal verzije formata)<br \/>\nza trenutnu verziju formata smatra se kao <code>0xed00 \u2295 broj stranice<\/code>;<\/li>\n<li>dvo-bajtni broja\u010d &#171;Verzija stranice&#187; (broj ciklusa prepisivanja memorije).<\/li>\n<\/ul>\n<p><\/p>\n<p>Zapisi na stranici \u010duvaju se u kompresiranom obliku (koristi se algoritam deflate). Svi zapisi na jednoj stranici komprimiraju se u jednom toku (koristi se zajedni\u010dki rje\u010dnik), a na svakoj novoj stranici kompresija zapo\u010dinje iznova. Dakle, za dekompresiju bilo kojeg zapisa potrebni su svi prethodni zapisi s te stranice (i samo s te).<\/p>\n<p><\/p>\n<p>Svaki zapis \u0107e se komprimirati s oznakom Z_SYNC_FLUSH, pri \u010demu na kraju komprimiranog toka ostaje 4 bajta 0x00, 0x00, 0xff, 0xff, prethodnih, mo\u017eda, jo\u0161 jednim ili dva nula bajta.<br \/>\nTu sekvencu (du\u017eine 4, 5 ili 6 bajtova) odbacujemo prilikom zapisivanja u flash memoriju.<\/p>\n<p><\/p>\n<p>Zaglavlje zapisa sastoji se od 1, 2 ili 3 bajta, koji sadr\u017ee:<\/p>\n<p><\/p>\n<ul>\n<li>jedan bit (T), koji ozna\u010dava vrstu zapisa: 0 \u2014 kontekst, 1 \u2014 dnevnik;<\/li>\n<li>polje promjenjive duljine (S) od 1 do 7 bita, koje odre\u0111uje duljinu zaglavlja i &#171;rep&#187;, koji treba dodati zapisu za raspakiranje;<\/li>\n<li>du\u017einu zapisa (L).<\/li>\n<\/ul>\n<p><\/p>\n<p>Tablica vrijednosti S:<\/p>\n<p><\/p>\n<p>S<br \/>\nDuljina zaglavlja, bajt<br \/>\nOdbacuje se prilikom zapisivanja, bajt<\/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>Poku\u0161ao sam ilustrirati, ne znam koliko je to jasno:<br \/>\n<img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/8c9b739eec5af4429395c32b4ba4044a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u017dutim je ozna\u010deno polje T, bijelim \u2014 polje S, zelenim L (du\u017eina komprimiranih podataka u bajtima), plavim \u2014 komprimirani podaci, crvenim \u2014 zavr\u0161ni bajtovi komprimiranih podataka, koji se ne upisuju u flash memoriju.<\/p>\n<p><\/p>\n<p>Tako \u0107emo mo\u0107i zapisati zaglavlja zapisa naj\u010de\u0161\u0107e du\u017eine (do 63+5 bajtova u komprimiranom obliku) s jednim bajtom.<\/p>\n<p><\/p>\n<p>Iza svakog zapisa pohranjuje se kontrolna suma CRC-32C, koja kao inicijalnu vrijednost (init) koristi inverznu vrijednost prethodne kontrolne sume.<\/p>\n<p><\/p>\n<p><em>CRC ima svojstvo &#171;nastavnosti&#187;, djeluje (plus-minus invertiranje bitova u procesu) prema ovoj formuli: <img decoding=\"async\" alt=\"Moja implementacija kru\u017enog bafera u NOR flash\" src=\"\/wp-content\/uploads\/2019\/12\/b194d6a3d18ae2f4ae5d4054a93c0ea3.jpg\" style=\"display:block;margin: 0 auto;\" \/>.<br \/>\nDakle, zapravo izra\u010dunavamo CRC svih prethodnih bajtova zaglavlja i podataka na ovoj stranici.<\/em><\/p>\n<p><\/p>\n<p>Odmah iza kontrolne sume nalazi se zaglavlje sljede\u0107eg zapisa.<\/p>\n<p><\/p>\n<p>Zaglavlje je konstruirano tako da je prvi njegov bajt uvijek razli\u010dit od 0x00 i 0xff (ako umjesto prvog bajta zaglavlja nai\u0111emo na 0xff, to zna\u010di da je to zasad neiskori\u0161teno podru\u010dje; 0x00 signalizira gre\u0161ku).<\/p>\n<p><\/p>\n<h2 id=\"primernye-algoritmy\">Primjeri algoritama<\/h2>\n<p><\/p>\n<h3 id=\"chtenie-iz-flesh-pamyati\">\u010citanje iz flash memorije<\/h3>\n<p><\/p>\n<p>Svako \u010ditanje ide s provjerom kontrolne sume.<br \/>\nAko kontrolna suma ne odgovara, \u010ditanje se ponavlja nekoliko puta s nadom da \u0107e se kona\u010dno pro\u010ditati to\u010dni podaci.<\/p>\n<p><\/p>\n<p><em>(to ima smisla, Linux ne ke\u0161ira \u010ditanje iz NOR Flash, provjereno)<\/em><\/p>\n<p><\/p>\n<h3 id=\"zapis-v-flesh-pamyat\">Zapisivanje u flash memoriju<\/h3>\n<p><\/p>\n<p>Zapisujemo podatke.<br \/>\n\u010citamo ih.<\/p>\n<p><\/p>\n<p>Ako pro\u010ditani podaci ne odgovaraju zapisanim, ispunjavamo podru\u010dje nulama i signaliziramo gre\u0161ku.<\/p>\n<p><\/p>\n<h3 id=\"podgotovka-novoy-mikroshemy-k-rabote\">Priprema nove mikro\u010dip za rad<\/h3>\n<p><\/p>\n<p>Za inicijalizaciju, u prvu (to\u010dnije, nultu) stranicu se zapisuje zaglavlje s verzijom 1.<br \/>\nNakon toga, u tu stranicu se zapisuje inicijalni kontekst (sadr\u017ei UUID ure\u0111aja i zadane postavke). <\/p>\n<p><\/p>\n<p>Sve, flash memorija je spremna za rad.<\/p>\n<p><\/p>\n<h3 id=\"zagruzka-avtomata\">U\u010ditavanje ure\u0111aja<\/h3>\n<p><\/p>\n<p>Pri u\u010ditavanju se \u010ditaju prvih 8 bajtova svake stranice (zaglavlje + CRC), stranice s nepoznatim Magic Numberom ili neispravnim CRC se ignoriraju.<br \/>\nIz &#171;ispravnih&#187; stranica se biraju stranice s maksimalnom verzijom, od njih se uzima stranica s najve\u0107im brojem.<br \/>\nPrva se zapis \u010dita, provjerava se ispravnost CRC-a, postoji li oznaka &#171;kontekst&#187;. Ako je sve u redu \u2014 ta se stranica smatra trenutnom. Ako nije \u2014 vra\u0107amo se na prethodnu, sve dok ne prona\u0111emo &#171;\u017eivu&#187; stranicu.<br \/>\nNa prona\u0111enoj stranici \u010ditamo sve zapise, one s oznakom &#171;kontekst&#187; primjenjujemo.<br \/>\nSpremamo zlib rje\u010dnik (bit \u0107e potreban za dodatno zapisivanje u ovu stranicu).<\/p>\n<p><\/p>\n<p>Sve, u\u010ditavanje je zavr\u0161eno, kontekst je obnovljen, mo\u017eemo raditi.<\/p>\n<p><\/p>\n<h3 id=\"dobavlenie-zapisi-v-zhurnal\">Dodavanje zapisa u dnevnik<\/h3>\n<p><\/p>\n<p>Stisnemo zapis s pravim rje\u010dnikom, ozna\u010davaju\u0107i Z_SYNC_FLUSH. Provjeravamo staje li kompresirani zapis na trenutnoj stranici.<br \/>\nAko ne stane (ili ako su na stranici bile gre\u0161ke CRC), zapo\u010dinjemo novu stranicu (vidi dolje).<br \/>\nZapisujemo zapis i CRC. Ako do\u0111e do gre\u0161ke, zapo\u010dinjemo novu stranicu.<\/p>\n<p><\/p>\n<h3 id=\"novaya-stranica\">Nova stranica<\/h3>\n<p><\/p>\n<p>Biramo slobodnu stranicu s minimalnim brojem (slobodnom smatramo stranicu s pogre\u0161nom kontrolnom sumom u zaglavlju ili s verzijom manjom od trenutne). Ako takvih stranica nema, biramo stranicu s minimalnim brojem koja ima verziju jednaku trenutnoj.<br \/>\nOdabranom stranici postavljamo erase. Provjeravamo sadr\u017eaj s 0xff. Ako ne\u0161to nije u redu, uzimamo sljede\u0107u slobodnu stranicu, itd.<br \/>\nNa izbrisanu stranicu zapisujemo zaglavlje, a prvim zapisom trenutno stanje konteksta, sljede\u0107im \u2014 neupisani zapis dnevnika (ako ga ima).<\/p>\n<p><\/p>\n<h1 id=\"primenimost-formata\">Primjenjivost formata<\/h1>\n<p><\/p>\n<p>Po mom mi\u0161ljenju, ovo je dobar format za pohranu bilo kakvih vi\u0161e-manje kompresibilnih informacija (plain text, JSON, MessagePack, CBOR, mo\u017eda protobuf) u NOR Flash.<\/p>\n<p><\/p>\n<p>Naravno, format je &#171;prilago\u0111en&#187; za SLC NOR Flash.<\/p>\n<p><\/p>\n<p>Ne bi ga trebalo koristiti s medijima s visokim BER-om, kao \u0161to su NAND ili MLC NOR. <em>(Postoji li uop\u0107e takva memorija na prodaji? Vidio sam spominjanje samo u radu o kodovima korekcije.)<\/em>.<\/p>\n<p><\/p>\n<p>Tim vi\u0161e, ne bi ga trebalo koristiti s ure\u0111ajima koji imaju vlastiti FTL: USB flash, SD, MicroSD itd. <em>(za takvu memoriju sam napravio format s veli\u010dinom stranice od 512 bajta, s potpisom na po\u010detku svake stranice i jedinstvenim brojevima zapisa \u2014 ponekad se iz &#171;zaklju\u010dane&#187; flash memorije moglo jednostavnim sekvencijalnim \u010ditanjem povratiti sve podatke)<\/em>.<\/p>\n<p><\/p>\n<p>Ovisno o zadatcima, format se mo\u017ee koristiti bez promjena na flash ure\u0111ajima od 128Kbit (16Kb) do 1Gbit (128Mb). Ako \u017eelite, mo\u017ee se koristiti i na \u010dipovima ve\u0107e kapacitete, samo je vjerojatno potrebno prilagoditi veli\u010dinu stranice. <em>(Ali tu se ve\u0107 postavlja pitanje ekonomske isplativosti, cijena na NOR Flash velikog kapaciteta nije ohrabruju\u0107a.)<\/em>.<\/p>\n<p><\/p>\n<p>Ako se nekome format u\u010dinio zanimljivim, i \u017eeli ga koristiti u otvorenom projektu \u2014 javite se, potrudit \u0107u se na\u0107i vremena, urediti kod i staviti na github.<\/p>\n<p><\/p>\n<h1 id=\"zaklyuchenie\">Zaklju\u010dak<\/h1>\n<p><\/p>\n<p>Kao \u0161to vidimo, na kraju se format pokazao jednostavnim. <em>i \u010dak dosadnim.<\/em>.<\/p>\n<p><\/p>\n<p>U \u010dlanku je te\u0161ko prikazati evoluciju svog stajali\u0161ta, ali vjerujte: isprva sam \u017eelio stvoriti ne\u0161to slo\u017eeno, neuni\u0161tivo, sposobno pre\u017eivjeti \u010dak i nakon nuklearnog udara u neposrednoj blizini. Me\u0111utim, razum (nadam se) je na kraju pobijedio i prioriteti su se postupno pomaknuli na jednostavnost i kompaktnost.<\/p>\n<p><\/p>\n<p>Mo\u017ee li se dogoditi da sam pogrije\u0161io? Da, naravno. Sve je mogu\u0107e, na primjer, da smo kupili seriju nekvalitetnih \u010dipova. Ili iz nekog drugog razloga oprema ne\u0107e ispuniti o\u010dekivanja u pogledu pouzdanosti.<\/p>\n<p><\/p>\n<p>Imam li plan za takav slu\u010daj? Mislim da iz \u010ditanja \u010dlanka ne sumnjate da plan postoji. I to ne jedan.<\/p>\n<p><\/p>\n<p>Ako malo ozbiljnije, format je razvijen istovremeno i kao radna verzija, i kao &#171;pokusni balon&#187;.<\/p>\n<p><\/p>\n<p>Trenutno sve radi normalno, doslovno u narednih nekoliko dana rje\u0161enje \u0107e biti implementirano. <em>(otprilike)<\/em> na stotinama ure\u0111aja, vidjet \u0107emo \u0161to \u0107e se dogoditi u &#171;borbenoj&#187; eksploataciji (sre\u0107om, nadam se, format omogu\u0107ava pouzdano otkrivanje gre\u0161aka; tako da \u0107e se mo\u0107i prikupiti potpuna statistika). Kroz nekoliko mjeseci mo\u0107i \u0107e se donijeti zaklju\u010dci. <em>(a ako ne budemo imali sre\u0107e \u2014 onda i ranije)<\/em>.<\/p>\n<p><\/p>\n<p>Ako se prilikom kori\u0161tenja otkriju ozbiljni problemi i potrebna su pobolj\u0161anja, obavezno \u0107u o tome napisati.<\/p>\n<p><\/p>\n<h1 id=\"literatura\">Literatura<\/h1>\n<p><\/p>\n<p>Nisam \u017eelio sastavljati dugi dosadni popis kori\u0161tenih radova, na kraju krajeva, Google svi imaju.<\/p>\n<p><\/p>\n<p>Ovdje sam odlu\u010dio ostaviti popis otkri\u0107a koja su mi se \u010dinila posebno zanimljivima, ali postupno su preba\u010dena izravno u tekst \u010dlanka, a u popisu je ostala samo jedna stavka:<\/p>\n<p><\/p>\n<ol>\n<li>Alat <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/madler\/infgen\/\">infgen<\/a><\/noindex> od autora zlib. Zna prikazivati sadr\u017eaj arhiva deflate\/zlib\/gzip na razumljiv na\u010din. Ako se bavite unutarnjom strukturom formata deflate (ili gzip) \u2014 toplo preporu\u010dujem.<\/li>\n<\/ol>\n<p>Izvor: <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.4 - 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\/hr\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"hr_HR\" \/>\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\/hr\/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\udd47Moja implementacija kru\u017enog bafera u NOR flash | ProHoster","description":"Pozadina Postoje automati za prodaju vlastitog dizajna. Unutra je Raspberry Pi i malo opreme na zasebnoj plo\u010di.","canonical_url":"https:\/\/prohoster.info\/hr\/blog\/administrirovanie\/moya-realizatsiya-koltsevogo-bufera-v-nor-flash","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"hr_HR","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\/hr\/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\/hr\/wp-json\/wp\/v2\/posts\/53751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/comments?post=53751"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/posts\/53751\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/media?parent=53751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/categories?post=53751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/hr\/wp-json\/wp\/v2\/tags?post=53751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}