Implementasi ring ring saya di NOR flash

Latar belakang


Ada mesin penjual otomatis desain kami sendiri. Di dalam Raspberry Pi dan sedikit pengikat di papan terpisah. Akseptor koin, akseptor tagihan, terminal bank terhubung ... Program yang ditulis sendiri mengatur semuanya. Seluruh riwayat pekerjaan ditulis ke majalah pada USB flash drive (MicroSD), yang kemudian dikirim melalui Internet (menggunakan modem USB) ke server, di mana ia ditambahkan ke database. Informasi penjualan dimuat dalam 1s, ada juga antarmuka web sederhana untuk pemantauan, dll.


Artinya, majalah itu vital - untuk akuntansi (ada pendapatan, penjualan, dll.), Pemantauan (semua jenis kegagalan dan keadaan force majeure lainnya); ini, bisa Anda katakan, semua informasi yang kami miliki tentang mesin ini.


Masalah


Flash drive menunjukkan diri mereka sebagai perangkat yang sangat tidak bisa diandalkan. Mereka gagal dengan keteraturan yang patut ditiru. Hal ini menyebabkan downtime mesin dan (jika karena alasan tertentu jurnal tidak dapat dikirim secara online) ke kehilangan data.


Ini bukan pengalaman pertama menggunakan flash drive, sebelum itu ada proyek lain dengan lebih dari seratus perangkat di mana majalah disimpan di USB flash drive, ada juga masalah dengan keandalan, kadang-kadang jumlah kegagalan per bulan adalah puluhan. Kami mencoba berbagai flash drive, termasuk yang bermerek pada memori SLC, dan beberapa model lebih andal daripada yang lain, tetapi mengganti flash drive tidak menyelesaikan masalah secara radikal.


Perhatian! Longrid! Jika Anda tidak tertarik pada "mengapa," tetapi hanya tertarik pada "bagaimana," Anda dapat langsung menuju ke akhir artikel.


Solusi


Hal pertama yang terlintas dalam pikiran: tinggalkan MicroSD, masukkan, misalnya, SSD, dan boot darinya. Secara teori itu mungkin, mungkin, tetapi relatif mahal, dan tidak begitu dapat diandalkan (adaptor USB-SATA ditambahkan; pada SSD anggaran, statistik kegagalan juga tidak bahagia).


HDD USB juga tidak terlihat solusi yang sangat menarik.


Oleh karena itu, kami sampai pada opsi ini: tinggalkan unduhan dari MicroSD, tetapi gunakan dalam mode read-only, dan simpan log operasi (dan informasi lain yang unik untuk perangkat keras tertentu - nomor seri, kalibrasi sensor, dll) di tempat lain.


Topik read-only FS untuk raspberry telah dipelajari terus-menerus, saya tidak akan membahas detail implementasi dalam artikel ini (tetapi jika ada minat, mungkin saya akan menulis artikel mini tentang topik ini) . Satu-satunya hal yang ingin saya perhatikan: baik dari pengalaman pribadi maupun dari ulasan yang telah menerapkan peningkatan keandalan, adalah. Ya, tidak mungkin untuk sepenuhnya menghilangkan kerusakan, tetapi sangat mungkin untuk mengurangi frekuensi mereka secara signifikan. Ya, dan kartu menjadi satu, yang sangat menyederhanakan penggantian untuk staf pemeliharaan.


Perangkat keras


Tidak ada keraguan tentang pilihan jenis memori - NOR Flash.
Argumen:


  • koneksi sederhana (paling sering SPI bus, pengalaman menggunakan yang sudah ada di sana, sehingga tidak ada masalah "besi" yang diharapkan);
  • harga yang konyol;
  • protokol operasi standar (implementasinya sudah ada di kernel Linux, jika Anda mau, Anda dapat mengambil pihak ketiga, yang juga hadir, atau bahkan menulis sendiri, manfaatnya sederhana);
  • keandalan dan sumber daya:
    dari lembar data biasa: data disimpan selama 20 tahun, 100.000 siklus hapus untuk setiap blok;
    dari sumber pihak ketiga: BER sangat rendah, dipostulatkan bahwa tidak perlu kode koreksi kesalahan (dalam beberapa makalah ECC untuk NOR dipertimbangkan, tetapi biasanya MLC NOR dimaksudkan di sana, itu terjadi) .

