.mpy 文件中的本机机器代码¶
本节介绍如何构建和使用包含非 Python 语言的本机机器代码的 .mpy 文件。这允许您使用 C 等语言编写代码,将其编译并链接到 .mpy 文件中,然后像导入普通 Python 模块一样导入该文件。这可用于实现对性能至关重要的功能,或用于包含用另一种语言编写的现有库。
使用本机 .mpy 文件的主要优点之一是本机机器代码可以通过脚本动态导入,而无需重建主要的 MicroPython 固件。这与MicroPython 外部 C 模块形成对比,后者也允许在 C 中定义自定义模块,但它们必须编译到主固件映像中。
这里的重点是使用 C 构建原生模块,但原则上任何可以编译为独立机器代码的语言都可以放入 .mpy 文件中。
本机 .mpy 模块是使用该mpy_ld.py
工具构建的,该工具
tools/
位于项目目录中。该工具采用一组目标文件(.o 文件)并将它们链接在一起以创建本机 .mpy 文件。它需要 CPython 3 和库 pyelftools v0.25 或更高版本。
支持的功能和限制¶
.mpy 文件可以包含 MicroPython 字节码和/或本机机器代码。如果它包含本机机器代码,则 .mpy 文件具有与之关联的特定体系结构。当前支持的架构是(这些是ARCH
变量的有效选项,见下文):
x86
(32 位)x64
(64 位 x86)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)
在编译和链接原生 .mpy 文件时,必须选择架构,并且只能在该架构上导入相应的文件。有关 .mpy 文件的更多详细信息,请参阅 MicroPython .mpy 文件。
本机代码必须编译为位置无关代码 (PIC) 并使用全局偏移表 (GOT),尽管这的细节因架构而异。当使用本机代码导入 .mpy 文件时,导入机制能够对本机代码进行一些基本的重定位。这包括重新定位文本、rodata 和 BSS 部分。
链接器和动态加载器支持的功能包括:
可执行代码(文本)
只读数据(rodata),包括字符串和常量数据(数组、结构体等)
归零数据 (BSS)
文本中指向文本、rodata 和 BSS 的指针
Rodata 中指向 text、rodata 和 BSS 的指针
已知的限制是:
不支持数据部分;解决方法:使用 BSS 数据并显式初始化数据值
不支持静态 BSS 变量;解决方法:使用全局 BSS 变量
因此,如果您的 C 代码具有可写数据,请确保数据是全局定义的,没有初始化程序,并且只写入函数内部。
链接器限制:本机模块未链接到完整 MicroPython 固件的符号表。相反,它与在mp_fun_table
(在py/nativeglue.h
) 中找到的显式导出符号表相关联,该表在固件构建时固定。例如,因此不可能简单地调用一些任意的 HAL/OS/RTOS/系统函数。
可以将新符号添加到表的末尾并重建固件。还需要将符号添加到同一位置tools/mpy_ld.py
的 fun_table
dict 中。这允许 mpy_ld.py
能够在导入 mpy 时挑选新符号并为它们提供重定位。最后,如果符号是函数,则应添加宏或存根 py/dynruntime.h
以方便调用该函数。
定义原生模块¶
本机 .mpy 模块由一组用于构建 .mpy 的文件定义。文件系统布局由两个主要部分组成,源文件和 Makefile:
在最简单的情况下,只需要一个 C 源文件,其中包含将被编译到 .mpy 模块中的所有代码。此 C 源代码必须包含
py/dynruntime.h
访问 MicroPython 动态 API的文件,并且必须至少定义一个名为mpy_init
. 此函数将作为模块的入口点,在导入模块时调用。如果需要,该模块可以拆分为多个 C 源文件。模块的某些部分也可以用 Python 实现。所有源文件都应该列在 Makefile 中,方法是将它们添加到
SRC
变量中(见下文)。这包括 C 源文件以及将包含在生成的 .mpy 文件中的任何 Python 文件。在
Makefile
包含了模块和列表用于构建.mpy模块的源文件的生成配置。它应该定义MPY_DIR
为 MicroPython 存储库的位置(以查找头文件、相关的 Makefile 片段和mpy_ld.py
工具)、MOD
作为模块的名称、SRC
作为源文件的列表、可选择通过 指定机器架构ARCH
,然后包括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, armv7m, xtensa, xtensawin)
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 制作。
目标架构的 AC 编译器(如果使用 C 源代码)。
可选
mpy-cross
,从 MicroPython 存储库构建(如果使用 .py 源)。
确保为ARCH
您将要运行的目标选择正确的。然后构建:
$ make
在不修改 Makefile 的情况下,您可以通过以下方式指定目标架构:
$ make ARCH=armv7m
MicroPython 中的模块使用¶
构建模块后,应该有一个名为factorial.mpy
. 复制它,以便它可以在 MicroPython 系统的文件系统上访问,并且可以在导入路径中找到。该模块现在可以像任何其他模块一样在 Python 中访问,例如:
import factorial
print(factorial.factorial(10))
# should display 3628800
进一步的例子¶
有关examples/natmod/
更多示例,请参阅显示本机 .mpy 模块的许多可用功能的示例。此类功能包括:
使用多个 C 源文件
包括 Python 代码和 C 代码
Rodata 和 BSS 数据
内存分配
浮点数的使用
异常处理
包括外部 C 库