.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.pyfun_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 库