MicroPython .mpy 文件¶
在 MicroPython 的定义中,.mpy 文件是一种包含预编译代码的二进制容器文件格式,导入它就与像普通的 .py 模块一样。一个 foo.mpy
文件,只要可以在机器上以正常的方式找到,就能通过 import foo
来导入。通常,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 not sys_mpy & 0x200: print(' -mno-unicode', end='') print()
通过校验 .mpy 文件的前两个字节,可以确认文件的有效性。第一个字节应该是大写的 ‘M’;第二个字节是版本号,它应该与上述的系统版本相匹配。如果发现不匹配,那么应当重新构建出 .mpy 文件。
检查系统的 .mpy 版本与
mpy-cross
提交的、用于编译 .mpy 文件的版本是否一致。如果发现不一致,那么应当从 git 仓库中检出mpy-cross --version
指定的 tag (或提交的 hash),重新编译mpy-cross
。确保你正在使用的是正确的
mpy-cross
的标志,mpy-cross
标志可通过上述代码获取,也可以在你使用的适配端的 Makefile 中查找MPY_CROSS_FLAGS
变量。
下表展示了 MicroPython 版本和 .mpy 版本之间的对应关系。
MicroPython 发布版本 |
.mpy 版本 |
---|---|
v1.12 及更高版本 |
5 |
v1.11 |
4 |
v1.9.3 - v1.10 |
3 |
v1.9 - v1.9.2 |
2 |
v1.5.1 - v1.8.7 |
0 |
为了完整起见,下表展示当 .mpy 版本更改时,对应的主 MicroPython 存储库的 Git 提交
.mpy version change |
Git commit |
---|---|
4 到 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 到 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 到 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 到 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 到 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
初始版本 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
.mpy 文件的二进制编码¶
MicroPython .mpy 文件是一种可将代码对象存储在嵌套层级结构中的二进制容器格式。为了让文件保持在较小尺寸的同时,仍然提供大范围的可能值,它在许多地方都使用了可变编码无符号整数(vuint)概念。与 utf-8 编码类似,这种编码每个字节存储 7 位,若其后仍有一个或多个字节,则设置第 8 位 (MSB)。无符号整数的位以 LSB 形式存储在 vuint 中。
.mpy 文件的最顶层由两部分组成
头。
模块外部范围的原始代码。这个外部作用域在 .mpy 文件被导入时被执行。
头¶
.mpy 的头为
大小 |
值 |
---|---|
byte |
0x4d (对应 ASCII ‘M’) |
byte |
.mpy 版本号 |
byte |
特性标志 |
byte |
小整型中的位个数 |
vuint |
qstr 的窗口尺寸 |
原始代码元素¶
原始代码元素包含代码,可能是字节码或原生机器代码。它的内容是:
大小 |
值 |
---|---|
vuint |
类型与大小 |
… |
代码(字节码或机器代码) |
vuint |
常量对象的数量 |
vuint |
子原始代码元素的数量 |
… |
常量对象 |
… |
子原始代码元素 |
原始代码元素中的第一个 vuint 值是该元素中的代码类型(两个最低有效位)和代码的解压缩长度(为其分配的 RAM 量)编码而成的。
在此 vuint 之后是代码本身。如果是字节码的情况下,它还包含压缩过的 qstr 值。
代码后面是一个代表常量对象的数量的 vuint 值,以及一个代表子原始代码元素的数量的 vuint 值。
然后接下来存储常量对象。
最后,任何子原始代码元素都被递归地存储下来。