Mari kita perkirakan persyaratan volume dan sumber daya.


Saya ingin dijamin untuk menyimpan data selama beberapa hari. Ini diperlukan agar jika terjadi masalah dengan koneksi, riwayat penjualan tidak hilang. Kami akan fokus pada 5 hari, selama periode ini (bahkan dengan memperhitungkan akhir pekan dan hari libur), kami dapat menyelesaikan masalah.


Kami sekarang mengetik sekitar 100kb majalah per hari (3-4 ribu catatan), tetapi lambat laun angka ini bertambah - perinciannya meningkat, acara-acara baru ditambahkan. Plus, terkadang ada semburan (beberapa sensor mulai melakukan spam dengan positif palsu, misalnya). Kami akan menghitung 10 ribu catatan 100 byte - megabita per hari.


Sebanyak 5 MB data bersih (dapat dikompres dengan baik) keluar. Mereka juga (perkiraan kasar) 1MB data layanan.


Artinya, kita membutuhkan microchip 8MB jika Anda tidak menggunakan kompresi, atau 4MB jika Anda menggunakannya. Angka nyata yang cukup untuk jenis memori ini.


Adapun sumber daya: jika kami merencanakan bahwa seluruh memori akan ditulis ulang tidak lebih dari sekali setiap 5 hari, maka dalam 10 tahun layanan kami mendapatkan kurang dari seribu siklus menulis ulang.
Saya ingat, pabrikan itu menjanjikan seratus ribu.


Sedikit tentang NOR vs NAND

Hari ini, tentu saja, memori NAND jauh lebih populer, tetapi untuk proyek ini saya tidak akan menggunakannya: NAND, tidak seperti NOR, tentu memerlukan penggunaan kode koreksi kesalahan, tabel blok buruk, dll., Dan kaki-kaki chip NAND biasanya jauh lebih banyak.


Kerugian dari NOR meliputi:


  • volume kecil (dan, karenanya, harga tinggi per megabyte);
  • nilai tukar rendah (sebagian besar disebabkan oleh fakta bahwa antarmuka serial digunakan, biasanya SPI atau I2C);
  • slow erase (tergantung pada ukuran blok, dibutuhkan dari fraksi detik hingga beberapa detik).

Sepertinya tidak ada yang penting bagi kami, jadi teruskan.


Jika detailnya menarik, chip at25df321a dipilih (namun, ini tidak signifikan, ada banyak analog di pasaran yang kompatibel dengan pinout dan sistem perintah; bahkan jika kita ingin meletakkan chip dari pabrikan lain dan / atau volume lainnya, semuanya akan bekerja tanpa mengubah kode) .


Saya menggunakan driver yang dibangun di kernel Linux, di Raspberry, berkat dukungan overlay pohon perangkat, semuanya sangat sederhana - Anda perlu meletakkan overlay yang dikompilasi di / boot / overlay dan memodifikasi sedikit / boot / config.txt.


Contoh file dts

Jujur, saya tidak yakin apa yang ditulis tanpa kesalahan, tetapi berhasil.


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

Dan baris lain di config.txt
 dtoverlay=at25:spimaxfrequency=50000000 

Saya akan menghilangkan deskripsi menghubungkan chip ke Raspberry Pi. Di satu sisi, saya bukan ahli elektronik, di sisi lain, semuanya sepele bahkan bagi saya: microcircuit hanya memiliki 8 kaki, yang mana kami membutuhkan tanah, daya, SPI (CS, SI, SO, SCK); level bertepatan dengan Raspberry Pi, tidak ada ikatan tambahan yang diperlukan - cukup sambungkan 6 kontak yang ditentukan.


Pernyataan masalah


Seperti biasa, perumusan masalah melewati beberapa iterasi, bagi saya sepertinya sudah tiba saatnya berikutnya. Jadi mari kita berhenti, menyusun apa yang sudah ditulis, dan mengklarifikasi detail yang tersisa di bayangan.


Jadi, kami memutuskan bahwa log akan disimpan dalam SPI NOR Flash.


Apa itu NOR Flash untuk mereka yang tidak tahu

