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文件的子版本:如果.mpy文件包含本机机器码,则文件的子版本必须与加载它的系统支持的版本匹配。否则,如果.mpy文件中没有本机机器码,则在加载时忽略子版本。
小整数位:.mpy 文件将需要小整数中的最少位数,并且加载它的系统必须至少支持这么多位。
本地架构:如果 .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 sub-version:', sys_mpy >> 8 & 3) print('mpy flags:', end='') if arch: print(' -march=' + arch, 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 发布 |
.mpy 版本 |
---|---|
v1.23.0 and up |
6.3 |
v1.22.x |
6.2 |
v1.20 - v1.21.0 |
6.1 |
v1.19.x |
6 |
v1.12 - v1.18 |
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 版本变更 |
Git commit |
---|---|
6.2 to 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 to 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 to 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
5 to 6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
4 to 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 to 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 to 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 to 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 to 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
初始版本 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
.mpy文件的二进制编码¶
MicroPython .mpy 文件是一种二进制容器格式,代码对象(字节码和本机机器码)内部存储在嵌套层次结构中。 首先存储外部模块的代码,然后存储它的子模块。每个子类可以有更多的子类,例如具有方法的类,或者定义lambda或推导式的函数。为了保持文件较小,同时仍提供大范围的可能值,它在许多地方使用了可变编码无符号整数 (vuint) 的概念。 与 utf-8 编码类似,这种编码每字节存储 7 位,如果后面跟着一个或多个字节,则设置第 8 位 (MSB)。 无符号整数的位以 LSB 形式存储在 vuint 中。
.mpy文件的顶层由三部分组成:
标题。
全局qstr表和常量表。
模块外部作用域的原始代码。这个外部作用域在导入.mpy文件时执行。
你可以使用 mpy-tool.py
来检查.mpy文件的内容,例如(从主MicroPython存储库的根目录运行):
$ ./tools/mpy-tool.py -xd myfile.mpy
标题¶
.mpy标题是:
尺寸 |
场 |
---|---|
byte |
value 0x4d (ASCII ‘M’) |
byte |
.mpy 主要版本号 |
byte |
原生arch和次要版本号(旧版本中的特性标志) |
byte |
小 int 中的位数 |
全局qstr表和常量表¶
一个.mpy文件包含一个qstr表和一个常量对象表。这些对.mpy文件是全局的,它们被所有嵌套的原始代码对象引用。qstr表将内部qstr号(在.mpy文件内部)映射到导入.mpy文件的运行时的解析后的qstr号。这将.mpy文件与它在其中执行的系统的其余部分链接起来。使用对.mpy文件所需的所有常量对象的引用填充常量对象表。
尺寸 |
场 |
---|---|
vuint |
qstr的数量 |
vuint |
常量对象的数量 |
… |
qstr数据 |
… |
编码常量对象 |
原始代码元素¶
原始代码元素包含代码,字节码或本地机器代码。 其内容是:
尺寸 |
场 |
---|---|
vuint |
类型、大小以及是否存在子原始代码元素 |
… |
代码(字节码或机器代码) |
vuint |
子原始代码元素的数量(仅当非零时) |
… |
子原始代码元素 |
原始代码元素中的第一个 vuint 编码存储在该元素中的代码类型(两个最低有效位),该原始代码是否有任何子代码(第三个最低有效位),以及后面代码的长度(为其分配的RAM量)。
在vuint之后是代码本身。除非代码类型是带有重定位的viper代码,否则该代码是常量数据,不需要修改。
如果此原始代码有任何子代码(如第一个vuint中的位所示),则代码后面是一个vuint,用于计算子原始代码元素的数量。
最后,递归地存储所有sub-raw-code元素。