TobudOS 中的 MicroPython 内存管理:gc 模块、mp_obj_t 对象模型与 Mark-Sweep 回收机制

发布时间:2026/10/10 1:25:24
TobudOS 中的 MicroPython 内存管理:gc 模块、mp_obj_t 对象模型与 Mark-Sweep 回收机制
【免费下载链接】TobudOSTobudOS 是面向物联网领域开发的实时操作系统早期版本基于腾讯自研的物联网操作系统TencentOS Tiny2020年由腾讯捐赠到开放原子开源基金会进行孵化2023年正式更名为TobudOSTobudOS具有低功耗低资源占用模块化安全可靠等特点可有效提升物联网终端产品开发效率提供精简的 RTOS 内核内核组件可裁剪可配置可快速移植到多种主流 MCU (如 STM32 全系列) 及模组芯片上。而且基于 RTOS 内核提供了丰富的物联网组件内部集成主流物联网协议栈如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等可助力物联网终端设备及业务快速接入物联网云平台。项目地址https://gitcode.com/openatomfoundation/TobudOS点击查看免费下载TobudOS 作为面向物联网领域的轻量级实时操作系统在语言组件中集成了 MicroPython为 MCU 应用提供了解释型 Python 能力。MicroPython 与 C/C 的最大差异之一在于它向开发者隐藏了内存管理细节——通过自动内存管理Automatic Memory Management消除忘记释放和使用已释放内存这两类经典缺陷。本文以 TobudOS 仓库中 MicroPython 官方开发文档 memorymgt 为主体完整讲解其垃圾回收策略、对象模型与堆分配机制并结合本仓库中 MicroPython 的移植源码说明这些机制在 TobudOS 目标平台上是如何落地配置的。一、垃圾回收Mark and Sweep 策略自动内存管理有诸多形态其中一种即垃圾回收GC。GC 通常承担两项职责在可用内存中分配新对象释放不再使用的内存。MicroPython 采用的是Mark and Sweep标记-清除策略标记阶段mark phase遍历堆标记所有仍然存活的对象清除阶段sweep phase再次遍历堆回收所有未被标记的对象。在 TobudOS 仓库的 MicroPython 移植中该实现位于 py/gc.c。从源码结构看其分配表字节ATB, allocation table byte用两个 bit 跟踪每个 block 的状态与文档描述一一对应// ATB allocation table byte // 0b00 FREE -- free block // 0b01 HEAD -- head of a chain of blocks // 0b10 TAIL -- in the tail of a chain of blocks // 0b11 MARK -- marked head block #define AT_FREE (0) #define AT_HEAD (1) #define AT_TAIL (2) #define AT_MARK (3)见 py/gc.c#L55-L64此外在启用 finaliser 时MICROPY_ENABLE_FINALISER还会有一张FTBfinaliser table byte位图用于标记可能带有析构器的 block这正是文档末尾提到的 FTB 属性// FTB finaliser table byte // if set, then the corresponding block may have a finaliser #define BLOCKS_PER_FTB (8)见 py/gc.c#L89-L98二、gc 内建模块开发者可用的内存接口MicroPython 通过gc内建模块向开发者暴露内存管理功能。官方文档给出的交互示例 x 5 x 5 import gc gc.enable() gc.mem_alloc() 1312 gc.mem_free() 2071392 gc.collect() 19 gc.disable()需要特别注意即使调用了gc.disable()仍然可以显式调用gc.collect()触发一次回收——disable()只是关闭自动触发。对照 TobudOS 仓库中该模块的实现 py/modgc.cgc模块的完整函数表为函数作用实现说明gc.collect()手动执行一次回收返回本次回收的对象数受MICROPY_PY_GC_COLLECT_RETVAL控制否则返回Nonegc.enable()开启自动回收置位gc_auto_collect_enabledgc.disable()关闭自动回收清零gc_auto_collect_enabledgc.isenabled()查询自动回收状态返回布尔值gc.mem_free()返回堆中可用字节数通过gc_info()读取info.freegc.mem_alloc()返回堆中已分配字节数通过gc_info()读取info.usedgc.threshold()查询/设置自动回收阈值以 block 为单位换算成字节gc.collect()的核心实现非常直接见 py/modgc.c#L34-L41STATIC mp_obj_t py_gc_collect(void) { gc_collect(); #if MICROPY_PY_GC_COLLECT_RETVAL return MP_OBJ_NEW_SMALL_INT(MP_STATE_MEM(gc_collected)); #else return mp_const_none; #endif }其中gc.collect()的返回值示例中的19就是本轮 sweep 阶段实际清除的对象数量。gc.threshold()则控制自动回收的触发时机无参调用返回当前阈值字节传入负值表示取消阈值限制。它在内部把字节阈值除以MICROPY_BYTES_PER_GC_BLOCK换算为 block 数见 py/modgc.c#L79-L96。这意味着在资源受限的 MCU 上开发者可以在大对象分配前手动gc.disable()批量分配、结束后一次性gc.collect()从而摊薄多次回收的开销。三、对象模型mp_obj_t 与指针标签3.1 所有对象都是 mp_obj_tMicroPython 中所有对象都由mp_obj_t数据类型表示。它通常是字长大小与目标架构上指针同宽因此常见为 32 位如 STM32、nRF、ESP32、Unix x86或 64 位Unix x64。在 TobudOS 的 MicroPython 移植中这一类型定义位于 py/obj.h#if MICROPY_OBJ_REPR MICROPY_OBJ_REPR_D typedef uint64_t mp_obj_t; #else typedef void *mp_obj_t; #endif值得注意的是某些对象表示下mp_obj_t可以大于字长——例如OBJ_REPR_D在 32 位架构上就是 64 位类型。具体可用的表示形式定义在 py/mpconfig.h 中。mp_obj_t可以代表整数、浮点数、类型、字典或类实例等对象。其中布尔值、小整数等对象的值直接存放在mp_obj_t里不需要额外内存其他对象的值存放在别处例如 GC 堆上mp_obj_t中存的是指向该内存的指针。mp_obj_t的一部分 bit 是tag标签用于标识对象类型。3.2 指针标签Pointer tagging由于指针都是字对齐的存储在mp_obj_t中的低位必然为零。以 32 位架构为例低 2 位为零********|********|********|******00这些零位被挪用为 tag 来携带附加信息——比新增一个字段更高效。tag 告诉解释器当前是小整数、interned 字符串还是具体对象三者各有不同的语义对象种类低位 tag小整数 small int*******1interned 字符串 / qstr*****010立即数对象如True*****110具体对象堆上对象指针******00小整数的完整映射为********|********|********|*******1星号部分是实际的整数值。TobudOS 移植使用的是REPR_A表示可在 port/mpconfigport.h 中看到// Object representation #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A)REPR_A 下各类型的判定与构造宏正对应文档中的 tag 布局见 py/obj.h#L84-L127static inline bool mp_obj_is_small_int(mp_const_obj_t o) { return (((mp_int_t)(o)) 1) ! 0; // tag 1 } #define MP_OBJ_NEW_SMALL_INT(small_int) ((mp_obj_t)((((mp_uint_t)(small_int)) 1) | 1)) static inline bool mp_obj_is_qstr(mp_const_obj_t o) { return (((mp_int_t)(o)) 7) 2; // tag 010 } #define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) 3) | 2)) static inline bool mp_obj_is_immediate_obj(mp_const_obj_t o) { return (((mp_int_t)(o)) 7) 6; // tag 110 } static inline bool mp_obj_is_obj(mp_const_obj_t o) { return (((mp_int_t)(o)) 3) 0; // tag 00堆上具体对象 }可以看到文档中抽象描述的 tag 位1、010、110、00在源码里就是这些低位的按位运算。3.3 对象的分配规则小整数的值直接存放在mp_obj_t中属于原地分配不落在堆上因此创建小整数不影响堆已经在其文本数据处存有内容的 interned 字符串以及None、False、True这类立即数值同理其余所有具体对象都分配在堆上且对象结构在头部header中保留一个字段用于存放对象类型。 type object header object items 对应地所有具体对象都必须以mp_obj_base_t作为首成员见 py/obj.hstruct _mp_obj_base_t { const mp_obj_type_t *type MICROPY_OBJ_BASE_ALIGNMENT; };小整数、qstr 对象和内联浮点数则不属于具体对象不携带该头。四、堆布局block、pool 与 bitmap4.1 最小分配单元是 block堆的最小分配单元是block大小为 4 个机器字32 位机上 16 字节64 位机上 32 字节。这一默认值由 py/mpconfig.h 定义#ifndef MICROPY_BYTES_PER_GC_BLOCK #define MICROPY_BYTES_PER_GC_BLOCK (4 * MP_BYTES_PER_OBJ_WORD) #endifpy/gc.c 在此基础上定义#define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / MP_BYTES_PER_OBJ_WORD) #define BYTES_PER_BLOCK (MICROPY_BYTES_PER_GC_BLOCK)4.2 bitmap 跟踪 block 占用堆上还会分配一个专门结构来跟踪每个 block 的对象分配状态称为bitmap位图。位图用两个 bit 记录每个 block 是空闲还是在用即上文 ATB 的 FREE/HEAD/TAIL/MARK 四种状态每个 ATB 字节管理 4 个 blockBLOCKS_PER_ATB (4)。Mark-Sweep GC 管理堆上对象的同时也利用这张位图来标记仍在使用的对象完整实现见 py/gc.c。4.3 堆布局中的 block 属性堆被组织为由 block 组成的 pool池每个 block 可能具有以下属性之一与文档列表对应ATBallocation table byte若置位则该 block 是普通 blockFREE空闲 blockHEAD一段 block 链的头部TAIL处于 block 链的尾部MARK被标记的头部 blockFTBfinaliser table byte若置位则该 block 带有 finaliser。从gc_init()的源码py/gc.c#L109-L138可以进一步确认池的构成总内存 分配表 finaliser 表 pool且 pool 的结束指针会先对齐到 block 边界void gc_init(void *start, void *end) { // align end pointer on block boundary end (void *)((uintptr_t)end (~(BYTES_PER_BLOCK - 1))); ... MP_STATE_MEM(gc_pool_start) (byte *)end - gc_pool_block_len * BYTES_PER_BLOCK; ... }也就是说GC 池并非简单占据整个传入区间——它要为分配表和 finaliser 表预留空间实际可用的 pool 大小由gc_pool_block_len * BYTES_PER_BLOCK决定。这也是gc.mem_free()读数总是小于物理 RAM 的原因。五、在 TobudOS 中验证内存行为TobudOS 中 MicroPython 作为独立语言组件编译进 RTOS 应用参见 examples/micropython_demo 与 board/BearPi_STM32L431RC/KEIL/micropython_basic_demo。在该移植中GC 与相关特性均处于开启状态见 port/mpconfigport.h#define MICROPY_ENABLE_GC (1)在 TobudOS 目标板上运行 MicroPython 后可以按以下方式验证本文所述的内存管理行为 import gc gc.isenabled() # 确认自动回收处于开启状态 True free0 gc.mem_free() # 记录回收前可用字节 big [list(range(1000)) for _ in range(50)] # 制造一批堆对象 gc.mem_free() # 观察可用内存明显下降 gc.collect() # 手动回收返回清除的对象数 gc.mem_free() # 再观察内存回升排查内存问题时的一般方法论用gc.mem_free()/gc.mem_alloc()在关键路径前后取样定位内存增长位置大对象批量操作前gc.disable()操作完成后gc.collect()一次性回收减少回收频次对实时性的影响利用gc.threshold()查看或调整自动回收阈值避免默认阈值在小堆场景下触发过频。小结MicroPython 的自动内存管理建立在三个层次之上API 层gc模块实现于 py/modgc.c提供 enable/disable/collect/mem_free/mem_alloc/threshold 等运维接口对象层mp_obj_t借助指针低位的 tag 区分小整数、qstr、立即数与堆对象小整数和立即数免堆分配堆层以 4 字长的 block 为最小单元由 ATB/FTB 位图跟踪 block 状态Mark-Sweep GC实现于 py/gc.c完成标记与清除。TobudOS 的 MicroPython 移植components/language/micropython选用 REPR_A 对象表示并默认开启 GC使得上述机制可以直接在 STM32 等 32 位 MCU 上工作。理解这些内部细节后开发者既能更合理地编写低内存消耗的 Python 应用也能在嵌入式资源紧张时精确控制回收时机。赞分享【免费下载链接】TobudOSTobudOS 是面向物联网领域开发的实时操作系统早期版本基于腾讯自研的物联网操作系统TencentOS Tiny2020年由腾讯捐赠到开放原子开源基金会进行孵化2023年正式更名为TobudOSTobudOS具有低功耗低资源占用模块化安全可靠等特点可有效提升物联网终端产品开发效率提供精简的 RTOS 内核内核组件可裁剪可配置可快速移植到多种主流 MCU (如 STM32 全系列) 及模组芯片上。而且基于 RTOS 内核提供了丰富的物联网组件内部集成主流物联网协议栈如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等可助力物联网终端设备及业务快速接入物联网云平台。项目地址https://gitcode.com/openatomfoundation/TobudOS点击查看免费下载相关推荐MicroPython 内存管理深入解析Python 堆、Mark-and-Sweep 垃圾回收与对象模型MicroPython 内存管理深入解析Python 堆、Mark and Sweep 垃圾回收与对象模型 本篇文章以 MicroPython 官方开发文档《嵌入式语言运行时编程语言解释器编译器物联网系统编程MicroPython gc 模块完全指南手动控制垃圾回收与堆内存MicroPython gc 模块完全指南手动控制垃圾回收与堆内存 gc 是 MicroPython 标准库中的核心模块它让你能够控制自动垃圾回收Garb嵌入式语言运行时编程语言解释器编译器物联网系统编程WAMR 生产优化 WASM 崩溃符号化实战debug-tools-optimized 示例深度解析WAMR 生产优化 WASM 崩溃符号化实战debug tools optimized 示例深度解析 本篇技术指南围绕 WAMRWebAssembly Mi语言运行时嵌入式物联网上一篇GPT Researcher完整指南5分钟跑通第一个研究代理从安装到混合检索实战下一篇freeCodeCamp 课程设计解析用递归分治实现快速排序Quick Sort创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考