Ini adalah memori non-volatile yang dengannya Anda dapat melakukan tiga operasi:


  1. Membaca:
    Bacaan yang paling umum: kami mengirimkan alamat dan membaca byte yang kami butuhkan;
  2. Rekam:
    Menulis ke NOR flash terlihat seperti yang biasa, tetapi memiliki satu kekhasan: Anda hanya dapat mengubah 1 ke 0, tetapi tidak sebaliknya. Misalnya, jika kita memiliki 0x55 di sel memori, maka setelah menulis 0x0f untuk itu, 0x05 sudah akan disimpan di sana (lihat tabel di bawah) ;
  3. Hapus:
    Tentu saja, kita harus dapat melakukan operasi mundur juga - ubah 0 ke 1, itulah sebabnya operasi hapus ada. Berbeda dengan dua yang pertama, ia beroperasi bukan dalam byte, tetapi dalam blok (blok hapus minimum dalam rangkaian mikro yang dipilih adalah 4 kb). Erase menghancurkan seluruh blok dan ini adalah satu-satunya cara untuk mengubah 0 ke 1. Oleh karena itu, ketika bekerja dengan memori flash, Anda sering harus menyelaraskan struktur data ke batas blok hapus.
    Rekam dalam NOR Flash:

Data biner
Apakah01010101
Direkam00001111
Telah menjadi00000101

Jurnal itu sendiri mewakili urutan catatan panjang variabel. Panjang rekaman tipikal adalah sekitar 30 byte (walaupun kadang-kadang rekaman beberapa kilobyte kadang terjadi). Dalam hal ini, kami bekerja dengan mereka seperti seperangkat byte, tetapi, jika Anda tertarik, CBOR digunakan di dalam catatan.


Selain jurnal, kita perlu menyimpan beberapa informasi "tuning", apakah diperbarui atau tidak: ID perangkat tertentu, kalibrasi sensor, bendera "perangkat dinonaktifkan sementara", dll.
Informasi ini adalah satu set catatan nilai kunci, juga disimpan dalam CBOR. Kami tidak memiliki banyak informasi ini (maksimum beberapa kilobyte), itu jarang diperbarui.
Di masa depan, kita akan menyebutnya konteks.


Jika Anda ingat di mana artikel ini dimulai, sangat penting untuk memastikan keandalan penyimpanan data dan, jika mungkin, operasi terus-menerus bahkan jika terjadi kegagalan perangkat keras / korupsi data.


Sumber masalah apa yang bisa dipertimbangkan?


  • Matikan selama operasi penulisan / hapus. Ini dari kategori "terhadap memo tidak ada penerimaan."
    Informasi dari diskusi tentang stackexchange: ketika daya dimatikan saat bekerja dengan flash, penghapusan itu (pengaturan ke 1), penulisan itu (pengaturan ke 0) mengarah pada perilaku yang tidak terdefinisi: data dapat ditulis, ditulis sebagian (misalnya, kami mentransfer 10 byte / 80 bit , dan hanya 45 bit yang berhasil direkam), juga dimungkinkan bahwa beberapa bit akan berada dalam keadaan "menengah" (membaca dapat menghasilkan 0 atau 1);
  • Kesalahan memori flash itu sendiri.
    BER, meskipun sangat rendah, tidak bisa sama dengan nol;
  • Kesalahan bus
    Data yang dikirimkan melalui SPI tidak dilindungi dengan cara apa pun, itu dapat terjadi sebagai kesalahan bit tunggal atau kesalahan sinkronisasi - kehilangan atau penyisipan bit (yang menyebabkan distorsi data besar-besaran);
  • Kesalahan / kegagalan lainnya
    Kesalahan dalam kode, Raspberry "gangguan", intervensi alien ...

Saya merumuskan persyaratan, yang pemenuhannya, menurut pendapat saya, diperlukan untuk memastikan keandalan:


  • catatan harus ditulis ke memori flash segera, rekaman tertunda tidak dipertimbangkan; - jika kesalahan telah terjadi, maka harus terdeteksi dan diproses sesegera mungkin; - sistem harus, jika mungkin, pulih dari kesalahan.
    (contoh dari kehidupan "sebagaimana mestinya tidak," yang, saya pikir, semua orang bertemu: setelah restart darurat, sistem file "rusak" dan sistem operasi tidak bisa boot)

