10.2. 字节码和字节码库启动
10.2.1. 需求场景
在嵌入式应用中,使用 PikaPython 频繁地从文件系统加载和编译 Python 源文件,尤其是对于较大的文件,不仅会导致程序启动时间过长,还可能增加存储介质(如闪存)的磨损。这是因为每次编译都会写入新生成的文件,而闪存等存储介质的写入次数有限,过多的写入操作会缩短其寿命。因此,为了提高效率、减少启动时间,并减少对存储介质的磨损,引入字节码和字节码库(PIKA APP软件包)作为一种有效的解决方案。
使用字节码和字节码库可以减少频繁的编译过程,因为字节码文件和字节码库文件只需要在第一次编译时生成,后续只需加载这些已编译的文件。这种方法不仅提高了程序的运行效率,也显著减少了对存储介质的写入操作,从而延长了其使用寿命。
10.2.2. 概念解释:Python源码编译与解释执行
10.2.2.1. PikaPython 编译概念
在 Python中,源码的编译不同于传统编译型语言(如C或C++)中的概念。Python是一种被广泛认为是解释型的语言,但实际上,它在执行前将源码编译成字节码。
这一过程与传统编译型语言将代码编译成机器码的过程有所不同。Python 的编译过程主要涉及以下步骤:
解析: 解释器首先将 Python 源代码解析成一种称为抽象语法树(Abstract Syntax Tree,AST)的中间形式。
编译: 接着,将 AST 编译成字节码。这种字节码不是机器码,而是一种更高级的、与平台无关的代码。
执行: 最后,PikaPython 虚拟机(PikaVM)执行这些字节码。
10.2.2.2. PikaPython的执行流程
PikaPython 提供了在目标平台(如MCU)上直接编译和解释执行 Python 源码的能力。这意味着 PikaPython 可以完全在 MCU 上处理 Python 源代码的编译到字节码的过程,然后运行这些字节码。
这一特性使得 PikaPython 在嵌入式系统中尤为有用,因为它允许开发者在资源有限的环境中直接使用 Python 编程,而无需依赖外部的编译工具链。具体来说:
在MCU中编译: PikaPython 允许在目标 MCU 平台上直接将 Python 源代码编译成字节码。
解释执行: 解释执行本质上是指编译 Python 源码到字节码,然后由 Python 虚拟机运行这些字节码的完整流程。
但是,解释执行意味着每次启动 Python 脚本时都要完成完整的 编译->执行 的流程,如果可以跳过编译过程,直接执行,那么就能节省编译的耗时和对存储介质的磨损。
跳过编译过程的方式有:
直接启动目标 MCU 之前编译产生的字节码。
在 PC 上编译字节码,在目标 MCU 启动。
10.2.3. 字节码与字节码库
10.2.3.1. 字节码(.py.o)
定义: PikaPython 的字节码是 Python 源代码编译后的中间表示形式,以
.py.o
格式存储。生成路径: 默认生成于
pikascript-api/
路径下,每个Python文件生成一个同名的.py.o
文件。使用: 加快单个脚本的加载和执行速度。
10.2.3.2. 字节码库(PIKA APP软件包)
定义: PikaPython 字节码库(亦称为PIKA APP软件包)是多个字节码文件的集合,格式为
.py.a
。生成路径: 所有
.py.o
文件被打包为一个pikascript-api/pikaModules.py.a
文件。使用: 适用于同时加载多个编译模块的复杂应用,便于整体管理和部署。
OTA更新优势: 在MCU应用中,通过OTA发送单个字节码库文件以更新整个PikaPython应用非常便利。这意味着无需逐一发送零散的Python源码文件,从而简化了更新流程,并减少了数据传输量。
应用管理优势: 字节码库类似于Windows上的.exe文件,每个PIKA APP软件包都代表一个完整且可以独立启动的应用。在具有多种应用需求的场景中,可以在目标系统放置多个独立的APP软件包,从而方便地选择和切换运行的应用。这避免了由于零散的Python源码文件可能导致的命名冲突和项目管理问题。
10.2.4. 加载方式
10.2.4.1. 加载单个字节码(字节码数组)
将单个字节码文件 .py.o
中的数据,传入 pikaVM_runByteCode()
API可以就直接运行单个字节码,可以参考g030中从字节码启动的用法。
https://gitee.com/Lyon1998/pikapython/blob/master/bsp/stm32g030c8/Booter/main.c
注意
pikaVM_runByteCode()
API 运行的字节码必须是const
类型,如果字节码不是const
,则需要使用pikaVM_runByteCodeInconstant()
。如果已经对接了文件系统,可以使用
pikaVM_runByteCodeFile()
API 直接运行.py.o
文件。
pikaVM_runByteCodeInconstant()
和pikaVM_runByteCodeFile()
需要内核版本>=v1.11.7
10.2.4.2. 加载单个字节码文件
// 示例:加载单个字节码文件
PikaObj *pikaMain = newRootObj("pikaMain", New_PikaMain);
pikaVM_runBytecodeFile(pikaMain, "pikascript-api/example.py.o");
此示例展示了如何从pikascript-api/
路径下加载特定的单个字节码文件。
10.2.4.3. 加载字节码库(PIKA APP软件包)并启动指定字节码
// 示例:加载字节码库并启动指定字节码
PikaObj *pikaMain = newRootObj("pikaMain", New_PikaMain);
obj_linkLibraryFile(pikaMain, "pikascript-api/pikaModules.py.a");
// 启动字节码库中的特定字节码
obj_runModule(pikaMain, "main");
在这个示例中,使用 obj_linkLibraryFile
函数来加载PIKA APP软件包,并通过obj_runModule
函数来启动字节码库中的指定模块,通常是从 main 模块启动(对应的 Python 源码文件是 main.py。
10.2.5. 在 PC 上将 Python 转为字节码:
编译字节码的过程也可以在 PC 完成,因为性能较弱的 MCU 在编译字节码时会需要花费很长的时间。
预编译器 rust-msc-latest-win10.exe
集成了字节码生成器,在预编译时会将 main.py 和被 main.py import
(包括间接 import
) 的 .py
文件编译为字节码,生成的字节码文件在 pikascript-api 文件夹下。
.py
文件会被生成为 .py.o
字节码文件,例如 main.py
会生成 pikascript-api/main.py.o
。
同时,所有的 .py.o
文件会被自动打包成一个库文件 pikascript-api/pikaModules.py.a
,库文件中包含了所有的字节码文件。
为了方便没有文件系统的 MCU 通过 C 语言编译的方式加载字节码库,预编译器还会把库文件自动转换为 C 的字节数组文件 pikascript-api/__asset_pikaModules_py_a.c
。
/* __asset_pikaModules_py_a.c */
#include "PikaPlatform.h"
/* warning: auto generated file, please do not modify */
PIKA_BYTECODE_ALIGN const unsigned char pikaModules_py_a[] = {
0x7f, 0x70, 0x79, 0x61, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
...