【免费下载链接】TobudOS
TobudOS 是面向物联网领域开发的实时操作系统,早期版本基于腾讯自研的物联网操作系统TencentOS Tiny,2020年由腾讯捐赠到开放原子开源基金会进行孵化,2023年正式更名为TobudOS,TobudOS具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率,提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU (如 STM32 全系列) 及模组芯片上。而且,基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入物联网云平台。
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),还会有一张FTB(finaliser 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.c,gc模块的完整函数表为:
| 函数 | 作用 | 实现说明 |
|---|---|---|
gc.collect() | 手动执行一次回收 | 返回本次回收的对象数(受MICROPY_PY_GC_COLLECT_RETVAL控制,否则返回None) |
gc.enable() | 开启自动回收 | 置位gc_auto_collect_enabled |
gc.disable() | 关闭自动回收 | 清零gc_auto_collect_enabled |
gc.isenabled() | 查询自动回收状态 | 返回布尔值 |
gc.mem_free() | 返回堆中可用字节数 | 通过gc_info()读取info.free |
gc.mem_alloc() | 返回堆中已分配字节数 | 通过gc_info()读取info.used |
gc.threshold() | 查询/设置自动回收阈值 | 以 block 为单位换算成字节 |
gc.collect()的核心实现非常直接(见 py/modgc.c#L34-L41):
STATIC 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_t
MicroPython 中所有对象都由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 | *******1 |
| interned 字符串 / 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-L127):
static 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.h):
struct _mp_obj_base_t { const mp_obj_type_t *type MICROPY_OBJ_BASE_ALIGNMENT; };小整数、qstr 对象和内联浮点数则不属于"具体对象",不携带该头。
四、堆布局:block、pool 与 bitmap
4.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 个 block:BLOCKS_PER_ATB (4))。
Mark-Sweep GC 管理堆上对象的同时也利用这张位图来标记仍在使用的对象,完整实现见 py/gc.c。
4.3 堆布局中的 block 属性
堆被组织为"由 block 组成的 pool(池)",每个 block 可能具有以下属性之一(与文档列表对应):
- ATB(allocation table byte):若置位,则该 block 是普通 block;
- FREE:空闲 block;
- HEAD:一段 block 链的头部;
- TAIL:处于 block 链的尾部;
- MARK:被标记的头部 block;
- FTB(finaliser 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 应用,也能在嵌入式资源紧张时精确控制回收时机。
【免费下载链接】TobudOS
TobudOS 是面向物联网领域开发的实时操作系统,早期版本基于腾讯自研的物联网操作系统TencentOS Tiny,2020年由腾讯捐赠到开放原子开源基金会进行孵化,2023年正式更名为TobudOS,TobudOS具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率,提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU (如 STM32 全系列) 及模组芯片上。而且,基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入物联网云平台。
相关推荐
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 示例深度解析 本篇技术指南围绕 WAMR(WebAssembly Mi
语言运行时嵌入式物联网
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考