Gagasan, Pendekatan, Pikiran


Ketika saya mulai memikirkan tugas ini, banyak ide muncul di benak saya, misalnya:


  • Gunakan kompresi data
  • Gunakan struktur data yang rumit, misalnya, menyimpan header catatan secara terpisah dari catatan itu sendiri, sehingga jika kesalahan terjadi dalam catatan, Anda dapat membaca sisanya tanpa masalah;
  • gunakan bidang bit untuk mengontrol kelengkapan rekaman saat daya dimatikan;
  • toko checksum untuk semuanya dan segalanya;
  • menggunakan semacam pengkodean koreksi kesalahan.

Beberapa ide ini digunakan, beberapa memutuskan untuk menolak. Mari kita mulai.


Kompresi data


Peristiwa yang kami rekam dalam jurnal itu sendiri cukup sama dan berulang ("melempar koin 5 rubel," "mengklik tombol ubah pengiriman", ...). Karena itu, kompresi harus cukup efektif.


Overhead untuk kompresi tidak signifikan (prosesor yang kami miliki cukup kuat, bahkan pada Pi pertama ada satu inti dengan frekuensi 700 MHz, pada model saat ini ada beberapa core dengan frekuensi lebih dari satu gigahertz), kecepatan pertukaran dengan penyimpanan rendah (beberapa megabyte per detik), ukuran rekaman kecil. Secara umum, jika kompresi akan memengaruhi kinerja, maka hanya positif (sama sekali tidak kritis, hanya menyatakan) . Plus, kami tidak memiliki embedded yang nyata, tetapi Linux biasa - jadi implementasi seharusnya tidak membutuhkan banyak upaya (cukup tautkan perpustakaan dan gunakan beberapa fungsi darinya).


Sepotong log diambil dari perangkat yang berfungsi (1,7 MB, 70 ribu catatan) dan untuk permulaannya diperiksa kompresibilitasnya menggunakan gzip, lz4, lzop, bzip2, xz, zstd yang tersedia di komputer.


  • gzip, xz, zstd menunjukkan hasil yang serupa (40Kb).
    Saya terkejut bahwa xz modis menunjukkan dirinya di sini di tingkat gzip atau zstd;
  • lzip dengan pengaturan default memberikan hasil yang sedikit lebih buruk;
  • lz4 dan lzop menunjukkan hasil yang tidak terlalu baik (150Kb);
  • bzip2 menunjukkan hasil yang sangat baik (18Kb).

Jadi data terkompresi dengan sangat baik.
Jadi (jika kita tidak menemukan kesalahan fatal) harus ada kompresi! Hanya karena lebih banyak data akan masuk pada flash drive yang sama.


Mari kita pikirkan kekurangannya.


Masalah pertama: kita sudah sepakat bahwa setiap record harus segera di flash. Biasanya, pengarsipan mengumpulkan data dari aliran input sampai memutuskan bahwa sudah saatnya untuk menulis ke output. Kita perlu segera mendapatkan blok data terkompresi dan menyimpannya dalam memori non-volatile.


Saya melihat tiga cara:


  1. Kompres setiap entri menggunakan kompresi kamus alih-alih algoritma yang dibahas di atas.
    Ini adalah opsi yang berfungsi, tetapi saya tidak menyukainya. Untuk memastikan tingkat kompresi yang lebih atau kurang layak, kamus harus β€œdipertajam” untuk data tertentu, setiap perubahan akan mengarah pada fakta bahwa tingkat kompresi turun drastis. Ya, masalahnya diselesaikan dengan membuat versi baru kamus, tetapi ini sakit kepala - kita perlu menyimpan semua versi kamus; di setiap entri kita perlu menunjukkan versi kamus mana yang dikompres ...
  2. Kompres setiap entri dengan algoritme "klasik", tetapi terlepas dari yang lainnya.
    Algoritma kompresi yang dipertimbangkan tidak dirancang untuk bekerja dengan catatan ukuran ini (puluhan byte), koefisien kompresi akan jelas kurang dari 1 (yaitu, peningkatan jumlah data, bukan kompresi);
  3. Lakukan FLUSH setelah setiap perekaman.
    Banyak pustaka kompresi memiliki dukungan untuk FLUSH. Ini adalah perintah (atau parameter untuk prosedur kompresi), setelah menerima pengarsipan menghasilkan aliran terkompresi sehingga pada dasarnya semua data terkompresi yang telah diterima dapat dikembalikan. Seperti analog sync dalam sistem file atau commit dalam sql.
    Yang penting, operasi kompresi selanjutnya akan dapat menggunakan kamus yang terakumulasi dan rasio kompresi tidak akan sebanyak seperti pada versi sebelumnya.

