قبل التاريخ
هناك آلات البيع لدينا تصميم الخاصة. داخل التوت بي وقليلا من الربط على لوحة منفصلة. متصل متقبل عملة ، متقبل فاتورة ، محطة البنك ... برنامج مكتوب ذاتيا يدير كل شيء. تتم كتابة محفوظات العمل بأكملها إلى المجلة على محرك أقراص محمول (MicroSD) ، والتي يتم إرسالها بعد ذلك عبر الإنترنت (باستخدام مودم USB) إلى الخادم ، حيث تتم إضافتها إلى قاعدة البيانات. يتم تحميل معلومات المبيعات في 1 ثانية ، وهناك أيضًا واجهة ويب بسيطة للمراقبة ، إلخ.
أي أن المجلة حيوية - للمحاسبة (هناك إيرادات ، مبيعات ، إلخ) ، والرصد (جميع أنواع الإخفاقات وظروف قاهرة أخرى) ؛ هذا ، يمكنك القول ، كل المعلومات التي لدينا حول هذا الجهاز.
المشكلة
تُظهر محركات أقراص فلاش نفسها كأجهزة غير موثوق بها للغاية. فشلوا مع انتظام تحسد عليه. يؤدي هذا إلى تعطل الجهاز (وسبب ذلك ، لسبب ما ، تعذر نقل المجلة عبر الإنترنت) إلى فقد البيانات.
ليست هذه هي التجربة الأولى في استخدام محركات أقراص الفلاش ، وقبل ذلك كان هناك مشروع آخر يضم أكثر من مئة جهاز تم تخزين المجلة على محركات أقراص USB المحمولة ، وكانت هناك أيضًا مشكلات تتعلق بالموثوقية ، وفي بعض الأحيان كان عدد حالات الفشل في الشهر في العشرات. لقد جربنا محركات أقراص فلاش مختلفة ، بما في ذلك محركات الأقراص ذات العلامات التجارية على ذاكرة SLC ، وبعض الطرز أكثر موثوقية من غيرها ، ولكن استبدال محركات أقراص الفلاش لم يحل المشكلة بشكل جذري.
تحذير! Longrid! إذا لم تكن مهتمًا بـ "لماذا" ، ولكنك مهتم بـ "كيف" فقط ، فيمكنك الانتقال فورًا إلى نهاية المقالة.
قرار
أول ما يتبادر إلى الذهن: التخلي عن MicroSD ، ووضع ، على سبيل المثال ، SSD ، والتمهيد منه. من الممكن نظريًا ، على الأرجح ، ولكنها مكلفة نسبيًا وغير موثوق بها (يتم إضافة محول USB-SATA ؛ في محركات الأقراص ذات الحالة الثابتة في الميزانية ، لا تكون إحصاءات الفشل أيضًا جيدة).
USB HDD أيضا لا يبدو حلا جذابا للغاية.
لذلك ، توصلنا إلى هذا الخيار: اترك التنزيل من MicroSD ، لكن استخدمه في وضع القراءة فقط ، وقم بتخزين سجل التشغيل (والمعلومات الأخرى الفريدة لجهاز معين - الرقم التسلسلي ، معايرة المستشعر ، إلخ) في مكان آخر.
تمت بالفعل دراسة موضوع FS للقراءة فقط للتوت على طول الطريق ، ولن أتناول تفاصيل التنفيذ في هذه المقالة (ولكن إذا كان هناك اهتمام ، فربما سأكتب مقالة مصغرة حول هذا الموضوع) . النقطة الوحيدة التي أود أن أشير إليها: سواء من التجربة الشخصية أو من الاستعراضات التي طبقت بالفعل زيادة في الموثوقية ، هي. نعم ، من المستحيل التخلص تمامًا من الأعطال ، لكن من الممكن تمامًا تقليل عدد مرات تكرارها. نعم ، أصبحت البطاقات موحدة ، مما يسهل إلى حد كبير استبدال موظفي الصيانة.
الجزء الأجهزة
لم يكن هناك شك في اختيار نوع الذاكرة - NOR Flash.
الحجج:
- اتصال بسيط (غالبًا ما يكون ناقل SPI ، تجربة استخدام موجودة بالفعل ، لذلك لا توجد مشكلات "حديدية") ؛
- سعر مثير للسخرية
- بروتوكول التشغيل القياسي (التنفيذ موجود بالفعل في Linux kernel ، إذا كنت ترغب في ذلك ، يمكنك أن تأخذ طرفًا ثالثًا ، والذي يوجد أيضًا ، أو حتى يكتب اسمك الخاص ، الفائدة بسيطة) ؛
- الموثوقية والموارد:
من ورقة البيانات النموذجية: يتم تخزين البيانات لمدة 20 سنة ، و 100000 دورة محو لكل كتلة ؛
من مصادر خارجية: معدل BER منخفض للغاية ، من المفترض أنه لا توجد حاجة إلى رموز تصحيح الأخطاء (في بعض الأوراق تعتبر ECC لـ NOR ، ولكن عادةً ما يكون MLC NOR هناك ، يحدث ذلك) .
دعونا نقدر متطلبات الحجم والموارد.
أريد أن أكون مضمونًا لحفظ البيانات لعدة أيام. يعد ذلك ضروريًا حتى لا تضيع محفوظات المبيعات في حالة حدوث أي مشكلات في الاتصال. سنركز على 5 أيام ، خلال هذه الفترة (حتى مع مراعاة عطلات نهاية الأسبوع والعطل الرسمية) ، يمكننا حل المشكلة.
نكتب الآن حوالي 100 كيلو بايت من المجلات يوميًا (من 3 إلى 4 آلاف سجل) ، ولكن هذا الرقم يتزايد تدريجيًا - التفاصيل تزداد ، تتم إضافة أحداث جديدة. بالإضافة إلى ذلك ، هناك في بعض الأحيان رشقات نارية (بعض أجهزة الاستشعار تبدأ في إرسال رسائل غير مرغوب فيها بإيجابيات كاذبة ، على سبيل المثال) سنقوم بحساب 10 آلاف سجل من 100 بايت - ميغابايت في اليوم الواحد.
ما مجموعه 5 ميغابايت من البيانات النظيفة (جيد الانضغاط) يخرج. هم أيضا (تقدير تقريبي) 1MB من بيانات الخدمة.
وهذا يعني أننا نحتاج إلى رقاقة 8 ميجابايت إذا كنت لا تستخدم الضغط ، أو 4 ميغابايت إذا كنت تستخدمه. أعداد حقيقية تماما لهذا النوع من الذاكرة.
بالنسبة للمورد: إذا كنا نخطط لإعادة كتابة الذاكرة بالكامل أكثر من مرة واحدة كل 5 أيام ، في 10 سنوات من الخدمة ، نحصل على أقل من ألف دورة إعادة كتابة.
أذكر ، الشركة المصنعة وعود مائة ألف.
قليلا عن NOR مقابل NANDاليوم ، بالطبع ، أصبحت ذاكرة NAND أكثر شيوعًا ، لكن بالنسبة لهذا المشروع ، لن أستخدمها: تتطلب NAND ، على عكس NOR ، بالضرورة استخدام رموز تصحيح الأخطاء ، وجدول الكتل السيئة ، وما إلى ذلك ، وأرجل شرائح NAND عادة أكثر من ذلك بكثير.
عيوب NOR تشمل:
- حجم صغير (وبالتالي ، ارتفاع سعر لكل ميغابايت) ؛
- انخفاض سعر الصرف (يرجع إلى حد كبير إلى حقيقة أن واجهة تسلسلية تستخدم ، عادة SPI أو I2C) ؛
- مسح بطيء (حسب حجم الكتلة ، يستغرق من كسور من ثانية إلى عدة ثوان).
يبدو أن لا شيء حرج بالنسبة لنا ، لذلك تابع.
إذا كانت التفاصيل مثيرة للاهتمام ، فقد تم اختيار شريحة at25df321a (ومع ذلك ، فإن هذا غير مهم ، فهناك الكثير من النظائر في السوق التي تتوافق مع نظام pinout ونظام الأوامر ؛ وحتى إذا أردنا وضع شريحة مصنع آخر و / أو وحدة تخزين أخرى ، فسيعمل كل شيء دون تغيير الكود) .
أستخدم برنامج التشغيل المضمن في Linux kernel ، على Raspberry ، بفضل دعم تراكب شجرة الجهاز ، كل شيء بسيط جدًا - تحتاج إلى وضع التراكب المترجم في / boot / تراكب وتعديل / boot /config.txt قليلاً.
مثال ملف dtsبصراحة ، لست متأكدًا مما كتب بدون أخطاء ، لكنه يعمل.
/* * 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?"; }; };
وخط آخر في config.txt dtoverlay=at25:spimaxfrequency=50000000
سأحذف وصف توصيل الشريحة بـ Raspberry Pi. من ناحية ، أنا لست خبيراً في مجال الإلكترونيات ، من ناحية أخرى ، كل شيء تافه حتى بالنسبة لي: الدائرة الصغيرة بها 8 أرجل فقط ، نحتاج إلى الأرض ، والطاقة ، SPI (CS ، SI ، SO ، SCK) ؛ تتزامن المستويات مع مستويات Raspberry Pi ، لا يلزم الربط الإضافي - فقط قم بتوصيل جهات الاتصال الـ 6 المحددة.
بيان المشكلة
كالعادة ، تمر صياغة المشكلة بعدة تكرارات ، يبدو لي أن الوقت قد حان للتالي. لذلك دعونا نتوقف ، ونجمع ما كتب بالفعل ، ونوضح التفاصيل المتبقية في الظل.
لذلك ، قررنا أن السجل سيتم تخزينه في SPI NOR Flash.
ما هو NOR Flash لأولئك الذين لا يعرفونهذه هي ذاكرة غير متقلبة يمكنك من خلالها إجراء ثلاث عمليات:
- القراءة:
القراءة الأكثر شيوعًا: نعبر العنوان ونقرأ ما نحتاج إليه من وحدات البايت ؛ - تسجيل:
تبدو الكتابة إلى فلاش NOR وكأنها واحدة عادية ، ولكنها تتميز بخصوصية واحدة: لا يمكنك تغيير من 1 إلى 0 ، ولكن ليس العكس. على سبيل المثال ، إذا كان لدينا 0x55 في خلية الذاكرة ، ثم بعد كتابة 0x0f لها ، سيتم تخزين 0x05 بالفعل هناك (انظر الجدول أدناه) ؛ - مسح:
بالطبع ، نحن بحاجة إلى أن نكون قادرين على القيام بالعملية العكسية أيضًا - تغيير 0 إلى 1 ، وهذا هو السبب في وجود عملية المسح. بخلاف الأولين ، لا يعمل بالبايتات ، لكن في الكتل (الحد الأدنى لمحو المحو في الدائرة الدقيقة المحددة هو 4 كيلو بايت). محو يدمر الكتلة بأكملها وهذه هي الطريقة الوحيدة لتغيير 0 إلى 1. لذلك ، عند العمل مع ذاكرة الفلاش ، غالبًا ما يتعين عليك محاذاة بنيات البيانات إلى حد كتلة المحو.
سجل في NOR Flash:
تمثل المجلة نفسها سلسلة من السجلات ذات الطول المتغير. يبلغ طول التسجيل النموذجي حوالي 30 بايت (على الرغم من حدوث تسجيلات لعدة كيلو بايت في الطول في بعض الأحيان). في هذه الحالة ، نعمل معهم تمامًا مثل مجموعة من وحدات البايت ، ولكن إذا كنت مهتمًا ، يتم استخدام CBOR داخل السجلات.
بالإضافة إلى المجلة ، نحتاج إلى تخزين بعض معلومات "التوليف" ، سواء تم تحديثها أم لا: معرف جهاز معين ، معايرة المستشعر ، علامة "تم تعطيل الجهاز مؤقتًا" ، إلخ.
هذه المعلومات هي عبارة عن مجموعة من السجلات ذات القيمة الأساسية ، والتي يتم تخزينها أيضًا في CBOR. ليس لدينا الكثير من هذه المعلومات (بضعة كيلوبايت كحد أقصى) ، يتم تحديثها بشكل غير منتظم.
في المستقبل ، سوف نسميها السياق.
إذا كنت تتذكر المكان الذي بدأت فيه هذه المقالة ، فمن المهم للغاية ضمان موثوقية تخزين البيانات والتشغيل المستمر ، إن أمكن ، حتى في حالة تعطل الأجهزة / تلف البيانات.
ما هي مصادر المشاكل التي يمكن النظر فيها؟
- أطفئ الطاقة أثناء عمليات الكتابة / المسح. هذا من فئة "ضد الخردة لا استقبال".
معلومات من المناقشة حول stackexchange: عند إيقاف تشغيل الطاقة أثناء العمل باستخدام الفلاش ، يؤدي هذا المحو (الإعداد إلى 1) ، والكتابة (الإعداد إلى 0) إلى سلوك غير محدد: يمكن كتابة البيانات أو كتابتها جزئيًا (على سبيل المثال ، نقلنا 10 بايت / 80 بت و 45 بت فقط يُمكن تسجيلها) ، من الممكن أيضًا أن تكون بعض البتات في الحالة "الوسيطة" (يمكن أن ينتج عن القراءة 0 أو 1) ؛ - أخطاء ذاكرة الفلاش نفسها.
BER ، على الرغم من أنه منخفض جدًا ، لا يمكن أن يساوي الصفر ؛ - أخطاء الحافلة
البيانات المرسلة عبر SPI ليست محمية بأي شكل من الأشكال ، فقد تحدث بشكل جيد كأخطاء بت واحدة أو أخطاء التزامن - فقدان أو إدخال وحدات البت (مما يؤدي إلى تشويه هائل للبيانات) ؛ - أخطاء أخرى / فشل
أخطاء في الكود ، "خلل" التوت ، تدخل أجنبي ...
قمت بصياغة متطلبات ، والتي أرى أن تحقيقها ضروري لضمان الموثوقية:
- يجب كتابة السجلات على ذاكرة الفلاش فورًا ، ولا يتم النظر في التسجيل ؛ - في حالة حدوث خطأ ، يجب اكتشافه ومعالجته في أقرب وقت ممكن ؛ - يجب على النظام ، إن أمكن ، استرداد من الأخطاء.
(مثال من الحياة "كما لا ينبغي أن يكون" ، والتي ، حسب اعتقادي ، اجتمع الجميع: بعد إعادة التشغيل في حالات الطوارئ ، "انقطع" نظام الملفات ولم يتم تشغيل نظام التشغيل)
الأفكار ، النهج ، الأفكار
عندما بدأت أفكر في هذه المهمة ، ظهرت مجموعة من الأفكار في رأسي ، على سبيل المثال:
- استخدام ضغط البيانات
- استخدم هياكل بيانات صعبة ، على سبيل المثال ، قم بتخزين رؤوس السجلات بشكل منفصل عن السجلات نفسها ، بحيث إذا حدث خطأ في سجل ، يمكنك قراءة الباقي دون مشاكل
- استخدام حقول البت للتحكم في اكتمال التسجيل عند إيقاف تشغيل الطاقة ؛
- تخزين اختباري لكل شيء وكل شيء ؛
- استخدام نوع من الترميز تصحيح الأخطاء.
تم استخدام بعض هذه الأفكار ، والبعض الآخر قرر الرفض. دعنا نذهب في النظام.
ضغط البيانات
الأحداث التي نسجلها في المجلة نفسها متشابهة ومتكررة ("ألقيت عملة مكونة من 5 روبل ،" "تم النقر على زر تسليم التغيير" ، ...). لذلك ، يجب أن يكون الضغط فعالا للغاية.
الحمل الضاغط غير مهم (المعالج الذي لدينا قوي للغاية ، حتى في أول Pi كان هناك نواة واحدة بتردد 700 ميجاهرتز ، في الطرز الحالية كان هناك عدة نوى بتردد أكثر من غيغا هرتز) ، وسرعة التبادل مع التخزين منخفضة (عدة ميغابايت في الثانية) ، وحجم السجل صغير. بشكل عام ، إذا كان الضغط سيؤثر على الأداء ، فعندئذ سيكون إيجابيًا فقط (غير نقدي تمامًا ، فقط ذكر) . بالإضافة إلى ذلك ، ليس لدينا نظام Linux مضمن حقيقي ، ولكن نظام Linux عاديًا - لذلك لا ينبغي أن يتطلب التنفيذ الكثير من الجهد (فقط قم بربط المكتبة واستخدم العديد من الوظائف منها).
تم أخذ جزء من السجل من جهاز العمل (1.7 ميجابايت ، 70 ألف سجل) ، وتم التحقق من البداية للتأكد من انضغاطه باستخدام gzip ، lz4 ، lzop ، bzip2 ، xz ، zstd المتاحة على الكمبيوتر.
- أظهر gzip ، xz ، zstd نتائج مماثلة (40 كيلو بايت).
لقد فوجئت بأن xz الأنيق أظهر نفسه هنا على مستوى gzip أو zstd ؛ - أعطى lzip مع الإعدادات الافتراضية نتيجة أسوأ قليلا ؛
- أظهر lz4 و lzop نتائج جيدة جدًا (150 كيلو بايت) ؛
- أظهر bzip2 نتائج جيدة بشكل مدهش (18 كيلو بايت).
لذلك يتم ضغط البيانات بشكل جيد للغاية.
لذلك (إذا لم نعثر على عيوب قاتلة) يجب أن يكون هناك ضغط! لمجرد احتواء المزيد من البيانات على نفس محرك الأقراص المحمول.
دعونا نفكر في العيوب.
المشكلة الأولى: لقد اتفقنا بالفعل على أن كل سجل يجب أن يحصل على الفور. عادةً ما يقوم أرشيف الأرشيف بتجميع البيانات من دفق الإدخال حتى يقرر أن الوقت قد حان للكتابة إلى الإخراج. نحن بحاجة إلى الحصول على كتلة بيانات مضغوطة على الفور وحفظها في ذاكرة غير متقلبة.
أرى ثلاث طرق:
- ضغط كل إدخال باستخدام ضغط القاموس بدلاً من الخوارزميات التي تمت مناقشتها أعلاه.
إنه خيار عملي ، لكني لا أحب ذلك. لضمان مستوى لائق أو أكثر من الضغط ، يجب أن "يتم تشديد" القاموس لبيانات محددة ، أي تغيير سيؤدي إلى حقيقة أن مستوى الضغط ينخفض بشكل كارثي. نعم ، تم حل المشكلة عن طريق إنشاء إصدار جديد من القاموس ، ولكن هذا صداع - سنحتاج إلى تخزين جميع إصدارات القاموس ؛ في كل إدخال سنحتاج إلى الإشارة إلى أي إصدار من القاموس تم ضغطه ... - ضغط كل إدخال مع الخوارزميات "الكلاسيكية" ، ولكن بشكل مستقل عن الآخرين.
خوارزميات الضغط قيد النظر ليست مصممة للعمل مع سجلات بهذا الحجم (عشرات البايت) ، سيكون معامل الضغط أقل بوضوح من 1 (أي زيادة في كمية البيانات بدلاً من الضغط) ؛ - هل تتدفق بعد كل تسجيل.
العديد من مكتبات الضغط لديها دعم لـ FLUSH. هذا هو أمر (أو معلمة لإجراء الضغط) ، وعند استلامه يقوم الأرشيف بإنشاء دفق مضغوط بحيث يمكن على أساسه استعادة جميع البيانات غير المضغوطة التي تم استلامها بالفعل. مثل هذا التناظرية من sync
في أنظمة الملفات أو commit
في مزود.
الأهم من ذلك ، ستكون عمليات الضغط اللاحقة قادرة على استخدام القاموس المتراكم ولن تعاني نسبة الضغط بنفس قدر المعاناة في الإصدار السابق.
أعتقد أنه من الواضح أنني اخترت الخيار الثالث ، فلنتناوله بمزيد من التفاصيل.
كان هناك مقال عظيم عن فلوش في زليب.
لقد أجريت اختبارًا مدفوعًا استنادًا إلى المقال ، وأخذت 70 ألف إدخالات يومية من جهاز حقيقي ، بحجم صفحة 60 كيلو بايت (سنعود إلى حجم الصفحة) :
للوهلة الأولى ، كان السعر المقدم من FLUSH مرتفعًا للغاية ، ولكن في الواقع لدينا خيار ضعيف - إما عدم الضغط على الإطلاق ، أو الضغط (وبكفاءة عالية) باستخدام FLUSH. لا تنس أن لدينا 70 ألف سجل ، فالتكرار المقدم من Z_PARTIAL_FLUSH هو فقط 4-5 بايت لكل سجل. وتبين أن نسبة الضغط تبلغ 5: 1 تقريبًا ، وهي أكثر من نتيجة ممتازة.
قد يبدو الأمر غير متوقع ، ولكن في الواقع Z_SYNC_FLUSH هو وسيلة أكثر فعالية للقيام FLUSHفي حالة استخدام Z_SYNC_FLUSH ، ستكون دائمًا البايتات الأربع الأخيرة من كل سجل هي 0x00 ، 0x00 ، 0xff ، 0xff. وإذا كنا نعرفهم ، فلا يمكننا تخزينهم ، وبالتالي فإن الحجم الكلي هو 324 كيلو بايت فقط.
المقالة التي أشير إليها تحتوي على شرح:
يتم إلحاق كتلة نوع 0 جديدة مع محتويات فارغة.
تتكون كتلة type 0 ذات محتويات فارغة من:
- رأس كتلة ثلاثة بت ؛
- 0 إلى 7 بت تساوي الصفر ، لتحقيق محاذاة البايت ؛
- تسلسل أربعة بايت 00 00 FF FF.
كما ترون ، في الكتلة الأخيرة قبل هذه البايتات الأربعة تأتي من 3 إلى 10 بتات. ومع ذلك ، فقد أظهرت الممارسة أن صفر بت هي في الواقع 10 على الأقل.
اتضح أن كتل البيانات القصيرة هذه عادة ما تكون (دائمًا؟) مشفرة باستخدام كتلة من النوع 1 (كتلة ثابتة) ، والتي تنتهي بالضرورة بـ 7 بتات صفرية ، وبالتالي نحصل على 10-17 بت صفرية مضمونة (والباقي سيكون صفرا باحتمالية حوالي 50 ٪).
لذلك ، في بيانات الاختبار ، في 100٪ من الحالات ، قبل 0x00 ، 0x00 ، 0xff ، 0xff ، يوجد بايت واحد ، وأكثر من الحالة الثالثة يوجد وحدتي بايت صفر (ربما تكون الحقيقة هي أنني أستخدم CBOR الثنائية ، وعند استخدام النص CBOR من المرجح أن تفي JSON بكتل من النوع 2 - الكتلة الديناميكية ، على التوالي ، ستحدث الكتل دون صفر بايت إضافية قبل 0x00 ، 0x00 ، 0xff ، 0xff) .
يمكن أن يصل إجمالي بيانات الاختبار المتاحة إلى أقل من 250 كيلو بايت من البيانات المضغوطة.
يمكنك حفظ أكثر بقليل عن طريق إجراء بتات juggling: الآن نتجاهل وجود عدة بتات صفرية في نهاية الكتلة ، كما لا تتغير عدة بتات في بداية الكتلة ...
لكن بعد ذلك اتخذت قرارًا قويًا بالإرادة للتوقف ، وإلا فبإمكانك الوصول بسرعة إلى تطوير ملف الأرشيف الخاص بك.
في المجموع ، حصلت على 3-4 بايت لكل سجل من بيانات الاختبار الخاصة بي ، وكانت نسبة الضغط أكثر من 6: 1. بصراحة ، لم أعول على مثل هذه النتيجة ، في رأيي ، كل شيء أفضل من 2: 1 هو بالفعل نتيجة تبرر استخدام الضغط.
كل شيء على ما يرام ، ولكن لا يزال zlib (فرغ) ممات خوارزمية ضغط مستحقة وجيدة الطراز. مجرد حقيقة أن آخر 32 كيلو بايت من دفق البيانات غير المضغوطة يتم استخدامه كقاموس يبدو غريباً اليوم (أي ، إذا كانت بعض كتل البيانات تشبه إلى حد كبير ما كان في دفق الإدخال 40 كيلو بايت مرة أخرى ، فسوف تبدأ في الأرشفة مرة أخرى ، ولكنها لن الرجوع إلى الإدخال الماضي). ال من المألوف غالبًا ما يتم قياس قاموس حجم المحفوظات الحديث بالميغابايت بدلاً من الكيلوبايت.
لذلك نواصل دراستنا المصغرة للأرشيفين.
تم اختبار bzip2 التالي (أذكر ، بدون FLUSH أظهر نسبة ضغط رائعة ، تقريبًا 100: 1). للأسف ، مع FLUSH أظهرت نفسها بشكل سيء للغاية ، كان حجم البيانات المضغوطة أكبر من غير المضغوطة.
افتراضاتي حول أسباب الفشليقدم Libbz2 خيار تدفق واحد فقط ، والذي يبدو أنه يمسح القاموس (على غرار Z_FULL_FLUSH في zlib) ، وليس هناك سبب للحديث عن نوع من الضغط الفعال.
وكان zstd آخر لفحصها. اعتمادًا على المعلمات ، يتم ضغطها إما على مستوى gzip ، ولكن بشكل أسرع ، أو gzip أفضل.
للأسف ، مع FLUSH ، أثبت أنه "ليس غاية": فقد بلغ حجم البيانات المضغوطة حوالي 700 كيلو بايت.
لقد طرحت سؤالًا على صفحة المشروع في جيثب ، تلقيت إجابة تفيد أن الأمر يستحق الاعتماد على ما يصل إلى 10 بايت من بيانات الخدمة لكل كتلة من البيانات المضغوطة ، والتي هي قريبة من النتائج ، لا يعمل اصطياد فرغ.
قررت التوقف عن ذلك في التجارب التي أجريت مع المحفوظات (أذكرك أن xz ، و lzip ، و lzo ، و lz4 ، لم يظهروا أنفسهم في مرحلة الاختبار بدون FLUSH ، لكنني لم أفكر في المزيد من خوارزميات الضغط الغريبة).
نعود إلى مشاكل الأرشفة.
المشكلة الثانية (كما يقولون بالترتيب ، ولكن ليس في القيمة) - البيانات المضغوطة هي دفق واحد يتم إرساله باستمرار إلى الأقسام السابقة. وبالتالي ، عند تلف جزء من البيانات المضغوطة ، فإننا لا نفقد فقط كتلة البيانات غير المضغوطة المرتبطة بها ، ولكن أيضًا كل البيانات اللاحقة.
هناك طريقة لحل هذه المشكلة:
- منع حدوث مشكلة - إضافة التكرار إلى البيانات المضغوطة ، مما سيسمح بتحديد الأخطاء وتصحيحها ؛ سنتحدث عن هذا لاحقا
- تقليل العواقب في حالة وجود مشكلة
لقد قلنا سابقًا أنه من الممكن ضغط كل كتلة بيانات بشكل مستقل ، وستختفي المشكلة من تلقاء نفسها (يؤدي تلف البيانات في كتلة واحدة إلى فقد بيانات هذه الكتلة فقط). ومع ذلك ، هذه حالة متطرفة يكون فيها ضغط البيانات غير فعال. الطرف المعاكس: استخدم كل 4 ميغا بايت من الدائرة المصغرة الخاصة بنا كأرشيف واحد ، مما سيعطينا ضغطًا رائعًا ، لكن عواقب وخيمة في حالة تلف البيانات.
نعم ، هناك حاجة إلى حل وسط من حيث الموثوقية. ولكن يجب أن نتذكر أننا نقوم بتطوير تنسيق تخزين بيانات للذاكرة غير المتطايرة مع معدل BER منخفض للغاية وفترة تخزين بيانات معلنة تبلغ 20 عامًا.
في أثناء التجارب ، وجدت أن هناك أكثر أو أقل من الخسائر الملحوظة في مستوى الضغط تبدأ في كتل البيانات المضغوطة بحجم أقل من 10 كيلو بايت.
لقد ذكرنا سابقًا أن الذاكرة المستخدمة بها تنظيم صفحة ، لا أرى أي سبب يمنعك من استخدام مراسلات "صفحة واحدة - كتلة واحدة من البيانات المضغوطة".
أي أن الحد الأدنى لحجم الصفحة المعقول هو 16 كيلو بايت (بهامش لمعلومات الخدمة). ومع ذلك ، فإن حجم الصفحة الصغير هذا يفرض قيودًا كبيرة على الحد الأقصى لحجم التسجيل.
على الرغم من أنني ما زلت لا أتوقع سجلات المزيد من وحدات الكيلوبايت في شكل مضغوط ، فقد قررت استخدام صفحات بحجم 32 كيلوبايت (أي ما مجموعه 128 صفحة لكل شريحة).
خلاصة القول:
- نقوم بتخزين البيانات المضغوطة باستخدام zlib (deflate)؛
- لكل سجل ، قم بتعيين Z_SYNC_FLUSH ؛
- لكل سجل مضغوط ، نقوم بقص البايتات النهائية (على سبيل المثال ، 0x00 ، 0x00 ، 0xff ، 0xff) ؛ في الرأس تشير إلى عدد البايتات التي نقطعها ؛
- نقوم بتخزين البيانات في صفحات 32 كيلو بايت. داخل الصفحة هناك دفق واحد من البيانات المضغوطة. في كل صفحة ، نبدأ الضغط مرة أخرى.
وقبل الانتهاء من الضغط ، أود أن أسترعي الانتباه إلى حقيقة أننا نحصل على عدد قليل من وحدات بايت من بيانات الكتابة ، لذلك من المهم للغاية عدم تضخيم معلومات الخدمة ، يتم حساب كل بايت.
تخزين رؤوس البيانات
نظرًا لأن لدينا سجلات ذات طول متغير ، نحتاج إلى تحديد موقع / حدود السجلات بطريقة أو بأخرى.
أعرف ثلاثة طرق:
- يتم تخزين جميع السجلات في دفق مستمر ، ويأتي أولاً رأس السجل الذي يحتوي على الطول ، ثم السجل نفسه.
في هذا النموذج ، قد يكون للرأس والبيانات طول متغير.
في الواقع ، نحصل على قائمة مفردة يتم استخدامها طوال الوقت ؛ - يتم تخزين الرؤوس والسجلات نفسها في تدفقات منفصلة.
باستخدام رؤوس ذات طول ثابت ، نضمن أن الضرر الذي يلحق برأس واحد لا يؤثر على البقية.
يتم استخدام نهج مماثل ، على سبيل المثال ، في العديد من أنظمة الملفات ؛ - يتم تخزين السجلات في دفق مستمر ، ويتم تحديد حدود السجل من خلال بعض العلامات (رمز / تسلسل الأحرف ، والذي هو / ممنوع داخل كتل البيانات). إذا تم العثور على علامة داخل السجل ، فإننا نستبدلها بتسلسل معين (هربًا منها).
يتم استخدام نهج مماثل ، على سبيل المثال ، في بروتوكول PPP.
سأوضح.
الخيار 1:

كل شيء بسيط للغاية هنا: معرفة طول السجل ، يمكننا حساب عنوان العنوان التالي. لذلك نتحرك خلال الرؤوس حتى نلتقي بمنطقة مليئة 0xff (منطقة حرة) أو نهاية الصفحة.
الخيار 2:

نظرًا للطول المتغير للسجل ، لا يمكننا أن نذكر مقدمًا عدد السجلات (وبالتالي الرؤوس) لكل صفحة نحتاجها. يمكنك نشر الرؤوس والبيانات نفسها في صفحات مختلفة ، لكنني أفضل طريقة مختلفة: نضع الرؤوس والبيانات في نفس الصفحة ، ومع ذلك ، فإن الرؤوس (ذات الحجم الثابت) تأتي من بداية الصفحة ، والبيانات (ذات الطول المتغير) من النهاية. بمجرد "الالتقاء" (لا توجد مساحة خالية كافية لسجل جديد) - نعتبر هذه الصفحة ممتلئة.
الخيار 3:

ليست هناك حاجة لتخزين طول العنوان أو المعلومات الأخرى حول موقع البيانات ، فهناك علامات كافية تشير إلى حدود السجلات. ومع ذلك ، يجب معالجة البيانات أثناء الكتابة / القراءة.
كعلامة ، أود استخدام 0xff (التي تمتلئ الصفحة بعد محوها) ، وبالتالي لن يتم التعامل مع المنطقة الحرة كبيانات بالتأكيد.
جدول المقارنة:
يحتوي الخيار 1 على عيب قاتل: في حالة تلف أي من الرؤوس ، يتم تدمير سلسلة السلسلة اللاحقة بالكامل. تتيح لك الخيارات الأخرى استرداد جزء من البيانات حتى مع حدوث أضرار جسيمة.
لكن من المناسب هنا التذكير بأننا قررنا تخزين البيانات في نموذج مضغوط ، وبالتالي فقدنا جميع البيانات الموجودة على الصفحة بعد السجل "المعطوب" ، لذلك على الرغم من أن الجدول ناقص ، إلا أننا لا نأخذ ذلك في الاعتبار.
الميثاق:
- في الإصدار الأول ، نحتاج إلى تخزين الطول في الرأس فقط ، إذا تم استخدام أعداد صحيحة من الطول المتغير ، فيمكننا في معظم الحالات القيام بايت واحد ؛
- في الخيار الثاني ، نحتاج إلى تخزين عنوان البداية والطول ؛ يجب أن يكون السجل بحجم ثابت ، وأقدر 4 بايت لكل سجل (وحدتي بايت لكل إزاحة ، واثنين من وحدات البايت لكل طول) ؛
- , - 1-2%. .
( ). , .
, - - . , , — , , ...
: , , .. , , , — , .
: " — " - .
, , :
.
, erase 1, 1 0, . " " 1, " " — 0.
flash:
- “ ”;
- ;
- “ ”;
- ;
- “ ”.
, “ ”, 4 .
“1111” — “1000” — ; , .
, , , , , ( ) .
: .
( ) , . , , .
, , ( , , — ) .
, , , — .
— CRC. , 100% , — . , , : , . — .
: 1 , 2 ( narod.ru, ) .
, CRC — . , .
, .
:
, :
, — — .
, : , , . , .
, , 32 ( 64 -) .
, , , - 32- (16 , 0.01%; 24 , , ).
: , 4 ? ? , , .
, CRC-32C.
6 22 (, c), 4 655 ( ), 2 .
, , : ?
"" :
- — ( /, , ..);
- deflate zlib "" , , , ( , zlib ).
"" :
- CRC "" , - ( , , , "" );
- , , .
.
: CRC-32C, , flash ( ).
, , , , ( ) .
, .
, - , RAID-6 .
, , , .
, . ?
- ( - , Raspberry, ...)
, ; - ( - flash- , )
, ; - ;
.
( ) . , - .
: , , , ( , ).
, ( ) , , .
- ""
- , .., , .
, , ; - .
— !
Magic Number (), ( , ) ; - ( ) , 1 ;
- .
- . .
Byte order
, , big-endian (network byte order), 0x1234 0x12, 0x34.
- .
32, , 1/4 ( 4 128 ).
( ).
( ), 0 ( 0, — 32, — 64 ..)
(ring buffer), 0, 1, ..., , .

4- , (CRC-32C), ", , ".
( -) :
- Magic Number ( — )
0xed00 ⊕
; - " " ( ).
( deflate). ( ), . ( ).
Z_SYNC_FLUSH, 4 0x00, 0x00, 0xff, 0xff, , , .
( 4, 5 6 ) -.
1, 2 3 , :
- (T), : 0 — , 1 — ;
- (S) 1 7 , "", ;
- (L).
S:
, , :

T, — S, L ( ), — , — , -.
, ( 63+5 ) .
CRC-32C, (init) .
CRC "", (- ) : .
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.
استنتاج
, .
, : - , , . , () - .
, ? نعم بالطبع. , , . - .
? , , . .
, , " ".
, () , , "" (, , ; ). ( — ) .
, .
أدب
, .
, , , :
- infgen zlib. deflate/zlib/gzip. deflate ( gzip) — .