Natív gépi kód .mpy fájlokban¶
Ez a fejezet azt írja le, hogyan lehet olyan .mpy fájlokat építeni és kezelni, amelyek a Pythontól eltérő nyelven írt natív gépi kódot tartalmaznak. Ez lehetővé teszi, hogy a kódot egy olyan nyelven írd meg, mint a C, lefordítsd és összelinkeld egy .mpy fájllá, majd ezt a fájlt egy normál Python modulként importáld. Ezzel olyan funkciókat valósíthatsz meg, amelyek teljesítmény szempontjából kritikusak, vagy beépíthetsz egy másik nyelven írt, már létező könyvtárat.
A natív .mpy fájlok használatának egyik fő előnye, hogy a natív gépi kódot egy szkript dinamikusan importálhatja, anélkül, hogy újra kellene építeni a fő MicroPython firmware-t. Ez ellentétben áll a Külső C modulok a MicroPython számára megoldással, amely szintén lehetővé teszi egyéni modulok definiálását C nyelven, de azokat bele kell fordítani a fő firmware-képbe.
A hangsúly itt a C nyelv használatán van natív modulok építéséhez, de elvileg bármely nyelv, amely önálló gépi kóddá fordítható, elhelyezhető egy .mpy fájlban.
A natív .mpy modul a mpy_ld.py eszközzel épül, amely a projekt tools/ könyvtárában található. Ez az eszköz egy sor objektumfájlt (.o fájlt) vesz, és összelinkeli őket egy natív .mpy fájl létrehozásához. CPython 3-at és a pyelftools könyvtár v0.25 vagy újabb verzióját igényli.
Támogatott funkciók és korlátozások¶
Egy .mpy fájl tartalmazhat MicroPython bájtkódot és/vagy natív gépi kódot. Ha natív gépi kódot tartalmaz, akkor a .mpy fájlhoz egy konkrét architektúra tartozik. A jelenleg támogatott architektúrák a következők (ezek az ARCH változó érvényes opciói, lásd alább):
x86(32 bites)x64(64 bites x86)armv6m(ARM Thumb, pl. Cortex-M0)armv7m(ARM Thumb 2, pl. Cortex-M3)armv7emsp(ARM Thumb 2, egyszeres pontosságú lebegőpontos, pl. Cortex-M4F, Cortex-M7)armv7emdp(ARM Thumb 2, dupla pontosságú lebegőpontos, pl. Cortex-M7)xtensa(nem ablakozott, pl. ESP8266)xtensawin(ablakozott, 8-as ablakmérettel, pl. ESP32, ESP32S3)rv32imc(RISC-V 32 bites, tömörített utasításokkal, pl. ESP32C3, ESP32C6)rv64imc(RISC-V 64 bites, tömörített utasításokkal)
Ha a kiválasztott platform támogat explicit architektúra-jelzőket, és azt szeretnéd, hogy a kimeneti .mpy fájl ezeknek a jelzőknek az értékét hordozza, akkor át kell adnod őket az ARCH_FLAGS jelzőváltozónak a .mpy fájl építésekor.
A natív .mpy fájl fordításakor és linkelésekor ki kell választani az architektúrát, és a megfelelő fájl csak azon az architektúrán importálható (és ha architektúra-jelzők vannak jelen, csak akkor, ha azok megfelelnek a célplatform képességeinek). A .mpy fájlokról bővebben lásd: MicroPython .mpy fájlok.
A natív kódot pozíciófüggetlen kódként (PIC) kell lefordítani, és globális eltolási táblát (GOT) kell használnia, bár ennek részletei architektúránként eltérnek. Natív kódot tartalmazó .mpy fájlok importálásakor az importmechanizmus képes a natív kód néhány alapvető áthelyezésére (relocation). Ez magában foglalja a text, rodata és BSS szakaszok áthelyezését.
A linker és a dinamikus betöltő támogatott funkciói:
végrehajtható kód (text)
csak olvasható adat (rodata), beleértve a sztringeket és a konstans adatokat (tömbök, struktúrák stb.)
nullázott adat (BSS)
mutatók a textben textre, rodatára és BSS-re
mutatók a rodatában textre, rodatára és BSS-re
Az ismert korlátozások a következők:
az adatszakaszok (data sections) nem támogatottak; megkerülő megoldás: használj BSS adatot, és inicializáld az adatértékeket explicit módon
a statikus BSS változók nem támogatottak; megkerülő megoldás: használj globális BSS változókat
a szálhoz kötött tárolási (thread-local storage) változók nem támogatottak az rv32imc architektúrán; megkerülő megoldás: használj globális BSS változókat, vagy foglalj némi helyet a kupacon (heap) a tárolásukhoz
Tehát, ha a C kódod írható adatot tartalmaz, győződj meg róla, hogy az adat globálisan van definiálva, inicializáló nélkül, és csak függvényeken belül kerül írásra.
A natív modul nem linkelődik automatikusan a szabványos statikus könyvtárakhoz, mint a libm.a és a libgcc.a, ami undefined symbol hibákhoz vezethet. A futásidejű könyvtárakat a LINK_RUNTIME = 1 beállításával linkelheted a Makefile-odban. Egyéni statikus könyvtárakat is linkelhetsz az MPY_LD_FLAGS += -l path/to/library.a hozzáadásával. Megjegyzendő, hogy ezek a natív modulba linkelődnek, és nem lesznek megosztva más modulokkal vagy a rendszerrel.
Linker korlátozás: a natív modul nem linkelődik a teljes MicroPython firmware szimbólumtáblájához. Ehelyett az exportált szimbólumoknak a mp_fun_table táblában (a py/nativeglue.h fájlban) található explicit táblájához linkelődik, amely a firmware építésének időpontjában rögzítve van. Így nem lehetséges egyszerűen meghívni egy tetszőleges HAL/OS/RTOS/rendszerfüggvényt, hacsak az nem egy rögzített címen található. Ebben az esetben egy linkerszkript elérési útja, amely szimbólumnevek és rögzített címeik sorozatát tartalmazza, átadható a mpy_ld.py eszköznek a --externs parancssori argumentummal. Így a linkerszkriptben megjelenő szimbólumok elsőbbséget élveznek azzal szemben, amit az objektumfájlok biztosítanak, de jelenleg az objektumfájlok implementációja továbbra is a végső MPY fájlban marad. A linkerszkript-elemző képességei korlátozottak, és jelenleg csak az ESP8266 port ROM szimbólumlistájának elemzésére használatos (lásd ports/esp8266/boards/eagle.rom.addr.v6.ld).
Új szimbólumok adhatók a tábla végéhez, és a firmware újraépíthető. A szimbólumokat hozzá kell adni a tools/mpy_ld.py fun_table szótárához is ugyanazon a helyen. Ez lehetővé teszi, hogy a mpy_ld.py felismerje az új szimbólumokat, és áthelyezéseket biztosítson számukra az mpy importálásakor. Végül, ha a szimbólum egy függvény, egy makrót vagy stubot kell hozzáadni a py/dynruntime.h fájlhoz, hogy a függvény könnyen meghívható legyen.
Natív modul definiálása¶
A natív .mpy modult egy fájlhalmaz definiálja, amelyeket a .mpy építéséhez használnak. A fájlrendszer-elrendezés két fő részből áll: a forrásfájlokból és a Makefile-ból:
A legegyszerűbb esetben csak egyetlen C forrásfájlra van szükség, amely tartalmazza az összes kódot, amely a .mpy modulba fordul. Ennek a C forráskódnak tartalmaznia kell a
py/dynruntime.hfájlt a MicroPython dinamikus API eléréséhez, és legalább egympy_initnevű függvényt kell definiálnia. Ez a függvény lesz a modul belépési pontja, amely a modul importálásakor hívódik meg.A modul kívánság szerint több C forrásfájlra is bontható. A modul egyes részei Pythonban is megvalósíthatók. Minden forrásfájlt fel kell sorolni a Makefile-ban, hozzáadva őket az
SRCváltozóhoz (lásd alább). Ez magában foglalja mind a C forrásfájlokat, mind az esetleges Python fájlokat, amelyek bekerülnek a kapott .mpy fájlba.A
Makefiletartalmazza a modul építési konfigurációját, és felsorolja a .mpy modul építéséhez használt forrásfájlokat. Definiálnia kell azMPY_DIRváltozót a MicroPython tároló helyeként (a fejlécfájlok, a vonatkozó Makefile-töredék és ampy_ld.pyeszköz megtalálásához), aMODváltozót a modul neveként, azSRCváltozót a forrásfájlok listájaként, opcionálisan megadhatja a gépi architektúrát azARCHváltozón keresztül, valamint opcionális gépi architektúra-jelzőket azARCH_FLAGSváltozón keresztül, majd be kell illesztenie apy/dynruntime.mkfájlt.
Minimális példa¶
Ez a szakasz egy teljesen működő példát mutat be egy factorial nevű egyszerű modulra. Ez a modul egyetlen factorial.factorial(x) függvényt biztosít, amely kiszámítja a bemenet faktoriálisát, és visszaadja az eredményt.
Könyvtárelrendezés:
factorial/
├── factorial.c
└── Makefile
A factorial.c fájl tartalma:
// 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
}
A Makefile fájl tartalma:
# 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
A modul fordítása¶
A natív .mpy fájl építéséhez szükséges előfeltétel-eszközök:
A MicroPython tároló (legalább a
py/éstools/könyvtárak).CPython 3, és a pyelftools könyvtár (pl.
pip install 'pyelftools>=0.25').GNU make.
C fordító a célarchitektúrához (ha C forrást használsz).
Opcionálisan
mpy-cross, a MicroPython tárolóból építve (ha .py forrást használsz).
Ügyelj rá, hogy a megfelelő ARCH értéket válaszd ki ahhoz a célplatformhoz, amelyen futtatni fogod. Majd építsd a következővel:
$ make
A Makefile módosítása nélkül a célarchitektúrát a következővel adhatod meg:
$ make ARCH=armv7m
Ugyanez vonatkozik az opcionális architektúra-jelzőkre a következővel:
$ make ARCH=rv32imc ARCH_FLAGS=zba
A modul használata MicroPythonban¶
Miután a modul felépült, létre kell jönnie egy factorial.mpy nevű fájlnak. Másold ezt át úgy, hogy elérhető legyen a MicroPython rendszered fájlrendszerén, és megtalálható legyen az importálási útvonalon. A modul mostantól ugyanúgy elérhető Pythonban, mint bármely más modul, például:
import factorial
print(factorial.factorial(10))
# should display 3628800
Picolibc használata modulok építésekor¶
A Picolibc használata C szabványkönyvtárként nemcsak támogatott, hanem valójában ez az alapértelmezett az rv32imc és rv64imc platformokon. Van azonban néhány említésre méltó dolog, hogy biztosan ne ütközz problémákba később a kód építésekor.
Néhány előre épített Picolibc verzió (például az Ubuntu Linux által biztosítottak a picolibc-arm-none-eabi, picolibc-riscv64-unknown-elf és picolibc-xtensa-lx106-elf csomagokként) azt feltételezi, hogy a szálhoz kötött tárolás (TLS) elérhető futásidőben, de sajnos a MicroPython modulok ezt egyes architektúrákon (nevezetesen az rv32imc és rv64imc architektúrákon) nem támogatják. Ez azt jelenti, hogy a Picolibc által biztosított egyes funkciók alapértelmezetten a TLS-t fogják használni, hibát adva vissza akár a fordítás, akár a linkelés során.
Egy példára, hogy ez hogyan érinthet téged, az examples/natmod/btree példamodul tartalmaz egy megkerülő megoldást annak biztosítására, hogy az errno működjön (keresd a __PICOLIBC_ERRNO_FUNCTION kifejezést a Makefile-ban, és kövesd onnan a nyomot).
További példák¶
Lásd az examples/natmod/ könyvtárat további példákért, amelyek a natív .mpy modulok számos elérhető funkcióját mutatják be. Ilyen funkciók többek között:
több C forrásfájl használata
Python kód beépítése a C kód mellé
rodata és BSS adat
memóriafoglalás
lebegőpontos számok használata
kivételkezelés
külső C könyvtárak beépítése