ملفات .mpy في MicroPython¶
يُعرّف MicroPython مفهوم ملف .mpy وهو صيغة ملف حاوية ثنائية تحتوي على شيفرة مُصرّفة مسبقًا، ويمكن استيراده مثل أي وحدة .py عادية. يمكن استيراد الملف foo.mpy عبر import foo طالما أمكن العثور على foo.mpy بالطريقة المعتادة من خلال آلية الاستيراد. وعادةً يُبحث في كل دليل مُدرَج في sys.path بالترتيب. وعند البحث في دليل معيّن يُبحث أولًا عن foo.py فإن لم يُعثر عليه يُبحث عن foo.mpy، ثم يستمر البحث في الدليل التالي إن لم يُعثر على أي منهما. وبهذا تكون الأولوية لـ foo.py على foo.mpy.
يمكن أن تحتوي ملفات .mpy هذه على bytecode الذي يُولَّد عادةً من ملفات مصدر Python (ملفات .py) عبر برنامج mpy-cross. وبالنسبة لبعض المعماريات يمكن أن يحتوي ملف .mpy أيضًا على شيفرة آلة أصلية، يمكن توليدها بطرق متنوعة، أبرزها من شيفرة مصدر C.
مُصرِّف mpy-cross¶
mpy-cross هو المُصرِّف العابر الذي يحوّل ملف المصدر .py إلى حاوية ثنائية .mpy جاهزة للاستيراد على الكاميرا. وهو جزء من شجرة مصدر MicroPython (نفسها المستخدمة لبناء البرنامج الثابت للكاميرا)، ويُنشر أيضًا كحزمة pip للاستخدام على الجهاز المضيف دون الحاجة إلى استنساخ البرنامج الثابت بالكامل:
$ pip install --user mpy-cross
أو عبر pipx:
$ pipx install mpy-cross
بمجرد التثبيت، شغّله على ملف مصدر واحد:
$ mpy-cross foo.py
ينتج عن هذا foo.mpy في الدليل الحالي، جاهزًا لنسخه إلى نظام ملفات الكاميرا إلى جانب الوحدات الأخرى أو لتغذيته إلى صورة ROMFS.
أكثر خيارات سطر الأوامر فائدةً:
-o <path>-- مسار الإخراج للملف.mpyالمُولَّد (يكون افتراضيًا اسم ملف الإدخال مع استبدال الامتداد؛ -o -يكتب إلى stdout).
-O<n>-- مستوى التحسين من0إلى3. القيمة الافتراضية0تحافظ على التأكيدات ومواقع المصدر الكاملة؛ بينما3يزيل التأكيدات وسلاسل التوثيق ويعيد كتابة كتلif __debug__. ويتحكم المستوى في نفس سطحmicropython.opt_levelالذي يكشفه وقت التشغيل.
-march=<arch>-- المعمارية الأصلية الهدف للدوال المُزخرفة بـ@nativeو@viper. وهو مطلوب عندما يستخدم المصدر تلك المزخرِفات. ويجب أن تطابق القيمة فئة وحدة المعالجة الدقيقة للكاميرا: اخترها من القائمة التي يطبعهاmpy-cross --help، أو اقرأها من الكاميرا في وقت التشغيل عبرsys.implementation._mpy.
-s <path>-- سلسلة مسار المصدر المضمَّنة في معلومات التصحيح الخاصة بملف.mpy. مفيد عندما يختلف المسار على القرص عن مسار الاستيراد الذي ينبغي أن يظهر تحته الملف في تتبعات الأخطاء.
-X emit=bytecode|native|viper-- اختر المُصدِّر الافتراضي للوحدة بأكملها (بديل على مستوى الدالة عن المزخرِفات@native/@viper).
--version-- يطبع نسخة صيغة.mpyالتي يصدرها هذا الملف الثنائي. ويجب أن يطابق هذا الرقم النسخة التي يدعمها وقت تشغيل الكاميرا (انظر جدول الإصدارات أدناه) وإلا سيرفع الاستيرادValueError('incompatible .mpy file').
شغّل mpy-cross --help للحصول على قائمة الرايات الكاملة.
تكشف حزمة pip أيضًا واجهة برمجة وحدة Python صغيرة بحيث يمكن لبرامج البناء النصية تشغيل المُصرِّف داخل العملية بدلًا من تفريع عملية فرعية يدويًا:
import mpy_cross
mpy_cross.compile('foo.py', dest='build/foo.mpy', opt=3,
march=mpy_cross.NATIVE_ARCH_ARMV7EMSP)
mpy_cross.compile و mpy_cross.run و mpy_cross.mpy_version هي نقاط الدخول الثلاث؛ بينما يحمل mpy_cross.CrossCompileError خرج الخطأ القياسي (stderr) للمُصرِّف عند حدوث خطأ ما. وتطابق ثوابت المعمارية (NATIVE_ARCH_ARMV7EMSP و NATIVE_ARCH_ARMV7EMDP وغيرها) السلاسل التي تقبلها الراية -march.
الإصدارات والتوافق لملفات .mpy¶
قد يكون ملف .mpy معيّن متوافقًا أو غير متوافق مع نظام MicroPython معيّن. ويستند التوافق إلى ما يلي:
نسخة ملف .mpy: يجب أن تطابق نسخة الملف النسخة التي يدعمها النظام الذي يحمّله.
النسخة الفرعية لملف .mpy: إذا كان ملف .mpy يحتوي على شيفرة آلة أصلية فيجب أن تطابق النسخة الفرعية للملف النسخة التي يدعمها النظام الذي يحمّله. أما إن لم تكن هناك شيفرة آلة أصلية في ملف .mpy فيُتجاهل رقم النسخة الفرعية عند التحميل.
بتات العدد الصحيح الصغير: سيتطلب ملف .mpy عددًا أدنى من البتات في small integer ويجب أن يدعم النظام الذي يحمّله هذا العدد من البتات على الأقل.
المعمارية الأصلية: إذا كان ملف .mpy يحتوي على شيفرة آلة أصلية فسيحدد معمارية تلك الشيفرة الآلية ويجب أن يدعم النظام الذي يحمّله تنفيذ شيفرة تلك المعمارية.
إذا كان نظام MicroPython يدعم استيراد ملفات .mpy فسيكون الحقل sys.implementation._mpy موجودًا ويعيد عددًا صحيحًا يُرمّز النسخة (البتات الثمانية الدنيا) والميزات والمعمارية الأصلية.
محاولة استيراد ملف .mpy يفشل في أحد الاختبارات الأربعة الأولى سترفع ValueError('incompatible .mpy file'). ومحاولة استيراد ملف .mpy يفشل في اختبار المعمارية الأصلية (إن كان يحتوي على شيفرة آلة أصلية) سترفع ValueError('incompatible .mpy arch').
إذا فشل استيراد ملف .mpy فجرّب ما يلي:
حدّد نسخة .mpy والرايات التي يدعمها نظام MicroPython لديك بتنفيذ:
import sys sys_mpy = sys.implementation._mpy arch = [None, 'x86', 'x64', 'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp', 'xtensa', 'xtensawin', 'rv32imc', 'rv64imc'][(sys_mpy >> 10) & 0x0F] print('mpy version:', sys_mpy & 0xff) print('mpy sub-version:', sys_mpy >> 8 & 3) print('mpy flags:', end='') if arch: print(' -march=' + arch, end='') if (sys_mpy >> 16) != 0: print(' -march-flags=' + (sys_mpy >> 16), end='') print()
تحقق من صحة ملف .mpy بفحص أول بايتين من الملف. ينبغي أن يكون البايت الأول حرف 'M' كبيرًا، وأن يكون البايت الثاني رقم النسخة، الذي ينبغي أن يطابق نسخة النظام أعلاه. فإن لم يطابق فأعد بناء ملف .mpy.
تحقق مما إذا كانت نسخة .mpy للنظام تطابق النسخة التي يصدرها
mpy-crossالذي استُخدم لبناء ملف .mpy، والتي تُعرف عبرmpy-cross --version. فإن لم تطابق فأعد تصريفmpy-crossمن مستودع Git المستنسخ عند الوسم (أو الهاش) الذي يبلّغ عنهmpy-cross --version.تأكد من أنك تستخدم رايات
mpy-crossالصحيحة، التي تُعرف عبر الشيفرة أعلاه، أو بفحص متغير Makefile باسمMPY_CROSS_FLAGSللمنفذ (port) الذي تستخدمه.إذا كان البايت الثالث من ملف .mpy يحمل البت رقم #6 مضبوطًا، فتحقق مما إذا كانت بتات الراية vuint الخاصة بالمعمارية المُرمَّزة متوافقة مع الهدف الذي تستورد الملف عليه.
يوضح الجدول التالي التطابق بين إصدار MicroPython ونسخة .mpy.
إصدار MicroPython |
نسخة .mpy |
|---|---|
v1.23.0 وما فوق |
6.3 |
v1.22.x |
6.2 |
v1.20 - v1.21.0 |
6.1 |
v1.19.x |
6 |
v1.12 - v1.18 |
5 |
v1.11 |
4 |
v1.9.3 - v1.10 |
3 |
v1.9 - v1.9.2 |
2 |
v1.5.1 - v1.8.7 |
0 |
وللاكتمال، يوضح الجدول التالي إيداع Git في مستودع MicroPython الرئيسي الذي تغيّرت عنده نسخة .mpy.
تغيّر نسخة .mpy |
إيداع Git |
|---|---|
6.2 إلى 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 إلى 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 إلى 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
5 إلى 6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
4 إلى 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 إلى 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 إلى 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 إلى 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 إلى 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
النسخة الأولية 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
الترميز الثنائي لملفات .mpy¶
ملفات .mpy في MicroPython هي صيغة حاوية ثنائية تحتوي على كائنات شيفرة (شيفرة بايت وشيفرة آلة أصلية) مخزّنة داخليًا في تسلسل هرمي متداخل. تُخزَّن شيفرة الوحدة الخارجية أولًا، ثم تتبعها فروعها. وقد يكون لكل فرع فروع إضافية، مثلًا في حالة صنف له توابع، أو دالة تعرّف lambda أو comprehension. وللحفاظ على صغر الملفات مع توفير نطاق واسع من القيم الممكنة، يستخدم مفهوم العدد الصحيح غير المُوقَّع المُرمَّز بصورة متغيرة (vuint) في عدة مواضع. وعلى غرار ترميز UTF-8، يخزّن هذا الترميز 7 بتات لكل بايت مع ضبط البت الثامن (الأكثر دلالة، MSB) إذا تبعه بايت واحد أو أكثر. وتُخزَّن بتات العدد الصحيح غير المُوقَّع في الـ vuint بصيغة الأقل دلالة أولًا (LSB).
يتكوّن المستوى الأعلى لملف .mpy من ثلاثة أجزاء:
الترويسة.
جداول qstr العامة والثوابت العامة.
الشيفرة الخام للنطاق الخارجي للوحدة. ويُنفَّذ هذا النطاق الخارجي عند استيراد ملف .mpy.
يمكنك فحص محتويات ملف .mpy باستخدام mpy-tool.py، على سبيل المثال (يُشغَّل من جذر مستودع MicroPython الرئيسي):
$ ./tools/mpy-tool.py -xd myfile.mpy
الترويسة¶
ترويسة .mpy هي:
الحجم |
الحقل |
|---|---|
بايت |
القيمة 0x4d (الحرف 'M' في ASCII) |
بايت |
رقم النسخة الرئيسية لـ .mpy |
بايت |
رايات الميزات، المعمارية الأصلية، رقم النسخة الفرعية (كانت رايات الميزات في النسخ الأقدم) |
بايت |
عدد البتات في عدد صحيح صغير |
البايت الثالث مقسّم كما يلي (الأكثر دلالة أولًا):
البت |
المعنى |
|---|---|
7 |
محجوز، يجب أن يكون 0 |
6 |
تتبع الترويسةَ راية vuint خاصة بالمعمارية |
5..2 |
رقم المعمارية الأصلية |
1..0 |
رقم النسخة الفرعية |
الرايات الخاصة بالمعمارية¶
إذا كان البت #6 من بايت رايات الميزات في الترويسة مضبوطًا، فستتبع الترويسةَ راية vuint تحتوي على معلومات اختيارية خاصة بالمعمارية. وتعتمد محتويات هذا العدد الصحيح على المعمارية الأصلية التي يستهدفها الملف.
يُستخدم هذا حاليًا لتخزين امتدادات معالج RISC-V التي يحتاجها ملف MPY ليعمل بصورة صحيحة، بخلاف I و M و C و Zicsr. وتُحدَّد النكهات المختلفة من ArmV7 برقم معماريتها الأصلية، لكن إعادة استخدام تلك الآلية ستُعقّد الأمور بالنسبة إلى RV32 و RV64.
ملفات MPY التي تستهدف RV32 أو RV64 ولا تحتاج أي امتدادات معالج معيّنة لا تحتاج إلى توفير عدد صحيح للرايات (إلى جانب ضبط البت المناسب في الترويسة). ويُستخدم غياب قيمة رايات لملفات MPY الخاصة بـ RV32 و RV64 للدلالة على عدم الحاجة إلى أي امتدادات معيّنة، ويوفّر بايتًا واحدًا في الملف الثنائي النهائي.
انظر أيضًا خيار سطر الأوامر -march-flags في كل من mpy-tool.py و mpy-cross، وخيار سطر الأوامر --arch-flags في mpy_ld.py لضبط هذه القيمة عند إنشاء ملفات MPY.
جداول qstr العامة والثوابت العامة¶
يحتوي ملف .mpy على جدول qstr واحد، وجدول كائنات ثوابت واحد. وهذان عامّان لملف .mpy، وتشير إليهما جميع كائنات الشيفرة الخام المتداخلة. ويربط جدول qstr رقم qstr الداخلي (الداخلي لملف .mpy) برقم qstr المُحلَّل لوقت التشغيل الذي يُستورَد إليه ملف .mpy. وهذا يربط ملف .mpy ببقية النظام الذي يُنفَّذ داخله. ويُملأ جدول كائنات الثوابت بالإشارات إلى جميع كائنات الثوابت التي يحتاجها ملف .mpy.
الحجم |
الحقل |
|---|---|
vuint |
عدد سلاسل qstr |
vuint |
عدد كائنات الثوابت |
... |
بيانات qstr |
... |
كائنات الثوابت المُرمَّزة |
عناصر الشيفرة الخام¶
يحتوي عنصر الشيفرة الخام على شيفرة، إما شيفرة بايت أو شيفرة آلة أصلية. ومحتوياته هي:
الحجم |
الحقل |
|---|---|
vuint |
النوع والحجم وما إذا كانت هناك عناصر شيفرة خام فرعية |
... |
الشيفرة (شيفرة بايت أو شيفرة آلة) |
vuint |
عدد عناصر الشيفرة الخام الفرعية (فقط إن كان غير صفري) |
... |
عناصر الشيفرة الخام الفرعية |
يُرمّز أول vuint في عنصر الشيفرة الخام نوع الشيفرة المخزّنة في هذا العنصر (البتان الأقل دلالة)، وما إذا كان لهذه الشيفرة الخام أي فروع (البت الثالث الأقل دلالة)، وطول الشيفرة التي تليها (مقدار ذاكرة RAM المطلوب تخصيصها لها).
بعد الـ vuint تأتي الشيفرة نفسها. وما لم يكن نوع الشيفرة شيفرة viper بإزاحات (relocations)، فهذه الشيفرة بيانات ثابتة ولا تحتاج إلى تعديل.
إذا كان لهذه الشيفرة الخام أي فروع (كما يدل عليه بت في أول vuint)، فبعد الشيفرة يأتي vuint يَعُدّ عدد عناصر الشيفرة الخام الفرعية.
وأخيرًا تُخزَّن أي عناصر شيفرة خام فرعية، بصورة تعاودية.