Saya pikir sudah jelas bahwa saya memilih opsi ketiga, mari kita bahas lebih detail.


Ada artikel bagus tentang FLUSH di zlib.


Saya melakukan tes termotivasi berdasarkan artikel, mengambil 70 ribu entri jurnal dari perangkat nyata, dengan ukuran halaman 60Kb (kami akan kembali ke ukuran halaman) :


Sumber dataKompresi Gzip -9 (tanpa FLUSH)zlib dengan Z_PARTIAL_FLUSHzlib dengan Z_SYNC_FLUSH
Volume, Kb169240352604

Pada pandangan pertama, harga yang diperkenalkan oleh FLUSH sangat tinggi, tetapi pada kenyataannya kami memiliki pilihan yang buruk - baik untuk tidak mengompres sama sekali, atau untuk mengompres (dan sangat efisien) dengan FLUSH. Jangan lupa bahwa kami memiliki 70 ribu catatan, redundansi yang diperkenalkan oleh Z_PARTIAL_FLUSH hanya 4-5 byte per rekaman. Dan rasio kompresi ternyata hampir 5: 1, yang lebih dari hasil yang sangat baik.


Ini mungkin tampak tidak terduga, tetapi sebenarnya Z_SYNC_FLUSH adalah cara yang lebih efisien untuk melakukan FLUSH

Dalam hal menggunakan Z_SYNC_FLUSH, 4 byte terakhir dari setiap record akan selalu 0x00, 0x00, 0xff, 0xff. Dan jika kita mengenal mereka, maka kita tidak dapat menyimpannya, sehingga ukuran totalnya hanya 324Kb.


Artikel yang saya maksudkan memiliki penjelasan:


Blok 0 tipe baru dengan konten kosong ditambahkan.

Blok tipe 0 dengan konten kosong terdiri dari:
  • header blok tiga-bit;
  • 0 hingga 7 bit sama dengan nol, untuk mencapai perataan byte;
  • urutan empat byte 00 00 FF FF.

Seperti yang Anda lihat, di blok terakhir sebelum 4 byte ini berasal dari 3 hingga 10 bit nol. Namun, praktik telah menunjukkan bahwa nol bit sebenarnya setidaknya 10.


Ternyata blok data pendek seperti itu biasanya (selalu?) Di-encode menggunakan blok tipe 1 (blok tetap), yang seharusnya berakhir dengan 7 bit nol, jadi kami mendapatkan 10-17 bit nol yang dijamin (dan sisanya akan menjadi nol dengan probabilitas sekitar 50%).


Jadi, pada data uji, dalam 100% kasus, sebelum 0x00, 0x00, 0xff, 0xff ada satu byte nol, dan lebih dari pada kasus ketiga ada dua nol byte (mungkin faktanya saya menggunakan binari CBOR, dan saat menggunakan teks CBOR JSON akan lebih cenderung memenuhi blok tipe 2 - blok dinamis, masing-masing, blok akan terjadi tanpa tambahan nol byte sebelum 0x00, 0x00, 0xff, 0xff) .


Total pada data uji yang tersedia dapat memuat kurang dari 250Kb data terkompresi.


Anda dapat menyimpan sedikit lebih banyak dengan melakukan juggling bits: sekarang kita mengabaikan keberadaan beberapa bit nol di ujung blok, beberapa bit di awal blok juga tidak berubah ...
Tapi kemudian saya membuat keputusan yang kuat untuk berhenti, jika tidak pada kecepatan seperti itu Anda dapat mencapai pengembangan pengarsipan Anda.


