الشيفرة الآلية الأصلية (Native) في ملفات .mpy¶
يصف هذا القسم كيفية بناء ملفات .mpy التي تحتوي على شيفرة آلية أصلية (native) مكتوبة بلغة غير Python والعمل بها. يتيح لك هذا كتابة الشيفرة بلغة مثل C وترجمتها وربطها في ملف .mpy، ثم استيراد هذا الملف مثل أي وحدة Python عادية. يمكن استخدام هذا لتنفيذ وظائف حرجة من حيث الأداء، أو لتضمين مكتبة موجودة مكتوبة بلغة أخرى.
إحدى المزايا الرئيسية لاستخدام ملفات .mpy الأصلية هي أن الشيفرة الآلية الأصلية يمكن استيرادها بواسطة برنامج نصي ديناميكياً، دون الحاجة إلى إعادة بناء برنامج MicroPython الثابت الرئيسي. وهذا على النقيض من وحدات C الخارجية في MicroPython التي تتيح أيضاً تعريف وحدات مخصصة بلغة C ولكن يجب ترجمتها داخل صورة البرنامج الثابت الرئيسية.
ينصب التركيز هنا على استخدام C لبناء الوحدات الأصلية، ولكن من حيث المبدأ يمكن وضع أي لغة قابلة للترجمة إلى شيفرة آلية مستقلة في ملف .mpy.
تُبنى وحدة .mpy الأصلية باستخدام أداة mpy_ld.py الموجودة في دليل tools/ الخاص بالمشروع. تأخذ هذه الأداة مجموعة من ملفات الكائنات (ملفات .o) وتربطها معاً لإنشاء ملف .mpy أصلي. وهي تتطلب CPython 3 ومكتبة pyelftools الإصدار v0.25 أو أحدث.
الميزات المدعومة والقيود¶
يمكن أن يحتوي ملف .mpy على شيفرة MicroPython الثنائية (bytecode) و/أو شيفرة آلية أصلية. إذا كان يحتوي على شيفرة آلية أصلية فإن ملف .mpy يكون مرتبطاً ببنية معمارية محددة. البنى المدعومة حالياً هي (وهذه هي الخيارات الصالحة لمتغير ARCH، انظر أدناه):
x86(32 بت)x64(x86 بطول 64 بت)armv6m(ARM Thumb، مثل Cortex-M0)armv7m(ARM Thumb 2، مثل Cortex-M3)armv7emsp(ARM Thumb 2، فاصلة عائمة أحادية الدقة، مثل Cortex-M4F وCortex-M7)armv7emdp(ARM Thumb 2، فاصلة عائمة مزدوجة الدقة، مثل Cortex-M7)xtensa(غير منزلق النوافذ، مثل ESP8266)xtensawin(منزلق النوافذ بحجم نافذة 8، مثل ESP32 وESP32S3)rv32imc(RISC-V بطول 32 بت مع تعليمات مضغوطة، مثل ESP32C3 وESP32C6)rv64imc(RISC-V بطول 64 بت مع تعليمات مضغوطة)
إذا كانت المنصة المختارة تدعم رايات بنية معمارية صريحة وأردت أن يحمل ملف .mpy الناتج قيمة تلك الرايات، فيجب تمريرها إلى متغير الرايات ARCH_FLAGS عند بناء ملف .mpy.
عند ترجمة وربط ملف .mpy الأصلي يجب اختيار البنية المعمارية ولا يمكن استيراد الملف المقابل إلا على تلك البنية (وإذا كانت رايات البنية موجودة، فقط إذا تطابقت مع قدرات الهدف). لمزيد من التفاصيل حول ملفات .mpy انظر ملفات .mpy في MicroPython.
يجب ترجمة الشيفرة الأصلية كشيفرة مستقلة عن الموقع (PIC) واستخدام جدول إزاحة عام (GOT)، رغم أن تفاصيل ذلك تختلف من بنية معمارية إلى أخرى. عند استيراد ملفات .mpy التي تحتوي على شيفرة أصلية تكون آلية الاستيراد قادرة على إجراء بعض النقل (relocation) الأساسي للشيفرة الأصلية. ويشمل هذا نقل مقاطع text وrodata وBSS.
الميزات المدعومة في الرابط (linker) والمحمّل الديناميكي هي:
الشيفرة القابلة للتنفيذ (text)
البيانات للقراءة فقط (rodata)، بما في ذلك السلاسل النصية والبيانات الثابتة (المصفوفات والهياكل وما إلى ذلك)
البيانات المصفّرة (BSS)
المؤشرات في text إلى text وrodata وBSS
المؤشرات في rodata إلى text وrodata وBSS
القيود المعروفة هي:
مقاطع البيانات (data) غير مدعومة؛ الحل البديل: استخدم بيانات BSS وقم بتهيئة قيم البيانات صراحةً
متغيرات BSS الساكنة (static) غير مدعومة؛ الحل البديل: استخدم متغيرات BSS العامة (global)
متغيرات التخزين المحلية للخيط (thread-local) غير مدعومة على rv32imc؛ الحل البديل: استخدم متغيرات BSS العامة أو خصص بعض المساحة على الكومة (heap) لتخزينها
لذا، إذا كانت شيفرة C الخاصة بك تحتوي على بيانات قابلة للكتابة، فتأكد من تعريف البيانات بشكل عام (globally)، دون مُهيّئ، وألا تُكتب إلا داخل الدوال.
لا تُربط الوحدة الأصلية تلقائياً بالمكتبات الساكنة القياسية مثل libm.a وlibgcc.a، مما قد يؤدي إلى أخطاء undefined symbol. يمكنك ربط مكتبات وقت التشغيل بضبط LINK_RUNTIME = 1 في ملف Makefile الخاص بك. كما يمكن ربط مكتبات ساكنة مخصصة بإضافة MPY_LD_FLAGS += -l path/to/library.a. لاحظ أن هذه تُربط داخل الوحدة الأصلية ولن تُشارك مع وحدات أخرى أو مع النظام.
قيد الرابط: لا تُربط الوحدة الأصلية بجدول الرموز الخاص ببرنامج MicroPython الثابت الكامل. بل تُربط بجدول صريح من الرموز المُصدَّرة الموجودة في mp_fun_table (في py/nativeglue.h)، وهو ثابت عند وقت بناء البرنامج الثابت. وبالتالي لا يمكن ببساطة استدعاء أي دالة عشوائية من HAL/OS/RTOS/النظام، على سبيل المثال، إلا إذا كانت تقع في عنوان ثابت. في تلك الحالة، يمكن تمرير مسار سكربت ربط (linkerscript) يحتوي على سلسلة من أسماء الرموز وعناوينها الثابتة إلى mpy_ld.py عبر وسيط سطر الأوامر --externs. وبهذه الطريقة تأخذ الرموز الظاهرة في سكربت الربط الأسبقية على ما توفره ملفات الكائنات، ولكن في الوقت الحالي سيظل تنفيذ ملفات الكائنات موجوداً في ملف MPY النهائي. مُحلِّل سكربت الربط محدود في قدراته، ويُستخدم حالياً فقط لتحليل قائمة رموز ROM الخاصة بمنفذ ESP8266 (انظر ports/esp8266/boards/eagle.rom.addr.v6.ld).
يمكن إضافة رموز جديدة إلى نهاية الجدول وإعادة بناء البرنامج الثابت. يجب أيضاً إضافة الرموز إلى قاموس fun_table في tools/mpy_ld.py في الموقع نفسه. يتيح هذا للأداة mpy_ld.py التقاط الرموز الجديدة وتوفير عمليات النقل (relocations) لها عند استيراد ملف mpy. أخيراً، إذا كان الرمز دالة، فينبغي إضافة ماكرو أو كعب (stub) إلى py/dynruntime.h لتسهيل استدعاء الدالة.
تعريف وحدة أصلية¶
تُعرَّف وحدة .mpy الأصلية بمجموعة من الملفات المستخدمة لبناء ملف .mpy. يتكون تخطيط نظام الملفات من جزأين رئيسيين، الملفات المصدرية وملف Makefile:
في أبسط الحالات يلزم ملف مصدري واحد فقط بلغة C، يحتوي على كل الشيفرة التي ستُترجم إلى وحدة .mpy. يجب أن تتضمن شيفرة C المصدرية هذه الملف
py/dynruntime.hللوصول إلى واجهة MicroPython الديناميكية، ويجب أن تُعرّف على الأقل دالة باسمmpy_init. ستكون هذه الدالة نقطة الدخول للوحدة، وتُستدعى عند استيراد الوحدة.يمكن تقسيم الوحدة إلى عدة ملفات مصدرية بلغة C إذا رغبت في ذلك. كما يمكن تنفيذ أجزاء من الوحدة بلغة Python. ينبغي إدراج جميع الملفات المصدرية في ملف Makefile، بإضافتها إلى متغير
SRC(انظر أدناه). يشمل هذا ملفات C المصدرية بالإضافة إلى أي ملفات Python ستُضمَّن في ملف .mpy الناتج.يحتوي ملف
Makefileعلى تكوين البناء للوحدة ويسرد الملفات المصدرية المستخدمة لبناء وحدة .mpy. ينبغي أن يُعرّفMPY_DIRكموقع مستودع MicroPython (للعثور على ملفات الترويسة، وجزء Makefile ذي الصلة، وأداةmpy_ld.py)، وMODكاسم الوحدة، وSRCكقائمة الملفات المصدرية، ويحدد اختيارياً البنية المعمارية عبرARCH، بالإضافة إلى رايات البنية الاختيارية المحددة عبرARCH_FLAGS، ثم يتضمنpy/dynruntime.mk.
مثال بسيط¶
يقدم هذا القسم مثالاً يعمل بالكامل لوحدة بسيطة باسم factorial. توفر هذه الوحدة دالة واحدة factorial.factorial(x) تحسب مضروب المُدخل وتعيد النتيجة.
تخطيط الدليل:
factorial/
├── factorial.c
└── Makefile
يحتوي الملف factorial.c على:
// Include the header file to get access to the MicroPython API
#include "py/dynruntime.h"
// Helper function to compute factorial
static mp_int_t factorial_helper(mp_int_t x) {
if (x == 0) {
return 1;
}
return x * factorial_helper(x - 1);
}
// This is the function which will be called from Python, as factorial(x)
static mp_obj_t factorial(mp_obj_t x_obj) {
// Extract the integer from the MicroPython input object
mp_int_t x = mp_obj_get_int(x_obj);
// Calculate the factorial
mp_int_t result = factorial_helper(x);
// Convert the result to a MicroPython integer object and return it
return mp_obj_new_int(result);
}
// Define a Python reference to the function above
static MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial);
// This is the entry point and is called when the module is imported
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
// This must be first, it sets up the globals dict and other things
MP_DYNRUNTIME_INIT_ENTRY
// Make the function available in the module's namespace
mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj));
// This must be last, it restores the globals dict
MP_DYNRUNTIME_INIT_EXIT
}
يحتوي الملف Makefile على:
# Location of top-level MicroPython directory
MPY_DIR = ../../..
# Name of module
MOD = factorial
# Source files (.c or .py)
SRC = factorial.c
# Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc)
ARCH = x64
# Include to get the rules for compiling and linking the module
include $(MPY_DIR)/py/dynruntime.mk
ترجمة الوحدة¶
الأدوات المُتطلَّبة مسبقاً اللازمة لبناء ملف .mpy أصلي هي:
مستودع MicroPython (على الأقل الدليلان
py/وtools/).CPython 3، ومكتبة pyelftools (مثلاً
pip install 'pyelftools>=0.25').GNU make.
مترجم C للبنية المعمارية المستهدفة (إذا استُخدم مصدر C).
اختيارياً
mpy-cross، المبني من مستودع MicroPython (إذا استُخدم مصدر .py).
احرص على اختيار ARCH الصحيح للهدف الذي ستُشغّل عليه. ثم ابنِ باستخدام:
$ make
دون تعديل ملف Makefile يمكنك تحديد البنية المعمارية المستهدفة عبر:
$ make ARCH=armv7m
ينطبق الأمر نفسه على رايات البنية الاختيارية عبر:
$ make ARCH=rv32imc ARCH_FLAGS=zba
استخدام الوحدة في MicroPython¶
بمجرد بناء الوحدة ينبغي أن يكون هناك ملف باسم factorial.mpy. انسخه بحيث يكون متاحاً على نظام ملفات نظام MicroPython الخاص بك ويمكن العثور عليه في مسار الاستيراد. يمكن الآن الوصول إلى الوحدة في Python مثل أي وحدة أخرى، على سبيل المثال:
import factorial
print(factorial.factorial(10))
# should display 3628800
استخدام Picolibc عند بناء الوحدات¶
استخدام Picolibc كمكتبة C القياسية الخاصة بك ليس مدعوماً فحسب، بل هو في الواقع الافتراضي لمنصتي rv32imc وrv64imc. ومع ذلك، هناك بضعة أمور جديرة بالذكر للتأكد من عدم مواجهتك لمشكلات لاحقاً عند بناء الشيفرة.
بعض إصدارات Picolibc المبنية مسبقاً (على سبيل المثال، تلك التي يوفرها Ubuntu Linux كحزم picolibc-arm-none-eabi وpicolibc-riscv64-unknown-elf وpicolibc-xtensa-lx106-elf) تفترض توفر التخزين المحلي للخيط (TLS) في وقت التشغيل، ولكن لسوء الحظ لا تدعم وحدات MicroPython ذلك على بعض البنى المعمارية (وتحديداً rv32imc وrv64imc). وهذا يعني أن بعض الوظائف التي يوفرها Picolibc ستلجأ افتراضياً إلى استخدام TLS، مما يعيد خطأً إما أثناء الترجمة أو أثناء الربط.
للاطلاع على مثال على كيفية تأثير هذا عليك، تحتوي وحدة المثال examples/natmod/btree على حل بديل للتأكد من عمل errno (ابحث عن __PICOLIBC_ERRNO_FUNCTION في ملف Makefile واتبع الأثر من هناك).
أمثلة إضافية¶
انظر examples/natmod/ للمزيد من الأمثلة التي تعرض العديد من الميزات المتاحة لوحدات .mpy الأصلية. وتشمل هذه الميزات:
استخدام عدة ملفات C مصدرية
تضمين شيفرة Python جنباً إلى جنب مع شيفرة C
بيانات rodata وBSS
تخصيص الذاكرة
استخدام الفاصلة العائمة
معالجة الاستثناءات
تضمين مكتبات C خارجية