# 字节码和字节码库启动 ## 需求场景 在嵌入式应用中,使用 PikaPython 频繁地从文件系统加载和编译 Python 源文件,尤其是对于较大的文件,不仅会导致程序启动时间过长,还可能增加存储介质(如闪存)的磨损。这是因为每次编译都会写入新生成的文件,而闪存等存储介质的写入次数有限,过多的写入操作会缩短其寿命。因此,为了提高效率、减少启动时间,并减少对存储介质的磨损,引入字节码和字节码库(PIKA APP软件包)作为一种有效的解决方案。 使用字节码和字节码库可以减少频繁的编译过程,因为字节码文件和字节码库文件只需要在第一次编译时生成,后续只需加载这些已编译的文件。这种方法不仅提高了程序的运行效率,也显著减少了对存储介质的写入操作,从而延长了其使用寿命。 ## 概念解释:Python源码编译与解释执行 ### PikaPython 编译概念 在 Python中,源码的编译不同于传统编译型语言(如C或C++)中的概念。Python是一种被广泛认为是解释型的语言,但实际上,它在执行前将源码编译成字节码。 这一过程与传统编译型语言将代码编译成机器码的过程有所不同。Python 的编译过程主要涉及以下步骤: 1. **解析**: 解释器首先将 Python 源代码解析成一种称为抽象语法树(Abstract Syntax Tree,AST)的中间形式。 2. **编译**: 接着,将 AST 编译成字节码。这种字节码不是机器码,而是一种更高级的、与平台无关的代码。 3. **执行**: 最后,PikaPython 虚拟机(PikaVM)执行这些字节码。 ### PikaPython的执行流程 PikaPython 提供了在目标平台(如MCU)上直接编译和解释执行 Python 源码的能力。这意味着 PikaPython 可以完全在 MCU 上处理 Python 源代码的编译到字节码的过程,然后运行这些字节码。 这一特性使得 PikaPython 在嵌入式系统中尤为有用,因为它允许开发者在资源有限的环境中直接使用 Python 编程,而无需依赖外部的编译工具链。具体来说: - **在MCU中编译**: PikaPython 允许在目标 MCU 平台上直接将 Python 源代码编译成字节码。 - **解释执行**: 解释执行本质上是指编译 Python 源码到字节码,然后由 Python 虚拟机运行这些字节码的完整流程。 但是,解释执行意味着每次启动 Python 脚本时都要完成完整的 编译->执行 的流程,如果可以跳过编译过程,直接执行,那么就能节省编译的耗时和对存储介质的磨损。 跳过编译过程的方式有: 1. 直接启动目标 MCU 之前编译产生的字节码。 2. 在 PC 上编译字节码,在目标 MCU 启动。 ## 字节码与字节码库 ### 字节码(.py.o) - **定义**: PikaPython 的字节码是 Python 源代码编译后的中间表示形式,以 `.py.o` 格式存储。 - **生成路径**: 默认生成于`pikascript-api/`路径下,每个Python文件生成一个同名的`.py.o`文件。 - **使用**: 加快单个脚本的加载和执行速度。 ### 字节码库(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源码文件可能导致的命名冲突和项目管理问题。 ## 加载方式 ### 加载单个字节码(字节码数组) 将单个字节码文件 `.py.o` 中的数据,传入 `pikaVM_runByteCode()` API可以就直接运行单个字节码,可以参考g030中从字节码启动的用法。 [https://gitee.com/Lyon1998/pikapython/blob/master/bsp/stm32g030c8/Booter/main.c](https://gitee.com/Lyon1998/pikapython/blob/master/bsp/stm32g030c8/Booter/main.c) > 注意 > 1. `pikaVM_runByteCode()` API 运行的字节码必须是 `const` 类型,如果字节码不是 `const`,则需要使用 `pikaVM_runByteCodeInconstant()`。 > 2. 如果已经对接了文件系统,可以使用 `pikaVM_runByteCodeFile()` API 直接运行 `.py.o` 文件。 > 3. `pikaVM_runByteCodeInconstant()` 和 `pikaVM_runByteCodeFile()` 需要内核版本 `>=v1.11.7` ### 加载单个字节码文件 ```c // 示例:加载单个字节码文件 PikaObj *pikaMain = newRootObj("pikaMain", New_PikaMain); pikaVM_runBytecodeFile(pikaMain, "pikascript-api/example.py.o"); ``` 此示例展示了如何从`pikascript-api/`路径下加载特定的单个字节码文件。 ### 加载字节码库(PIKA APP软件包)并启动指定字节码 ```c // 示例:加载字节码库并启动指定字节码 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。 ## 在 PC 上将 Python 转为字节码: 编译字节码的过程也可以在 PC 完成,因为性能较弱的 MCU 在编译字节码时会需要花费很长的时间。 ![](assets/1639281281608-011ffd89-5851-47d8-9dca-438ed963f5d4-164649975346225.png) 预编译器 `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`,库文件中包含了所有的字节码文件。 ![](assets/image-20220823155644618-16612416630901-16612416855853.png) 为了方便没有文件系统的 MCU 通过 C 语言编译的方式加载字节码库,预编译器还会把库文件自动转换为 C 的字节数组文件 `pikascript-api/__asset_pikaModules_py_a.c`。 ``` 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, ... ```