Secara total, saya mendapat 3-4 byte per catatan dari data pengujian saya, rasio kompresi lebih dari 6: 1. Jujur, saya tidak mengandalkan hasil seperti itu, menurut saya segala sesuatu yang lebih baik dari 2: 1 sudah merupakan hasil yang membenarkan penggunaan kompresi.


Semuanya baik-baik saja, tetapi zlib (deflate) masih kuno algoritma kompresi yang layak dan sedikit kuno. Fakta bahwa 32Kb terakhir dari aliran data yang tidak dikompres digunakan sebagai kamus yang terlihat aneh hari ini (yaitu, jika beberapa blok data sangat mirip dengan apa yang ada di arus input 40Kb kembali, itu akan mulai diarsipkan lagi, tetapi tidak akan diarsipkan lagi, tetapi tidak akan lihat entri sebelumnya). Masuk modis Kamus ukuran arsip modern sering diukur dalam megabita daripada kilobyte.


Jadi kami melanjutkan studi kecil kami tentang arsip.


Selanjutnya bzip2 diuji (ingat, tanpa FLUSH itu menunjukkan rasio kompresi yang fantastis, hampir 100: 1). Sayangnya, dengan FLUSH itu menunjukkan dirinya sangat buruk, ukuran data yang dikompresi lebih besar daripada yang tidak terkompresi.


Asumsi saya tentang alasan kegagalan

Libbz2 hanya menawarkan satu opsi flush, yang tampaknya membersihkan kamus (mirip dengan Z_FULL_FLUSH di zlib), tidak ada alasan untuk membicarakan semacam kompresi yang efisien.


Dan zstd adalah yang terakhir diuji. Bergantung pada parameternya, kompresnya berada pada level gzip, tetapi jauh lebih cepat, atau gzip lebih baik.


Sayangnya, dengan FLUSH ia terbukti "tidak terlalu": ​​ukuran data yang dikompresi keluar sekitar 700Kb.


Saya mengajukan pertanyaan pada halaman proyek di github, saya mendapat jawaban bahwa perlu menghitung hingga 10 byte data layanan untuk setiap blok data terkompresi, yang dekat dengan hasil, menangkap deflate tidak berfungsi.


Saya memutuskan untuk menghentikan ini dalam percobaan dengan pengarsip (saya ingatkan Anda bahwa xz, lzip, lzo, lz4 tidak menunjukkan diri pada tahap pengujian tanpa FLUSH, tetapi saya tidak mempertimbangkan algoritma kompresi yang lebih eksotis).


Kami kembali ke masalah pengarsipan.


Masalah kedua (seperti yang mereka katakan secara berurutan, tetapi tidak dalam nilainya) - data terkompresi adalah aliran tunggal yang secara konstan mengirim ke bagian sebelumnya. Jadi, ketika bagian dari data terkompresi rusak, kami tidak hanya kehilangan blok data yang tidak terkompresi yang terkait dengannya, tetapi juga semua yang berikutnya.


Ada pendekatan untuk memecahkan masalah ini:


  1. Cegah terjadinya masalah - tambahkan redundansi ke data yang dikompresi, yang akan memungkinkan untuk mengidentifikasi dan memperbaiki kesalahan; kita akan membicarakan ini nanti;
  2. Minimalkan konsekuensi jika terjadi masalah
    Kami telah mengatakan sebelumnya bahwa adalah mungkin untuk mengompres setiap blok data secara independen, dan masalahnya akan hilang dengan sendirinya (korupsi data satu blok akan menyebabkan hilangnya data hanya blok ini). Namun, ini adalah kasus ekstrem di mana kompresi data tidak efisien. Ekstrem yang berlawanan: gunakan semua 4MB dari sirkuit mikro kami sebagai arsip tunggal, yang akan memberi kami kompresi yang sangat baik, tetapi konsekuensi yang sangat besar jika terjadi kerusakan data.
    Ya, kompromi diperlukan dalam hal keandalan. Tetapi kita harus ingat bahwa kita sedang mengembangkan format penyimpanan data untuk memori non-volatil dengan BER sangat rendah dan periode penyimpanan data yang dinyatakan 20 tahun.

