.. _mpy_files: MicroPython .mpy 文件 ====================== MicroPython 定义了 .mpy 文件的概念,它是一种包含预编译代码的二进制容器文件格式,可以像普通的 .py 模块一样导入。 文件 ``foo.mpy`` 可以通过 ``import foo`` 导入,只要 ``foo.mpy`` 可以被导入机器以通常的方式找到。 通常, ``sys.path`` 中列出的每个目录是按顺序搜索的。 当搜索特定目录时,首先寻找 ``foo.py`` ,如果没有找到,则寻找 ``foo.mpy`` , 如果都没有找到,则在下一个目录中继续搜索。 因此,``foo.py`` 将优先于 ``foo.mpy`` 。 这些 .mpy 文件可以包含通常通过 ``mpy-cross`` 程序从 Python 源文件(.py 文件)生成的字节码。 对于某些体系结构,.mpy 文件还可以包含本地机器代码,这些代码可以通过多种方式生成,最显着的是从 C 源代码生成。 .mpy 文件的版本控制和兼容性 ------------------------------------------ 给定的 .mpy 文件可能与给定的 MicroPython 系统兼容,也可能不兼容。兼容性基于以下几点: * .mpy 文件的版本:文件的版本必须与加载它的系统支持的版本相匹配。 * .mpy 文件中使用的字节码特性:有两个字节码特性必须在文件和系统之间匹配:unicode 支持和字节码中地图查找的内联缓存。 * 小整数位:.mpy 文件将需要小整数中的最少位数,并且加载它的系统必须至少支持这么多位。 * Qstr 压缩窗口大小:.mpy 文件将需要 qstr 解压缩的最小窗口大小,并且加载它的系统必须具有大于或等于此大小的窗口。 * 本地架构:如果 .mpy 文件包含本地机器代码,那么它将指定该机器代码的架构,并且加载它的系统必须支持该架构代码的执行。 如果 MicroPython 系统支持导入 .mpy 文件,则 ``sys.implementation.mpy`` 字段将存在并返回一个整数,该整数对版本(低 8 位)、功能和本机架构进行编码。 尝试导入前四个测试之一失败的 .mpy 文件将引发 ``ValueError('incompatible .mpy file')``。 尝试导入未通过本机架构测试(如果它包含本机机器代码)的 .mpy 文件将引发 ``ValueError('incompatible .mpy arch')``。 如果导入.mpy文件失败,请尝试以下操作: * 通过执行以下命令确定 MicroPython 系统支持的 .mpy 版本和标志: import sys sys_mpy = sys.implementation.mpy arch = [None, 'x86', 'x64', 'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp', 'xtensa', 'xtensawin'][sys_mpy >> 10] print('mpy version:', sys_mpy & 0xff) print('mpy flags:', end='') if arch: print(' -march=' + arch, end='') if sys_mpy & 0x100: print(' -mcache-lookup-bc', end='') if not sys_mpy & 0x200: print(' -mno-unicode', end='') print() * 通过检查文件的前两个字节来检查 .mpy 文件的有效性。 第一个字节应该是大写的 'M' ,第二个字节是版本号,它应该与上面的系统版本匹配。 如果不匹配,则重建 .mpy 文件。 * 检查系统 .mpy 版本是否与 ``mpy-cross`` 发出的版本匹配,该版本用于构建 .mpy 文件,由 ``mpy-cross --version`` 找到。 如果它不匹配,则从在 ``mpy-cross --version`` 报告的标记(或哈希)处检出的 Git 存储库中重新编译 ``mpy-cross`` 。 * 确保您使用了正确的 ``mpy-cross`` 标志,由上面的代码找到,或者通过检查您正在使用的端口的 ``MPY_CROSS_FLAGS`` Makefile 变量。 下表显示了 MicroPython 版本和 .mpy 版本之间的对应关系。 =================== ============ MicroPython release .mpy version =================== ============ v1.12 and up 5 v1.11 4 v1.9.3 - v1.10 3 v1.9 - v1.9.2 2 v1.5.1 - v1.8.7 0 =================== ============ 为完整起见,下表显示了主 MicroPython 存储库的 Git 提交,在该存储库中 .mpy 版本发生了更改。 =================== ======================================== .mpy version change Git commit =================== ======================================== 4 to 5 5716c5cf65e9b2cb46c2906f40302401bdd27517 3 to 4 9a5f92ea72754c01cc03e5efcdfe94021120531e 2 to 3 ff93fd4f50321c6190e1659b19e64fef3045a484 1 to 2 dd11af209d226b7d18d5148b239662e30ed60bad 0 to 1 6a11048af1d01c78bdacddadd1b72dc7ba7c6478 initial version 0 d8c834c95d506db979ec871417de90b7951edc30 =================== ======================================== mpy文件的二进制编码 ----------------------------- MicroPython .mpy 文件是一种二进制容器格式,代码对象内部存储在嵌套层次结构中。 为了保持文件较小,同时仍提供大范围的可能值, 它在许多地方使用了可变编码无符号整数 (vuint) 的概念。 与 utf-8 编码类似,这种编码每字节存储 7 位,如果后面跟着一个或多个字节, 则设置第 8 位 (MSB)。 无符号整数的位以 LSB 形式存储在 vuint 中。 mpy文件的顶层由两部分组成: * 标题。 * 模块外部范围的原始代码。 这个外部范围在导入.mpy文件时执行。 标题 ~~~~~~~~~~ mpy标题是: ====== ================================ size field ====== ================================ byte value 0x4d (ASCII 'M') byte .mpy version number byte feature flags byte number of bits in a small int vuint size of qstr window ====== ================================ 原始代码元素 ~~~~~~~~~~~~~~~~~ 原始代码元素包含代码,字节码或本地机器代码。 其内容是: ====== ================================ size field ====== ================================ vuint type and size ... code (bytecode or machine code) vuint number of constant objects vuint number of sub-raw-code elements ... constant objects ... sub-raw-code elements ====== ================================ 原始代码元素中的第一个 vuint 对存储在该元素中的代码类型(两个最低有效位)和代码的解压缩长度(为其分配的 RAM 量)进行编码。 在 vuint 之后是代码本身。 在字节码的情况下,它还包含压缩的 qstr 值。 代码之后是一个计算常量对象数量的 vuint,以及另一个计算子原始代码元素数量的 vuint。 然后接下来存储常量对象。 最后,递归地存储所有sub-raw-code元素。