Dalam percobaan, saya menemukan bahwa kerugian yang terlihat pada tingkat kompresi dimulai pada blok data terkompresi dengan ukuran kurang dari 10Kb.
Telah disebutkan sebelumnya bahwa memori yang digunakan memiliki organisasi halaman, saya tidak melihat alasan mengapa Anda tidak harus menggunakan korespondensi "satu halaman - satu blok data terkompresi".


Artinya, ukuran halaman wajar minimum adalah 16Kb (dengan margin untuk informasi layanan). Namun, ukuran halaman sekecil itu memberlakukan batasan signifikan pada ukuran perekaman maksimum.


Meskipun saya masih tidak mengharapkan catatan unit kilobyte lebih banyak dalam bentuk terkompresi, saya memutuskan untuk menggunakan halaman 32KB (total 128 halaman per chip).


Ringkasan:


  • Kami menyimpan data yang dikompres menggunakan zlib (deflate);
  • Untuk setiap catatan, atur Z_SYNC_FLUSH;
  • Untuk setiap catatan terkompresi, kami memotong byte terakhir (misalnya, 0x00, 0x00, 0xff, 0xff) ; di header menunjukkan berapa banyak byte yang kami potong;
  • Kami menyimpan data dalam halaman 32Kb; di dalam halaman ada satu aliran data terkompresi; pada setiap halaman, kami memulai kompresi lagi.

Dan, sebelum selesai dengan kompresi, saya ingin menarik perhatian pada fakta bahwa kami hanya mendapatkan beberapa byte data tulis, jadi sangat penting untuk tidak mengembang informasi layanan, setiap byte dihitung.


Menyimpan Header Data


Karena kita memiliki catatan panjang variabel, kita perlu entah bagaimana menentukan lokasi / batas catatan.


Saya tahu tiga pendekatan:


  1. Semua catatan disimpan dalam aliran kontinu, pertama datang header catatan yang berisi panjang, dan kemudian catatan itu sendiri.
    Dalam perwujudan ini, header dan data mungkin memiliki panjang variabel.
    Bahkan, kami mendapatkan daftar tautan tunggal yang digunakan sepanjang waktu;
  2. Header dan catatan sendiri disimpan di aliran terpisah.
    Menggunakan header dengan panjang konstan, kami memastikan bahwa kerusakan pada satu header tidak mempengaruhi sisanya.
    Pendekatan serupa digunakan, misalnya, dalam banyak sistem file;
  3. Catatan disimpan dalam aliran berkelanjutan, batas catatan ditentukan oleh beberapa penanda (simbol / urutan karakter, yang / yang dilarang di dalam blok data). Jika marker ditemukan di dalam rekaman, maka kami menggantinya dengan urutan tertentu (lepas darinya).
    Pendekatan serupa digunakan, misalnya, dalam protokol PPP.

Saya akan ilustrasikan.


Opsi 1:
Opsi 1
Semuanya sangat sederhana di sini: mengetahui panjang catatan, kita dapat menghitung alamat header berikutnya. Jadi kami bergerak melalui tajuk hingga kami bertemu wilayah yang diisi dengan 0xff (wilayah bebas) atau akhir halaman.


Opsi 2:
Opsi 2
Karena panjang variabel catatan, kami tidak dapat mengatakan sebelumnya berapa banyak catatan (dan karena itu tajuk) per halaman yang kami butuhkan. Anda dapat menyebarkan tajuk dan data itu sendiri ke halaman yang berbeda, tetapi saya lebih suka pendekatan yang berbeda: kami menempatkan tajuk dan data pada halaman yang sama, namun, tajuk (ukuran konstan) berasal dari awal halaman, dan data (panjang variabel) dari akhir. Begitu mereka "bertemu" (tidak ada cukup ruang kosong untuk catatan baru) - kami menganggap halaman ini penuh.


Opsi 3:
Opsi 3
Tidak perlu menyimpan di header panjang atau informasi lain tentang lokasi data, ada cukup penanda yang menunjukkan batas-batas catatan. Namun, data harus diproses saat menulis / membaca.
Sebagai penanda, saya akan menggunakan 0xff (yang halamannya diisi setelah dihapus), sehingga area bebas tidak akan diperlakukan sebagai data.


Tabel perbandingan:


Opsi 1Opsi 2Opsi 3
Toleransi kesalahan-++
Kekompakan+-+
Kompleksitas implementasi*****

Opsi 1 memiliki cacat fatal: jika salah satu header rusak, seluruh rantai kami selanjutnya dihancurkan. Opsi lain memungkinkan Anda memulihkan sebagian data bahkan dengan kerusakan besar.
Tetapi di sini pantas untuk diingat bahwa kami memutuskan untuk menyimpan data dalam bentuk terkompresi, jadi kami kehilangan semua data pada halaman setelah catatan "rusak", jadi meskipun tabelnya minus, kami tidak memperhitungkannya.


Kekompakan:


  • pada versi pertama, kita hanya perlu menyimpan panjang di header, jika bilangan bulat dengan panjang variabel digunakan, maka dalam kebanyakan kasus kita dapat melakukannya dengan satu byte;
  • pada opsi kedua, kita perlu menyimpan alamat awal dan panjangnya; catatan harus berupa ukuran konstan, saya perkirakan 4 byte per record (dua byte per offset, dan dua byte per panjang);
  • , - 1-2%. .

( ). , .


, - - . , , β€” , , ...


: , , .. , , , β€” , .


: " β€” " - .



, , :
.
, erase 1, 1 0, . " " 1, " " β€” 0.


flash:


  1. β€œ ”;
  2. ;
  3. β€œ ”;
  4. ;
  5. β€œ ”.

, β€œ ”, 4 .


β€œ1111” β€” β€œ1000” β€” ; , .


, , , , , ( ) .


: .



( ) , . , , .


, , ( , , β€” ) .


, , , β€” .


β€” CRC. , 100% , β€” 2βˆ’n. , , : , . β€” .


: 1 , 2 ( narod.ru, ) .


, CRC β€” . , .


, .


:
10βˆ’3, :


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

, β€” β€” .


, : , , . , .


, , 32 ( 64 -) .


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


: , 4 ? ? , , .


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


CRC.


crc-32c β€” , CRC .


, , , , .


, , : ?


"" :


  • β€” ( /, , ..);
  • deflate zlib "" , , , ( , zlib ).

"" :


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

.


: CRC-32C, , flash ( ).



, , , , ( ) .


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


, . ?


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

  4. .

( ) . , - .


: , , , ( , ).



, ( ) , , .


  • ""
    - , .., , .
    , , ;
  • .
    β€” !
    Magic Number (), ( , ) ;
  • ( ) , 1 ;
  • .

- . .



Byte order


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



- .


32, , 1/4 ( 4 128 ).


( ).


( ), 0 ( 0, β€” 32, β€” 64 ..)


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



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


( -) :


  • Magic Number ( β€” )
    0xed00 βŠ• ;
  • " " ( ).

( deflate). ( ), . ( ).


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


1, 2 3 , :


  • (T), : 0 β€” , 1 β€” ;
  • (S) 1 7 , "", ;
  • (L).

S:


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

, , :
Entri Judul
T, β€” S, L ( ), β€” , β€” , -.


, ( 63+5 ) .


CRC-32C, (init) .


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


.


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



-


.
β€” - .


( , Linux NOR Flash, )


-


.
.


β€” .



( ) 1.
( UUID ).


, - .



8 ( + CRC), Magic Number CRC .
"" , , .
, CRC, "". β€” . β€” , "" .
, , "" .
zlib ( ).


, , , .



, Z_SYNC_FLUSH., .
( CRC) β€” (. ).
CRC. β€” .



( ). β€” , .
erase. 0xff. - β€” , ..
, , β€” ( ).



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


, "" SLC NOR Flash.


BER, NAND MLC NOR ( ? ) .


, , FTL: USB flash, SD, MicroSD, etc ( 512 , β€” "" ) .


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


- , β€” , , github.


Kesimpulan


, .


, : - , , . , () - .


, ? Ya tentu saja , , . - .


? , , . .


, , " ".


, () , , "" (, , ; ). ( β€” ) .


, .


Sastra


, .


, , , :


  1. infgen zlib. deflate/zlib/gzip. deflate ( gzip) β€” .

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


All Articles